Chore/refactor contracts (#582)

* add eu id support

* add ofac and disclosure euid support

* add contract support for euid cards

* update contracts

* add comment out to remember the interface what we need to implement

* prettier

* get combine verifyVcAndDisclose function and get bytes data

* unified library and clean constant V2

* fix verifyVcAndDisclose interface

* add prettier and run prettier except .sol files

* prettier

* circuit compilation for local environment

* add register id circuit in the contracts dir

* clean up ignition deploy scritps

* refactor deploy scripts

* prettier

* update serialized dsc tree

* fix ofac check

* fix passport attestation id

---------

Co-authored-by: turnoffthiscomputer <colin.remi07@gmail.com>
This commit is contained in:
nicoshark
2025-06-03 14:58:41 +09:00
committed by GitHub
parent 5cec8a0f9f
commit 668732a57b
33 changed files with 1583 additions and 17549 deletions

View File

@@ -4,10 +4,11 @@
"license": "MIT",
"author": "",
"scripts": {
"build-all": "bash scripts/build/build_register_circuits.sh && bash scripts/build/build_dsc_circuits.sh && bash scripts/build/build_disclose_circuits.sh",
"build-all": "bash scripts/build/build_register_circuits.sh && bash scripts/build/build_register_circuits_id.sh && bash scripts/build/build_dsc_circuits.sh && bash scripts/build/build_disclose_circuits.sh",
"build-disclose": "bash scripts/build/build_disclose_circuits.sh",
"build-dsc": "bash scripts/build/build_dsc_circuits.sh",
"build-register": "bash scripts/build/build_register_circuits.sh",
"build-register-id": "bash scripts/build/build_register_circuits_id.sh",
"download": "bash scripts/server/download_circuits_from_AWS.sh",
"format": "prettier --write .",
"nice": "prettier --write .",
@@ -74,4 +75,4 @@
"ts-mocha": "^10.0.0",
"ts-node": "^10.9.2"
}
}
}

View File

@@ -2,6 +2,12 @@
source "scripts/build/common.sh"
# Set environment (change this value as needed)
# ENV="prod"
ENV="staging"
echo -e "${GREEN}Building disclose circuits for $ENV environment${NC}"
# Circuit-specific configurations
CIRCUIT_TYPE="disclose"
OUTPUT_DIR="build/${CIRCUIT_TYPE}"
@@ -12,4 +18,7 @@ CIRCUITS=(
"vc_and_disclose:20:true"
)
build_circuits "$CIRCUIT_TYPE" "$OUTPUT_DIR" "${CIRCUITS[@]}"
build_circuits "$CIRCUIT_TYPE" "$OUTPUT_DIR" "${CIRCUITS[@]}"
echo -e "${GREEN}Disclose circuits build completed for $ENV environment!${NC}"
echo -e "${YELLOW}Generated files are located in: contracts/verifiers/local/${ENV}/${CIRCUIT_TYPE}/${NC}"

View File

@@ -2,6 +2,12 @@
source "scripts/build/common.sh"
# Set environment (change this value as needed)
# ENV="prod"
ENV="staging"
echo -e "${GREEN}Building DSC circuits for $ENV environment${NC}"
# Circuit-specific configurations
CIRCUIT_TYPE="dsc"
OUTPUT_DIR="build/${CIRCUIT_TYPE}"
@@ -33,4 +39,7 @@ CIRCUITS=(
"dsc_sha512_rsapss_65537_64_4096:22:false"
)
build_circuits "$CIRCUIT_TYPE" "$OUTPUT_DIR" "${CIRCUITS[@]}"
build_circuits "$CIRCUIT_TYPE" "$OUTPUT_DIR" "${CIRCUITS[@]}"
echo -e "${GREEN}DSC circuits build completed for $ENV environment!${NC}"
echo -e "${YELLOW}Generated files are located in: contracts/verifiers/local/${ENV}/${CIRCUIT_TYPE}/${NC}"

View File

@@ -2,6 +2,12 @@
source "scripts/build/common.sh"
# Set environment (change this value as needed)
# ENV="prod"
ENV="staging"
echo -e "${GREEN}Building register circuits for $ENV environment${NC}"
# Circuit-specific configurations
CIRCUIT_TYPE="register"
OUTPUT_DIR="build/${CIRCUIT_TYPE}"
@@ -31,4 +37,7 @@ CIRCUITS=(
"register_sha512_sha512_sha512_rsa_65537_4096:21:false"
)
build_circuits "$CIRCUIT_TYPE" "$OUTPUT_DIR" "${CIRCUITS[@]}"
build_circuits "$CIRCUIT_TYPE" "$OUTPUT_DIR" "${CIRCUITS[@]}"
echo -e "${GREEN}Register circuits build completed for $ENV environment!${NC}"
echo -e "${YELLOW}Generated files are located in: contracts/verifiers/local/${ENV}/${CIRCUIT_TYPE}/${NC}"

View File

@@ -2,6 +2,12 @@
source "scripts/build/common.sh"
# Set environment (change this value as needed)
# ENV="prod"
ENV="staging"
echo -e "${GREEN}Building register_id circuits for $ENV environment${NC}"
# Circuit-specific configurations
CIRCUIT_TYPE="register_id"
OUTPUT_DIR="build/${CIRCUIT_TYPE}"
@@ -12,4 +18,7 @@ CIRCUITS=(
"register_id_sha256_sha256_sha256_rsa_65537_4096:20:true"
)
build_circuits "$CIRCUIT_TYPE" "$OUTPUT_DIR" "${CIRCUITS[@]}"
build_circuits "$CIRCUIT_TYPE" "$OUTPUT_DIR" "${CIRCUITS[@]}"
echo -e "${GREEN}Register_id circuits build completed for $ENV environment!${NC}"
echo -e "${YELLOW}Generated files are located in: contracts/verifiers/local/${ENV}/${CIRCUIT_TYPE}/${NC}"

View File

