init repo - add ProofOfPassportWeb2Verifier and test

This commit is contained in:
turnoffthiscomputer
2024-06-11 11:02:19 +02:00
committed by 0xturboblitz
parent 1cc42e6b10
commit 438ee8daa7
6 changed files with 517 additions and 0 deletions

View File

@@ -301,3 +301,271 @@ qzOBhID0Nxk4k9sW1uT6ocW1xp1SB2WotORssOKIAOLJM8IbPl6n/DkYNcfvyXI7
6/QfJTKVixJpVfDh386ALXc97EPWDMWIalUwYoV/eRSMnuV8nZ0+Ctp3Qrtk/JYd
+FWhKbtlPeRjmGVr6mVlvDJ7KqtY5/RqqwfWeXhXezGhQqQ/OoQQCRkCAwEAAQ==
-----END RSA PUBLIC KEY-----`;
export const DEFAULT_RPC_URL = "https://mainnet.optimism.io";
export const REGISTER_CONTRACT_ADDRESS = "0xFd84F23Be557133DCa47Fc9aa22031AcCE557335";
/*** ABI ***/
export const REGISTER_ABI = [
{
"inputs": [],
"name": "Register__InvalidMerkleRoot",
"type": "error"
},
{
"inputs": [],
"name": "Register__InvalidProof",
"type": "error"
},
{
"inputs": [],
"name": "Register__InvalidSignatureAlgorithm",
"type": "error"
},
{
"inputs": [],
"name": "Register__InvalidVerifierAddress",
"type": "error"
},
{
"inputs": [],
"name": "Register__SignatureAlgorithmAlreadySet",
"type": "error"
},
{
"inputs": [],
"name": "Register__YouAreUsingTheSameNullifierTwice",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "index",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "commitment",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "merkle_root",
"type": "uint256"
}
],
"name": "AddCommitment",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "merkle_root",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "nullifier",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "commitment",
"type": "uint256"
}
],
"name": "ProofValidated",
"type": "event"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "root",
"type": "uint256"
}
],
"name": "checkRoot",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getMerkleRoot",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getMerkleTreeSize",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "commitment",
"type": "uint256"
}
],
"name": "indexOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "uint256",
"name": "commitment",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "nullifier",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "merkle_root",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "attestation_id",
"type": "uint256"
},
{
"internalType": "uint256[2]",
"name": "a",
"type": "uint256[2]"
},
{
"internalType": "uint256[2][2]",
"name": "b",
"type": "uint256[2][2]"
},
{
"internalType": "uint256[2]",
"name": "c",
"type": "uint256[2]"
}
],
"internalType": "struct IRegister.RegisterProof",
"name": "proof",
"type": "tuple"
},
{
"internalType": "uint256",
"name": "signature_algorithm",
"type": "uint256"
}
],
"name": "validateProof",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "uint256",
"name": "commitment",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "nullifier",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "merkle_root",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "attestation_id",
"type": "uint256"
},
{
"internalType": "uint256[2]",
"name": "a",
"type": "uint256[2]"
},
{
"internalType": "uint256[2][2]",
"name": "b",
"type": "uint256[2][2]"
},
{
"internalType": "uint256[2]",
"name": "c",
"type": "uint256[2]"
}
],
"internalType": "struct IRegister.RegisterProof",
"name": "proof",
"type": "tuple"
},
{
"internalType": "uint256",
"name": "signature_algorithm",
"type": "uint256"
}
],
"name": "verifyProof",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
}
]

3
sdk/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
node_modules/
yarn.lock
yarn-error.log

38
sdk/package.json Normal file
View File

@@ -0,0 +1,38 @@
{
"name": "sdk",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"@types/chai-as-promised": "^7.1.8",
"@types/expect": "^24.3.0",
"@types/mocha": "^10.0.6",
"@types/node": "^20.11.19",
"@types/node-forge": "^1.3.5",
"@zk-kit/lean-imt": "^2.0.1",
"chai-as-promised": "^7.1.1",
"ethers": "^6.13.0",
"fs": "^0.0.1-security",
"js-sha1": "^0.7.0",
"js-sha256": "^0.11.0",
"js-sha512": "^0.9.0",
"mocha": "^10.4.0",
"poseidon-lite": "^0.2.0",
"snarkjs": "^0.7.4",
"ts-mocha": "^10.0.0",
"ts-node": "^10.9.2",
"typescript": "^5.4.5"
},
"devDependencies": {
"@types/chai": "^4.3.6",
"@types/circomlibjs": "^0.1.6",
"@types/mocha": "^10.0.6",
"chai": "^4.3.8",
"mocha": "^10.3.0",
"ts-mocha": "^10.0.0",
"ts-node": "^10.9.2"
},
"scripts": {
"test": "yarn ts-mocha -p ./tsconfig.json tests/sdk.test.ts --exit"
}
}

136
sdk/sdk.ts Normal file
View File

@@ -0,0 +1,136 @@
import { groth16 } from 'snarkjs';
import fs from 'fs';
import { attributeToPosition, countryCodes, DEFAULT_RPC_URL, REGISTER_ABI, REGISTER_CONTRACT_ADDRESS } from '../common/src/constants/constants';
import { ethers } from 'ethers';
import { getCurrentDateYYMMDD } from '../common/src/utils/utils';
const path_disclose_vkey = "../circuits/build/disclose_vkey.json";
const MOCK_MERKLE_ROOT_CHECK = true;
export class ProofOfPassportWeb2Verifier {
scope: string;
attestationId: string;
requirements: Array<[string, number | string]>;
rpcUrl: string;
constructor(scope: string, attestationId: string, requirements: Array<[string, number | string]>, rpcUrl: string = DEFAULT_RPC_URL) {
this.scope = scope;
this.attestationId = attestationId;
this.requirements = requirements.map(requirement => {
if (!attributeToPosition.hasOwnProperty(requirement[0])) {
throw new Error(`Attribute ${requirement[0]} is not recognized.`);
}
return requirement;
});
this.rpcUrl = rpcUrl;
}
async verifyInputs(publicSignals, proof) {
const parsedPublicSignals = parsePublicSignals(publicSignals);
//1. Verify the scope
if (parsedPublicSignals.scope !== this.scope) {
throw new Error(`Scope ${parsedPublicSignals.scope} does not match the scope ${this.scope}`);
}
console.log('\x1b[32m%s\x1b[0m', `- scope verified`);
//2. Verify the merkle_root
const merkleRootIsValid = await checkMerkleRoot(this.rpcUrl, parsedPublicSignals.merkle_root);
if (!(merkleRootIsValid || MOCK_MERKLE_ROOT_CHECK)) {
throw new Error(`Merkle root is not valid`);
}
console.log('\x1b[32m%s\x1b[0m', `- merkle_root verified`);
//3. Verify the attestation_id
if (parsedPublicSignals.attestation_id !== this.attestationId) {
throw new Error(`Attestation id ${parsedPublicSignals.attestation_id} does not match the attestation id ${this.attestationId}`);
}
console.log('\x1b[32m%s\x1b[0m', `- attestation_id verified`);
//4. Verify the current_date
if (parsedPublicSignals.current_date.toString() !== getCurrentDateFormatted().toString()) {
throw new Error(`Current date ${parsedPublicSignals.current_date} does not match the current date ${getCurrentDateFormatted()}`);
}
console.log('\x1b[32m%s\x1b[0m', `- current_date verified`);
//5. Verify requirements
const unpackedReveal = unpackReveal(parsedPublicSignals.revealedData_packed);
for (const requirement of this.requirements) {
const attribute = requirement[0];
const value = requirement[1];
const position = attributeToPosition[attribute];
let attributeValue = '';
for (let i = position[0]; i <= position[1]; i++) {
attributeValue += unpackedReveal[i];
}
if (requirement[0] === "nationality" || requirement[0] === "issuing_state") {
if (!countryCodes[attributeValue] || countryCodes[attributeValue] !== value) {
throw new Error(`Attribute ${attribute} does not match the value ${value}`);
}
}
else {
if (attributeValue !== value) {
throw new Error(`Attribute ${attribute} does not match the value ${value}`);
}
}
console.log('\x1b[32m%s\x1b[0m', `- requirement ${requirement[0]} verified`);
}
//6. Verify the proof
const vkey_disclose = JSON.parse(fs.readFileSync(path_disclose_vkey) as unknown as string);
const verified_disclose = await groth16.verify(
vkey_disclose,
publicSignals,
proof
)
if (!verified_disclose) {
throw new Error(`Proof is not valid`);
}
console.log('\x1b[32m%s\x1b[0m', `- proof verified`);
const result = {
nullifier: parsedPublicSignals.nullifier,
user_identifier: parsedPublicSignals.user_identifier,
};
return result;
}
}
function getCurrentDateFormatted() {
return getCurrentDateYYMMDD().map(datePart => BigInt(datePart).toString());
}
async function checkMerkleRoot(rpcUrl: string, merkleRoot: number) {
const provider = new ethers.JsonRpcProvider(rpcUrl);
const contract = new ethers.Contract(REGISTER_CONTRACT_ADDRESS, REGISTER_ABI, provider);
return await contract.checkRoot(merkleRoot);
}
function parsePublicSignals(publicSignals) {
return {
nullifier: publicSignals[0],
revealedData_packed: [publicSignals[1], publicSignals[2], publicSignals[3]],
attestation_id: publicSignals[4],
merkle_root: publicSignals[5],
scope: publicSignals[6],
current_date: [publicSignals[7], publicSignals[8], publicSignals[9], publicSignals[10], publicSignals[11], publicSignals[12]],
user_identifier: publicSignals[13],
}
}
export function unpackReveal(revealedData_packed: string[]): string[] {
const bytesCount = [31, 31, 28]; // nb of bytes in each of the first three field elements
const bytesArray = revealedData_packed.flatMap((element: string, index: number) => {
const bytes = bytesCount[index];
const elementBigInt = BigInt(element);
const byteMask = BigInt(255); // 0xFF
const bytesOfElement = [...Array(bytes)].map((_, byteIndex) => {
return (elementBigInt >> (BigInt(byteIndex) * BigInt(8))) & byteMask;
});
return bytesOfElement;
});
return bytesArray.map((byte: bigint) => String.fromCharCode(Number(byte)));
}

63
sdk/tests/sdk.test.ts Normal file
View File

@@ -0,0 +1,63 @@
import { assert, expect } from 'chai'
import { groth16 } from 'snarkjs';
import { generateCircuitInputsDisclose } from '../../common/src/utils/generateInputs';
import { mockPassportData_sha256WithRSAEncryption_65537 } from '../../common/src/utils/mockPassportData';
import { LeanIMT } from "@zk-kit/lean-imt";
import { poseidon2, poseidon6 } from "poseidon-lite";
import { PASSPORT_ATTESTATION_ID } from "../../common/src/constants/constants";
import { formatMrz, packBytes } from '../../common/src/utils/utils';
import { getLeaf } from '../../common/src/utils/pubkeyTree';
import { ProofOfPassportWeb2Verifier } from '../sdk';
const path_disclose_wasm = "../circuits/build/disclose_js/disclose.wasm";
const path_disclose_zkey = "../circuits/build/disclose_final.zkey";
describe('Circuit Proving Tests', () => {
it('should generate a valid proof for the disclose circuit', async () => {
const passportData = mockPassportData_sha256WithRSAEncryption_65537;
const imt = new LeanIMT((a: bigint, b: bigint) => poseidon2([a, b]), []);
const bitmap = Array(90).fill("1");
const scope = BigInt(1).toString();
const majority = ["1", "8"];
const secret = BigInt(0).toString();
const attestation_id = PASSPORT_ATTESTATION_ID;
const mrz_bytes = packBytes(formatMrz(passportData.mrz));
const pubkey_leaf = getLeaf({
signatureAlgorithm: passportData.signatureAlgorithm,
modulus: passportData.pubKey.modulus,
exponent: passportData.pubKey.exponent,
}).toString();
const commitment = poseidon6([
secret,
attestation_id,
pubkey_leaf,
mrz_bytes[0],
mrz_bytes[1],
mrz_bytes[2]
])
imt.insert(commitment);
const inputs = generateCircuitInputsDisclose(
secret,
attestation_id,
passportData,
imt as any,
majority,
bitmap,
scope,
BigInt(5).toString()
);
const { proof, publicSignals } = await groth16.fullProve(
inputs,
path_disclose_wasm,
path_disclose_zkey
);
const proofOfPassportWeb2Verifier = new ProofOfPassportWeb2Verifier(scope, attestation_id, [["older_than", "18"], ["nationality", "France"]]);
const result = await proofOfPassportWeb2Verifier.verifyInputs(publicSignals, proof);
console.log('\x1b[34m%s\x1b[0m', "- nullifier: " + result.nullifier);
console.log('\x1b[34m%s\x1b[0m', "- user_identifier: " + result.user_identifier);
});
});

9
sdk/tsconfig.json Normal file
View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"resolveJsonModule": true,
"esModuleInterop": true,
"target": "ES2020",
"moduleResolution": "node",
"module": "CommonJS",
}
}