Files
self/circuits/tests/dsc/dsc.test.ts
Vishalkulkarni45 b3f93afb68 feat: add dsc_sha384_rsapss_65537_48_3072 (#1197)
* feat: add dsc_sha384_rsapss_65537_48_3072

* fix: aadhaar tests

* chore: fix import
2025-10-03 19:39:19 +05:30

355 lines
14 KiB
TypeScript

import { expect } from 'chai';
import { wasm as wasm_tester } from 'circom_tester';
import dotenv from 'dotenv';
import path from 'path';
import serialized_csca_tree from '../../../common/pubkeys/serialized_csca_tree.json' with { type: 'json' };
import { parseCertificateSimple } from '@selfxyz/common/utils/certificate_parsing/parseCertificateSimple';
import { getCircuitNameFromPassportData } from '@selfxyz/common/utils/circuits/circuitsName';
import { generateCircuitInputsDSC } from '@selfxyz/common/utils/circuits/generateInputs';
import { genAndInitMockPassportData } from '@selfxyz/common/utils/passports/genMockPassportData';
import { parseDscCertificateData } from '@selfxyz/common/utils/passports/passport_parsing/parseDscCertificateData';
import { getLeafDscTreeFromParsedDsc } from '@selfxyz/common/utils/trees';
import { SignatureAlgorithm } from '@selfxyz/common/utils/types';
import { fullSigAlgs, sigAlgs } from './test_cases.js';
import { fileURLToPath } from 'url';
dotenv.config();
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const testSuite = process.env.FULL_TEST_SUITE === 'true' ? fullSigAlgs : sigAlgs;
testSuite.forEach(({ sigAlg, hashFunction, domainParameter, keyLength }) => {
const passportData = genAndInitMockPassportData(
hashFunction,
hashFunction,
`${sigAlg}_${hashFunction}_${domainParameter}_${keyLength}` as SignatureAlgorithm,
'FRA',
'000101',
'300101'
);
const passportMetadata = passportData.passportMetadata;
describe(`DSC chain certificate - ${passportMetadata.cscaHashFunction?.toUpperCase()} ${passportMetadata.cscaSignatureAlgorithm?.toUpperCase()} ${passportMetadata.cscaCurveOrExponent?.toUpperCase()} ${
passportData.csca_parsed.publicKeyDetails.bits
}`, function () {
this.timeout(0); // Disable timeout
let circuit;
const inputs = generateCircuitInputsDSC(passportData, serialized_csca_tree);
before(async () => {
circuit = await wasm_tester(
path.join(
__dirname,
`../../circuits/dsc/instances/${getCircuitNameFromPassportData(passportData, 'dsc')}.circom`
),
{
include: [
'node_modules',
'node_modules/@zk-kit/binary-merkle-root.circom/src',
'node_modules/circomlib/circuits',
],
}
);
});
it('should compile and load the circuit', async function () {
expect(circuit).to.not.be.undefined;
});
it('should compute a valid witness and output the right dsc_tree_leaf', async () => {
const witness = await circuit.calculateWitness(inputs, true);
await circuit.checkConstraints(witness);
console.log('\x1b[34m%s\x1b[0m', 'witness generated ', sigAlg);
const dsc_tree_leaf = (await circuit.getOutput(witness, ['dsc_tree_leaf'])).dsc_tree_leaf;
console.log('\x1b[34m%s\x1b[0m', 'circom: dsc_tree_leaf: ', dsc_tree_leaf);
expect(dsc_tree_leaf).to.be.a('string');
const dsc_tree_leaf_js = getLeafDscTreeFromParsedDsc(passportData.dsc_parsed);
console.log('\x1b[34m%s\x1b[0m', 'js: dsc_tree_leaf: ', dsc_tree_leaf_js);
expect(dsc_tree_leaf).to.be.equal(dsc_tree_leaf_js);
});
it('should fail if raw_csca_actual_length higher than the correct length', async () => {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.raw_csca_actual_length = (
Number(tamperedInputs.raw_csca_actual_length) + 1
).toString();
await circuit.calculateWitness(tamperedInputs);
expect.fail('Expected an error but none was thrown.');
} catch (error) {
expect(error.message).to.include('Assert Failed');
}
});
it('should fail if raw_csca_actual_length lower than the correct length', async () => {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.raw_csca_actual_length = (
Number(tamperedInputs.raw_csca_actual_length) - 1
).toString();
await circuit.calculateWitness(tamperedInputs);
expect.fail('Expected an error but none was thrown.');
} catch (error) {
expect(error.message).to.include('Assert Failed');
}
});
it('should fail if raw_csca[raw_csca_actual_length - 1] is not 255', async () => {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.raw_csca[Number(tamperedInputs.raw_csca_actual_length) - 1] = '254';
await circuit.calculateWitness(tamperedInputs);
expect.fail('Expected an error but none was thrown.');
} catch (error) {
expect(error.message).to.include('Assert Failed');
}
});
it('should fail if a byte of raw_csca after raw_csca_actual_length is not 0', async () => {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.raw_csca[Number(tamperedInputs.raw_csca_actual_length)] = '1';
await circuit.calculateWitness(tamperedInputs);
expect.fail('Expected an error but none was thrown.');
} catch (error) {
expect(error.message).to.include('Assert Failed');
}
});
it('should fail if csca_pubKey_actual_size is lower than the minimum key length', async () => {
try {
const dscParsed = parseCertificateSimple(passportData.dsc);
const dscMetadata = parseDscCertificateData(dscParsed);
const cscaParsed = parseCertificateSimple(dscMetadata.csca);
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
if (cscaParsed.signatureAlgorithm === 'rsa') {
tamperedInputs.csca_pubKey_actual_size = (256 - 1).toString(); // 256 is the minimum key length for RSA
} else {
// for ecdsa and rsapss, the minimum key length is fixed for each circuit
tamperedInputs.csca_pubKey_actual_size = (
Number(tamperedInputs.csca_pubKey_actual_size) - 1
).toString();
}
await circuit.calculateWitness(tamperedInputs);
expect.fail('Expected an error but none was thrown.');
} catch (error) {
expect(error.message).to.include('Assert Failed');
}
});
it('should fail if csca_pubKey_offset + csca_pubKey_actual_size > raw_csca_actual_length', async () => {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.csca_pubKey_offset = (
Number(tamperedInputs.raw_csca_actual_length) -
Number(tamperedInputs.csca_pubKey_actual_size) +
1
).toString();
await circuit.calculateWitness(tamperedInputs);
expect.fail('Expected an error but none was thrown.');
} catch (error) {
expect(error.message).to.include('Assert Failed');
}
});
it('should fail if csca_pubKey_actual_size is larger than the key in certificate', async () => {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.csca_pubKey_actual_size = (
Number(tamperedInputs.csca_pubKey_actual_size) + 8
).toString();
await circuit.calculateWitness(tamperedInputs);
expect.fail('Expected an error but none was thrown.');
} catch (error) {
expect(error.message).to.include('Assert Failed');
}
});
it('should fail if csca_pubKey is invalid', async () => {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.csca_pubKey[0] = BigInt(
Number(tamperedInputs.csca_pubKey[0]) + 1
).toString();
await circuit.calculateWitness(tamperedInputs);
expect.fail('Expected an error but none was thrown.');
} catch (error) {
expect(error.message).to.include('Assert Failed');
}
});
it('should fail to compute a witness with an invalid merkle_root', async () => {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.merkle_root = BigInt(Number(tamperedInputs.merkle_root) + 1).toString();
await circuit.calculateWitness(tamperedInputs);
expect.fail('Expected an error but none was thrown.');
} catch (error) {
expect(error.message).to.include('Assert Failed');
}
});
it('should fail to compute a witness with an invalid path', async () => {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.path[0] = BigInt(Number(tamperedInputs.path[0]) + 1).toString();
await circuit.calculateWitness(tamperedInputs);
expect.fail('Expected an error but none was thrown.');
} catch (error) {
expect(error.message).to.include('Assert Failed');
}
});
it('should fail to compute a witness with an invalid merkle proof', async () => {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.siblings[0] = BigInt(Number(tamperedInputs.siblings[0]) + 1).toString();
await circuit.calculateWitness(tamperedInputs);
expect.fail('Expected an error but none was thrown.');
} catch (error) {
expect(error.message).to.include('Assert Failed');
}
});
it('should fail to compute a witness with a dsc that is not padded with 0s after the sha padding', async () => {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.raw_dsc[tamperedInputs.raw_dsc.length - 1] = (
Number(tamperedInputs.raw_dsc[tamperedInputs.raw_dsc.length - 1]) + 1
).toString();
await circuit.calculateWitness(tamperedInputs);
expect.fail('Expected an error but none was thrown.');
} catch (error) {
expect(error.message).to.include('Assert Failed');
}
});
[64, -64, 1, -1].forEach((delta) => {
it(`should fail to compute a witness when raw_dsc_padded_length is adjusted by ${delta}`, async () => {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.raw_dsc_padded_length = (
Number(tamperedInputs.raw_dsc_padded_length) + delta
).toString();
await circuit.calculateWitness(tamperedInputs);
expect.fail('Expected an error but none was thrown.');
} catch (error) {
expect(error.message).to.include('Assert Failed');
}
});
});
it('should fail to compute a witness with an invalid signature', async () => {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.signature[0] = BigInt(Number(tamperedInputs.signature[0]) + 1).toString();
await circuit.calculateWitness(tamperedInputs);
expect.fail('Expected an error but none was thrown.');
} catch (error) {
expect(error.message).to.include('Assert Failed');
}
});
it('should fail if ECDSA public key coordinates are swapped', async () => {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
const halfLength = tamperedInputs.csca_pubKey.length / 2;
const firstHalf = tamperedInputs.csca_pubKey.slice(0, halfLength).reverse();
const secondHalf = tamperedInputs.csca_pubKey.slice(halfLength).reverse();
tamperedInputs.csca_pubKey = [...firstHalf, ...secondHalf];
await circuit.calculateWitness(tamperedInputs);
expect.fail('Expected an error but none was thrown.');
} catch (error) {
expect(error.message).to.include('Assert Failed');
}
});
if (sigAlg.startsWith('rsa') || sigAlg.startsWith('rsapss')) {
it('should fail if RSA public key prefix is invalid', async function () {
const invalidPrefixes = [
[0x03, 0x82, 0x01, 0x01, 0x00],
[0x02, 0x83, 0x01, 0x01, 0x00],
[0x02, 0x82, 0x02, 0x02, 0x00],
];
for (const invalidPrefix of invalidPrefixes) {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
for (let i = 0; i < invalidPrefix.length; i++) {
tamperedInputs.raw_csca[
Number(tamperedInputs.csca_pubKey_offset) - invalidPrefix.length + i
] = invalidPrefix[i].toString();
}
await circuit.calculateWitness(tamperedInputs);
expect.fail('Expected an error but none was thrown.');
} catch (error: any) {
expect(error.message).to.include('Assert Failed');
}
}
});
it('should pass with valid RSA prefix for the key length', async function () {
const keyLengthToPrefix = {
2048: [0x02, 0x82, 0x01, 0x01, 0x00],
3072: [0x02, 0x82, 0x01, 0x81, 0x00],
4096: [0x02, 0x82, 0x02, 0x01, 0x00],
};
const expectedPrefix = keyLengthToPrefix[passportData.csca_parsed.publicKeyDetails.bits];
for (let i = 0; i < 5; i++) {
const prefixByte = parseInt(inputs.raw_csca[Number(inputs.csca_pubKey_offset) - 5 + i]);
expect(prefixByte).to.equal(
expectedPrefix[i],
`Prefix byte ${i} mismatch for ${passportData.csca_parsed.publicKeyDetails.bits} bit key`
);
}
});
}
it('should not allow tampering of raw_dsc[raw_dsc_padded_length]', async () => {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
const paddedLength = Number(tamperedInputs.raw_dsc_padded_length);
tamperedInputs.raw_dsc[paddedLength] = '255'; // or any nonzero value
await circuit.calculateWitness(tamperedInputs);
expect.fail('Expected an error but none was thrown.');
} catch (error: any) {
expect(error.message).to.include('Assert Failed');
}
});
it('should fail if raw_csca has a signal that is longer than a byte', async function () {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.raw_csca[0] = (parseInt(tamperedInputs.raw_csca[0], 10) + 256).toString();
await circuit.calculateWitness(tamperedInputs);
expect.fail('Expected an error but none was thrown.');
} catch (error: any) {
expect(error.message).to.include('Assert Failed');
}
});
});
});