@@ -7,6 +7,24 @@ YELLOW='\033[0;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Environment validation
validate_environment() {
if [ -z "$ENV" ]; then
echo -e "${RED}Error: ENV variable is not set${NC}"
echo -e "${YELLOW}Please set ENV to 'prod' or 'staging' in your build script${NC}"
echo -e "${YELLOW}Example: ENV=\"prod\"${NC}"
exit 1
fi
if [ "$ENV" != "prod" ] && [ "$ENV" != "staging" ]; then
echo -e "${RED}Error: ENV must be 'prod' or 'staging'${NC}"
echo -e "${YELLOW}Current value: $ENV${NC}"
exit 1
fi
echo -e "${GREEN}Environment set to: $ENV${NC}"
}
download_ptau() {
local POWEROFTAU=$1
mkdir -p build
@@ -15,7 +33,7 @@ download_ptau() {
echo -e "${YELLOW}Download power of tau....${NC}"
wget https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_${POWEROFTAU}.ptau
echo -e "${GREEN}Finished download!${NC}"
else
else
echo -e "${YELLOW}Powers of tau file already downloaded${NC}"
fi
cd ..
@@ -36,11 +54,14 @@ build_circuit() {
local OUTPUT_DIR=$4
local START_TIME=$(date +%s)
echo -e "${BLUE}Compiling circuit: $CIRCUIT_NAME${NC}"
# Validate environment before building
validate_environment
echo -e "${BLUE}Compiling circuit: $CIRCUIT_NAME for $ENV environment${NC}"
# Create output directory
mkdir -p ${OUTPUT_DIR}/${CIRCUIT_NAME}/
# Set circuit path based on CIRCUIT_TYPE
local CIRCUIT_PATH
if [ "$CIRCUIT_TYPE" = "register" ] || [ "$CIRCUIT_TYPE" = "dsc" ] || [ "$CIRCUIT_TYPE" = "register_id" ]; then
@@ -48,7 +69,7 @@ build_circuit() {
else
CIRCUIT_PATH="circuits/${CIRCUIT_TYPE}/${CIRCUIT_NAME}.circom"
fi
# Compile circuit
circom ${CIRCUIT_PATH} \
-l node_modules \
@@ -74,10 +95,21 @@ build_circuit() {
${OUTPUT_DIR}/${CIRCUIT_NAME}/${CIRCUIT_NAME}_final.zkey \
${OUTPUT_DIR}/${CIRCUIT_NAME}/${CIRCUIT_NAME}_vkey.json
# Generate and copy Solidity verifier
# Generate Solidity verifier with environment-specific naming
local VERIFIER_CONTRACT_NAME
local VERIFIER_FILE_NAME
if [ "$ENV" = "staging" ]; then
VERIFIER_CONTRACT_NAME="Verifier_${CIRCUIT_NAME}_${ENV}"
VERIFIER_FILE_NAME="Verifier_${CIRCUIT_NAME}_${ENV}.sol"
else
VERIFIER_CONTRACT_NAME="Verifier_${CIRCUIT_NAME}"
VERIFIER_FILE_NAME="Verifier_${CIRCUIT_NAME}.sol"
fi
yarn snarkjs zkey export solidityverifier \
${OUTPUT_DIR}/${CIRCUIT_NAME}/${CIRCUIT_NAME}_final.zkey \
${OUTPUT_DIR}/${CIRCUIT_NAME}/Verifier_${CIRCUIT_NAME}.sol
${OUTPUT_DIR}/${CIRCUIT_NAME}/${VERIFIER_FILE_NAME}
OS=""
@@ -94,23 +126,27 @@ build_circuit() {
;;
esac
# Replace contract name with environment-specific name
if [ "$OS" = 'Mac' ]; then
sed -i '' "s/Groth16Verifier/Verifier_${CIRCUIT_NAME}/g" \
${OUTPUT_DIR}/${CIRCUIT_NAME}/Verifier_${CIRCUIT_NAME}.sol
sed -i '' "s/Groth16Verifier/${VERIFIER_CONTRACT_NAME}/g" \
${OUTPUT_DIR}/${CIRCUIT_NAME}/${VERIFIER_FILE_NAME}
elif [ "$OS" = 'Linux' ]; then
sed -i "s/Groth16Verifier/Verifier_${CIRCUIT_NAME}/g" \
${OUTPUT_DIR}/${CIRCUIT_NAME}/Verifier_${CIRCUIT_NAME}.sol
sed -i "s/Groth16Verifier/${VERIFIER_CONTRACT_NAME}/g" \
${OUTPUT_DIR}/${CIRCUIT_NAME}/${VERIFIER_FILE_NAME}
fi
# Copy verifier to contracts directory
mkdir -p ../contracts/contracts/verifiers/local/${CIRCUIT_TYPE}/
cp ${OUTPUT_DIR}/${CIRCUIT_NAME}/Verifier_${CIRCUIT_NAME}.sol \
../contracts/contracts/verifiers/local/${CIRCUIT_TYPE}/Verifier_${CIRCUIT_NAME}.sol
echo -e "${BLUE}Copied Verifier_${CIRCUIT_NAME}.sol to contracts${NC}"
# Copy verifier to environment-specific contracts directory
local CONTRACTS_OUTPUT_DIR="../contracts/contracts/verifiers/local/${ENV}/${CIRCUIT_TYPE}/"
mkdir -p ${CONTRACTS_OUTPUT_DIR}
cp ${OUTPUT_DIR}/${CIRCUIT_NAME}/${VERIFIER_FILE_NAME} \
${CONTRACTS_OUTPUT_DIR}${VERIFIER_FILE_NAME}
echo -e "${BLUE}Copied ${VERIFIER_FILE_NAME} to contracts/${ENV}/${CIRCUIT_TYPE}/${NC}"
# Print build statistics
echo -e "${GREEN}Build of $CIRCUIT_NAME completed in $(($(date +%s) - START_TIME)) seconds${NC}"
echo -e "${GREEN}Build of $CIRCUIT_NAME for $ENV completed in $(($(date +%s) - START_TIME)) seconds${NC}"
echo -e "${BLUE}Contract name: ${VERIFIER_CONTRACT_NAME}${NC}"
echo -e "${BLUE}Output path: ${CONTRACTS_OUTPUT_DIR}${VERIFIER_FILE_NAME}${NC}"
echo -e "${BLUE}Size of ${CIRCUIT_NAME}.r1cs: $(wc -c < ${OUTPUT_DIR}/${CIRCUIT_NAME}/${CIRCUIT_NAME}.r1cs) bytes${NC}"
echo -e "${BLUE}Size of ${CIRCUIT_NAME}.wasm: $(wc -c < ${OUTPUT_DIR}/${CIRCUIT_NAME}/${CIRCUIT_NAME}_js/${CIRCUIT_NAME}.wasm) bytes${NC}"
echo -e "${BLUE}Size of ${CIRCUIT_NAME}_final.zkey: $(wc -c < ${OUTPUT_DIR}/${CIRCUIT_NAME}/${CIRCUIT_NAME}_final.zkey) bytes${NC}"
@@ -120,7 +156,7 @@ build_circuits() {
local CIRCUITS=("$@")
local CIRCUIT_TYPE="$1"
local OUTPUT_DIR="$2"
shift 2
shift 2
local TOTAL_START_TIME=$(date +%s)
# Build circuits
@@ -139,4 +175,4 @@ build_circuits() {
done
echo -e "${GREEN}Total completed in $(($(date +%s) - TOTAL_START_TIME)) seconds${NC}"
}
}

View File

@@ -3,8 +3,8 @@ import { assert, expect } from 'chai';
import path from 'path';
import { wasm as wasm_tester } from 'circom_tester';
import {
attributeToPosition_ID,
ID_CARD_ATTESTATION_ID
attributeToPosition_ID,
ID_CARD_ATTESTATION_ID,
} from '../../../common/src/constants/constants';
import { poseidon1, poseidon2 } from 'poseidon-lite';
import { LeanIMT } from '@openpassport/zk-kit-lean-imt';
@@ -15,283 +15,276 @@ import nameAndDobjson from '../../../common/ofacdata/outputs/nameAndDobSMT_ID.js
import nameAndYobjson from '../../../common/ofacdata/outputs/nameAndYobSMT_ID.json';
import passportNojson from '../../../common/ofacdata/outputs/passportNoAndNationalitySMT.json';
import {
formatAndUnpackForbiddenCountriesList,
formatAndUnpackReveal,
getAttributeFromUnpackedReveal,
formatAndUnpackForbiddenCountriesList,
formatAndUnpackReveal,
getAttributeFromUnpackedReveal,
} from '../../../common/src/utils/circuits/formatOutputs';
import { generateCommitment } from '../../../common/src/utils/passports/passport';
import { hashEndpointWithScope } from '../../../common/src/utils/scope';
import { genMockIdDoc } from '../../../common/src/utils/passports/genMockIdDoc';
describe('Disclose', function () {
this.timeout(0);
let inputs: any;
let circuit: any;
let w: any;
const passportData = genMockIdDoc({
idType: 'mock_id_card'
});
console.log(passportData.mrz);
const forbidden_countries_list = ['ALG', 'DZA'];
this.timeout(0);
let inputs: any;
let circuit: any;
let w: any;
const passportData = genMockIdDoc({
idType: 'mock_id_card',
});
console.log(passportData.mrz);
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(90).fill('1');
const selector_older_than = '1';
const endpoint = 'https://example.com';
const scope = 'scope';
const fullScope = hashEndpointWithScope(endpoint, scope);
const attestation_id = ID_CARD_ATTESTATION_ID;
const secret = BigInt(Math.floor(Math.random() * Math.pow(2, 254))).toString();
const majority = '18';
const user_identifier = crypto.randomUUID();
const selector_dg1 = Array(90).fill('1');
const selector_older_than = '1';
const endpoint = 'https://example.com';
const scope = 'scope';
const fullScope = hashEndpointWithScope(endpoint, scope);
const attestation_id = ID_CARD_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));
// 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 passportNo_smt = new SMT(poseidon2, true);
passportNo_smt.import(passportNojson);
const nameAndDob_smt = new SMT(poseidon2, true);
nameAndDob_smt.import(nameAndDobjson);
const nameAndDob_smt = new SMT(poseidon2, true);
nameAndDob_smt.import(nameAndDobjson);
const nameAndYob_smt = new SMT(poseidon2, true);
nameAndYob_smt.import(nameAndYobjson);
const nameAndYob_smt = new SMT(poseidon2, true);
nameAndYob_smt.import(nameAndYobjson);
const selector_ofac = 1;
const selector_ofac = 1;
before(async () => {
circuit = await wasm_tester(
path.join(__dirname, '../../circuits/disclose/vc_and_disclose_id.circom'),
{
include: [
'node_modules',
'./node_modules/@zk-kit/binary-merkle-root.circom/src',
'./node_modules/circomlib/circuits',
],
}
);
before(async () => {
circuit = await wasm_tester(
path.join(__dirname, '../../circuits/disclose/vc_and_disclose_id.circom'),
{
include: [
'node_modules',
'./node_modules/@zk-kit/binary-merkle-root.circom/src',
'./node_modules/circomlib/circuits',
],
}
);
inputs = generateCircuitInputsVCandDisclose(
secret,
ID_CARD_ATTESTATION_ID,
passportData,
fullScope,
selector_dg1,
selector_older_than,
tree,
majority,
passportNo_smt,
nameAndDob_smt,
nameAndYob_smt,
selector_ofac,
forbidden_countries_list,
user_identifier
);
});
inputs = generateCircuitInputsVCandDisclose(
secret,
ID_CARD_ATTESTATION_ID,
passportData,
fullScope,
selector_dg1,
selector_older_than,
tree,
majority,
passportNo_smt,
nameAndDob_smt,
nameAndYob_smt,
selector_ofac,
forbidden_countries_list,
user_identifier
);
});
// it('should compile and load the circuit', async function () {
// expect(circuit).to.not.be.undefined;
// });
// it('should compile and load the circuit', async function () {
// expect(circuit).to.not.be.undefined;
// });
it('should have nullifier == poseidon(secret, scope)', async function () {
w = await circuit.calculateWitness(inputs);
const nullifier_js = poseidon2([inputs.secret, inputs.scope]).toString();
const nullifier_circom = (await circuit.getOutput(w, ['nullifier'])).nullifier;
console.log('nullifier_circom', nullifier_circom);
console.log('nullifier_js', nullifier_js);
expect(nullifier_circom).to.equal(nullifier_js);
});
describe('MRZ selective disclosure', function () {
const attributeCombinations = [
['issuing_state', 'name'],
['passport_number', 'nationality', 'date_of_birth'],
['gender', 'expiry_date'],
];
attributeCombinations.forEach((combination) => {
it(`Disclosing ${combination.join(', ')}`, async function () {
const attributeToReveal = Object.keys(attributeToPosition_ID).reduce((acc, attribute) => {
acc[attribute] = combination.includes(attribute);
return acc;
}, {});
const selector_dg1 = Array(90).fill('0');
Object.entries(attributeToReveal).forEach(([attribute, reveal]) => {
if (reveal) {
const [start, end] = attributeToPosition_ID[attribute];
selector_dg1.fill('1', start, end + 1);
}
});
inputs = {
...inputs,
selector_dg1: selector_dg1.map(String),
};
it('should have nullifier == poseidon(secret, scope)', async function () {
w = await circuit.calculateWitness(inputs);
const nullifier_js = poseidon2([inputs.secret, inputs.scope]).toString();
const nullifier_circom = (await circuit.getOutput(w, ['nullifier'])).nullifier;
console.log('nullifier_circom', nullifier_circom);
console.log('nullifier_js', nullifier_js);
expect(nullifier_circom).to.equal(nullifier_js);
});
describe('MRZ selective disclosure', function () {
const attributeCombinations = [
['issuing_state', 'name'],
['passport_number', 'nationality', 'date_of_birth'],
['gender', 'expiry_date'],
];
attributeCombinations.forEach((combination) => {
it(`Disclosing ${combination.join(', ')}`, async function () {
const attributeToReveal = Object.keys(attributeToPosition_ID).reduce((acc, attribute) => {
acc[attribute] = combination.includes(attribute);
return acc;
}, {});
const selector_dg1 = Array(90).fill('0');
Object.entries(attributeToReveal).forEach(([attribute, reveal]) => {
if (reveal) {
const [start, end] = attributeToPosition_ID[attribute];
selector_dg1.fill('1', start, end + 1);
}
});
inputs = {
...inputs,
selector_dg1: selector_dg1.map(String),
};
w = await circuit.calculateWitness(inputs);
const revealedData_packed = await circuit.getOutput(w, ['revealedData_packed[4]']);
const reveal_unpacked = formatAndUnpackReveal(revealedData_packed, 'id');
for (let i = 0; i < 88; i++) {
if (selector_dg1[i] == '1') {
const char = String.fromCharCode(Number(inputs.dg1[i + 5]));
assert(reveal_unpacked[i] == char, 'Should reveal the right character');
} else {
assert(reveal_unpacked[i] == '\x00', 'Should not reveal');
}
}
const forbidden_countries_list_packed = await circuit.getOutput(w, [
'forbidden_countries_list_packed[4]',
]);
const forbidden_countries_list_unpacked = formatAndUnpackForbiddenCountriesList(
forbidden_countries_list_packed
);
expect(forbidden_countries_list_unpacked).to.deep.equal(forbidden_countries_list);
});
});
});
it('should allow disclosing majority', async function () {
const selector_dg1 = Array(90).fill('0');
w = await circuit.calculateWitness({
...inputs,
selector_dg1: selector_dg1.map(String),
});
const revealedData_packed = await circuit.getOutput(w, ['revealedData_packed[4]']);
const reveal_unpacked = formatAndUnpackReveal(revealedData_packed, 'id');
const older_than = getAttributeFromUnpackedReveal(reveal_unpacked, 'older_than', 'id');
expect(older_than).to.equal('18');
});
it("shouldn't allow disclosing wrong majority", async function () {
const selector_dg1 = Array(90).fill('0');
w = await circuit.calculateWitness({
...inputs,
majority: ['5', '0'].map((char) => BigInt(char.charCodeAt(0)).toString()),
selector_dg1: selector_dg1.map(String),
});
const revealedData_packed = await circuit.getOutput(w, ['revealedData_packed[4]']);
const reveal_unpacked = formatAndUnpackReveal(revealedData_packed, 'id');
expect(reveal_unpacked[88]).to.equal('\x00');
expect(reveal_unpacked[89]).to.equal('\x00');
for (let i = 0; i < 88; i++) {
if (selector_dg1[i] == '1') {
const char = String.fromCharCode(Number(inputs.dg1[i + 5]));
assert(reveal_unpacked[i] == char, 'Should reveal the right character');
} else {
assert(reveal_unpacked[i] == '\x00', 'Should not reveal');
}
}
const forbidden_countries_list_packed = await circuit.getOutput(w, [
'forbidden_countries_list_packed[4]',
]);
const forbidden_countries_list_unpacked = formatAndUnpackForbiddenCountriesList(
forbidden_countries_list_packed
);
expect(forbidden_countries_list_unpacked).to.deep.equal(forbidden_countries_list);
});
});
});
it('should allow disclosing majority', async function () {
const selector_dg1 = Array(90).fill('0');
w = await circuit.calculateWitness({
...inputs,
selector_dg1: selector_dg1.map(String),
});
const revealedData_packed = await circuit.getOutput(w, ['revealedData_packed[4]']);
const reveal_unpacked = formatAndUnpackReveal(revealedData_packed, 'id');
const older_than = getAttributeFromUnpackedReveal(reveal_unpacked, 'older_than', 'id');
expect(older_than).to.equal('18');
});
it("shouldn't allow disclosing wrong majority", async function () {
const selector_dg1 = Array(90).fill('0');
w = await circuit.calculateWitness({
...inputs,
majority: ['5', '0'].map((char) => BigInt(char.charCodeAt(0)).toString()),
selector_dg1: selector_dg1.map(String),
});
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[4]']);
const reveal_unpacked = formatAndUnpackReveal(revealedData_packed, 'id');
const ofac_results = reveal_unpacked.slice(92, 94);
expect(ofac_results).to.deep.equal(
['\x01', '\x01'],
'OFAC result bits should be [1, 1]'
);
expect(ofac_results).to.not.equal(['\x00', '\x00'], 'OFAC result should be revealed');
});
const revealedData_packed = await circuit.getOutput(w, ['revealedData_packed[4]']);
it('should not disclose OFAC check result when selector is 0', async function () {
w = await circuit.calculateWitness({
...inputs,
selector_ofac: '0',
});
const reveal_unpacked = formatAndUnpackReveal(revealedData_packed, 'id');
expect(reveal_unpacked[88]).to.equal('\x00');
expect(reveal_unpacked[89]).to.equal('\x00');
});
const revealedData_packed = await circuit.getOutput(w, ['revealedData_packed[4]']);
const reveal_unpacked = formatAndUnpackReveal(revealedData_packed, 'id');
// OFAC result should be hidden (null byte)
const ofac_results = reveal_unpacked.slice(92, 94);
expect(ofac_results).to.deep.equal(
['\x00', '\x00'],
'OFAC result bits should be [0, 0]'
);
expect(ofac_results).to.not.equal(['\x01', '\x01'], '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: genMockIdDoc({ idType: 'mock_id_card' }),
expectedBits: ['\x01', '\x01'],
},
{
desc: 'Name and DOB matches (so YOB matches too)',
data: genMockIdDoc({
idType: 'mock_id_card',
passportNumber: 'DIF123456',
lastName: 'HENAO MONTOYA',
firstName: 'ARCANGEL DE JESUS',
birthDate: '541007',
expiryDate: '300101',
}),
expectedBits: ['\x00', '\x00'],
},
{
desc: 'Only name and YOB match',
data: genMockIdDoc({
idType: 'mock_id_card',
passportNumber: 'DIF123456',
lastName: 'HENAO MONTOYA',
firstName: 'ARCANGEL DE JESUS',
birthDate: '541299',
expiryDate: '300101', // Same year (54) different month/day
}),
expectedBits: ['\x01', '\x00'],
},
];
for (const testCase of testCases) {
console.log(`Testing: ${testCase.desc}`);
const passportData = testCase.data;
const sanctionedCommitment = generateCommitment(
secret,
ID_CARD_ATTESTATION_ID,
passportData
);
tree.insert(BigInt(sanctionedCommitment));
const testInputs = generateCircuitInputsVCandDisclose(
secret,
ID_CARD_ATTESTATION_ID,
passportData,
fullScope,
Array(90).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[4]']);
const reveal_unpacked = formatAndUnpackReveal(revealedData_packed, 'id');
const ofac_results = reveal_unpacked.slice(92, 94);
console.log(`${testCase.desc} - OFAC bits:`, ofac_results);
expect(ofac_results).to.deep.equal(
testCase.expectedBits,
`Failed matching pattern for: ${testCase.desc}`
);
}
});
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[4]']);
const reveal_unpacked = formatAndUnpackReveal(revealedData_packed, 'id');
const ofac_results = reveal_unpacked.slice(92, 94);
expect(ofac_results).to.deep.equal(['\x01', '\x01'], 'OFAC result bits should be [1, 1]');
expect(ofac_results).to.not.equal(['\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[4]']);
const reveal_unpacked = formatAndUnpackReveal(revealedData_packed, 'id');
// OFAC result should be hidden (null byte)
const ofac_results = reveal_unpacked.slice(92, 94);
expect(ofac_results).to.deep.equal(['\x00', '\x00'], 'OFAC result bits should be [0, 0]');
expect(ofac_results).to.not.equal(['\x01', '\x01'], '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: genMockIdDoc({ idType: 'mock_id_card' }),
expectedBits: ['\x01', '\x01'],
},
{
desc: 'Name and DOB matches (so YOB matches too)',
data: genMockIdDoc({
idType: 'mock_id_card',
passportNumber: 'DIF123456',
lastName: 'HENAO MONTOYA',
firstName: 'ARCANGEL DE JESUS',
birthDate: '541007',
expiryDate: '300101',
}),
expectedBits: ['\x00', '\x00'],
},
{
desc: 'Only name and YOB match',
data: genMockIdDoc({
idType: 'mock_id_card',
passportNumber: 'DIF123456',
lastName: 'HENAO MONTOYA',
firstName: 'ARCANGEL DE JESUS',
birthDate: '541299',
expiryDate: '300101', // Same year (54) different month/day
}),
expectedBits: ['\x01', '\x00'],
},
];
for (const testCase of testCases) {
console.log(`Testing: ${testCase.desc}`);
const passportData = testCase.data;
const sanctionedCommitment = generateCommitment(
secret,
ID_CARD_ATTESTATION_ID,
passportData
);
tree.insert(BigInt(sanctionedCommitment));
const testInputs = generateCircuitInputsVCandDisclose(
secret,
ID_CARD_ATTESTATION_ID,
passportData,
fullScope,
Array(90).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[4]']);
const reveal_unpacked = formatAndUnpackReveal(revealedData_packed, 'id');
const ofac_results = reveal_unpacked.slice(92, 94);
console.log(`${testCase.desc} - OFAC bits:`, ofac_results);
expect(ofac_results).to.deep.equal(
testCase.expectedBits,
`Failed matching pattern for: ${testCase.desc}`
);
}
});
});
});

