mirror of
https://github.com/selfxyz/self.git
synced 2026-04-27 03:01:15 -04:00
init repo - add ProofOfPassportWeb2Verifier and test
This commit is contained in:
committed by
0xturboblitz
parent
1cc42e6b10
commit
438ee8daa7
@@ -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
3
sdk/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
yarn.lock
|
||||
yarn-error.log
|
||||
38
sdk/package.json
Normal file
38
sdk/package.json
Normal 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
136
sdk/sdk.ts
Normal 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
63
sdk/tests/sdk.test.ts
Normal 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
9
sdk/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"target": "ES2020",
|
||||
"moduleResolution": "node",
|
||||
"module": "CommonJS",
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user