mirror of
https://github.com/selfxyz/self.git
synced 2026-02-19 02:24:25 -05:00
355 lines
14 KiB
TypeScript
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');
|
|
}
|
|
});
|
|
});
|
|
});
|