View File

@@ -15,9 +15,7 @@ import { genAndInitMockPassportData } from '../../../common/src/utils/passports/
let circuit: any;
// Mock passport not added in ofac list
const mockIdData = genMockIdDoc(
{ 'idType': 'mock_passport' }
);
const mockIdData = genMockIdDoc({ idType: 'mock_passport' });
const passportData = genAndInitMockPassportData(
'sha256',
'sha256',
@@ -40,28 +38,27 @@ const passportDataInOfac = genAndInitMockPassportData(
);
const mockIdDataInOfac = genMockIdDoc({
'idType': 'mock_passport',
'nationality': 'FRA',
'birthDate': '541007',
'lastName': 'HENAO MONTOYA',
'passportNumber': '98lh90556',
'firstName': 'ARCANGEL DE JESUS'
})
idType: 'mock_passport',
nationality: 'FRA',
birthDate: '541007',
lastName: 'HENAO MONTOYA',
passportNumber: '98lh90556',
firstName: 'ARCANGEL DE JESUS',
});
// Mock ID Card not in OFAC list
const idCardData = genMockIdDoc({
'idType': 'mock_id_card'
})
idType: 'mock_id_card',
});
// Mock ID Card in OFAC list
const idCardDataInOfac = genMockIdDoc({
'idType': 'mock_id_card',
'nationality': 'FRA',
'birthDate': '541007',
'firstName': 'ARCANGEL DE JESUS',
'lastName': 'HENAO MONTOYA'
})
idType: 'mock_id_card',
nationality: 'FRA',
birthDate: '541007',
firstName: 'ARCANGEL DE JESUS',
lastName: 'HENAO MONTOYA',
});
// POSSIBLE TESTS (for each of 3 circuits):
// 0. Cicuits compiles and loads

View File

