mirror of
https://github.com/selfxyz/self.git
synced 2026-01-10 07:08:10 -05:00
New OFAC updates (#47)
Co-authored-by: nicoshark <i.am.nicoshark@gmail.com>
This commit is contained in:
@@ -12,7 +12,9 @@ import { generateCircuitInputsVCandDisclose } from '../../../common/src/utils/ci
|
||||
import crypto from 'crypto';
|
||||
import { genMockPassportData } from '../../../common/src/utils/passports/genMockPassportData';
|
||||
import { SMT } from '@openpassport/zk-kit-smt';
|
||||
import namejson from '../../../common/ofacdata/outputs/nameSMT.json';
|
||||
import nameAndDobjson from '../../../common/ofacdata/outputs/nameAndDobSMT.json';
|
||||
import nameAndYobjson from '../../../common/ofacdata/outputs/nameAndYobSMT.json';
|
||||
import passportNojson from '../../../common/ofacdata/outputs/passportNoAndNationalitySMT.json';
|
||||
import {
|
||||
formatAndUnpackReveal,
|
||||
formatAndUnpackForbiddenCountriesList,
|
||||
@@ -37,8 +39,33 @@ describe('Disclose', function () {
|
||||
'300101'
|
||||
);
|
||||
passportData = initPassportDataParsing(passportData);
|
||||
let tree: any;
|
||||
let forbidden_countries_list: any;
|
||||
const forbidden_countries_list = ['ALG', 'DZA'];
|
||||
|
||||
const secret = BigInt(Math.floor(Math.random() * Math.pow(2, 254))).toString();
|
||||
const majority = '18';
|
||||
const user_identifier = crypto.randomUUID();
|
||||
const selector_dg1 = Array(88).fill('1');
|
||||
const selector_older_than = '1';
|
||||
const scope = '@coboyApp';
|
||||
const attestation_id = PASSPORT_ATTESTATION_ID;
|
||||
|
||||
// compute the commitment and insert it in the tree
|
||||
const commitment = generateCommitment(secret, attestation_id, passportData);
|
||||
console.log('commitment in js ', commitment);
|
||||
const tree: any = new LeanIMT((a, b) => poseidon2([a, b]), []);
|
||||
tree.insert(BigInt(commitment));
|
||||
|
||||
const passportNo_smt = new SMT(poseidon2, true);
|
||||
passportNo_smt.import(passportNojson);
|
||||
|
||||
const nameAndDob_smt = new SMT(poseidon2, true);
|
||||
nameAndDob_smt.import(nameAndDobjson);
|
||||
|
||||
const nameAndYob_smt = new SMT(poseidon2, true);
|
||||
nameAndYob_smt.import(nameAndYobjson);
|
||||
|
||||
const selector_ofac = 1;
|
||||
|
||||
before(async () => {
|
||||
circuit = await wasm_tester(
|
||||
path.join(__dirname, '../../circuits/disclose/vc_and_disclose.circom'),
|
||||
@@ -51,26 +78,6 @@ describe('Disclose', function () {
|
||||
}
|
||||
);
|
||||
|
||||
const secret = BigInt(Math.floor(Math.random() * Math.pow(2, 254))).toString();
|
||||
|
||||
const majority = '18';
|
||||
const user_identifier = crypto.randomUUID();
|
||||
const selector_dg1 = Array(88).fill('1');
|
||||
const selector_older_than = '1';
|
||||
const scope = '@coboyApp';
|
||||
const attestation_id = PASSPORT_ATTESTATION_ID;
|
||||
|
||||
// compute the commitment and insert it in the tree
|
||||
const commitment = generateCommitment(secret, attestation_id, passportData);
|
||||
console.log('commitment in js ', commitment);
|
||||
tree = new LeanIMT((a, b) => poseidon2([a, b]), []);
|
||||
tree.insert(BigInt(commitment));
|
||||
let smt = new SMT(poseidon2, true);
|
||||
smt.import(namejson);
|
||||
|
||||
const selector_ofac = 1;
|
||||
forbidden_countries_list = ['ALG', 'DZA'];
|
||||
|
||||
inputs = generateCircuitInputsVCandDisclose(
|
||||
secret,
|
||||
PASSPORT_ATTESTATION_ID,
|
||||
@@ -80,7 +87,9 @@ describe('Disclose', function () {
|
||||
selector_older_than,
|
||||
tree,
|
||||
majority,
|
||||
smt,
|
||||
passportNo_smt,
|
||||
nameAndDob_smt,
|
||||
nameAndYob_smt,
|
||||
selector_ofac,
|
||||
forbidden_countries_list,
|
||||
user_identifier
|
||||
@@ -199,4 +208,190 @@ describe('Disclose', function () {
|
||||
expect(reveal_unpacked[88]).to.equal('\x00');
|
||||
expect(reveal_unpacked[89]).to.equal('\x00');
|
||||
});
|
||||
|
||||
describe('OFAC disclosure', function () {
|
||||
it('should allow disclosing OFAC check result when selector is 1', async function () {
|
||||
w = await circuit.calculateWitness(inputs);
|
||||
|
||||
const revealedData_packed = await circuit.getOutput(w, ['revealedData_packed[3]']);
|
||||
const reveal_unpacked = formatAndUnpackReveal(revealedData_packed);
|
||||
|
||||
console.log('reveal_unpacked', reveal_unpacked);
|
||||
// OFAC result is stored at index 90 in the revealed data
|
||||
const ofac_results = reveal_unpacked.slice(90, 93);
|
||||
|
||||
console.log('ofac_results', ofac_results);
|
||||
|
||||
expect(ofac_results).to.deep.equal(
|
||||
['\x01', '\x01', '\x01'],
|
||||
'OFAC result bits should be [1, 1, 1]'
|
||||
);
|
||||
expect(ofac_results).to.not.equal(['\x00', '\x00', '\x00'], 'OFAC result should be revealed');
|
||||
});
|
||||
|
||||
it('should not disclose OFAC check result when selector is 0', async function () {
|
||||
w = await circuit.calculateWitness({
|
||||
...inputs,
|
||||
selector_ofac: '0',
|
||||
});
|
||||
|
||||
const revealedData_packed = await circuit.getOutput(w, ['revealedData_packed[3]']);
|
||||
const reveal_unpacked = formatAndUnpackReveal(revealedData_packed);
|
||||
|
||||
// OFAC result should be hidden (null byte)
|
||||
const ofac_result = reveal_unpacked[90];
|
||||
expect(ofac_result).to.equal('\x00', 'OFAC result should not be revealed');
|
||||
});
|
||||
|
||||
it('should show different levels of OFAC matching', async function () {
|
||||
// Test cases for different matching scenarios
|
||||
const testCases = [
|
||||
{
|
||||
desc: 'No details match',
|
||||
data: genMockPassportData(
|
||||
'sha256',
|
||||
'sha256',
|
||||
'rsa_sha256_65537_2048',
|
||||
'USA',
|
||||
'010101',
|
||||
'300101',
|
||||
'DIF123456',
|
||||
'DIFFERENT NAME',
|
||||
'DIFFERENT SURNAME'
|
||||
),
|
||||
expectedBits: ['\x01', '\x01', '\x01'],
|
||||
},
|
||||
{
|
||||
desc: 'Only passport number matches',
|
||||
data: genMockPassportData(
|
||||
'sha256',
|
||||
'sha256',
|
||||
'rsa_sha256_65537_2048',
|
||||
'ESP', // different nationality
|
||||
'000101',
|
||||
'300101',
|
||||
'98lh90556', // Matching passport number
|
||||
'DIFFERENT NAME',
|
||||
'DIFFERENT SURNAME'
|
||||
),
|
||||
expectedBits: ['\x01', '\x01', '\x01'],
|
||||
},
|
||||
{
|
||||
desc: 'Only nationality matches',
|
||||
data: genMockPassportData(
|
||||
'sha256',
|
||||
'sha256',
|
||||
'rsa_sha256_65537_2048',
|
||||
'FRA',
|
||||
'991231',
|
||||
'300101',
|
||||
'DIF123456', // different passport number
|
||||
'DIFFERENT NAME',
|
||||
'DIFFERENT SURNAME'
|
||||
),
|
||||
expectedBits: ['\x01', '\x01', '\x01'],
|
||||
},
|
||||
{
|
||||
desc: 'Only passport number and nationality matches',
|
||||
data: genMockPassportData(
|
||||
'sha256',
|
||||
'sha256',
|
||||
'rsa_sha256_65537_2048',
|
||||
'FRA',
|
||||
'991231',
|
||||
'300101',
|
||||
'98lh90556',
|
||||
'DIFFERENT NAME',
|
||||
'DIFFERENT SURNAME'
|
||||
),
|
||||
expectedBits: ['\x00', '\x01', '\x01'],
|
||||
},
|
||||
{
|
||||
desc: 'Name and DOB matches (so YOB matches too)',
|
||||
data: genMockPassportData(
|
||||
'sha256',
|
||||
'sha256',
|
||||
'rsa_sha256_65537_2048',
|
||||
'FRA',
|
||||
'541007',
|
||||
'300101',
|
||||
'DIF123456',
|
||||
'HENAO MONTOYA',
|
||||
'ARCANGEL DE JESUS'
|
||||
),
|
||||
expectedBits: ['\x01', '\x00', '\x00'],
|
||||
},
|
||||
{
|
||||
desc: 'Only name and YOB match',
|
||||
data: genMockPassportData(
|
||||
'sha256',
|
||||
'sha256',
|
||||
'rsa_sha256_65537_2048',
|
||||
'FRA',
|
||||
'541299',
|
||||
'300101', // Same year (54) different month/day
|
||||
'DIF123456',
|
||||
'HENAO MONTOYA',
|
||||
'ARCANGEL DE JESUS'
|
||||
),
|
||||
expectedBits: ['\x01', '\x01', '\x00'],
|
||||
},
|
||||
{
|
||||
desc: 'All details match',
|
||||
data: genMockPassportData(
|
||||
'sha256',
|
||||
'sha256',
|
||||
'rsa_sha256_65537_2048',
|
||||
'FRA',
|
||||
'541007',
|
||||
'300101',
|
||||
'98lh90556',
|
||||
'HENAO MONTOYA',
|
||||
'ARCANGEL DE JESUS'
|
||||
),
|
||||
expectedBits: ['\x00', '\x00', '\x00'],
|
||||
},
|
||||
];
|
||||
|
||||
for (const testCase of testCases) {
|
||||
console.log(`Testing: ${testCase.desc}`);
|
||||
|
||||
const passportData = initPassportDataParsing(testCase.data);
|
||||
const sanctionedCommitment = generateCommitment(
|
||||
secret,
|
||||
PASSPORT_ATTESTATION_ID,
|
||||
passportData
|
||||
);
|
||||
tree.insert(BigInt(sanctionedCommitment));
|
||||
|
||||
const testInputs = generateCircuitInputsVCandDisclose(
|
||||
secret,
|
||||
PASSPORT_ATTESTATION_ID,
|
||||
passportData,
|
||||
scope,
|
||||
Array(88).fill('0'), // selector_dg1
|
||||
selector_older_than,
|
||||
tree,
|
||||
majority,
|
||||
passportNo_smt,
|
||||
nameAndDob_smt,
|
||||
nameAndYob_smt,
|
||||
'1', // selector_ofac
|
||||
forbidden_countries_list,
|
||||
user_identifier
|
||||
);
|
||||
|
||||
w = await circuit.calculateWitness(testInputs);
|
||||
const revealedData_packed = await circuit.getOutput(w, ['revealedData_packed[3]']);
|
||||
const reveal_unpacked = formatAndUnpackReveal(revealedData_packed);
|
||||
const ofac_results = reveal_unpacked.slice(90, 93);
|
||||
|
||||
console.log(`${testCase.desc} - OFAC bits:`, ofac_results);
|
||||
expect(ofac_results).to.deep.equal(
|
||||
testCase.expectedBits,
|
||||
`Failed matching pattern for: ${testCase.desc}`
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,14 +4,14 @@ import { wasm as wasm_tester } from 'circom_tester';
|
||||
import { generateCircuitInputsOfac } from '../../../common/src/utils/circuits/generateInputs';
|
||||
import { SMT } from '@openpassport/zk-kit-smt';
|
||||
import { poseidon2 } from 'poseidon-lite';
|
||||
import passportNojson from '../../../common/ofacdata/outputs/passportNoSMT.json';
|
||||
import nameDobjson from '../../../common/ofacdata/outputs/nameDobSMT.json';
|
||||
import namejson from '../../../common/ofacdata/outputs/nameSMT.json';
|
||||
import passportNoAndNationalityjson from '../../../common/ofacdata/outputs/passportNoAndNationalitySMT.json';
|
||||
import nameAndDobjson from '../../../common/ofacdata/outputs/nameAndDobSMT.json';
|
||||
import nameAndYobjson from '../../../common/ofacdata/outputs/nameAndYobSMT.json';
|
||||
import { genMockPassportData } from '../../../common/src/utils/passports/genMockPassportData';
|
||||
|
||||
let circuit: any;
|
||||
|
||||
// Mock passport added in ofac list to test circuits
|
||||
// Mock passport not added in ofac list
|
||||
const passportData = genMockPassportData(
|
||||
'sha256',
|
||||
'sha256',
|
||||
@@ -20,7 +20,7 @@ const passportData = genMockPassportData(
|
||||
'040211',
|
||||
'300101'
|
||||
);
|
||||
// Mock passport not added in ofac list to test circuits
|
||||
// Mock passport in ofac list
|
||||
const passportDataInOfac = genMockPassportData(
|
||||
'sha256',
|
||||
'sha256',
|
||||
@@ -39,19 +39,16 @@ const passportDataInOfac = genMockPassportData(
|
||||
// 2. Invalid proof : Correct path and corresponding closest leaf AND leaf == pasport_hash ; Valid prove of membership ; Hence non-membership proof would fail
|
||||
// 3. Invalid proof : Correct path but wrong corresponding siblings ; fails due to calculatedRoot != smt_root
|
||||
|
||||
// Level 3: Passport number match in OfacList
|
||||
describe('OFAC - Passport number match', function () {
|
||||
// Level 3: Passport number and Nationality match in OfacList
|
||||
describe('OFAC - Passport number and Nationality match', function () {
|
||||
this.timeout(0);
|
||||
let passno_smt = new SMT(poseidon2, true);
|
||||
let passNoAndNationality_smt = new SMT(poseidon2, true);
|
||||
let memSmtInputs: any;
|
||||
let nonMemSmtInputs: any;
|
||||
|
||||
before(async () => {
|
||||
circuit = await wasm_tester(
|
||||
path.join(
|
||||
__dirname,
|
||||
'../../circuits/ofac/../../circuits/tests/ofac/ofac_passport_number_tester.circom'
|
||||
),
|
||||
path.join(__dirname, '../../circuits/tests/ofac/ofac_passport_number_tester.circom'),
|
||||
{
|
||||
include: [
|
||||
'node_modules',
|
||||
@@ -61,11 +58,17 @@ describe('OFAC - Passport number match', function () {
|
||||
}
|
||||
);
|
||||
|
||||
passno_smt.import(passportNojson);
|
||||
const proofLevel = 3;
|
||||
memSmtInputs = generateCircuitInputsOfac(passportDataInOfac, passno_smt, proofLevel);
|
||||
passNoAndNationality_smt.import(passportNoAndNationalityjson);
|
||||
memSmtInputs = generateCircuitInputsOfac(
|
||||
passportDataInOfac,
|
||||
passNoAndNationality_smt,
|
||||
proofLevel
|
||||
);
|
||||
// console.log('memSmtInputs', memSmtInputs);
|
||||
|
||||
nonMemSmtInputs = generateCircuitInputsOfac(passportData, passno_smt, proofLevel);
|
||||
nonMemSmtInputs = generateCircuitInputsOfac(passportData, passNoAndNationality_smt, proofLevel);
|
||||
// console.log('nonMemSmtInputs', nonMemSmtInputs);
|
||||
});
|
||||
|
||||
// Compile circuit
|
||||
@@ -73,7 +76,7 @@ describe('OFAC - Passport number match', function () {
|
||||
expect(circuit).to.not.be.undefined;
|
||||
});
|
||||
|
||||
// Correct siblings and closest leaf : Everything correct as a proof
|
||||
// Correct siblings and closest leaf: Everything correct as a proof
|
||||
it('should pass without errors, all conditions satisfied', async function () {
|
||||
let w = await circuit.calculateWitness(nonMemSmtInputs);
|
||||
const ofacCheckResult = (await circuit.getOutput(w, ['ofacCheckResult'])).ofacCheckResult;
|
||||
@@ -87,7 +90,7 @@ describe('OFAC - Passport number match', function () {
|
||||
expect(ofacCheckResult).to.equal('0');
|
||||
});
|
||||
|
||||
// Give wrong closest leaf but correct siblings array : Fail of SMT Verification
|
||||
// Give wrong closest leaf but correct siblings array: Fail of SMT Verification
|
||||
it('should pass - wrong merkleroot, level 3', async function () {
|
||||
const wrongInputs = {
|
||||
...nonMemSmtInputs,
|
||||
@@ -118,7 +121,7 @@ describe('OFAC - Name and DOB match', function () {
|
||||
}
|
||||
);
|
||||
|
||||
namedob_smt.import(nameDobjson);
|
||||
namedob_smt.import(nameAndDobjson);
|
||||
const proofLevel = 2;
|
||||
memSmtInputs = generateCircuitInputsOfac(
|
||||
// proof of membership
|
||||
@@ -167,8 +170,8 @@ describe('OFAC - Name and DOB match', function () {
|
||||
});
|
||||
});
|
||||
|
||||
// Level 1: Name match in OfacList
|
||||
describe('OFAC - Name match', function () {
|
||||
// Level 1: Name and YOB match in OfacList
|
||||
describe('OFAC - Name and YOB match', function () {
|
||||
this.timeout(0);
|
||||
let name_smt = new SMT(poseidon2, true);
|
||||
let memSmtInputs: any;
|
||||
@@ -176,7 +179,7 @@ describe('OFAC - Name match', function () {
|
||||
|
||||
before(async () => {
|
||||
circuit = await wasm_tester(
|
||||
path.join(__dirname, '../../circuits/tests/ofac/ofac_name_tester.circom'),
|
||||
path.join(__dirname, '../../circuits/tests/ofac/ofac_name_yob_tester.circom'),
|
||||
{
|
||||
include: [
|
||||
'node_modules',
|
||||
@@ -186,7 +189,7 @@ describe('OFAC - Name match', function () {
|
||||
}
|
||||
);
|
||||
|
||||
name_smt.import(namejson);
|
||||
name_smt.import(nameAndYobjson);
|
||||
const proofLevel = 1;
|
||||
memSmtInputs = generateCircuitInputsOfac(
|
||||
// proof of membership
|
||||
@@ -233,3 +236,101 @@ describe('OFAC - Name match', function () {
|
||||
expect(ofacCheckResult).to.equal('0');
|
||||
});
|
||||
});
|
||||
|
||||
describe.only('OFAC - SMT Security Tests', function () {
|
||||
this.timeout(0);
|
||||
let passNoAndNationality_smt = new SMT(poseidon2, true);
|
||||
let circuit: any;
|
||||
let baseInputs: any;
|
||||
|
||||
before(async () => {
|
||||
circuit = await wasm_tester(
|
||||
path.join(__dirname, '../../circuits/tests/ofac/ofac_passport_number_tester.circom'),
|
||||
{
|
||||
include: [
|
||||
'node_modules',
|
||||
'./node_modules/@zk-kit/binary-merkle-root.circom/src',
|
||||
'./node_modules/circomlib/circuits',
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
passNoAndNationality_smt.import(passportNoAndNationalityjson);
|
||||
const proofLevel = 3;
|
||||
baseInputs = generateCircuitInputsOfac(passportData, passNoAndNationality_smt, proofLevel);
|
||||
});
|
||||
|
||||
it('should reject proof with invalid siblings length', async function () {
|
||||
const overflowInputs = {
|
||||
...baseInputs,
|
||||
smt_siblings: Array(65)
|
||||
.fill('0')
|
||||
.map((x) => x.toString()), // More siblings than tree depth
|
||||
};
|
||||
|
||||
try {
|
||||
await circuit.calculateWitness(overflowInputs);
|
||||
expect.fail('Should have thrown error');
|
||||
} catch (err) {
|
||||
expect(err.toString()).to.include('Too many values for input signal');
|
||||
}
|
||||
});
|
||||
|
||||
it('should reject proof with malformed path bits', async function () {
|
||||
const malformedPathInputs = {
|
||||
...baseInputs,
|
||||
// Modify one of the siblings to be an invalid value
|
||||
smt_siblings: baseInputs.smt_siblings.map((x: string, i: number) =>
|
||||
i === 0 ? '2'.repeat(254) : x
|
||||
),
|
||||
};
|
||||
|
||||
try {
|
||||
await circuit.calculateWitness(malformedPathInputs);
|
||||
expect.fail('Should have thrown error');
|
||||
} catch (err) {
|
||||
expect(err.toString()).to.include('Error');
|
||||
}
|
||||
});
|
||||
|
||||
// Test against zero value attack
|
||||
it('should handle zero values in siblings array correctly', async function () {
|
||||
const zeroSiblingsInputs = {
|
||||
...baseInputs,
|
||||
smt_siblings: Array(64)
|
||||
.fill('0')
|
||||
.map((x) => x.toString()),
|
||||
};
|
||||
|
||||
let w = await circuit.calculateWitness(zeroSiblingsInputs);
|
||||
const ofacCheckResult = (await circuit.getOutput(w, ['ofacCheckResult'])).ofacCheckResult;
|
||||
expect(ofacCheckResult).to.equal('0');
|
||||
});
|
||||
|
||||
// Test against incorrect tree height
|
||||
it('should reject proof with incorrect number of siblings', async function () {
|
||||
const wrongHeightInputs = {
|
||||
...baseInputs,
|
||||
smt_siblings: baseInputs.smt_siblings.slice(0, 32), // Only half the siblings
|
||||
};
|
||||
|
||||
try {
|
||||
await circuit.calculateWitness(wrongHeightInputs);
|
||||
expect.fail('Should have thrown error');
|
||||
} catch (err) {
|
||||
expect(err.toString()).to.include('Not enough values for input signal');
|
||||
}
|
||||
});
|
||||
|
||||
// Test against invalid root
|
||||
it('should reject proof with invalid merkle root', async function () {
|
||||
const invalidRootInputs = {
|
||||
...baseInputs,
|
||||
smt_root: (BigInt(baseInputs.smt_root) ^ 1n).toString(), // Modify smt_root by one bit
|
||||
};
|
||||
|
||||
let w = await circuit.calculateWitness(invalidRootInputs);
|
||||
const ofacCheckResult = (await circuit.getOutput(w, ['ofacCheckResult'])).ofacCheckResult;
|
||||
expect(ofacCheckResult).to.equal('0');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user