update sdk

This commit is contained in:
turnoffthiscomputer
2024-10-09 19:47:32 -07:00
parent be2d293666
commit d1d3a5c0bb
3 changed files with 118 additions and 322 deletions

View File

@@ -19,17 +19,20 @@
"js-sha256": "^0.11.0",
"js-sha512": "^0.9.0",
"lottie-react": "^2.4.0",
"msgpack-lite": "^0.1.26",
"next": "^14.2.8",
"node-forge": "https://github.com/remicolin/forge",
"pako": "^2.1.0",
"pkijs": "^3.2.4",
"poseidon-lite": "^0.2.0",
"qrcode.react": "^4.0.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-spinners": "^0.14.1",
"snarkjs": "^0.7.4",
"socket.io-client": "^4.7.5",
"uuid": "^10.0.0",
"node-forge": "https://github.com/remicolin/forge"
"zlib": "^1.0.5"
},
"devDependencies": {
"@types/chai": "^4.3.6",
@@ -39,10 +42,16 @@
"@types/mocha": "^10.0.6",
"@types/node": "^20.11.19",
"@types/node-forge": "^1.3.5",
"@types/pako": "^2.0.3",
"@types/snarkjs": "^0.7.8",
"@zk-kit/imt": "^2.0.0-beta.5",
"@zk-kit/lean-imt": "^2.0.1",
"asn1js": "^3.0.5",
"axios": "^1.7.2",
"chai": "^4.3.8",
"chai-as-promised": "^7.1.1",
"dotenv": "^16.4.5",
"ethers": "^6.13.0",
"mocha": "^10.3.0",
"prettier": "^3.3.3",
"ts-loader": "^9.5.1",
@@ -50,12 +59,7 @@
"ts-node": "^10.9.2",
"typescript": "^5.4.5",
"webpack": "^5.94.0",
"webpack-cli": "^5.1.4",
"@zk-kit/imt": "^2.0.0-beta.5",
"@zk-kit/lean-imt": "^2.0.1",
"asn1js": "^3.0.5",
"axios": "^1.7.2",
"ethers": "^6.13.0"
"webpack-cli": "^5.1.4"
},
"scripts": {
"build:ts": "tsc && tsc -p tsconfig.react.json",

View File

@@ -1,216 +1,113 @@
import { groth16 } from 'snarkjs';
import {
countryCodes,
DEFAULT_RPC_URL,
ECDSA_K_LENGTH_FACTOR,
k_dsc,
k_dsc_ecdsa,
n_dsc,
PASSPORT_ATTESTATION_ID,
} from '../../common/src/constants/constants';
import { getAttributeFromUnpackedReveal } from '../../common/src/utils/utils';
import {
areArraysEqual,
getCurrentDateFormatted,
getVkeyFromArtifacts,
verifyDSCValidity,
} from '../utils/utils';
import { unpackReveal } from '../../common/src/utils/revealBitmap';
import { OpenPassportVerifierReport } from './OpenPassportVerifierReport';
import {
OpenPassportAttestation,
parsePublicSignalsProve,
} from '../../common/src/utils/openPassportAttestation';
import forge from 'node-forge';
import { castToScope, splitToWords } from '../../common/src/utils/utils';
import { parseDSC } from '../../common/src/utils/certificates/handleCertificate';
import { CircuitMode, CircuitName } from '../../common/src/utils/appType';
import { ArgumentsProveOffChain, ArgumentsRegisterOffChain, ArgumentsRegisterOnChain, 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";
export class OpenPassportVerifier {
scope: string;
attestationId: string;
olderThan?: string;
nationality?: (typeof countryCodes)[keyof typeof countryCodes];
ofac?: string;
forbidden_countries_list?: string[];
rpcUrl: string;
report: OpenPassportVerifierReport;
dev_mode: boolean;
parsedPublicSignals: any;
circuit: string;
circuitMode?: string;
constructor(options: {
scope: string;
attestationId?: string;
olderThan?: string;
nationality?: (typeof countryCodes)[keyof typeof countryCodes];
ofac?: string;
forbidden_countries_list?: string[];
rpcUrl?: string;
dev_mode?: boolean;
circuit: CircuitName;
circuitMode?: CircuitMode;
}) {
this.scope = options.scope;
this.attestationId = options.attestationId || PASSPORT_ATTESTATION_ID;
this.olderThan = options.olderThan || null;
this.nationality = options.nationality || null;
this.ofac = options.ofac || null;
this.forbidden_countries_list = options.forbidden_countries_list || null;
this.rpcUrl = options.rpcUrl || DEFAULT_RPC_URL;
this.report = new OpenPassportVerifierReport();
this.dev_mode = options.dev_mode || false;
this.circuit = options.circuit;
this.circuitMode = options.circuitMode || 'prove_offchain';
private mode: Mode;
private scope: string;
private minimumAge: { enabled: boolean; value: number } = { enabled: false, value: 0 };
private nationality: { enabled: boolean; value: typeof countryNames[number] } = { enabled: false, value: '' as typeof countryNames[number] };
private excludedCountries: { enabled: boolean; value: typeof countryNames[number][] } = { enabled: false, value: [] };
private ofac: boolean = false;
private modalServerUrl: string = MODAL_SERVER_ADDRESS;
private rpcUrl: string = DEFAULT_RPC_URL;
private cscaMerkleTreeUrl: string = "";
private devMode: boolean = false;
constructor(mode: Mode, scope: string) {
this.mode = mode;
this.scope = scope;
}
async verify(attestation: OpenPassportAttestation): Promise<OpenPassportVerifierReport> {
const {
proof: {
value: { proof, publicSignals },
},
dsc: { value: dsc },
dscProof: {
value: { proof: dscProof, publicSignals: dscPublicSignals },
},
} = attestation;
// Disclose
setMinimumAge(age: number): this {
this.minimumAge = { enabled: true, value: age };
return this;
}
const { signatureAlgorithm, hashFunction } = parseDSC(dsc);
const kScaled = signatureAlgorithm === 'ecdsa' ? ECDSA_K_LENGTH_FACTOR * k_dsc_ecdsa : k_dsc;
this.parsedPublicSignals = parsePublicSignalsProve(publicSignals, kScaled);
console.log('this.parsedPublicSignals', this.parsedPublicSignals);
setNationality(country: typeof countryNames[number]): this {
this.nationality = { enabled: true, value: country };
return this;
}
await this.verifyProof(proof, publicSignals, dsc);
console.log('this.circuit', this.circuit);
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);
}
excludeCountries(...countries: typeof countryNames[number][]): this {
this.excludedCountries = { enabled: true, value: countries };
return this;
}
enableOFACCheck(): this {
this.ofac = true;
return this;
}
// Register
setModalServerUrl(modalServerUrl: string): this {
this.modalServerUrl = modalServerUrl;
return this;
}
// On chain
setRpcUrl(rpcUrl: string): this {
this.rpcUrl = rpcUrl;
return this;
}
setDevMode(devMode: boolean): this {
this.devMode = devMode;
return this;
}
getIntent(appName: string, userId: string, userIdType: UserIdType, sessionId: string, websocketUrl: string = WEBSOCKET_URL): string {
const intent_raw: OpenPassportAppPartial = {
appName: appName,
mode: this.mode,
scope: this.scope,
websocketUrl: websocketUrl,
};
let openPassportArguments: ArgumentsProveOffChain | ArgumentsRegisterOnChain;
switch (this.mode) {
case "prove_offchain":
const argsProveOffChain: ArgumentsProveOffChain = {
disclosureOptions: {
minimumAge: this.minimumAge,
nationality: this.nationality,
excludedCountries: this.excludedCountries,
ofac: this.ofac,
},
};
openPassportArguments = argsProveOffChain;
break;
case 'disclose':
await this.verifyDiscloseArguments();
case "register":
const argsRegisterOnChain: ArgumentsRegisterOnChain = {
modalServerUrl: this.modalServerUrl,
cscaMerkleTreeUrl: this.cscaMerkleTreeUrl,
rpcUrl: this.rpcUrl,
};
openPassportArguments = argsRegisterOnChain;
break;
}
return this.report;
}
private async verifyProof(proof: string[], publicSignals: string[], dsc: string) {
const vkey = this.getVkey(dsc);
const verified_prove = await groth16.verify(vkey, publicSignals, proof as any);
this.verifyAttribute('proof', verified_prove.toString(), 'true');
}
private async verifyProveArguments() {
this.verifyAttribute('scope', castToScope(this.parsedPublicSignals.scope), this.scope);
this.verifyAttribute(
'current_date',
this.parsedPublicSignals.current_date.toString(),
getCurrentDateFormatted().toString()
);
// requirements
const unpackedReveal = unpackReveal(this.parsedPublicSignals.revealedData_packed);
if (this.olderThan) {
const attributeValue = this.parsedPublicSignals.older_than;
const attributeValueInt = parseInt(String.fromCharCode(...attributeValue));
const selfAttributeOlderThan = parseInt(this.olderThan);
console.log('attributeValue', attributeValueInt);
console.log('selfAttributeOlderThan', selfAttributeOlderThan);
if (attributeValueInt < selfAttributeOlderThan) {
console.log(attributeValueInt, selfAttributeOlderThan);
this.report.exposeAttribute('older_than', attributeValueInt.toString(), this.olderThan);
}
}
if (this.nationality) {
const attributeValue = getAttributeFromUnpackedReveal(unpackedReveal, 'nationality');
this.verifyAttribute('nationality', countryCodes[attributeValue], this.nationality);
}
// if (this.ofac) {
// const attributeValue = getAttributeFromUnpackedReveal(unpackedReveal, 'ofac');
// this.verifyAttribute('ofac', attributeValue, this.ofac);
// }
if (this.forbidden_countries_list) {
const countryList1 = unpackReveal(this.parsedPublicSignals.forbidden_countries_list_packed_disclosed[0]);
const countryList2 = unpackReveal(this.parsedPublicSignals.forbidden_countries_list_packed_disclosed[1]);
const concatenatedCountryList = countryList1.concat(countryList2);
// dump every '\x00' value from the list
const cleanedCountryList = concatenatedCountryList.filter(value => value !== '\x00');
// Concatenate every 3 elements to form country codes
const formattedCountryList = [];
for (let i = 0; i < cleanedCountryList.length; i += 3) {
const countryCode = cleanedCountryList.slice(i, i + 3).join('');
if (countryCode.length === 3) {
formattedCountryList.push(countryCode);
}
}
console.log('formattedCountryList', formattedCountryList);
this.verifyAttribute('forbidden_countries_list', formattedCountryList.toString(), this.forbidden_countries_list.toString());
}
return this.report;
}
private async verifyDiscloseArguments() { }
private verifyAttribute(
attribute: keyof OpenPassportVerifierReport,
value: string,
expectedValue: string
) {
if (value !== expectedValue) {
this.report.exposeAttribute(attribute, this.parsedPublicSignals[attribute], expectedValue);
}
console.log('\x1b[34m%s\x1b[0m', `- attribute ${attribute} verified`);
}
private getVkey(dsc: string) {
const { signatureAlgorithm, hashFunction } = parseDSC(dsc);
if (this.circuit === 'prove') {
return getVkeyFromArtifacts(this.circuit, signatureAlgorithm, hashFunction);
} else {
throw new Error('vkey of ' + this.circuit + ' not found');
const intent: OpenPassportApp = {
...intent_raw,
args: openPassportArguments,
};
const encoded = msgpack.encode(intent);
try {
const compressedData = pako.deflate(encoded);
return btoa(String.fromCharCode(...new Uint8Array(compressedData)));
} catch (err) {
console.error(err);
return '';
}
}
private getVkeyDsc(dsc: string) {
const { signatureAlgorithm, hashFunction } = parseDSC(dsc);
return getVkeyFromArtifacts('dsc', signatureAlgorithm, hashFunction);
}
private verifyDsc(dsc: string) {
const dscCertificate = forge.pki.certificateFromPem(dsc);
const verified_certificate = verifyDSCValidity(dscCertificate, this.dev_mode);
console.log('\x1b[34m%s\x1b[0m', '- certificate verified');
if (!verified_certificate) {
this.report.exposeAttribute('dsc', dsc, 'certificate chain is not valid');
}
const dsc_modulus = BigInt((dscCertificate.publicKey as any).n);
const dsc_modulus_words = splitToWords(dsc_modulus, n_dsc, k_dsc);
const pubKeyFromProof = this.parsedPublicSignals.pubKey_disclosed;
const verified_modulus = areArraysEqual(dsc_modulus_words, pubKeyFromProof);
console.log('\x1b[34m%s\x1b[0m', '- modulus verified');
if (!verified_modulus) {
this.report.exposeAttribute('pubKey', pubKeyFromProof, dsc_modulus_words);
}
}
private async verifyDscProof(proof: string[], publicSignals: string[], dsc: string) {
console.log('verifyDscProof', publicSignals, proof);
const vkey = this.getVkeyDsc(dsc);
const verified_dscProof = await groth16.verify(vkey, publicSignals, proof as any);
this.verifyAttribute('dscProof', verified_dscProof.toString(), 'true');
}
private verifyRegisterArguments() {
// verify that the blindedDscCommitment is the same in both proofs
const blindedPubKeyCommitmentFromLocalProof = this.parsedPublicSignals.blinded_dsc_commitment;
verify(attestation: OpenPassportAttestation): boolean {
return true;
}
}

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react';
import {
OpenPassportVerifierInputs,
OpenPassportAttestation,
OpenPassportVerifier,
OpenPassportVerifierReport,
} from '../index.web';
@@ -15,7 +15,7 @@ import {
WEBSOCKET_URL,
} from '../../../common/src/constants/constants';
import { UserIdType } from '../../../common/src/utils/utils';
import { CircuitMode, CircuitName, reconstructAppType } from '../../../common/src/utils/appType';
import { CircuitName, Mode } from '../../../common/src/utils/appType';
import { v4 as uuidv4 } from 'uuid';
import { QRcodeSteps } from './utils/utils';
import { containerStyle, ledContainerStyle, qrContainerStyle } from './utils/styles';
@@ -26,122 +26,17 @@ const QRCodeSVG = dynamic(() => import('qrcode.react').then((mod) => mod.QRCodeS
});
interface OpenPassportQRcodeProps {
appName: string;
scope: string;
userId?: string;
userIdType?: UserIdType;
olderThan?: string;
nationality?: string;
ofac?: string;
forbidden_countries_list?: string[];
onSuccess?: (proof: OpenPassportVerifierInputs, report: OpenPassportVerifierReport) => void;
circuit: CircuitName;
circuitMode?: CircuitMode;
devMode?: boolean;
size?: number;
openPassportVerifier: OpenPassportVerifier;
onSuccess: (proof: OpenPassportAttestation, report: OpenPassportVerifierReport) => void;
websocketUrl?: string;
merkleTreeUrl?: string;
modalServerUrl?: string;
size?: number;
}
const OpenPassportQRcode: React.FC<OpenPassportQRcodeProps> = ({
appName,
scope,
userId,
userIdType = DEFAULT_USER_ID_TYPE,
olderThan = '',
nationality = '',
ofac = null,
forbidden_countries_list = null,
onSuccess = (proof: OpenPassportVerifierInputs, report: OpenPassportVerifierReport) => {
console.log(proof, report);
},
circuit,
circuitMode = 'prove_onchain',
devMode = false,
size = 300,
websocketUrl = WEBSOCKET_URL,
modalServerUrl = MODAL_SERVER_ADDRESS,
merkleTreeUrl,
}) => {
const OpenPassportQRcode: React.FC<OpenPassportQRcodeProps> = ({ openPassportVerifier, onSuccess, websocketUrl = WEBSOCKET_URL, size = 300 }) => {
const [proofStep, setProofStep] = useState(QRcodeSteps.WAITING_FOR_MOBILE);
const [proofVerified, setProofVerified] = useState(null);
const [sessionId, setSessionId] = useState(uuidv4());
const openPassportVerifier = new OpenPassportVerifier({
scope: scope,
olderThan: olderThan,
nationality: nationality,
ofac: ofac,
forbidden_countries_list: forbidden_countries_list,
dev_mode: devMode,
circuit: circuit,
circuitMode: circuitMode,
});
const getAppStringified = () => {
if (circuit === 'prove') {
if (circuitMode == 'register') {
return JSON.stringify(
reconstructAppType({
name: appName,
scope: scope,
userId: userId,
userIdType: userIdType,
sessionId: sessionId,
circuit: circuit,
circuitMode: circuitMode,
arguments: {
modalServerUrl: modalServerUrl,
merkleTreeUrl: merkleTreeUrl,
},
websocketUrl: websocketUrl,
})
);
} else if (circuitMode === 'prove_offchain') {
const disclosureOptions = [
['nationality', nationality],
['older_than', olderThan],
['ofac', ofac],
['forbidden_countries_list', forbidden_countries_list],
];
return JSON.stringify(
reconstructAppType({
name: appName,
scope: scope,
userId: userId,
userIdType: userIdType,
sessionId: sessionId,
circuit: circuit,
circuitMode: circuitMode,
arguments: {
disclosureOptions: Object.fromEntries(disclosureOptions),
},
websocketUrl: websocketUrl,
})
);
}
}
// } else if (circuit === 'prove' && circuitMode === 'register') {
// return JSON.stringify(
// reconstructAppType({
// name: appName,
// scope: scope,
// userId: userId,
// userIdType: userIdType,
// sessionId: sessionId,
// circuit: circuit,
// circuitMode: circuitMode,
// arguments: {
// attestation_id: attestationId,
// merkleTreeUrl: merkleTreeUrl,
// },
// websocketUrl: websocketUrl,
// })
// );
// }
};
useEffect(() => {
initWebSocket(
websocketUrl,
@@ -189,7 +84,7 @@ const OpenPassportQRcode: React.FC<OpenPassportQRcodeProps> = ({
);
}
default:
return <QRCodeSVG value={getAppStringified()} size={size} />;
return <QRCodeSVG value={openPassportVerifier.getIntent("Mock App", "mockUid", "uuid", sessionId)} size={size} />;
}
})()}
</div>