@@ -13,7 +13,7 @@ import {
generateNullifier,
} from '../../../common/src/utils/passports/passport';
import { poseidon6 } from 'poseidon-lite';
import { hashAlgosTypes, PASSPORT_ATTESTATION_ID } from '../../../common/src/constants/constants';
import { hashAlgosTypes, ID_CARD_ATTESTATION_ID } from '../../../common/src/constants/constants';
import { parseCertificateSimple } from '../../../common/src/utils/certificate_parsing/parseCertificateSimple';
import serialized_dsc_tree from '../../../common/pubkeys/serialized_dsc_tree.json';
import { genMockIdDoc } from '../../../common/src/utils/passports/genMockIdDoc';
@@ -31,300 +31,302 @@ testSuite.forEach(
keyLength,
saltLength,
}) => {
describe(`Register - ${dgHashAlgo.toUpperCase()} ${eContentHashAlgo.toUpperCase()} ${hashFunction.toUpperCase()} ${sigAlg.toUpperCase()} ${domainParameter
} ${keyLength}${saltLength ? ` Salt:${saltLength}` : ''}`, function () {
this.timeout(0);
let circuit: any;
describe(`Register - ${dgHashAlgo.toUpperCase()} ${eContentHashAlgo.toUpperCase()} ${hashFunction.toUpperCase()} ${sigAlg.toUpperCase()} ${
domainParameter
} ${keyLength}${saltLength ? ` Salt:${saltLength}` : ''}`, function () {
this.timeout(0);
let circuit: any;
const passportData = genMockIdDoc({
idType: 'mock_id_card',
dgHashAlgo: dgHashAlgo as hashAlgosTypes,
eContentHashAlgo: eContentHashAlgo as hashAlgosTypes,
signatureType: `${sigAlg}_${hashFunction}_${domainParameter}_${keyLength}${saltLength ? `_${saltLength}` : ''}` as SignatureAlgorithm,
})
const secret = poseidon6('SECRET'.split('').map((x) => BigInt(x.charCodeAt(0)))).toString();
const inputs = generateCircuitInputsRegister(
secret,
passportData,
serialized_dsc_tree as string
);
before(async () => {
circuit = await wasm_tester(
path.join(
__dirname,
`../../circuits/register_id/instances/${getCircuitNameFromPassportData(passportData, 'register')}.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 calculate the witness with correct inputs, and have the right nullifier and commitment', async function () {
const w = await circuit.calculateWitness(inputs);
await circuit.checkConstraints(w);
const nullifier_js = generateNullifier(passportData);
console.log('\x1b[35m%s\x1b[0m', 'js: nullifier:', nullifier_js);
const nullifier = (await circuit.getOutput(w, ['nullifier'])).nullifier;
console.log('\x1b[34m%s\x1b[0m', 'circom: nullifier', nullifier);
expect(nullifier).to.be.equal(nullifier_js);
const commitment_js = generateCommitment(
secret.toString(),
PASSPORT_ATTESTATION_ID,
passportData
);
console.log('\x1b[35m%s\x1b[0m', 'js: commitment:', commitment_js);
const commitment = (await circuit.getOutput(w, ['commitment'])).commitment;
console.log('\x1b[34m%s\x1b[0m', 'circom commitment', commitment);
expect(commitment).to.be.equal(commitment_js);
});
it('should fail if dsc_pubKey_actual_size is lower than the minimum key length', async () => {
try {
const dscParsed = parseCertificateSimple(passportData.dsc);
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
if (dscParsed.signatureAlgorithm === 'rsa') {
tamperedInputs.dsc_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.dsc_pubKey_actual_size = (
Number(tamperedInputs.dsc_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');
}
});
// ----- Tests for dsc_pubKey offset and size checks -----
it('should fail if dsc_pubKey_offset + dsc_pubKey_actual_size > raw_dsc_actual_length', async function () {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.dsc_pubKey_offset = (
Number(tamperedInputs.raw_dsc_actual_length) -
Number(tamperedInputs.dsc_pubKey_actual_size) +
1
).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 fail if dsc_pubKey_actual_size is larger than the actual key size in certificate', async function () {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.dsc_pubKey_actual_size = (
Number(tamperedInputs.dsc_pubKey_actual_size) + 8
).toString();
await circuit.calculateWitness(tamperedInputs);
expect.fail('Expected an error but none was thrown.');
} catch (error: any) {
expect(error.message).to.include('Assert Failed');
}
});
// ----- Tests for Merkle tree inclusion -----
it('should fail if merkle_root is invalid', async function () {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.merkle_root = (BigInt(tamperedInputs.merkle_root) + 1n).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 fail if leaf_depth is tampered', async function () {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
// Change leaf_depth to an incorrect value (e.g., add 1)
tamperedInputs.leaf_depth = (Number(tamperedInputs.leaf_depth) + 1).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 fail if a value in the merkle path is invalid', async function () {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.path[0] = (BigInt(tamperedInputs.path[0]) + 1n).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 fail if a sibling in the merkle proof is invalid', async function () {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.siblings[0] = (BigInt(tamperedInputs.siblings[0]) + 1n).toString();
await circuit.calculateWitness(tamperedInputs);
expect.fail('Expected an error but none was thrown.');
} catch (error: any) {
expect(error.message).to.include('Assert Failed');
}
});
// ----- Tests for passport signature and data integrity -----
it('should fail to calculate witness with invalid mrz', async function () {
try {
const ininputs = {
...inputs,
dg1: Array(95)
.fill(0)
.map((byte) => BigInt(byte).toString()),
};
await circuit.calculateWitness(ininputs);
expect.fail('Expected an error but none was thrown.');
} catch (error) {
expect(error.message).to.include('Assert Failed');
}
});
it('should fail to calculate witness with invalid eContent', async function () {
try {
const ininputs = {
...inputs,
eContent: inputs.eContent.map((byte: string) => String((parseInt(byte, 10) + 1) % 256)),
};
await circuit.calculateWitness(ininputs);
expect.fail('Expected an error but none was thrown.');
} catch (error) {
expect(error.message).to.include('Assert Failed');
}
});
it('should fail if signed_attr is invalid', async function () {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.signed_attr = tamperedInputs.signed_attr.map((byte: string) =>
((parseInt(byte, 10) + 1) % 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');
}
});
it('should fail if signature_passport is invalid', async function () {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.signature_passport = tamperedInputs.signature_passport.map(
(byte: string) => ((parseInt(byte, 10) + 1) % 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');
}
});
// ----- Test for tampering with csca_hash (used in commitment) -----
it('should fail if csca_hash is tampered', async function () {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.csca_tree_leaf = (BigInt(tamperedInputs.csca_tree_leaf) + 1n).toString();
await circuit.calculateWitness(tamperedInputs);
expect.fail('Expected an error but none was thrown.');
} catch (error: any) {
expect(error.message).to.include('Assert Failed');
}
});
// ----- Test for tampering with secret (affects commitment and nullifier) -----
it('should compute different outputs if secret is changed', async function () {
const wValid = await circuit.calculateWitness(inputs);
await circuit.checkConstraints(wValid);
const nullifierValid = (await circuit.getOutput(wValid, ['nullifier'])).nullifier;
const commitmentValid = (await circuit.getOutput(wValid, ['commitment'])).commitment;
const tamperedInputs = { ...inputs, secret: (BigInt(inputs.secret[0]) + 1n).toString() };
const wTampered = await circuit.calculateWitness(tamperedInputs);
await circuit.checkConstraints(wTampered);
const nullifierTampered = (await circuit.getOutput(wTampered, ['nullifier'])).nullifier;
const commitmentTampered = (await circuit.getOutput(wTampered, ['commitment'])).commitment;
expect(nullifierTampered).to.equal(nullifierValid);
expect(commitmentTampered).to.not.be.equal(commitmentValid);
});
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_dsc[
Number(tamperedInputs.dsc_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[keyLength];
for (let i = 0; i < 5; i++) {
const prefixByte = parseInt(inputs.raw_dsc[Number(inputs.dsc_pubKey_offset) - 5 + i]);
expect(prefixByte).to.equal(
expectedPrefix[i],
`Prefix byte ${i} mismatch for ${keyLength} bit key`
);
}
});
}
it('should fail if raw_dsc has a signal that is longer than a byte', async function () {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.raw_dsc[0] = (parseInt(tamperedInputs.raw_dsc[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');
}
});
const passportData = genMockIdDoc({
idType: 'mock_id_card',
dgHashAlgo: dgHashAlgo as hashAlgosTypes,
eContentHashAlgo: eContentHashAlgo as hashAlgosTypes,
signatureType:
`${sigAlg}_${hashFunction}_${domainParameter}_${keyLength}${saltLength ? `_${saltLength}` : ''}` as SignatureAlgorithm,
});
const secret = poseidon6('SECRET'.split('').map((x) => BigInt(x.charCodeAt(0)))).toString();
const inputs = generateCircuitInputsRegister(
secret,
passportData,
serialized_dsc_tree as string
);
before(async () => {
circuit = await wasm_tester(
path.join(
__dirname,
`../../circuits/register_id/instances/${getCircuitNameFromPassportData(passportData, 'register')}.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 calculate the witness with correct inputs, and have the right nullifier and commitment', async function () {
const w = await circuit.calculateWitness(inputs);
await circuit.checkConstraints(w);
const nullifier_js = generateNullifier(passportData);
console.log('\x1b[35m%s\x1b[0m', 'js: nullifier:', nullifier_js);
const nullifier = (await circuit.getOutput(w, ['nullifier'])).nullifier;
console.log('\x1b[34m%s\x1b[0m', 'circom: nullifier', nullifier);
expect(nullifier).to.be.equal(nullifier_js);
const commitment_js = generateCommitment(
secret.toString(),
ID_CARD_ATTESTATION_ID,
passportData
);
console.log('\x1b[35m%s\x1b[0m', 'js: commitment:', commitment_js);
const commitment = (await circuit.getOutput(w, ['commitment'])).commitment;
console.log('\x1b[34m%s\x1b[0m', 'circom commitment', commitment);
expect(commitment).to.be.equal(commitment_js);
});
it('should fail if dsc_pubKey_actual_size is lower than the minimum key length', async () => {
try {
const dscParsed = parseCertificateSimple(passportData.dsc);
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
if (dscParsed.signatureAlgorithm === 'rsa') {
tamperedInputs.dsc_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.dsc_pubKey_actual_size = (
Number(tamperedInputs.dsc_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');
}
});
// ----- Tests for dsc_pubKey offset and size checks -----
it('should fail if dsc_pubKey_offset + dsc_pubKey_actual_size > raw_dsc_actual_length', async function () {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.dsc_pubKey_offset = (
Number(tamperedInputs.raw_dsc_actual_length) -
Number(tamperedInputs.dsc_pubKey_actual_size) +
1
).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 fail if dsc_pubKey_actual_size is larger than the actual key size in certificate', async function () {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.dsc_pubKey_actual_size = (
Number(tamperedInputs.dsc_pubKey_actual_size) + 8
).toString();
await circuit.calculateWitness(tamperedInputs);
expect.fail('Expected an error but none was thrown.');
} catch (error: any) {
expect(error.message).to.include('Assert Failed');
}
});
// ----- Tests for Merkle tree inclusion -----
it('should fail if merkle_root is invalid', async function () {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.merkle_root = (BigInt(tamperedInputs.merkle_root) + 1n).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 fail if leaf_depth is tampered', async function () {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
// Change leaf_depth to an incorrect value (e.g., add 1)
tamperedInputs.leaf_depth = (Number(tamperedInputs.leaf_depth) + 1).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 fail if a value in the merkle path is invalid', async function () {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.path[0] = (BigInt(tamperedInputs.path[0]) + 1n).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 fail if a sibling in the merkle proof is invalid', async function () {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.siblings[0] = (BigInt(tamperedInputs.siblings[0]) + 1n).toString();
await circuit.calculateWitness(tamperedInputs);
expect.fail('Expected an error but none was thrown.');
} catch (error: any) {
expect(error.message).to.include('Assert Failed');
}
});
// ----- Tests for passport signature and data integrity -----
it('should fail to calculate witness with invalid mrz', async function () {
try {
const ininputs = {
...inputs,
dg1: Array(95)
.fill(0)
.map((byte) => BigInt(byte).toString()),
};
await circuit.calculateWitness(ininputs);
expect.fail('Expected an error but none was thrown.');
} catch (error) {
expect(error.message).to.include('Assert Failed');
}
});
it('should fail to calculate witness with invalid eContent', async function () {
try {
const ininputs = {
...inputs,
eContent: inputs.eContent.map((byte: string) => String((parseInt(byte, 10) + 1) % 256)),
};
await circuit.calculateWitness(ininputs);
expect.fail('Expected an error but none was thrown.');
} catch (error) {
expect(error.message).to.include('Assert Failed');
}
});
it('should fail if signed_attr is invalid', async function () {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.signed_attr = tamperedInputs.signed_attr.map((byte: string) =>
((parseInt(byte, 10) + 1) % 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');
}
});
it('should fail if signature_passport is invalid', async function () {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.signature_passport = tamperedInputs.signature_passport.map(
(byte: string) => ((parseInt(byte, 10) + 1) % 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');
}
});
// ----- Test for tampering with csca_hash (used in commitment) -----
it('should fail if csca_hash is tampered', async function () {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.csca_tree_leaf = (BigInt(tamperedInputs.csca_tree_leaf) + 1n).toString();
await circuit.calculateWitness(tamperedInputs);
expect.fail('Expected an error but none was thrown.');
} catch (error: any) {
expect(error.message).to.include('Assert Failed');
}
});
// ----- Test for tampering with secret (affects commitment and nullifier) -----
it('should compute different outputs if secret is changed', async function () {
const wValid = await circuit.calculateWitness(inputs);
await circuit.checkConstraints(wValid);
const nullifierValid = (await circuit.getOutput(wValid, ['nullifier'])).nullifier;
const commitmentValid = (await circuit.getOutput(wValid, ['commitment'])).commitment;
const tamperedInputs = { ...inputs, secret: (BigInt(inputs.secret[0]) + 1n).toString() };
const wTampered = await circuit.calculateWitness(tamperedInputs);
await circuit.checkConstraints(wTampered);
const nullifierTampered = (await circuit.getOutput(wTampered, ['nullifier'])).nullifier;
const commitmentTampered = (await circuit.getOutput(wTampered, ['commitment'])).commitment;
expect(nullifierTampered).to.equal(nullifierValid);
expect(commitmentTampered).to.not.be.equal(commitmentValid);
});
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_dsc[
Number(tamperedInputs.dsc_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[keyLength];
for (let i = 0; i < 5; i++) {
const prefixByte = parseInt(inputs.raw_dsc[Number(inputs.dsc_pubKey_offset) - 5 + i]);
expect(prefixByte).to.equal(
expectedPrefix[i],
`Prefix byte ${i} mismatch for ${keyLength} bit key`
);
}
});
}
it('should fail if raw_dsc has a signal that is longer than a byte', async function () {
try {
const tamperedInputs = JSON.parse(JSON.stringify(inputs));
tamperedInputs.raw_dsc[0] = (parseInt(tamperedInputs.raw_dsc[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');
}
});
});
}
);

View File

@@ -16,7 +16,7 @@ export const sigAlgs: TestCase[] = [
hashFunction: 'sha256',
sigAlg: 'rsa',
domainParameter: '65537',
keyLength: '2048'
keyLength: '2048',
},
];

View File

@@ -38,57 +38,17 @@ export type document_type = 'passport' | 'id_card';
/**
* Maximum number of countries in the forbidden countries list.
*
*
* IMPORTANT: This value must match in both backend and frontend SDK.
* Any mismatch will result in an INVALID_FORBIDDEN_COUNTRIES error.
*/
export const MAX_FORBIDDEN_COUNTRIES_LIST_LENGTH = 40;
export const DEPLOYED_CIRCUITS_REGISTER = [
'register_sha1_sha1_sha1_rsa_65537_4096',
'register_sha1_sha256_sha256_rsa_65537_4096',
'register_sha224_sha224_sha224_ecdsa_brainpoolP224r1',
'register_sha256_sha224_sha224_ecdsa_secp224r1',
'register_sha256_sha256_sha256_ecdsa_brainpoolP256r1',
'register_sha256_sha256_sha256_ecdsa_brainpoolP384r1',
'register_sha256_sha256_sha256_ecdsa_secp256r1',
'register_sha256_sha256_sha256_ecdsa_secp384r1',
'register_sha256_sha256_sha256_rsa_3_4096',
'register_sha256_sha256_sha256_rsa_65537_4096',
'register_sha256_sha256_sha256_rsapss_3_32_2048',
'register_sha256_sha256_sha256_rsapss_65537_32_2048',
'register_sha256_sha256_sha256_rsapss_65537_32_3072',
'register_sha384_sha384_sha384_ecdsa_brainpoolP384r1',
'register_sha384_sha384_sha384_ecdsa_brainpoolP512r1',
'register_sha384_sha384_sha384_ecdsa_secp384r1',
'register_sha384_sha384_sha384_rsapss_65537_48_2048',
'register_sha1_sha1_sha1_ecdsa_brainpoolP224r1',
'register_sha512_sha512_sha512_ecdsa_brainpoolP512r1',
'register_sha512_sha512_sha512_rsa_65537_4096',
'register_sha512_sha512_sha512_rsapss_65537_64_2048',
]
// Note: Circuit lists are now managed through RegisterVerifierId and DscVerifierId enums below
// instead of separate arrays for better type safety and maintainability
export const OFAC_TREE_LEVELS = 64;
export const DEPLOYED_CIRCUITS_DSC = [
'dsc_sha1_ecdsa_brainpoolP256r1',
'dsc_sha1_rsa_65537_4096',
'dsc_sha256_ecdsa_brainpoolP256r1',
'dsc_sha256_ecdsa_brainpoolP384r1',
'dsc_sha256_ecdsa_secp256r1',
'dsc_sha256_ecdsa_secp384r1',
'dsc_sha256_rsa_65537_4096',
'dsc_sha256_rsapss_3_32_3072',
'dsc_sha256_rsapss_65537_32_3072',
'dsc_sha256_rsapss_65537_32_4096',
'dsc_sha384_ecdsa_brainpoolP384r1',
'dsc_sha384_ecdsa_brainpoolP512r1',
'dsc_sha384_ecdsa_secp384r1',
'dsc_sha512_ecdsa_brainpoolP512r1',
'dsc_sha512_rsa_65537_4096',
'dsc_sha512_rsapss_65537_64_4096',
]
export const MAX_PADDED_ECONTENT_LEN: Partial<Record<(typeof hashAlgos)[number], number>> = {
sha1: 384,
sha224: 512,

View File

@@ -42,7 +42,7 @@ function genDG1IdCard(idDocInput: IdDocInput) {
const nationality = formatDG1Attribute(nationality_index, idDocInput.nationality);
const optional_data_2 = formatDG1Attribute(optional_data_2_index, '');
const overall_check_digit = formatDG1Attribute(overall_check_digit_index, '1');
const name = formatDG1Attribute(name_index, formatName(idDocInput.firstName, idDocInput.lastName));
const name = formatDG1Attribute(name_index, formatName(idDocInput.firstName, idDocInput.lastName, name_index[1] - name_index[0] + 1));
const dg1 = `${doc_type}${issuing_state}${document_number}${document_number_check_digit}${optional_data}${date_of_birth}${date_of_birth_check_digit}${sex}${expiration_date}${expiration_date_check_digit}${nationality}${optional_data_2}${overall_check_digit}${name}`;
if (dg1.length !== 90) {
@@ -69,7 +69,7 @@ function genDG1Passport(idDocInput: IdDocInput) {
const doc_type = formatDG1Attribute(doc_type_index, 'P');
const issuing_state = formatDG1Attribute(issuing_state_index, idDocInput.nationality);
const name = formatDG1Attribute(name_index, formatName(idDocInput.firstName, idDocInput.lastName));
const name = formatDG1Attribute(name_index, formatName(idDocInput.firstName, idDocInput.lastName, name_index[1] - name_index[0] + 1));
const document_number = formatDG1Attribute(document_number_index, idDocInput.passportNumber);
const document_number_check_digit = formatDG1Attribute(document_number_check_digit_index, '4');
const nationality = formatDG1Attribute(nationality_index, idDocInput.nationality);

View File

@@ -130,11 +130,25 @@ export function formatDg2Hash(dg2Hash: number[]) {
export function formatDG1Attribute(index: number[], value: string) {
const max_length = index[1] - index[0] + 1;
if (value.length > max_length) {
throw new Error(`Value is too long for index ${index[0]}-${index[1]}`);
throw new Error(`Value is too long for index ${index[0]}-${index[1]} value: ${value} value.length: ${value.length} maxLength: ${max_length}`);
}
return value.padEnd(max_length, '<');
}
export function formatName(firstName: string, lastName: string) {
return `${lastName.toUpperCase()}<<${firstName.toUpperCase()}`;
export function formatName(firstName: string, lastName: string, targetLength: number) {
// Split names by spaces and join parts with '<'
const formattedLastName = lastName.toUpperCase().split(' ').join('<');
const formattedFirstName = firstName.toUpperCase().split(' ').join('<');
// Combine with '<<' separator
let result = `${formattedLastName}<<${formattedFirstName}`;
// Pad with '<' or truncate to target length
if (result.length < targetLength) {
result = result.padEnd(targetLength, '<');
} else if (result.length > targetLength) {
result = result.substring(0, targetLength);
}
return result;
}

View File

@@ -1,4 +1,7 @@
NETWORK=
PRIVATE_KEY='0x123'
CELO_KEY=
MAINNET_RPC_URL=
SEPOLIA_RPC_URL=https://rpc.sepolia.org
@@ -6,4 +9,5 @@ CELO_RPC_URL=
CELO_ALFAJORES_RPC_URL=
CELO_BAKLAVA_RPC_URL=
ETHERSCAN_API_KEY=
ETHERSCAN_API_KEY=
CELOSCAN_API_KEY=

View File

@@ -0,0 +1,182 @@
// SPDX-License-Identifier: GPL-3.0
/*
Copyright 2021 0KIMS association.
This file is generated with [snarkJS](https://github.com/iden3/snarkjs).
snarkJS is a free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
snarkJS is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with snarkJS. If not, see <https://www.gnu.org/licenses/>.
*/
pragma solidity >=0.7.0 <0.9.0;
contract Verifier_register_id_sha256_sha256_sha256_rsa_65537_4096 {
// Scalar field size
uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
// Base field size
uint256 constant q = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
// Verification Key data
uint256 constant alphax = 20491192805390485299153009773594534940189261866228447918068658471970481763042;
uint256 constant alphay = 9383485363053290200918347156157836566562967994039712273449902621266178545958;
uint256 constant betax1 = 4252822878758300859123897981450591353533073413197771768651442665752259397132;
uint256 constant betax2 = 6375614351688725206403948262868962793625744043794305715222011528459656738731;
uint256 constant betay1 = 21847035105528745403288232691147584728191162732299865338377159692350059136679;
uint256 constant betay2 = 10505242626370262277552901082094356697409835680220590971873171140371331206856;
uint256 constant gammax1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634;
uint256 constant gammax2 = 10857046999023057135944570762232829481370756359578518086990519993285655852781;
uint256 constant gammay1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531;
uint256 constant gammay2 = 8495653923123431417604973247489272438418190587263600148770280649306958101930;
uint256 constant deltax1 = 4792639757706688844115711424239360806333190024413309473231737437875226477670;
uint256 constant deltax2 = 17405709437999930743609060641158524978458283912947831892566166150949870424294;
uint256 constant deltay1 = 17093903636529404715977441649383548797208493053670591927268936136905628890632;
uint256 constant deltay2 = 4972965029582644081071578284460620299080512423640837516215418184958287485742;
uint256 constant IC0x = 10355093401382030671225395655869217898600304666279529568537338332467402365304;
uint256 constant IC0y = 4965486376320532678254373789150183805614443765077280090063627634214290673841;
uint256 constant IC1x = 15499415295419376168702866885907180620949913073936284071089537160198730049399;
uint256 constant IC1y = 9281988302763410034273237781396743554700695834575740098855226007996985888530;
uint256 constant IC2x = 14098031970434182418452511377672297745474345532972352145312731453903292311930;
uint256 constant IC2y = 5496357494362502413877905425401698232434560336945612609023708765434745421701;
uint256 constant IC3x = 6540495825150439480926083011463382216384205445522489211977434270583326380648;
uint256 constant IC3y = 4164688383627561794431395614711252825194458846441353030452544157419373614848;
// Memory data
uint16 constant pVk = 0;
uint16 constant pPairing = 128;
uint16 constant pLastMem = 896;
function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[3] calldata _pubSignals) public view returns (bool) {
assembly {
function checkField(v) {
if iszero(lt(v, r)) {
mstore(0, 0)
return(0, 0x20)
}
}
// G1 function to multiply a G1 value(x,y) to value in an address
function g1_mulAccC(pR, x, y, s) {
let success
let mIn := mload(0x40)
mstore(mIn, x)
mstore(add(mIn, 32), y)
mstore(add(mIn, 64), s)
success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64)
if iszero(success) {
mstore(0, 0)
return(0, 0x20)
}
mstore(add(mIn, 64), mload(pR))
mstore(add(mIn, 96), mload(add(pR, 32)))
success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64)
if iszero(success) {
mstore(0, 0)
return(0, 0x20)
}
}
function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk {
let _pPairing := add(pMem, pPairing)
let _pVk := add(pMem, pVk)
mstore(_pVk, IC0x)
mstore(add(_pVk, 32), IC0y)
// Compute the linear combination vk_x
g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0)))
g1_mulAccC(_pVk, IC2x, IC2y, calldataload(add(pubSignals, 32)))
g1_mulAccC(_pVk, IC3x, IC3y, calldataload(add(pubSignals, 64)))
// -A
mstore(_pPairing, calldataload(pA))
mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q))
// B
mstore(add(_pPairing, 64), calldataload(pB))
mstore(add(_pPairing, 96), calldataload(add(pB, 32)))
mstore(add(_pPairing, 128), calldataload(add(pB, 64)))
mstore(add(_pPairing, 160), calldataload(add(pB, 96)))
// alpha1
mstore(add(_pPairing, 192), alphax)
mstore(add(_pPairing, 224), alphay)
// beta2
mstore(add(_pPairing, 256), betax1)
mstore(add(_pPairing, 288), betax2)
mstore(add(_pPairing, 320), betay1)
mstore(add(_pPairing, 352), betay2)
// vk_x
mstore(add(_pPairing, 384), mload(add(pMem, pVk)))
mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32))))
// gamma2
mstore(add(_pPairing, 448), gammax1)
mstore(add(_pPairing, 480), gammax2)
mstore(add(_pPairing, 512), gammay1)
mstore(add(_pPairing, 544), gammay2)
// C
mstore(add(_pPairing, 576), calldataload(pC))
mstore(add(_pPairing, 608), calldataload(add(pC, 32)))
// delta2
mstore(add(_pPairing, 640), deltax1)
mstore(add(_pPairing, 672), deltax2)
mstore(add(_pPairing, 704), deltay1)
mstore(add(_pPairing, 736), deltay2)
let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20)
isOk := and(success, mload(_pPairing))
}
let pMem := mload(0x40)
mstore(0x40, add(pMem, pLastMem))
// Validate that all evaluations ∈ F
checkField(calldataload(add(_pubSignals, 0)))
checkField(calldataload(add(_pubSignals, 32)))
checkField(calldataload(add(_pubSignals, 64)))
// Validate all evaluations
let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem)
mstore(0, isValid)
return(0, 0x20)
}
}
}

