diff --git a/app/src/utils/generateInputsInApp.ts b/app/src/utils/generateInputsInApp.ts index 9d22ff104..3e5441c78 100644 --- a/app/src/utils/generateInputsInApp.ts +++ b/app/src/utils/generateInputsInApp.ts @@ -1,7 +1,7 @@ -import { ArgumentsProveOffChain, DisclosureOptions, OpenPassportApp } from '../../../common/src/utils/appType'; +import { ArgumentsProveOffChain, ArgumentsRegister, DisclosureOptions, OpenPassportApp } from '../../../common/src/utils/appType'; import { PassportData } from '../../../common/src/utils/types'; import { generateCircuitInputsProve } from '../../../common/src/utils/generateInputs'; -import { circuitToSelectorMode, DEFAULT_MAJORITY } from '../../../common/src/constants/constants'; +import { circuitToSelectorMode, DEFAULT_MAJORITY, getCountryCode } from '../../../common/src/constants/constants'; import { revealBitmapFromAttributes } from '../../../common/src/utils/revealBitmap'; import useUserStore from '../stores/userStore'; import namejson from '../../../common/ofacdata/outputs/nameSMT.json'; @@ -11,17 +11,18 @@ export const generateCircuitInputsInApp = ( passportData: PassportData, app: OpenPassportApp ): any => { - + const { secret, dscSecret } = useUserStore.getState(); + const selector_mode = circuitToSelectorMode[app.mode as keyof typeof circuitToSelectorMode]; + let smt = new SMT(poseidon2, true); + smt.import(namejson); switch (app.mode) { case "prove_offchain": + case "prove_onchain": const disclosureOptions: DisclosureOptions = (app.args as ArgumentsProveOffChain).disclosureOptions; - const { secret, dscSecret } = useUserStore.getState(); - const selector_mode = circuitToSelectorMode[app.mode as keyof typeof circuitToSelectorMode]; const selector_dg1 = revealBitmapFromAttributes(disclosureOptions); const selector_older_than = disclosureOptions.minimumAge.enabled ? 1 : 0; const selector_ofac = disclosureOptions.ofac ? 1 : 0; - let smt = new SMT(poseidon2, true); - smt.import(namejson); + return generateCircuitInputsProve( selector_mode, secret, @@ -33,11 +34,31 @@ export const generateCircuitInputsInApp = ( disclosureOptions.minimumAge.value ?? DEFAULT_MAJORITY, smt, selector_ofac, - disclosureOptions.excludedCountries.value, + disclosureOptions.excludedCountries.value.map(country => getCountryCode(country)), app.userId, app.userIdType ); - + break; + case "register": + const selector_dg1_zero = new Array(88).fill(0); + const selector_older_than_zero = 0; + const selector_ofac_zero = 0; + return generateCircuitInputsProve( + selector_mode, + secret, + dscSecret as string, + passportData, + app.scope, + selector_dg1_zero, + selector_older_than_zero, + DEFAULT_MAJORITY, + smt, + selector_ofac_zero, + [], + app.userId, + app.userIdType + ); + break; } diff --git a/common/src/constants/constants.ts b/common/src/constants/constants.ts index a801abd13..661587ebc 100644 --- a/common/src/constants/constants.ts +++ b/common/src/constants/constants.ts @@ -337,7 +337,11 @@ export const countryCodes = { "ZMB": "Zambia", "ZWE": "Zimbabwe" } - +export function getCountryCode(countryName: string): string | string { + const entries = Object.entries(countryCodes); + const found = entries.find(([_, name]) => name.toLowerCase() === countryName.toLowerCase()); + return found ? found[0] : 'undefined'; +} export const countryNames = [ "Afghanistan", "Aland Islands", diff --git a/common/src/utils/appType.ts b/common/src/utils/appType.ts index b7f7fad31..4a388545c 100644 --- a/common/src/utils/appType.ts +++ b/common/src/utils/appType.ts @@ -17,7 +17,7 @@ export interface OpenPassportAppPartial { } export interface OpenPassportApp extends OpenPassportAppPartial { - args: ArgumentsProveOffChain | ArgumentsProveOnChain | ArgumentsRegisterOffChain | ArgumentsRegisterOnChain | ArgumentsDisclose + args: ArgumentsProveOffChain | ArgumentsProveOnChain | ArgumentsRegister | ArgumentsDisclose } export interface ArgumentsProveOffChain { @@ -31,14 +31,11 @@ export interface ArgumentsProveOnChain { rpcUrl: string, } -export interface ArgumentsRegisterOffChain { +export interface ArgumentsRegister { cscaMerkleTreeUrl: string, modalServerUrl: string, } -export interface ArgumentsRegisterOnChain extends ArgumentsRegisterOffChain { - rpcUrl: string, -} export interface ArgumentsDisclose { disclosureOptions: DisclosureOptions, diff --git a/common/src/utils/openPassportAttestation.ts b/common/src/utils/openPassportAttestation.ts index 5cb772c51..3d44dca22 100644 --- a/common/src/utils/openPassportAttestation.ts +++ b/common/src/utils/openPassportAttestation.ts @@ -39,6 +39,8 @@ export interface OpenPassportAttestation { nullifier?: string; }; proof: { + circuit: string; + signatureAlgorithm: string; type: string; verificationMethod: string; value: { @@ -48,6 +50,8 @@ export interface OpenPassportAttestation { vkey: string; }; dscProof: { + circuit: string; + signatureAlgorithm: string; type: string; verificationMethod: string; value: { @@ -161,6 +165,8 @@ export function buildAttestation(options: { issuanceDate: new Date().toISOString(), credentialSubject: credentialSubject, proof: { + circuit: '', + signatureAlgorithm: '', type: 'ZeroKnowledgeProof', verificationMethod: 'https://github.com/zk-passport/openpassport', @@ -171,6 +177,8 @@ export function buildAttestation(options: { vkey: '', }, dscProof: { + circuit: '', + signatureAlgorithm: '', type: 'ZeroKnowledgeProof', verificationMethod: 'https://github.com/zk-passport/openpassport', @@ -217,6 +225,8 @@ export class OpenPassportDynamicAttestation implements OpenPassportAttestation { nullifier?: string; }; proof: { + circuit: string; + signatureAlgorithm: string; type: string; verificationMethod: string; value: { @@ -226,6 +236,8 @@ export class OpenPassportDynamicAttestation implements OpenPassportAttestation { vkey; }; dscProof: { + circuit: string; + signatureAlgorithm: string; type: string; verificationMethod: string; value: { diff --git a/sdk/src/AttestationVerifier.ts b/sdk/src/AttestationVerifier.ts new file mode 100644 index 000000000..c9d80cc31 --- /dev/null +++ b/sdk/src/AttestationVerifier.ts @@ -0,0 +1,92 @@ +import { groth16 } from 'snarkjs'; +import { n_dsc, k_dsc, ECDSA_K_LENGTH_FACTOR, k_dsc_ecdsa } from '../../common/src/constants/constants'; +import { + areArraysEqual, + getVkeyFromArtifacts, + verifyDSCValidity, +} from '../utils/utils'; +import forge from 'node-forge'; +import { splitToWords } from '../../common/src/utils/utils'; +import { parseDSC } from '../../common/src/utils/certificates/handleCertificate'; +import { OpenPassportAttestation, OpenPassportVerifierReport } from './index.web'; +import { parsePublicSignalsProve } from '../../common/src/utils/openPassportAttestation'; + + +export class AttestationVerifier { + protected parsedPublicSignals: any; + protected devMode: boolean; + protected report: OpenPassportVerifierReport; + + constructor(devMode: boolean = false) { + this.devMode = devMode; + } + + async verify(attestation: OpenPassportAttestation): Promise { + const { + proof: { + value: { proof, publicSignals }, + }, + dsc: { value: dsc }, + dscProof: { + value: { proof: dscProof, publicSignals: dscPublicSignals }, + }, + } = attestation; + + const { signatureAlgorithm, hashFunction } = parseDSC(dsc); // inacurracy in the case of register circuit + + const kScaled = signatureAlgorithm === 'ecdsa' ? ECDSA_K_LENGTH_FACTOR * k_dsc_ecdsa : k_dsc; + const parsedPublicSignals = parsePublicSignalsProve(publicSignals, kScaled); + + await this.verifyProof(proof, publicSignals, dsc, 'prove'); + switch (this.circuit) { + case 'prove': + if (this.circuitMode === 'prove_offchain') { + await this.verifyProveArguments(); + await this.verifyDsc(dsc); + } else if (this.circuitMode === 'register') { + await this.verifyRegisterArguments(); + await this.verifyDscProof(dscProof, dscPublicSignals, dsc); + } + break; + case 'disclose': + await this.verifyDiscloseArguments(); + break; + } + return this.report.valid; + } + + protected async verifyProof(proof: string[], publicSignals: string[], dsc: string, circuit: string): Promise { + const vkey = this.getVerificationKey(dsc, circuit); + const isVerified = await groth16.verify(vkey, publicSignals, proof as any); + if (!isVerified) { + // throw new Error('Proof verification failed'); + } + } + + protected getVerificationKey(dsc: string, circuit: string) { + const { signatureAlgorithm, hashFunction } = parseDSC(dsc); + return getVkeyFromArtifacts(circuit, signatureAlgorithm, hashFunction); + } + + protected async verifyDsc(dsc: string, pubKeyFromProof: string[]) { + const dscCertificate = forge.pki.certificateFromPem(dsc); + const isValidCertificate = verifyDSCValidity(dscCertificate, this.devMode); + + if (!isValidCertificate) { + // throw new Error('Invalid certificate chain'); + } + + const dscModulus = BigInt((dscCertificate.publicKey as any).n); + const dscModulusWords = splitToWords(dscModulus, n_dsc, k_dsc); + const isModulusMatching = areArraysEqual(dscModulusWords, pubKeyFromProof); + + if (!isModulusMatching) { + // throw new Error('Public key modulus does not match'); + } + } + + private getParsedPublicSignals(publicSignals: string[], kScaled: number) { + return parsePublicSignalsProve(publicSignals, kScaled); + } + +} diff --git a/sdk/src/OpenPassportVerifier.ts b/sdk/src/OpenPassportVerifier.ts index 62855c519..7c18f539e 100644 --- a/sdk/src/OpenPassportVerifier.ts +++ b/sdk/src/OpenPassportVerifier.ts @@ -1,12 +1,12 @@ -import { ArgumentsProveOffChain, ArgumentsRegisterOffChain, ArgumentsRegisterOnChain, Mode, OpenPassportAppPartial } from "../../common/src/utils/appType"; +import { ArgumentsProveOffChain, ArgumentsRegister, Mode, OpenPassportAppPartial } from "../../common/src/utils/appType"; import { DEFAULT_RPC_URL, MODAL_SERVER_ADDRESS, WEBSOCKET_URL, countryNames } from "../../common/src/constants/constants"; import { OpenPassportApp } from "../../common/src/utils/appType"; import { UserIdType } from "../../common/src/utils/utils"; import * as pako from 'pako'; import msgpack from 'msgpack-lite'; import { OpenPassportAttestation } from "./index.web"; -import { StringifyOptions } from "querystring"; -export class OpenPassportVerifier { +import { AttestationVerifier } from './AttestationVerifier'; +export class OpenPassportVerifier extends AttestationVerifier { private mode: Mode; private scope: string; private minimumAge: { enabled: boolean; value: string } = { enabled: false, value: '18' }; @@ -16,9 +16,9 @@ export class OpenPassportVerifier { private modalServerUrl: string = MODAL_SERVER_ADDRESS; private rpcUrl: string = DEFAULT_RPC_URL; private cscaMerkleTreeUrl: string = ""; - private devMode: boolean = false; - constructor(mode: Mode, scope: string) { + constructor(mode: Mode, scope: string, devMode: boolean = false) { + super(devMode); this.mode = mode; this.scope = scope; } @@ -78,7 +78,7 @@ export class OpenPassportVerifier { userIdType: userIdType, }; - let openPassportArguments: ArgumentsProveOffChain | ArgumentsRegisterOnChain; + let openPassportArguments: ArgumentsProveOffChain | ArgumentsRegister; switch (this.mode) { case "prove_offchain": const argsProveOffChain: ArgumentsProveOffChain = { @@ -92,10 +92,9 @@ export class OpenPassportVerifier { openPassportArguments = argsProveOffChain; break; case "register": - const argsRegisterOnChain: ArgumentsRegisterOnChain = { + const argsRegisterOnChain: ArgumentsRegister = { modalServerUrl: this.modalServerUrl, cscaMerkleTreeUrl: this.cscaMerkleTreeUrl, - rpcUrl: this.rpcUrl, }; openPassportArguments = argsRegisterOnChain; break; @@ -116,8 +115,5 @@ export class OpenPassportVerifier { } } - verify(attestation: OpenPassportAttestation): boolean { - return true; - } } diff --git a/sdk/src/QRcode/utils/websocket.ts b/sdk/src/QRcode/utils/websocket.ts index af503382a..dad078a13 100644 --- a/sdk/src/QRcode/utils/websocket.ts +++ b/sdk/src/QRcode/utils/websocket.ts @@ -17,7 +17,7 @@ const handleWebSocketMessage = setProofStep: (step: number) => void, setProofVerified: (proofVerified: boolean) => void, openPassportVerifier: OpenPassportVerifier, - onSuccess: (proof: OpenPassportAttestation, report: OpenPassportVerifierReport) => void + onSuccess: (proof: OpenPassportAttestation) => void ) => async (data) => { console.log('received mobile status:', data.status); @@ -44,18 +44,18 @@ const handleWebSocketMessage = if (data.proof) { console.log(data.proof); try { - const local_proofVerified: OpenPassportVerifierReport = await openPassportVerifier.verify( + const local_proofVerified = await openPassportVerifier.verify( data.proof ); - setProofVerified(local_proofVerified.valid); + setProofVerified(local_proofVerified); setProofStep(QRcodeSteps.PROOF_VERIFIED); setTimeout(() => { newSocket.emit('proof_verified', { sessionId, proofVerified: local_proofVerified.toString(), }); - if (local_proofVerified.valid) { - onSuccess(data.proof, local_proofVerified); + if (local_proofVerified) { + onSuccess(data.proof); } }, 1500); // wait for animation to finish before sending the proof to mobile } catch (error) { diff --git a/sdk/tests/web-app/src/app/register/page.tsx b/sdk/tests/web-app/src/app/register/page.tsx index 17ca06446..2deb7ec00 100644 --- a/sdk/tests/web-app/src/app/register/page.tsx +++ b/sdk/tests/web-app/src/app/register/page.tsx @@ -1,33 +1,23 @@ 'use client'; import { OpenPassportQRcode } from '../../../../../src/QRcode/OpenPassportQRcode'; -// import { OpenPassportQRcode } from '../../../../dist/bundle.web.js' -import { TextField } from '@mui/material'; -import { useState } from 'react'; -import { COMMITMENT_TREE_TRACKER_URL } from '../../../../../../common/src/constants/constants'; import { v4 as uuidv4 } from 'uuid'; -export default function Register() { - const [appName, setAppName] = useState('🌐 OpenPassport'); +import { OpenPassportVerifier } from '../../../../../src/OpenPassportVerifier'; +export default function Prove() { const userId = uuidv4(); - return ( -
-
Register circuit
- + const scope = "scope" - setAppName(e.target.value)} + const openPassportVerifier = new OpenPassportVerifier('register', scope); + return ( +
+ { + // send the code to the backend server + }} />
);