View File

@@ -1,20 +0,0 @@
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
import { DEPLOYED_CIRCUITS_REGISTER, DEPLOYED_CIRCUITS_DSC } from "../../../common/src/constants/constants";
export default buildModule("DeployAllVerifiers", (m) => {
const deployedContracts: Record<string, any> = {};
deployedContracts.vcAndDiscloseVerifier = m.contract("Verifier_vc_and_disclose");
DEPLOYED_CIRCUITS_REGISTER.forEach((circuit) => {
const contractName = `Verifier_${circuit}`;
deployedContracts[circuit] = m.contract(contractName);
});
DEPLOYED_CIRCUITS_DSC.forEach((circuit) => {
const contractName = `Verifier_${circuit}`;
deployedContracts[circuit] = m.contract(contractName);
});
return deployedContracts;
});

View File

@@ -1,39 +0,0 @@
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
import { artifacts, ethers } from "hardhat";
import hre from "hardhat";
import fs from "fs";
import path from "path";
function getHubInitializeData() {
const hubArtifact = artifacts.readArtifactSync("IdentityVerificationHubImplV1");
return new ethers.Interface(hubArtifact.abi);
}
export default buildModule("DeployHub", (m) => {
const networkName = hre.network.config.chainId;
const deployedAddressesPath = path.join(__dirname, `../deployments/chain-${networkName}/deployed_addresses.json`);
const deployedAddresses = JSON.parse(fs.readFileSync(deployedAddressesPath, "utf8"));
const registryAddress = deployedAddresses["DeployRegistryModule#IdentityRegistry"];
const vcAndDiscloseVerifierAddress = deployedAddresses["DeployAllVerifiers#Verifier_vc_and_disclose"];
const identityVerificationHubImpl = m.contract("IdentityVerificationHubImplV1");
const hubInterface = getHubInitializeData();
const initializeData = hubInterface.encodeFunctionData("initialize", [
registryAddress,
vcAndDiscloseVerifierAddress,
[],
[],
[],
[],
]);
const hub = m.contract("IdentityVerificationHub", [identityVerificationHubImpl, initializeData]);
return {
hub,
identityVerificationHubImpl,
};
});

View File

@@ -0,0 +1,37 @@
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
import { artifacts, ethers } from "hardhat";
import hre from "hardhat";
import fs from "fs";
import path from "path";
function getHubInitializeData() {
const hubArtifact = artifacts.readArtifactSync("IdentityVerificationHubImplV1");
return new ethers.Interface(hubArtifact.abi);
}
/**
* Deploy Identity Verification Hub V1
* This module deploys the V1 implementation of the Identity Verification Hub
*/
export default buildModule("DeployHubV1", (m) => {
// Deploy V1 implementation
const identityVerificationHubImplV1 = m.contract("IdentityVerificationHubImplV1");
const hubInterface = getHubInitializeData();
const initializeData = hubInterface.encodeFunctionData("initialize", [
"",
"",
[],
[],
[],
[],
]);
// Deploy proxy with V1 implementation
const hubV1 = m.contract("IdentityVerificationHub", [identityVerificationHubImplV1, initializeData]);
return {
hubV1,
identityVerificationHubImplV1,
};
});

View File

@@ -0,0 +1,32 @@
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
import { artifacts, ethers } from "hardhat";
import hre from "hardhat";
function getHubImplV2InitializeData() {
const hubArtifact = artifacts.readArtifactSync("IdentityVerificationHubImplV2");
return new ethers.Interface(hubArtifact.abi);
}
export default buildModule("DeployHubV2", (m) => {
const identityVerificationHubImplV2 = m.contract("IdentityVerificationHubImplV2");
const hubInterface = getHubImplV2InitializeData();
// Initialize with empty values as per instructions
const initializeData = hubInterface.encodeFunctionData("initialize", [
[], // attestationIds
[], // registryAddresses
[], // vcAndDiscloseCircuitVerifierAddresses
[], // registerCircuitVerifierIds
[], // registerCircuitVerifierAddresses
[], // dscCircuitVerifierIds
[], // dscCircuitVerifierAddresses
]);
const hub = m.contract("IdentityVerificationHub", [identityVerificationHubImplV2, initializeData]);
return {
hub,
identityVerificationHubImplV2,
};
});

View File

@@ -0,0 +1,30 @@
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
import { artifacts } from "hardhat";
import { ethers } from "ethers";
export default buildModule("DeployIdCardRegistryModule", (m) => {
const poseidonT3 = m.library("PoseidonT3");
const identityRegistryIdCardImpl = m.contract("IdentityRegistryIdCardImplV1", [], {
libraries: { PoseidonT3: poseidonT3 },
});
const registryInterface = getRegistryInitializeData();
const registryInitData = registryInterface.encodeFunctionData("initialize", [
"0x0000000000000000000000000000000000000000",
]);
const idCardRegistry = m.contract("IdentityRegistryIdCard", [identityRegistryIdCardImpl, registryInitData]);
return {
poseidonT3,
identityRegistryIdCardImpl,
idCardRegistry,
};
});
function getRegistryInitializeData() {
const registryArtifact = artifacts.readArtifactSync("IdentityRegistryIdCardImplV1");
const registryInterface = new ethers.Interface(registryArtifact.abi);
return registryInterface;
}

View File

@@ -1,12 +1,6 @@
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
import hre from "hardhat";
import fs from "fs";
import path from "path";
export default buildModule("DeployPCR0", (m) => {
const networkName = hre.network.config.chainId;
// Deploy the PCR0Manager contract (implementation from PCR0.sol)
const pcr0Manager = m.contract("PCR0Manager");
return {

View File

@@ -0,0 +1,72 @@
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
import { RegisterVerifierId, DscVerifierId } from "../../../../common/src/constants/constants";
import * as fs from "fs";
import * as path from "path";
/**
* Get enum keys (circuit names) excluding numeric values
*/
function getEnumKeys<T extends Record<string, string | number>>(enumObject: T): string[] {
return Object.keys(enumObject).filter(key => isNaN(Number(key)));
}
/**
* Check if a contract file exists
*/
function contractExists(contractName: string): boolean {
const contractsDir = path.join(__dirname, "../../../contracts");
const possiblePaths = [
path.join(contractsDir, "verifiers/register", `${contractName}.sol`),
path.join(contractsDir, "verifiers/dsc", `${contractName}.sol`),
path.join(contractsDir, "verifiers/disclose", `${contractName}.sol`),
path.join(contractsDir, "verifiers", `${contractName}.sol`),
];
return possiblePaths.some(filePath => fs.existsSync(filePath));
}
export default buildModule("DeployAllVerifiers", (m) => {
const deployedContracts: Record<string, any> = {};
// Deploy VC and Disclose verifier
console.log("Deploying VC and Disclose verifier...");
deployedContracts.vcAndDiscloseVerifier = m.contract("Verifier_vc_and_disclose");
// Deploy Register verifiers using RegisterVerifierId enum
console.log("Deploying Register verifiers...");
const registerCircuits = getEnumKeys(RegisterVerifierId);
let successfulRegisterDeployments = 0;
registerCircuits.forEach((circuitName) => {
const contractName = `Verifier_${circuitName}`;
if (contractExists(contractName)) {
console.log(` - Deploying ${contractName}`);
deployedContracts[circuitName] = m.contract(contractName);
successfulRegisterDeployments++;
} else {
console.warn(` - Warning: Contract ${contractName} not found, skipping...`);
}
});
// Deploy DSC verifiers using DscVerifierId enum
console.log("Deploying DSC verifiers...");
const dscCircuits = getEnumKeys(DscVerifierId);
let successfulDscDeployments = 0;
dscCircuits.forEach((circuitName) => {
const contractName = `Verifier_${circuitName}`;
if (contractExists(contractName)) {
console.log(` - Deploying ${contractName}`);
deployedContracts[circuitName] = m.contract(contractName);
successfulDscDeployments++;
} else {
console.warn(` - Warning: Contract ${contractName} not found, skipping...`);
}
});
console.log(`Total verifiers deployment summary:`);
console.log(` - VC and Disclose: 1`);
console.log(` - Register: ${successfulRegisterDeployments}/${registerCircuits.length} (${registerCircuits.length - successfulRegisterDeployments} skipped)`);
console.log(` - DSC: ${successfulDscDeployments}/${dscCircuits.length} (${dscCircuits.length - successfulDscDeployments} skipped)`);
console.log(` - Total successful deployments: ${1 + successfulRegisterDeployments + successfulDscDeployments}`);
return deployedContracts;
});

View File

@@ -0,0 +1,14 @@
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
/**
* This module deploys the ID Card Verifier contract specifically for register_id
* with SHA256+SHA256+SHA256+RSA verifier
*/
export default buildModule("DeployIdCardVerifier", (m) => {
// Deploy the ID Card Verifier contract
const idCardVerifier = m.contract("Verifier_register_id_sha256_sha256_sha256_rsa_65537_4096");
return {
idCardVerifier,
};
});

16795
contracts/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -15,57 +15,44 @@
],
"scripts": {
"build": "npx hardhat clean && npx hardhat compile",
"deploy:all:localhost": "npm run deploy:verifiers:localhost && npm run deploy:registry:localhost && npm run deploy:hub:localhost",
"deploy:hub:localhost": "npx hardhat ignition deploy ignition/modules/deployHub.ts --network localhost --verify",
"deploy:registry:localhost": "npx hardhat ignition deploy ignition/modules/deployRegistry.ts --network localhost --verify",
"deploy:verifiers:localhost": "npx hardhat ignition deploy ignition/modules/deployVerifiers.ts --network localhost --verify",
"deploy:verifyall:localhost": "npx hardhat ignition deploy ignition/modules/deployVerifyAll.ts --network localhost --verify",
"update:cscaroot:localhost": "npx hardhat ignition deploy ignition/modules/scripts/updateRegistryCscaRoot.ts --network localhost",
"update:hub:localhost": "npx hardhat ignition deploy ignition/modules/scripts/updateRegistryHub.ts --network localhost",
"update:ofacroot:localhost": "npx hardhat ignition deploy ignition/modules/scripts/updateRegistryOfacRoot.ts --network localhost",
"deploy:all:sepolia": "npm run deploy:verifiers:sepolia && npm run deploy:registry:sepolia && npm run deploy:hub:sepolia",
"deploy:hub:sepolia": "npx hardhat ignition deploy ignition/modules/deployHub.ts --network sepolia --verify",
"deploy:registry:sepolia": "npx hardhat ignition deploy ignition/modules/deployRegistry.ts --network sepolia --verify",
"deploy:verifiers:sepolia": "npx hardhat ignition deploy ignition/modules/deployVerifiers.ts --network sepolia --verify",
"deploy:verifyall:sepolia": "npx hardhat ignition deploy ignition/modules/deployVerifyAll.ts --network sepolia --verify",
"deploy:all:celo": "npm run deploy:verifiers:celo && npm run deploy:registry:celo && npm run deploy:hub:celo",
"deploy:hub:celo": "npx hardhat ignition deploy ignition/modules/deployHub.ts --network celo --verify",
"deploy:registry:celo": "npx hardhat ignition deploy ignition/modules/deployRegistry.ts --network celo --verify",
"deploy:verifiers:celo": "npx hardhat ignition deploy ignition/modules/deployVerifiers.ts --network celo --verify",
"deploy:allverifiers:celo": "npx hardhat ignition deploy ignition/modules/deployAllVerifiers.ts --network celo --verify",
"deploy:verifyall:celo": "npx hardhat ignition deploy ignition/modules/deployVerifyAll.ts --network celo --verify",
"upgrade:hub:celo": "npx hardhat ignition deploy ignition/modules/deployNewHubAndUpgrade.ts --network celo --verify",
"upgrade:registry:celo": "npx hardhat ignition deploy ignition/modules/deployNewRegistryAndUpgrade.ts --network celo --verify",
"deploy_all": "npx hardhat ignition deploy ignition/modules/Deploy_All.ts --network optimism --verify",
"deploy:all": "npm run deploy:verifiers:all && npm run deploy:registry && npm run deploy:registry:idcard && npm run deploy:hub:v2",
"deploy:hub": "npx dotenv-cli -- bash -c 'npx hardhat ignition deploy ignition/modules/hub/deployHub.ts --network ${NETWORK:-localhost} ${VERIFY:+--verify}'",
"deploy:hub:v2": "npx dotenv-cli -- bash -c 'npx hardhat ignition deploy ignition/modules/hub/deployHubV2.ts --network ${NETWORK:-localhost} ${VERIFY:+--verify}'",
"deploy:registry": "npx dotenv-cli -- bash -c 'npx hardhat ignition deploy ignition/modules/registry/deployRegistry.ts --network ${NETWORK:-localhost} ${VERIFY:+--verify}'",
"deploy:registry:idcard": "npx dotenv-cli -- bash -c 'npx hardhat ignition deploy ignition/modules/registry/deployIdCardRegistry.ts --network ${NETWORK:-localhost} ${VERIFY:+--verify}'",
"deploy:verifiers": "npx dotenv-cli -- bash -c 'npx hardhat ignition deploy ignition/modules/verifiers/deployVerifiers.ts --network ${NETWORK:-localhost} ${VERIFY:+--verify}'",
"deploy:verifiers:all": "npx dotenv-cli -- bash -c 'npx hardhat ignition deploy ignition/modules/verifiers/deployAllVerifiers.ts --network ${NETWORK:-localhost} ${VERIFY:+--verify}'",
"deploy:pcr0": "npx dotenv-cli -- bash -c 'npx hardhat ignition deploy ignition/modules/utils/deployPCR0.ts --network ${NETWORK:-localhost} ${VERIFY:+--verify}'",
"set:registry": "npx dotenv-cli -- bash -c 'NETWORK=${NETWORK:-staging} npx ts-node scripts/setRegistry.ts'",
"set:registry:idcard": "npx dotenv-cli -- bash -c 'NETWORK=${NETWORK:-staging} npx ts-node scripts/setRegistryId.ts'",
"set:hub:v2": "npx dotenv-cli -- bash -c 'NETWORK=${NETWORK:-staging} npx ts-node scripts/setHubV2.ts'",
"update:cscaroot": "npx dotenv-cli -- bash -c 'npx hardhat ignition deploy ignition/modules/scripts/updateRegistryCscaRoot.ts --network ${NETWORK:-localhost} ${VERIFY:+--verify}'",
"update:hub": "npx dotenv-cli -- bash -c 'NETWORK=${NETWORK:-staging} npx ts-node scripts/setRegistry.ts'",
"update:ofacroot": "npx dotenv-cli -- bash -c 'npx hardhat ignition deploy ignition/modules/scripts/updateRegistryOfacRoot.ts --network ${NETWORK:-localhost} ${VERIFY:+--verify}'",
"update:pcr0": "npx dotenv-cli -- bash -c 'PCR0_ACTION=${PCR0_ACTION:-add} PCR0_KEY=${PCR0_KEY} npx hardhat ignition deploy ignition/modules/scripts/updatePCR0.ts --network ${NETWORK:-localhost} --reset'",
"upgrade:hub": "npx dotenv-cli -- bash -c 'npx hardhat ignition deploy ignition/modules/upgrade/deployNewHubAndUpgrade.ts --network ${NETWORK:-localhost} ${VERIFY:+--verify}'",
"upgrade:registry": "npx dotenv-cli -- bash -c 'npx hardhat ignition deploy ignition/modules/upgrade/deployNewRegistryAndUpgrade.ts --network ${NETWORK:-localhost} ${VERIFY:+--verify}'",
"publish": "npm publish --access public",
"export-prod": "bash ./scripts/prod.sh",
"test": "npx hardhat test",
"test:airdrop:local": "TEST_ENV=local npx hardhat test test/example/airdrop.test.ts",
"test:attribute:local": "TEST_ENV=local npx hardhat test test/unit/CircuitAttributeHandler.test.ts",
"test:airdrop": "npx dotenv-cli -- bash -c 'TEST_ENV=${TEST_ENV:-local} npx hardhat test test/example/airdrop.test.ts'",
"test:attribute": "npx dotenv-cli -- bash -c 'TEST_ENV=${TEST_ENV:-local} npx hardhat test test/unit/CircuitAttributeHandler.test.ts'",
"test:coverage": "npx hardhat coverage",
"test:coverage:local": "TEST_ENV=local npx hardhat coverage",
"test:disclose:local": "TEST_ENV=local npx hardhat test test/integration/vcAndDisclose.test.ts",
"test:endtoend:local": "TEST_ENV=local npx hardhat test test/Integration/endToEnd.test.ts",
"test:verifyall:local": "TEST_ENV=local npx hardhat test test/integration/verifyAll.test.ts",
"test:sdkcore:local": "TEST_ENV=local npx hardhat test test/sdk/sdkCore.test.ts --network localhost",
"test:example:local": "TEST_ENV=local npx hardhat test test/example/*",
"test:formatter:local": "TEST_ENV=local npx hardhat test test/unit/formatter.test.ts",
"test:hub:local": "TEST_ENV=local npx hardhat test test/unit/IdentityVerificationHub.test.ts",
"test:integration:local": "TEST_ENV=local npx hardhat test test/integration/*",
"test:disclose": "npx dotenv-cli -- bash -c 'TEST_ENV=${TEST_ENV:-local} npx hardhat test test/integration/vcAndDisclose.test.ts'",
"test:endtoend": "npx dotenv-cli -- bash -c 'TEST_ENV=${TEST_ENV:-local} npx hardhat test test/Integration/endToEnd.test.ts'",
"test:verifyall": "npx dotenv-cli -- bash -c 'TEST_ENV=${TEST_ENV:-local} npx hardhat test test/integration/verifyAll.test.ts'",
"test:sdkcore": "npx dotenv-cli -- bash -c 'TEST_ENV=${TEST_ENV:-local} npx hardhat test test/sdk/sdkCore.test.ts --network localhost'",
"test:example": "npx dotenv-cli -- bash -c 'TEST_ENV=${TEST_ENV:-local} npx hardhat test test/example/*'",
"test:formatter": "npx dotenv-cli -- bash -c 'TEST_ENV=${TEST_ENV:-local} npx hardhat test test/unit/formatter.test.ts'",
"test:hub": "npx dotenv-cli -- bash -c 'TEST_ENV=${TEST_ENV:-local} npx hardhat test test/unit/IdentityVerificationHub.test.ts'",
"test:integration": "npx dotenv-cli -- bash -c 'TEST_ENV=${TEST_ENV:-local} npx hardhat test test/integration/*'",
"test:local": "TEST_ENV=local npx hardhat test",
"test:register:local": "TEST_ENV=local npx hardhat test test/integration/commitmentRegistration.test.ts",
"test:registry:local": "TEST_ENV=local npx hardhat test test/unit/IdentityRegistry.test.ts",
"test:pcr:local": "TEST_ENV=local npx hardhat test test/unit/PCR0Manager.test.ts",
"test:unit:local": "TEST_ENV=local npx hardhat test test/unit/*",
"test:register": "npx dotenv-cli -- bash -c 'TEST_ENV=${TEST_ENV:-local} npx hardhat test test/integration/commitmentRegistration.test.ts'",
"test:registry": "npx dotenv-cli -- bash -c 'TEST_ENV=${TEST_ENV:-local} npx hardhat test test/unit/IdentityRegistry.test.ts'",
"test:pcr": "npx dotenv-cli -- bash -c 'TEST_ENV=${TEST_ENV:-local} npx hardhat test test/unit/PCR0Manager.test.ts'",
"test:unit": "npx dotenv-cli -- bash -c 'TEST_ENV=${TEST_ENV:-local} npx hardhat test test/unit/*'",
"test:view": "npx hardhat test test/view.ts",
"update:cscaroot:sepolia": "npx hardhat ignition deploy ignition/modules/scripts/updateRegistryCscaRoot.ts --network sepolia",
"update:hub:sepolia": "npx hardhat ignition deploy ignition/modules/scripts/updateRegistryHub.ts --network sepolia",
"update:ofacroot:sepolia": "npx hardhat ignition deploy ignition/modules/scripts/updateRegistryOfacRoot.ts --network sepolia",
"update:cscaroot:celo": "npx hardhat ignition deploy ignition/modules/scripts/updateRegistryCscaRoot.ts --network celo --verify",
"update:hub:celo": "npx hardhat ignition deploy ignition/modules/scripts/updateRegistryHub.ts --network celo --verify",
"update:ofacroot:celo": "npx hardhat ignition deploy ignition/modules/scripts/updateRegistryOfacRoot.ts --network celo --verify",
"deploy:pcr0:celo": "npx hardhat ignition deploy ignition/modules/deployPCR0.ts --network celo --verify",
"update:pcr0:celo": "PCR0_ACTION=add PCR0_KEY=123 npx hardhat ignition deploy ignition/modules/scripts/updatePCR0.ts --network celo --reset",
"prettier:write": "prettier --write '**/*.{json,md,yml,sol,ts}'",
"prettier:check": "prettier --list-different '**/*.{json,md,yml,sol,ts}'"
},
@@ -107,6 +94,7 @@
"@types/mocha": "^10.0.6",
"@types/snarkjs": "^0.7.7",
"chai": "^4.4.1",
"dotenv-cli": "^7.4.2",
"ethers": "^6.12.1",
"hardhat": "^2.22.6",
"hardhat-gas-reporter": "^1.0.10",

View File

@@ -0,0 +1,232 @@
import { ethers } from "ethers";
import * as dotenv from "dotenv";
import * as fs from "fs";
import * as path from "path";
import { RegisterVerifierId, DscVerifierId } from "../../common/src/constants/constants";
dotenv.config();
// Environment configuration
const NETWORK = process.env.NETWORK || "localhost"; // Default to staging
const RPC_URL_KEY = NETWORK === "celo" ? "CELO_RPC_URL" : "CELO_ALFAJORES_RPC_URL";
const PRIVATE_KEY = process.env.CELO_KEY;
// Network to Chain ID mapping
const NETWORK_TO_CHAIN_ID: Record<string, string> = {
"localhost": "31337",
"celoAlfajores": "44787",
"celo": "42220",
};
// Get chain ID from network name
const getChainId = (network: string): string => {
return NETWORK_TO_CHAIN_ID[network] || NETWORK_TO_CHAIN_ID["staging"];
};
const CHAIN_ID = getChainId(NETWORK);
// Define AttestationId constants directly based on values from AttestationId.sol
const AttestationId = {
// Pad with zeros to create full 32 bytes length
E_PASSPORT: "0x0000000000000000000000000000000000000000000000000000000000000001",
EU_ID_CARD: "0x0000000000000000000000000000000000000000000000000000000000000002",
};
// Dynamic paths based on chain ID
const deployedAddressesPath = path.join(__dirname, `../ignition/deployments/chain-${CHAIN_ID}/deployed_addresses.json`);
const contractAbiPath = path.join(__dirname, `../ignition/deployments/chain-${CHAIN_ID}/artifacts/DeployHubV2#IdentityVerificationHubImplV2.json`);
// Debug logs for paths and files
console.log("Network:", NETWORK);
console.log("Chain ID:", CHAIN_ID);
console.log("Current directory:", __dirname);
console.log("Deployed addresses path:", deployedAddressesPath);
console.log("Contract ABI path:", contractAbiPath);
// Debug logs for environment variables (redacted for security)
console.log(`${RPC_URL_KEY} configured:`, !!process.env[RPC_URL_KEY]);
console.log("CELO_KEY configured:", !!PRIVATE_KEY);
try {
const deployedAddresses = JSON.parse(fs.readFileSync(deployedAddressesPath, "utf-8"));
console.log("Deployed addresses loaded:", deployedAddresses);
const identityVerificationHubAbiFile = fs.readFileSync(contractAbiPath, "utf-8");
console.log("ABI file loaded");
const identityVerificationHubAbi = JSON.parse(identityVerificationHubAbiFile).abi;
console.log("ABI parsed");
function getContractAddressByPartialName(partialName: string): string | unknown {
for (const [key, value] of Object.entries(deployedAddresses)) {
if (key.includes(partialName)) {
return value;
}
}
return undefined;
}
function getContractAddressByExactName(exactName: string): string | unknown {
if (exactName in deployedAddresses) {
return deployedAddresses[exactName];
}
return undefined;
}
function getAttestationIdBytes32(attestationIdName: string): string {
return AttestationId[attestationIdName as keyof typeof AttestationId];
}
async function main() {
const provider = new ethers.JsonRpcProvider(process.env[RPC_URL_KEY] as string);
console.log("Provider created");
const wallet = new ethers.Wallet(PRIVATE_KEY as string, provider);
console.log("Wallet created");
// const hubAddress = deployedAddresses["DeployHub#IdentityVerificationHub"];
const hubAddress = "0xbaB9B3c376e84FEB4aFD9423e3aB278B47d34a0a"; // Update with your actual hub address
console.log("Hub address:", hubAddress);
if (!hubAddress) {
throw new Error("Hub address not found in deployed_addresses.json");
}
const identityVerificationHub = new ethers.Contract(hubAddress, identityVerificationHubAbi, wallet);
console.log("Contract instance created");
// Update registry addresses for different attestation types
const attestationTypes = ["E_PASSPORT", "EU_ID_CARD"];
for (const attestationType of attestationTypes) {
let registryName: any;
if (attestationType == "E_PASSPORT") {
registryName = "DeployRegistryModule#IdentityRegistry";
} else if (attestationType == "EU_ID_CARD") {
registryName = "DeployIdCardRegistryModule#IdentityRegistryIdCard";
}
const registryAddress = getContractAddressByExactName(registryName);
if (!registryAddress) {
console.log(`Skipping registry update for ${attestationType} because no deployed address was found.`);
continue;
}
console.log(`Updating registry for ${attestationType}`);
const attestationId = getAttestationIdBytes32(attestationType);
try {
const tx = await identityVerificationHub.updateRegistry(attestationId, registryAddress);
const receipt = await tx.wait();
console.log(`Registry for ${attestationType} updated with tx: ${receipt.hash}`);
} catch (error) {
console.error(`Error updating registry for ${attestationType}:`, error);
}
}
// Update VC and Disclose circuit verifiers for different attestation types
for (const attestationType of attestationTypes) {
let verifierName: any;
if (attestationType == "E_PASSPORT") {
verifierName = "DeployAllVerifiers#Verifier_vc_and_disclose";
} else if (attestationType == "EU_ID_CARD") {
verifierName = "";
}
const verifierAddress = getContractAddressByExactName(verifierName);
if (!verifierAddress) {
console.log(
`Skipping VC and Disclose circuit update for ${attestationType} because no deployed address was found.`,
);
continue;
}
console.log(`Updating VC and Disclose circuit for ${attestationType}`);
const attestationId = getAttestationIdBytes32(attestationType);
try {
const tx = await identityVerificationHub.updateVcAndDiscloseCircuit(attestationId, verifierAddress);
const receipt = await tx.wait();
console.log(`VC and Disclose circuit for ${attestationType} updated with tx: ${receipt.hash}`);
} catch (error) {
console.error(`Error updating VC and Disclose circuit for ${attestationType}:`, error);
}
}
// Batch update register circuit verifiers
const registerVerifierKeys = Object.keys(RegisterVerifierId).filter((key) => isNaN(Number(key)));
const registerCircuitVerifierIds: number[] = [];
const registerCircuitVerifierAddresses: string[] = [];
for (const key of registerVerifierKeys) {
const verifierName = `Verifier_${key}`;
const verifierAddress = getContractAddressByPartialName(verifierName);
if (!verifierAddress) {
console.log(`Skipping ${verifierName} because no deployed address was found.`);
continue;
}
const verifierId = RegisterVerifierId[key as keyof typeof RegisterVerifierId];
registerCircuitVerifierIds.push(verifierId);
registerCircuitVerifierAddresses.push(verifierAddress as string);
}
if (registerCircuitVerifierIds.length > 0) {
console.log("Batch updating register circuit verifiers");
try {
const tx = await identityVerificationHub.batchUpdateRegisterCircuitVerifiers(
registerCircuitVerifierIds,
registerCircuitVerifierAddresses,
);
const receipt = await tx.wait();
console.log(`Register circuit verifiers updated with tx: ${receipt.hash}`);
} catch (error) {
console.error("Error batch updating register circuit verifiers:", error);
}
}
// Batch update DSC circuit verifiers
const dscKeys = Object.keys(DscVerifierId).filter((key) => isNaN(Number(key)));
const dscCircuitVerifierIds: number[] = [];
const dscCircuitVerifierAddresses: string[] = [];
for (const key of dscKeys) {
const verifierName = `Verifier_${key}`;
const verifierAddress = getContractAddressByPartialName(verifierName);
if (!verifierAddress) {
console.log(`Skipping ${verifierName} because no deployed address was found.`);
continue;
}
const verifierId = DscVerifierId[key as keyof typeof DscVerifierId];
dscCircuitVerifierIds.push(verifierId);
dscCircuitVerifierAddresses.push(verifierAddress as string);
}
if (dscCircuitVerifierIds.length > 0) {
console.log("Batch updating DSC circuit verifiers");
try {
const tx = await identityVerificationHub.batchUpdateDscCircuitVerifiers(
dscCircuitVerifierIds,
dscCircuitVerifierAddresses,
);
const receipt = await tx.wait();
console.log(`DSC circuit verifiers updated with tx: ${receipt.hash}`);
} catch (error) {
console.error("Error batch updating DSC circuit verifiers:", error);
}
}
}
main().catch((error) => {
console.error("Execution error:", error);
process.exitCode = 1;
});
} catch (error) {
console.error("Initial setup error:", error);
process.exitCode = 1;
}

View File

@@ -0,0 +1,132 @@
import { ethers } from "ethers";
import * as dotenv from "dotenv";
import * as fs from "fs";
import * as path from "path";
// import { getCscaTreeRoot } from "../../common/src/utils/trees";
// import serialized_csca_tree from "../../common/pubkeys/serialized_csca_tree.json";
dotenv.config();
// Environment configuration
const NETWORK = process.env.NETWORK || "localhost"; // Default to localhost
const RPC_URL_KEY = NETWORK === "celo" ? "CELO_RPC_URL" : "CELO_ALFAJORES_RPC_URL";
const PRIVATE_KEY = process.env.CELO_KEY;
const SKIP_CSCA_UPDATE = process.env.SKIP_CSCA_UPDATE === "true";
const CSCA_ROOT = process.env.CSCA_ROOT; // Allow manual CSCA root setting
// Network to Chain ID mapping
const NETWORK_TO_CHAIN_ID: Record<string, string> = {
"localhost": "31337",
"celoAlfajores": "44787",
"celo": "42220",
};
// Get chain ID from network name
const getChainId = (network: string): string => {
return NETWORK_TO_CHAIN_ID[network] || NETWORK_TO_CHAIN_ID["localhost"];
};
const CHAIN_ID = getChainId(NETWORK);
// Dynamic paths based on chain ID
const deployedAddressesPath = path.join(__dirname, `../ignition/deployments/chain-${CHAIN_ID}/deployed_addresses.json`);
const contractAbiPath = path.join(__dirname, `../ignition/deployments/chain-${CHAIN_ID}/artifacts/DeployRegistryModule#IdentityRegistryImplV1.json`);
// Debug logs for paths and files
console.log("Network:", NETWORK);
console.log("Chain ID:", CHAIN_ID);
console.log("Current directory:", __dirname);
console.log("Deployed addresses path:", deployedAddressesPath);
console.log("Contract ABI path:", contractAbiPath);
// Debug logs for environment variables (redacted for security)
console.log(`${RPC_URL_KEY} configured:`, !!process.env[RPC_URL_KEY]);
console.log("CELO_KEY configured:", !!PRIVATE_KEY);
try {
const deployedAddresses = JSON.parse(fs.readFileSync(deployedAddressesPath, "utf-8"));
console.log("Deployed addresses loaded:", deployedAddresses);
const identityRegistryAbiFile = fs.readFileSync(contractAbiPath, "utf-8");
console.log("Registry ABI file loaded");
const identityRegistryAbi = JSON.parse(identityRegistryAbiFile).abi;
console.log("Registry ABI parsed");
function getContractAddressByExactName(exactName: string): string | unknown {
if (exactName in deployedAddresses) {
return deployedAddresses[exactName];
}
return undefined;
}
async function main() {
const provider = new ethers.JsonRpcProvider(process.env[RPC_URL_KEY] as string);
console.log("Provider created");
const wallet = new ethers.Wallet(PRIVATE_KEY as string, provider);
console.log("Wallet created");
// Get registry address
const registryAddress = getContractAddressByExactName("DeployRegistryModule#IdentityRegistry");
console.log("Registry address:", registryAddress);
if (!registryAddress) {
throw new Error("Registry address not found in deployed_addresses.json");
}
// Get hub address
const hubAddress = getContractAddressByExactName("DeployHubV2#IdentityVerificationHubImplV2");
console.log("Hub address:", hubAddress);
if (!hubAddress) {
throw new Error("Hub address not found in deployed_addresses.json");
}
const identityRegistry = new ethers.Contract(registryAddress as string, identityRegistryAbi, wallet);
console.log("Registry contract instance created");
// Update hub address
console.log("Updating hub address...");
try {
const tx1 = await identityRegistry.updateHub(hubAddress);
const receipt1 = await tx1.wait();
console.log(`Hub address updated with tx: ${receipt1.hash}`);
} catch (error) {
console.error("Error updating hub address:", error);
}
// Update CSCA root
console.log("Updating CSCA root...");
try {
if (SKIP_CSCA_UPDATE) {
console.log("Skipping CSCA root update as per configuration");
return;
}
if (!CSCA_ROOT) {
console.log("CSCA_ROOT environment variable not set, skipping CSCA root update");
console.log("To set CSCA root, use: CSCA_ROOT=<your_root_value> npm run set:registry");
return;
}
console.log("CSCA Merkle root:", CSCA_ROOT);
const tx2 = await identityRegistry.updateCscaRoot(CSCA_ROOT);
const receipt2 = await tx2.wait();
console.log(`CSCA root updated with tx: ${receipt2.hash}`);
} catch (error) {
console.error("Error updating CSCA root:", error);
}
console.log("Registry setup completed successfully!");
}
main().catch((error) => {
console.error("Execution error:", error);
process.exitCode = 1;
});
} catch (error) {
console.error("Initial setup error:", error);
process.exitCode = 1;
}

View File

@@ -0,0 +1,132 @@
import { ethers } from "ethers";
import * as dotenv from "dotenv";
import * as fs from "fs";
import * as path from "path";
// import { getCscaTreeRoot } from "../../common/src/utils/trees";
// import serialized_csca_tree from "../../common/pubkeys/serialized_csca_tree.json";
dotenv.config();
// Environment configuration
const NETWORK = process.env.NETWORK || "localhost"; // Default to localhost
const RPC_URL_KEY = NETWORK === "celo" ? "CELO_RPC_URL" : "CELO_ALFAJORES_RPC_URL";
const PRIVATE_KEY = process.env.CELO_KEY;
const SKIP_CSCA_UPDATE = process.env.SKIP_CSCA_UPDATE === "true";
const CSCA_ROOT = process.env.CSCA_ROOT; // Allow manual CSCA root setting
// Network to Chain ID mapping
const NETWORK_TO_CHAIN_ID: Record<string, string> = {
"localhost": "31337",
"celoAlfajores": "44787",
"celo": "42220",
};
// Get chain ID from network name
const getChainId = (network: string): string => {
return NETWORK_TO_CHAIN_ID[network] || NETWORK_TO_CHAIN_ID["localhost"];
};
const CHAIN_ID = getChainId(NETWORK);
// Dynamic paths based on chain ID
const deployedAddressesPath = path.join(__dirname, `../ignition/deployments/chain-${CHAIN_ID}/deployed_addresses.json`);
const contractAbiPath = path.join(__dirname, `../ignition/deployments/chain-${CHAIN_ID}/artifacts/DeployIdCardRegistryModule#IdentityRegistryIdCardImplV1.json`);
// Debug logs for paths and files
console.log("Network:", NETWORK);
console.log("Chain ID:", CHAIN_ID);
console.log("Current directory:", __dirname);
console.log("Deployed addresses path:", deployedAddressesPath);
console.log("Contract ABI path:", contractAbiPath);
// Debug logs for environment variables (redacted for security)
console.log(`${RPC_URL_KEY} configured:`, !!process.env[RPC_URL_KEY]);
console.log("CELO_KEY configured:", !!PRIVATE_KEY);
try {
const deployedAddresses = JSON.parse(fs.readFileSync(deployedAddressesPath, "utf-8"));
console.log("Deployed addresses loaded:", deployedAddresses);
const identityRegistryIdCardAbiFile = fs.readFileSync(contractAbiPath, "utf-8");
console.log("Registry ID Card ABI file loaded");
const identityRegistryIdCardAbi = JSON.parse(identityRegistryIdCardAbiFile).abi;
console.log("Registry ID Card ABI parsed");
function getContractAddressByExactName(exactName: string): string | unknown {
if (exactName in deployedAddresses) {
return deployedAddresses[exactName];
}
return undefined;
}
async function main() {
const provider = new ethers.JsonRpcProvider(process.env[RPC_URL_KEY] as string);
console.log("Provider created");
const wallet = new ethers.Wallet(PRIVATE_KEY as string, provider);
console.log("Wallet created");
// Get registry ID card address
const registryIdCardAddress = getContractAddressByExactName("DeployIdCardRegistryModule#IdentityRegistryIdCard");
console.log("Registry ID Card address:", registryIdCardAddress);
if (!registryIdCardAddress) {
throw new Error("Registry ID Card address not found in deployed_addresses.json");
}
// Get hub address
const hubAddress = getContractAddressByExactName("DeployHubV2#IdentityVerificationHubImplV2");
console.log("Hub address:", hubAddress);
if (!hubAddress) {
throw new Error("Hub address not found in deployed_addresses.json");
}
const identityRegistryIdCard = new ethers.Contract(registryIdCardAddress as string, identityRegistryIdCardAbi, wallet);
console.log("Registry ID Card contract instance created");
// Update hub address
console.log("Updating hub address...");
try {
const tx1 = await identityRegistryIdCard.updateHub(hubAddress);
const receipt1 = await tx1.wait();
console.log(`Hub address updated with tx: ${receipt1.hash}`);
} catch (error) {
console.error("Error updating hub address:", error);
}
// Update CSCA root (same value as passport registry)
console.log("Updating CSCA root...");
try {
if (SKIP_CSCA_UPDATE) {
console.log("Skipping CSCA root update as per configuration");
return;
}
if (!CSCA_ROOT) {
console.log("CSCA_ROOT environment variable not set, skipping CSCA root update");
console.log("To set CSCA root, use: CSCA_ROOT=<your_root_value> npm run set:registry:idcard");
return;
}
console.log("CSCA Merkle root:", CSCA_ROOT);
const tx2 = await identityRegistryIdCard.updateCscaRoot(CSCA_ROOT);
const receipt2 = await tx2.wait();
console.log(`CSCA root updated with tx: ${receipt2.hash}`);
} catch (error) {
console.error("Error updating CSCA root:", error);
}
console.log("Registry ID Card setup completed successfully!");
}
main().catch((error) => {
console.error("Execution error:", error);
process.exitCode = 1;
});
} catch (error) {
console.error("Initial setup error:", error);
process.exitCode = 1;
}