@zkpassport/sdk 0.3.4 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/assets/abi/ZKPassportVerifier.json +56 -14
- package/dist/cjs/index.d.ts +19 -7
- package/dist/cjs/index.js +192 -104
- package/dist/esm/assets/abi/ZKPassportVerifier.json +56 -14
- package/dist/esm/index.d.ts +19 -7
- package/dist/esm/index.js +193 -105
- package/package.json +3 -2
- package/src/assets/abi/ZKPassportVerifier.json +56 -14
- package/src/index.ts +221 -120
- package/src/encryption.ts +0 -45
- package/src/json-rpc.ts +0 -61
- package/src/mobile.ts +0 -186
- package/src/websocket.ts +0 -16
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { type DisclosableIDCredential, type IDCredential, type IDCredentialValue, type NumericalIDCredential, type ProofResult, type QueryResult, ProofMode } from "@zkpassport/utils";
|
|
1
|
+
import { type DisclosableIDCredential, type IDCredential, type IDCredentialValue, type NumericalIDCredential, type ProofResult, type QueryResult, ProofMode, BoundData } from "@zkpassport/utils";
|
|
2
2
|
export type QueryResultError<T> = {
|
|
3
3
|
expected?: T;
|
|
4
4
|
received?: T;
|
|
5
5
|
message: string;
|
|
6
6
|
};
|
|
7
7
|
export type QueryResultErrors = {
|
|
8
|
-
[key in IDCredential | "sig_check_dsc" | "sig_check_id_data" | "data_check_integrity" | "outer" | "disclose"]: {
|
|
8
|
+
[key in IDCredential | "sig_check_dsc" | "sig_check_id_data" | "data_check_integrity" | "outer" | "disclose" | "bind"]: {
|
|
9
9
|
disclose?: QueryResultError<string | number | Date>;
|
|
10
10
|
gte?: QueryResultError<number | Date>;
|
|
11
11
|
lte?: QueryResultError<number | Date>;
|
|
@@ -150,6 +150,12 @@ export type QueryBuilder = {
|
|
|
150
150
|
* @param key The attribute to disclose.
|
|
151
151
|
*/
|
|
152
152
|
disclose: (key: DisclosableIDCredential) => QueryBuilder;
|
|
153
|
+
/**
|
|
154
|
+
* Binds a value to the request.
|
|
155
|
+
* @param key The key of the value to bind.
|
|
156
|
+
* @param value The value to bind the request to.
|
|
157
|
+
*/
|
|
158
|
+
bind: (key: keyof BoundData, value: BoundData[keyof BoundData]) => QueryBuilder;
|
|
153
159
|
/**
|
|
154
160
|
* Builds the request.
|
|
155
161
|
*
|
|
@@ -163,9 +169,8 @@ export declare class ZKPassport {
|
|
|
163
169
|
private domain;
|
|
164
170
|
private topicToConfig;
|
|
165
171
|
private topicToLocalConfig;
|
|
166
|
-
private
|
|
167
|
-
private
|
|
168
|
-
private topicToSharedSecret;
|
|
172
|
+
private topicToPublicKey;
|
|
173
|
+
private topicToBridge;
|
|
169
174
|
private topicToRequestReceived;
|
|
170
175
|
private topicToService;
|
|
171
176
|
private topicToProofs;
|
|
@@ -197,14 +202,16 @@ export declare class ZKPassport {
|
|
|
197
202
|
* @param scope Scope this request to a specific use case
|
|
198
203
|
* @param validity How many days ago should have the ID been last scanned by the user?
|
|
199
204
|
* @param devMode Whether to enable dev mode. This will allow you to verify mock proofs (i.e. from ZKR)
|
|
205
|
+
* @param evmChain The EVM chain to use for the request (if using the proof onchain)
|
|
200
206
|
* @returns The query builder object.
|
|
201
207
|
*/
|
|
202
|
-
request({ name, logo, purpose, scope, mode, validity, devMode, topicOverride, keyPairOverride, }: {
|
|
208
|
+
request({ name, logo, purpose, scope, mode, evmChain, validity, devMode, topicOverride, keyPairOverride, }: {
|
|
203
209
|
name: string;
|
|
204
210
|
logo: string;
|
|
205
211
|
purpose: string;
|
|
206
212
|
scope?: string;
|
|
207
213
|
mode?: ProofMode;
|
|
214
|
+
evmChain?: EVMChain;
|
|
208
215
|
validity?: number;
|
|
209
216
|
devMode?: boolean;
|
|
210
217
|
topicOverride?: string;
|
|
@@ -223,20 +230,25 @@ export declare class ZKPassport {
|
|
|
223
230
|
private checkIssuingCountryInclusionPublicInputs;
|
|
224
231
|
private checkScopeFromDisclosureProof;
|
|
225
232
|
private checkCertificateRegistryRoot;
|
|
233
|
+
private checkBindPublicInputs;
|
|
226
234
|
private checkPublicInputs;
|
|
227
235
|
/**
|
|
228
236
|
* @notice Verify the proofs received from the mobile app.
|
|
229
237
|
* @param proofs The proofs to verify.
|
|
230
238
|
* @param queryResult The query result to verify against
|
|
231
239
|
* @param validity How many days ago should have the ID been last scanned by the user?
|
|
240
|
+
* @param scope Scope this request to a specific use case
|
|
241
|
+
* @param evmChain The EVM chain to use for the verification (if using the proof onchain)
|
|
242
|
+
* @param devMode Whether to enable dev mode. This will allow you to verify mock proofs (i.e. from ZKR)
|
|
232
243
|
* @returns An object containing the unique identifier associated to the user
|
|
233
244
|
* and a boolean indicating whether the proofs were successfully verified.
|
|
234
245
|
*/
|
|
235
|
-
verify({ proofs, queryResult, validity, scope, devMode, }: {
|
|
246
|
+
verify({ proofs, queryResult, validity, scope, evmChain, devMode, }: {
|
|
236
247
|
proofs: Array<ProofResult>;
|
|
237
248
|
queryResult: QueryResult;
|
|
238
249
|
validity?: number;
|
|
239
250
|
scope?: string;
|
|
251
|
+
evmChain?: EVMChain;
|
|
240
252
|
devMode?: boolean;
|
|
241
253
|
}): Promise<{
|
|
242
254
|
uniqueIdentifier: string | undefined;
|
package/dist/esm/index.js
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
import { randomBytes } from "crypto";
|
|
2
1
|
import { getAlpha3Code, registerLocale } from "i18n-iso-countries";
|
|
3
|
-
import { getProofData, getCommitmentFromDSCProof, getCommitmentInFromIDDataProof, getCommitmentOutFromIDDataProof, getNullifierFromDisclosureProof, getCommitmentInFromIntegrityProof, getCommitmentOutFromIntegrityProof, getCommitmentInFromDisclosureProof, getMerkleRootFromDSCProof, getCurrentDateFromIntegrityProof, DisclosedData, formatName, getHostedPackagedCircuitByName, getNumberOfPublicInputs, getParameterCommitmentFromDisclosureProof, getCountryParameterCommitment, getDiscloseParameterCommitment, getDateParameterCommitment, getCertificateRegistryRootFromOuterProof, getParamCommitmentsFromOuterProof, getCurrentDateFromCommittedInputs, getMinAgeFromCommittedInputs, getMaxAgeFromCommittedInputs, getAgeParameterCommitment, getMinDateFromCommittedInputs, getMaxDateFromCommittedInputs, getCurrentDateFromOuterProof, getNullifierFromOuterProof, getAgeEVMParameterCommitment, getDateEVMParameterCommitment, getDiscloseEVMParameterCommitment, getCountryEVMParameterCommitment, rightPadArrayWithZeros, getCommittedInputCount, ProofType, getScopeHash, getScopeFromOuterProof, getSubscopeFromOuterProof, } from "@zkpassport/utils";
|
|
2
|
+
import { getProofData, getCommitmentFromDSCProof, getCommitmentInFromIDDataProof, getCommitmentOutFromIDDataProof, getNullifierFromDisclosureProof, getCommitmentInFromIntegrityProof, getCommitmentOutFromIntegrityProof, getCommitmentInFromDisclosureProof, getMerkleRootFromDSCProof, getCurrentDateFromIntegrityProof, DisclosedData, formatName, getHostedPackagedCircuitByName, getNumberOfPublicInputs, getParameterCommitmentFromDisclosureProof, getCountryParameterCommitment, getDiscloseParameterCommitment, getDateParameterCommitment, getCertificateRegistryRootFromOuterProof, getParamCommitmentsFromOuterProof, getCurrentDateFromCommittedInputs, getMinAgeFromCommittedInputs, getMaxAgeFromCommittedInputs, getAgeParameterCommitment, getMinDateFromCommittedInputs, getMaxDateFromCommittedInputs, getCurrentDateFromOuterProof, getNullifierFromOuterProof, getAgeEVMParameterCommitment, getDateEVMParameterCommitment, getDiscloseEVMParameterCommitment, getCountryEVMParameterCommitment, rightPadArrayWithZeros, getCommittedInputCount, ProofType, getScopeHash, getScopeFromOuterProof, getSubscopeFromOuterProof, getServiceScopeHash, getBindEVMParameterCommitment, getBindParameterCommitment, formatBoundData, } from "@zkpassport/utils";
|
|
4
3
|
import { bytesToHex } from "@noble/ciphers/utils";
|
|
5
|
-
import { getWebSocketClient } from "./websocket";
|
|
6
|
-
import { createEncryptedJsonRpcRequest } from "./json-rpc";
|
|
7
|
-
import { decrypt, generateECDHKeyPair, getSharedSecret } from "./encryption";
|
|
8
4
|
import { noLogger as logger } from "./logger";
|
|
9
|
-
import { inflate } from "pako";
|
|
10
5
|
import i18en from "i18n-iso-countries/langs/en.json";
|
|
11
6
|
import { Buffer } from "buffer/";
|
|
12
7
|
import { sha256 } from "@noble/hashes/sha2";
|
|
13
8
|
import { hexToBytes } from "@noble/hashes/utils";
|
|
14
9
|
import ZKPassportVerifierAbi from "./assets/abi/ZKPassportVerifier.json";
|
|
15
10
|
import { RegistryClient } from "@zkpassport/registry";
|
|
11
|
+
import { Bridge } from "@obsidion/bridge";
|
|
16
12
|
const DEFAULT_DATE_VALUE = new Date(1111, 10, 11);
|
|
17
13
|
// If Buffer is not defined, then we use the Buffer from the buffer package
|
|
18
14
|
if (typeof globalThis.Buffer === "undefined") {
|
|
@@ -21,6 +17,24 @@ if (typeof globalThis.Buffer === "undefined") {
|
|
|
21
17
|
window.Buffer = Buffer;
|
|
22
18
|
}
|
|
23
19
|
}
|
|
20
|
+
function getChainIdFromEVMChain(chain) {
|
|
21
|
+
if (chain === "ethereum_sepolia") {
|
|
22
|
+
return 11155111;
|
|
23
|
+
}
|
|
24
|
+
else if (chain === "local_anvil") {
|
|
25
|
+
return 31337;
|
|
26
|
+
}
|
|
27
|
+
throw new Error(`Unsupported chain: ${chain}`);
|
|
28
|
+
}
|
|
29
|
+
function getEVMChainFromChainId(chainId) {
|
|
30
|
+
if (chainId === 11155111) {
|
|
31
|
+
return "ethereum_sepolia";
|
|
32
|
+
}
|
|
33
|
+
else if (chainId === 31337) {
|
|
34
|
+
return "local_anvil";
|
|
35
|
+
}
|
|
36
|
+
throw new Error(`Unsupported chain ID: ${chainId}`);
|
|
37
|
+
}
|
|
24
38
|
registerLocale(i18en);
|
|
25
39
|
function hasRequestedAccessToField(credentialsRequest, field) {
|
|
26
40
|
const fieldValue = credentialsRequest[field];
|
|
@@ -69,9 +83,8 @@ export class ZKPassport {
|
|
|
69
83
|
constructor(_domain) {
|
|
70
84
|
this.topicToConfig = {};
|
|
71
85
|
this.topicToLocalConfig = {};
|
|
72
|
-
this.
|
|
73
|
-
this.
|
|
74
|
-
this.topicToSharedSecret = {};
|
|
86
|
+
this.topicToPublicKey = {};
|
|
87
|
+
this.topicToBridge = {};
|
|
75
88
|
this.topicToRequestReceived = {};
|
|
76
89
|
this.topicToService = {};
|
|
77
90
|
this.topicToProofs = {};
|
|
@@ -100,6 +113,9 @@ export class ZKPassport {
|
|
|
100
113
|
queryResult: result,
|
|
101
114
|
validity: this.topicToLocalConfig[topic]?.validity,
|
|
102
115
|
scope: this.topicToService[topic]?.scope,
|
|
116
|
+
evmChain: this.topicToService[topic]?.chainId
|
|
117
|
+
? getEVMChainFromChainId(this.topicToService[topic]?.chainId)
|
|
118
|
+
: undefined,
|
|
103
119
|
devMode: this.topicToLocalConfig[topic]?.devMode,
|
|
104
120
|
});
|
|
105
121
|
delete this.topicToProofs[topic];
|
|
@@ -175,6 +191,9 @@ export class ZKPassport {
|
|
|
175
191
|
}
|
|
176
192
|
}
|
|
177
193
|
}
|
|
194
|
+
if (this.topicToConfig[topic].bind) {
|
|
195
|
+
neededCircuits.push("bind");
|
|
196
|
+
}
|
|
178
197
|
// From the circuits needed, determine the expected proof count
|
|
179
198
|
// There are at least 4 proofs, 3 base proofs and 1 disclosure proof minimum
|
|
180
199
|
// Each separate needed circuit adds 1 disclosure proof
|
|
@@ -187,7 +206,7 @@ export class ZKPassport {
|
|
|
187
206
|
* @param request The request.
|
|
188
207
|
* @param outerRequest The outer request.
|
|
189
208
|
*/
|
|
190
|
-
async handleEncryptedMessage(topic, request
|
|
209
|
+
async handleEncryptedMessage(topic, request) {
|
|
191
210
|
logger.debug("Received encrypted message:", request);
|
|
192
211
|
if (request.method === "accept") {
|
|
193
212
|
logger.debug(`User accepted the request and is generating a proof`);
|
|
@@ -199,30 +218,8 @@ export class ZKPassport {
|
|
|
199
218
|
}
|
|
200
219
|
else if (request.method === "proof") {
|
|
201
220
|
logger.debug(`User generated proof`);
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const bytesCommittedInputs = request.params.committedInputs
|
|
205
|
-
? Buffer.from(request.params.committedInputs, "base64")
|
|
206
|
-
: null;
|
|
207
|
-
const uncompressedProof = inflate(bytesProof);
|
|
208
|
-
const uncompressedCommittedInputs = bytesCommittedInputs
|
|
209
|
-
? inflate(bytesCommittedInputs)
|
|
210
|
-
: null;
|
|
211
|
-
// The gzip lib in the app compress the proof as ASCII
|
|
212
|
-
// and since the app passes the proof as a hex string, we can
|
|
213
|
-
// just decode the bytes as hex characters using the TextDecoder
|
|
214
|
-
const hexProof = new TextDecoder().decode(uncompressedProof);
|
|
215
|
-
const processedProof = {
|
|
216
|
-
proof: hexProof,
|
|
217
|
-
vkeyHash: request.params.vkeyHash,
|
|
218
|
-
name: request.params.name,
|
|
219
|
-
version: request.params.version,
|
|
220
|
-
committedInputs: uncompressedCommittedInputs
|
|
221
|
-
? JSON.parse(new TextDecoder().decode(uncompressedCommittedInputs))
|
|
222
|
-
: undefined,
|
|
223
|
-
};
|
|
224
|
-
this.topicToProofs[topic].push(processedProof);
|
|
225
|
-
await Promise.all(this.onProofGeneratedCallbacks[topic].map((callback) => callback(processedProof)));
|
|
221
|
+
this.topicToProofs[topic].push(request.params);
|
|
222
|
+
await Promise.all(this.onProofGeneratedCallbacks[topic].map((callback) => callback(request.params)));
|
|
226
223
|
// If the results were received before all the proofs were generated,
|
|
227
224
|
// we can handle the result now
|
|
228
225
|
if (this.topicToResults[topic] &&
|
|
@@ -319,10 +316,17 @@ export class ZKPassport {
|
|
|
319
316
|
};
|
|
320
317
|
return this.getZkPassportRequest(topic);
|
|
321
318
|
},
|
|
319
|
+
bind: (key, value) => {
|
|
320
|
+
this.topicToConfig[topic].bind = {
|
|
321
|
+
...this.topicToConfig[topic].bind,
|
|
322
|
+
[key]: value,
|
|
323
|
+
};
|
|
324
|
+
return this.getZkPassportRequest(topic);
|
|
325
|
+
},
|
|
322
326
|
done: () => {
|
|
323
327
|
const base64Config = Buffer.from(JSON.stringify(this.topicToConfig[topic])).toString("base64");
|
|
324
328
|
const base64Service = Buffer.from(JSON.stringify(this.topicToService[topic])).toString("base64");
|
|
325
|
-
const pubkey =
|
|
329
|
+
const pubkey = this.topicToPublicKey[topic];
|
|
326
330
|
this.setExpectedProofCount(topic);
|
|
327
331
|
return {
|
|
328
332
|
url: `https://zkpassport.id/r?d=${this.domain}&t=${topic}&c=${base64Config}&s=${base64Service}&p=${pubkey}&m=${this.topicToLocalConfig[topic].mode}`,
|
|
@@ -334,7 +338,7 @@ export class ZKPassport {
|
|
|
334
338
|
onResult: (callback) => this.onResultCallbacks[topic].push(callback),
|
|
335
339
|
onReject: (callback) => this.onRejectCallbacks[topic].push(callback),
|
|
336
340
|
onError: (callback) => this.onErrorCallbacks[topic].push(callback),
|
|
337
|
-
isBridgeConnected: () => this.
|
|
341
|
+
isBridgeConnected: () => this.topicToBridge[topic].isBridgeConnected(),
|
|
338
342
|
requestReceived: () => this.topicToRequestReceived[topic] === true,
|
|
339
343
|
};
|
|
340
344
|
},
|
|
@@ -348,17 +352,23 @@ export class ZKPassport {
|
|
|
348
352
|
* @param scope Scope this request to a specific use case
|
|
349
353
|
* @param validity How many days ago should have the ID been last scanned by the user?
|
|
350
354
|
* @param devMode Whether to enable dev mode. This will allow you to verify mock proofs (i.e. from ZKR)
|
|
355
|
+
* @param evmChain The EVM chain to use for the request (if using the proof onchain)
|
|
351
356
|
* @returns The query builder object.
|
|
352
357
|
*/
|
|
353
|
-
async request({ name, logo, purpose, scope, mode, validity, devMode, topicOverride, keyPairOverride, }) {
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
};
|
|
358
|
+
async request({ name, logo, purpose, scope, mode, evmChain, validity, devMode, topicOverride, keyPairOverride, }) {
|
|
359
|
+
const bridge = await Bridge.create({
|
|
360
|
+
keyPair: keyPairOverride,
|
|
361
|
+
bridgeId: topicOverride,
|
|
362
|
+
});
|
|
363
|
+
const topic = bridge.connection.getBridgeId();
|
|
360
364
|
this.topicToConfig[topic] = {};
|
|
361
|
-
this.topicToService[topic] = {
|
|
365
|
+
this.topicToService[topic] = {
|
|
366
|
+
name,
|
|
367
|
+
logo,
|
|
368
|
+
purpose,
|
|
369
|
+
scope,
|
|
370
|
+
chainId: evmChain ? getChainIdFromEVMChain(evmChain) : undefined,
|
|
371
|
+
};
|
|
362
372
|
this.topicToProofs[topic] = [];
|
|
363
373
|
this.topicToExpectedProofCount[topic] = 0;
|
|
364
374
|
this.topicToLocalConfig[topic] = {
|
|
@@ -374,53 +384,21 @@ export class ZKPassport {
|
|
|
374
384
|
this.onResultCallbacks[topic] = [];
|
|
375
385
|
this.onRejectCallbacks[topic] = [];
|
|
376
386
|
this.onErrorCallbacks[topic] = [];
|
|
377
|
-
|
|
378
|
-
this.
|
|
379
|
-
|
|
380
|
-
logger.
|
|
387
|
+
this.topicToPublicKey[topic] = bridge.getPublicKey();
|
|
388
|
+
this.topicToBridge[topic] = bridge;
|
|
389
|
+
bridge.onConnect(async (reconnection) => {
|
|
390
|
+
logger.debug("Bridge connected");
|
|
391
|
+
logger.debug("Is reconnection:", reconnection);
|
|
381
392
|
await Promise.all(this.onBridgeConnectCallbacks[topic].map((callback) => callback()));
|
|
382
|
-
};
|
|
383
|
-
wsClient.addEventListener("message", async (event) => {
|
|
384
|
-
logger.debug("[frontend] Received message:", event.data);
|
|
385
|
-
try {
|
|
386
|
-
const data = JSON.parse(event.data);
|
|
387
|
-
// Handshake happens when the mobile app scans the QR code and connects to the bridge
|
|
388
|
-
if (data.method === "handshake") {
|
|
389
|
-
logger.debug("[frontend] Received handshake:", event.data);
|
|
390
|
-
this.topicToRequestReceived[topic] = true;
|
|
391
|
-
this.topicToSharedSecret[topic] = await getSharedSecret(bytesToHex(keyPair.privateKey), data.params.pubkey);
|
|
392
|
-
logger.debug("[frontend] Shared secret:", Buffer.from(this.topicToSharedSecret[topic]).toString("hex"));
|
|
393
|
-
const encryptedMessage = await createEncryptedJsonRpcRequest("hello", null, this.topicToSharedSecret[topic], topic);
|
|
394
|
-
logger.debug("[frontend] Sending encrypted message:", encryptedMessage);
|
|
395
|
-
wsClient.send(JSON.stringify(encryptedMessage));
|
|
396
|
-
await Promise.all(this.onRequestReceivedCallbacks[topic].map((callback) => callback()));
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
// Handle encrypted messages
|
|
400
|
-
if (data.method === "encryptedMessage") {
|
|
401
|
-
// Decode the payload from base64 to Uint8Array
|
|
402
|
-
const payload = new Uint8Array(atob(data.params.payload)
|
|
403
|
-
.split("")
|
|
404
|
-
.map((c) => c.charCodeAt(0)));
|
|
405
|
-
try {
|
|
406
|
-
// Decrypt the payload using the shared secret
|
|
407
|
-
const decrypted = await decrypt(payload, this.topicToSharedSecret[topic], topic);
|
|
408
|
-
const decryptedJson = JSON.parse(decrypted);
|
|
409
|
-
this.handleEncryptedMessage(topic, decryptedJson, data);
|
|
410
|
-
}
|
|
411
|
-
catch (error) {
|
|
412
|
-
logger.error("[frontend] Error decrypting message:", error);
|
|
413
|
-
}
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
catch (error) {
|
|
418
|
-
logger.error("[frontend] Error:", error);
|
|
419
|
-
}
|
|
420
393
|
});
|
|
421
|
-
|
|
422
|
-
logger.
|
|
423
|
-
|
|
394
|
+
bridge.onSecureChannelEstablished(async () => {
|
|
395
|
+
logger.debug("Secure channel established");
|
|
396
|
+
await Promise.all(this.onRequestReceivedCallbacks[topic].map((callback) => callback()));
|
|
397
|
+
});
|
|
398
|
+
bridge.onSecureMessage(async (message) => {
|
|
399
|
+
logger.debug("Received message:", message);
|
|
400
|
+
this.handleEncryptedMessage(topic, message);
|
|
401
|
+
});
|
|
424
402
|
return this.getZkPassportRequest(topic);
|
|
425
403
|
}
|
|
426
404
|
checkDiscloseBytesPublicInputs(proof, queryResult) {
|
|
@@ -441,6 +419,7 @@ export class ZKPassport {
|
|
|
441
419
|
fullname: {},
|
|
442
420
|
document_number: {},
|
|
443
421
|
outer: {},
|
|
422
|
+
bind: {},
|
|
444
423
|
};
|
|
445
424
|
let isCorrect = true;
|
|
446
425
|
// We can't be certain that the disclosed data is for a passport or an ID card
|
|
@@ -753,6 +732,7 @@ export class ZKPassport {
|
|
|
753
732
|
fullname: {},
|
|
754
733
|
document_number: {},
|
|
755
734
|
outer: {},
|
|
735
|
+
bind: {},
|
|
756
736
|
};
|
|
757
737
|
let isCorrect = true;
|
|
758
738
|
const currentTime = new Date();
|
|
@@ -862,6 +842,7 @@ export class ZKPassport {
|
|
|
862
842
|
fullname: {},
|
|
863
843
|
document_number: {},
|
|
864
844
|
outer: {},
|
|
845
|
+
bind: {},
|
|
865
846
|
};
|
|
866
847
|
let isCorrect = true;
|
|
867
848
|
const currentTime = new Date();
|
|
@@ -965,6 +946,7 @@ export class ZKPassport {
|
|
|
965
946
|
fullname: {},
|
|
966
947
|
document_number: {},
|
|
967
948
|
outer: {},
|
|
949
|
+
bind: {},
|
|
968
950
|
};
|
|
969
951
|
let isCorrect = true;
|
|
970
952
|
const currentTime = new Date();
|
|
@@ -1068,6 +1050,7 @@ export class ZKPassport {
|
|
|
1068
1050
|
fullname: {},
|
|
1069
1051
|
document_number: {},
|
|
1070
1052
|
outer: {},
|
|
1053
|
+
bind: {},
|
|
1071
1054
|
};
|
|
1072
1055
|
let isCorrect = true;
|
|
1073
1056
|
if (queryResult.nationality &&
|
|
@@ -1122,6 +1105,7 @@ export class ZKPassport {
|
|
|
1122
1105
|
fullname: {},
|
|
1123
1106
|
document_number: {},
|
|
1124
1107
|
outer: {},
|
|
1108
|
+
bind: {},
|
|
1125
1109
|
};
|
|
1126
1110
|
let isCorrect = true;
|
|
1127
1111
|
if (queryResult.issuing_country &&
|
|
@@ -1176,6 +1160,7 @@ export class ZKPassport {
|
|
|
1176
1160
|
fullname: {},
|
|
1177
1161
|
document_number: {},
|
|
1178
1162
|
outer: {},
|
|
1163
|
+
bind: {},
|
|
1179
1164
|
};
|
|
1180
1165
|
let isCorrect = true;
|
|
1181
1166
|
if (queryResult.nationality &&
|
|
@@ -1218,6 +1203,7 @@ export class ZKPassport {
|
|
|
1218
1203
|
fullname: {},
|
|
1219
1204
|
document_number: {},
|
|
1220
1205
|
outer: {},
|
|
1206
|
+
bind: {},
|
|
1221
1207
|
};
|
|
1222
1208
|
let isCorrect = true;
|
|
1223
1209
|
if (queryResult.issuing_country &&
|
|
@@ -1242,13 +1228,14 @@ export class ZKPassport {
|
|
|
1242
1228
|
}
|
|
1243
1229
|
return { isCorrect, queryResultErrors };
|
|
1244
1230
|
}
|
|
1245
|
-
checkScopeFromDisclosureProof(proofData, queryResultErrors, key, scope) {
|
|
1231
|
+
checkScopeFromDisclosureProof(proofData, queryResultErrors, key, scope, chainId) {
|
|
1246
1232
|
let isCorrect = true;
|
|
1247
|
-
if (this.domain &&
|
|
1233
|
+
if (this.domain &&
|
|
1234
|
+
getServiceScopeHash(this.domain, chainId) !== BigInt(proofData.publicInputs[1])) {
|
|
1248
1235
|
console.warn("The proof comes from a different domain than the one expected");
|
|
1249
1236
|
isCorrect = false;
|
|
1250
1237
|
queryResultErrors[key].scope = {
|
|
1251
|
-
expected: `Scope: ${
|
|
1238
|
+
expected: `Scope: ${getServiceScopeHash(this.domain, chainId).toString()}`,
|
|
1252
1239
|
received: `Scope: ${BigInt(proofData.publicInputs[1]).toString()}`,
|
|
1253
1240
|
message: "The proof comes from a different domain than the one expected",
|
|
1254
1241
|
};
|
|
@@ -1292,7 +1279,52 @@ export class ZKPassport {
|
|
|
1292
1279
|
}
|
|
1293
1280
|
return { isCorrect, queryResultErrors };
|
|
1294
1281
|
}
|
|
1295
|
-
|
|
1282
|
+
checkBindPublicInputs(queryResult, boundData) {
|
|
1283
|
+
const queryResultErrors = {
|
|
1284
|
+
sig_check_dsc: {},
|
|
1285
|
+
sig_check_id_data: {},
|
|
1286
|
+
data_check_integrity: {},
|
|
1287
|
+
disclose: {},
|
|
1288
|
+
age: {},
|
|
1289
|
+
birthdate: {},
|
|
1290
|
+
expiry_date: {},
|
|
1291
|
+
document_type: {},
|
|
1292
|
+
issuing_country: {},
|
|
1293
|
+
gender: {},
|
|
1294
|
+
nationality: {},
|
|
1295
|
+
firstname: {},
|
|
1296
|
+
lastname: {},
|
|
1297
|
+
fullname: {},
|
|
1298
|
+
document_number: {},
|
|
1299
|
+
outer: {},
|
|
1300
|
+
bind: {},
|
|
1301
|
+
};
|
|
1302
|
+
let isCorrect = true;
|
|
1303
|
+
if (queryResult.bind) {
|
|
1304
|
+
if (queryResult.bind.user_address?.toLowerCase().replace("0x", "") !==
|
|
1305
|
+
boundData.user_address?.toLowerCase().replace("0x", "")) {
|
|
1306
|
+
console.warn("Bound user address does not match the one from the query results");
|
|
1307
|
+
isCorrect = false;
|
|
1308
|
+
queryResultErrors.bind.eq = {
|
|
1309
|
+
expected: queryResult.bind.user_address,
|
|
1310
|
+
received: boundData.user_address,
|
|
1311
|
+
message: "Bound user address does not match the one from the query results",
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
if (queryResult.bind.custom_data?.trim().toLowerCase() !==
|
|
1315
|
+
boundData.custom_data?.trim().toLowerCase()) {
|
|
1316
|
+
console.warn("Bound custom data does not match the one from the query results");
|
|
1317
|
+
isCorrect = false;
|
|
1318
|
+
queryResultErrors.bind.eq = {
|
|
1319
|
+
expected: queryResult.bind.custom_data,
|
|
1320
|
+
received: boundData.custom_data,
|
|
1321
|
+
message: "Bound custom data does not match the one from the query results",
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
return { isCorrect, queryResultErrors };
|
|
1326
|
+
}
|
|
1327
|
+
async checkPublicInputs(proofs, queryResult, validity, scope, chainId) {
|
|
1296
1328
|
let commitmentIn;
|
|
1297
1329
|
let commitmentOut;
|
|
1298
1330
|
let isCorrect = true;
|
|
@@ -1316,6 +1348,7 @@ export class ZKPassport {
|
|
|
1316
1348
|
fullname: {},
|
|
1317
1349
|
document_number: {},
|
|
1318
1350
|
outer: {},
|
|
1351
|
+
bind: {},
|
|
1319
1352
|
};
|
|
1320
1353
|
// Since the order is important for the commitments, we need to sort the proofs
|
|
1321
1354
|
// by their expected order: root signature check -> ID signature check -> integrity check -> disclosure
|
|
@@ -1332,6 +1365,7 @@ export class ZKPassport {
|
|
|
1332
1365
|
"inclusion_check_nationality",
|
|
1333
1366
|
"exclusion_check_issuing_country",
|
|
1334
1367
|
"inclusion_check_issuing_country",
|
|
1368
|
+
"bind",
|
|
1335
1369
|
];
|
|
1336
1370
|
const getIndex = (proof) => {
|
|
1337
1371
|
const name = proof.name || "";
|
|
@@ -1377,11 +1411,12 @@ export class ZKPassport {
|
|
|
1377
1411
|
message: "The proof does not verify all the requested conditions and information",
|
|
1378
1412
|
};
|
|
1379
1413
|
}
|
|
1380
|
-
if (this.domain &&
|
|
1414
|
+
if (this.domain &&
|
|
1415
|
+
getServiceScopeHash(this.domain, chainId) !== getScopeFromOuterProof(proofData)) {
|
|
1381
1416
|
console.warn("The proof comes from a different domain than the one expected");
|
|
1382
1417
|
isCorrect = false;
|
|
1383
1418
|
queryResultErrors.outer.scope = {
|
|
1384
|
-
expected: `Scope: ${
|
|
1419
|
+
expected: `Scope: ${getServiceScopeHash(this.domain, chainId).toString()}`,
|
|
1385
1420
|
received: `Scope: ${getScopeFromOuterProof(proofData).toString()}`,
|
|
1386
1421
|
message: "The proof comes from a different domain than the one expected",
|
|
1387
1422
|
};
|
|
@@ -1567,6 +1602,27 @@ export class ZKPassport {
|
|
|
1567
1602
|
...queryResultErrorsIssuingCountryExclusion,
|
|
1568
1603
|
};
|
|
1569
1604
|
}
|
|
1605
|
+
else if (!!committedInputs?.bind) {
|
|
1606
|
+
const bindCommittedInputs = committedInputs?.bind;
|
|
1607
|
+
const bindParameterCommitment = isForEVM
|
|
1608
|
+
? await getBindEVMParameterCommitment(formatBoundData(bindCommittedInputs.data))
|
|
1609
|
+
: await getBindParameterCommitment(formatBoundData(bindCommittedInputs.data));
|
|
1610
|
+
if (!paramCommitments.includes(bindParameterCommitment)) {
|
|
1611
|
+
console.warn("This proof does not verify the bound data");
|
|
1612
|
+
isCorrect = false;
|
|
1613
|
+
queryResultErrors.bind.commitment = {
|
|
1614
|
+
expected: `Bind parameter commitment: ${bindParameterCommitment.toString()}`,
|
|
1615
|
+
received: `Parameter commitments included: ${paramCommitments.join(", ")}`,
|
|
1616
|
+
message: "This proof does not verify the bound data",
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1619
|
+
const { isCorrect: isCorrectBind, queryResultErrors: queryResultErrorsBind } = this.checkBindPublicInputs(queryResult, bindCommittedInputs.data);
|
|
1620
|
+
isCorrect = isCorrect && isCorrectBind;
|
|
1621
|
+
queryResultErrors = {
|
|
1622
|
+
...queryResultErrors,
|
|
1623
|
+
...queryResultErrorsBind,
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1570
1626
|
uniqueIdentifier = getNullifierFromOuterProof(proofData).toString(10);
|
|
1571
1627
|
}
|
|
1572
1628
|
else if (proof.name?.startsWith("sig_check_dsc")) {
|
|
@@ -1888,6 +1944,27 @@ export class ZKPassport {
|
|
|
1888
1944
|
};
|
|
1889
1945
|
uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10);
|
|
1890
1946
|
}
|
|
1947
|
+
else if (proof.name === "bind") {
|
|
1948
|
+
const bindCommittedInputs = proof.committedInputs?.bind;
|
|
1949
|
+
const paramCommittment = getParameterCommitmentFromDisclosureProof(proofData);
|
|
1950
|
+
const calculatedParamCommitment = await getBindParameterCommitment(formatBoundData(bindCommittedInputs.data));
|
|
1951
|
+
if (paramCommittment !== calculatedParamCommitment) {
|
|
1952
|
+
console.warn("The bound data does not match the one from the proof");
|
|
1953
|
+
isCorrect = false;
|
|
1954
|
+
queryResultErrors.bind.commitment = {
|
|
1955
|
+
expected: `Commitment: ${calculatedParamCommitment}`,
|
|
1956
|
+
received: `Commitment: ${paramCommittment}`,
|
|
1957
|
+
message: "The bound data does not match the one from the proof",
|
|
1958
|
+
};
|
|
1959
|
+
}
|
|
1960
|
+
const { isCorrect: isCorrectBind, queryResultErrors: queryResultErrorsBind } = this.checkBindPublicInputs(queryResult, bindCommittedInputs.data);
|
|
1961
|
+
isCorrect = isCorrect && isCorrectBind;
|
|
1962
|
+
queryResultErrors = {
|
|
1963
|
+
...queryResultErrors,
|
|
1964
|
+
...queryResultErrorsBind,
|
|
1965
|
+
};
|
|
1966
|
+
uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10);
|
|
1967
|
+
}
|
|
1891
1968
|
}
|
|
1892
1969
|
return { isCorrect, uniqueIdentifier, queryResultErrors };
|
|
1893
1970
|
}
|
|
@@ -1896,10 +1973,13 @@ export class ZKPassport {
|
|
|
1896
1973
|
* @param proofs The proofs to verify.
|
|
1897
1974
|
* @param queryResult The query result to verify against
|
|
1898
1975
|
* @param validity How many days ago should have the ID been last scanned by the user?
|
|
1976
|
+
* @param scope Scope this request to a specific use case
|
|
1977
|
+
* @param evmChain The EVM chain to use for the verification (if using the proof onchain)
|
|
1978
|
+
* @param devMode Whether to enable dev mode. This will allow you to verify mock proofs (i.e. from ZKR)
|
|
1899
1979
|
* @returns An object containing the unique identifier associated to the user
|
|
1900
1980
|
* and a boolean indicating whether the proofs were successfully verified.
|
|
1901
1981
|
*/
|
|
1902
|
-
async verify({ proofs, queryResult, validity, scope, devMode = false, }) {
|
|
1982
|
+
async verify({ proofs, queryResult, validity, scope, evmChain, devMode = false, }) {
|
|
1903
1983
|
// If no proofs were generated, the results can't be trusted.
|
|
1904
1984
|
// We still return it but verified will be false
|
|
1905
1985
|
if (!proofs || proofs.length === 0) {
|
|
@@ -1921,7 +2001,8 @@ export class ZKPassport {
|
|
|
1921
2001
|
let verified = true;
|
|
1922
2002
|
let uniqueIdentifier;
|
|
1923
2003
|
let queryResultErrors;
|
|
1924
|
-
const
|
|
2004
|
+
const chainId = evmChain ? getChainIdFromEVMChain(evmChain) : undefined;
|
|
2005
|
+
const { isCorrect, uniqueIdentifier: uniqueIdentifierFromPublicInputs, queryResultErrors: queryResultErrorsFromPublicInputs, } = await this.checkPublicInputs(proofs, formattedResult, validity, scope, chainId);
|
|
1925
2006
|
uniqueIdentifier = uniqueIdentifierFromPublicInputs;
|
|
1926
2007
|
verified = isCorrect;
|
|
1927
2008
|
queryResultErrors = isCorrect ? undefined : queryResultErrorsFromPublicInputs;
|
|
@@ -1999,7 +2080,7 @@ export class ZKPassport {
|
|
|
1999
2080
|
if (network === "ethereum_sepolia") {
|
|
2000
2081
|
return {
|
|
2001
2082
|
...baseConfig,
|
|
2002
|
-
address: "
|
|
2083
|
+
address: "0x5e4B11F7B7995F5Cee0134692a422b045091112F",
|
|
2003
2084
|
};
|
|
2004
2085
|
}
|
|
2005
2086
|
else if (network === "local_anvil") {
|
|
@@ -2092,6 +2173,14 @@ export class ZKPassport {
|
|
|
2092
2173
|
value.discloseMask.map((x) => x.toString(16).padStart(2, "0")).join("") +
|
|
2093
2174
|
value.disclosedBytes.map((x) => x.toString(16).padStart(2, "0")).join("");
|
|
2094
2175
|
}
|
|
2176
|
+
else if (circuitName === "bind_evm") {
|
|
2177
|
+
const value = proof.committedInputs[circuitName];
|
|
2178
|
+
compressedCommittedInputs =
|
|
2179
|
+
ProofType.BIND.toString(16).padStart(2, "0") +
|
|
2180
|
+
rightPadArrayWithZeros(formatBoundData(value.data), 500)
|
|
2181
|
+
.map((x) => x.toString(16).padStart(2, "0"))
|
|
2182
|
+
.join("");
|
|
2183
|
+
}
|
|
2095
2184
|
else {
|
|
2096
2185
|
throw new Error(`Unsupported circuit for EVM verification: ${circuitName}`);
|
|
2097
2186
|
}
|
|
@@ -2148,7 +2237,7 @@ export class ZKPassport {
|
|
|
2148
2237
|
* @returns The URL of the request.
|
|
2149
2238
|
*/
|
|
2150
2239
|
getUrl(requestId) {
|
|
2151
|
-
const pubkey =
|
|
2240
|
+
const pubkey = this.topicToPublicKey[requestId];
|
|
2152
2241
|
const base64Config = Buffer.from(JSON.stringify(this.topicToConfig[requestId])).toString("base64");
|
|
2153
2242
|
const base64Service = Buffer.from(JSON.stringify(this.topicToService[requestId])).toString("base64");
|
|
2154
2243
|
return `https://zkpassport.id/r?d=${this.domain}&t=${requestId}&c=${base64Config}&s=${base64Service}&p=${pubkey}&m=${this.topicToLocalConfig[requestId].mode}`;
|
|
@@ -2158,14 +2247,13 @@ export class ZKPassport {
|
|
|
2158
2247
|
* @param requestId The request ID.
|
|
2159
2248
|
*/
|
|
2160
2249
|
cancelRequest(requestId) {
|
|
2161
|
-
if (this.
|
|
2162
|
-
this.
|
|
2163
|
-
delete this.
|
|
2250
|
+
if (this.topicToBridge[requestId]) {
|
|
2251
|
+
this.topicToBridge[requestId].close();
|
|
2252
|
+
delete this.topicToBridge[requestId];
|
|
2164
2253
|
}
|
|
2165
|
-
delete this.
|
|
2254
|
+
delete this.topicToPublicKey[requestId];
|
|
2166
2255
|
delete this.topicToConfig[requestId];
|
|
2167
2256
|
delete this.topicToLocalConfig[requestId];
|
|
2168
|
-
delete this.topicToSharedSecret[requestId];
|
|
2169
2257
|
delete this.topicToProofs[requestId];
|
|
2170
2258
|
delete this.topicToExpectedProofCount[requestId];
|
|
2171
2259
|
delete this.topicToFailedProofCount[requestId];
|
|
@@ -2181,7 +2269,7 @@ export class ZKPassport {
|
|
|
2181
2269
|
* @notice Clears all requests.
|
|
2182
2270
|
*/
|
|
2183
2271
|
clearAllRequests() {
|
|
2184
|
-
for (const requestId in this.
|
|
2272
|
+
for (const requestId in this.topicToBridge) {
|
|
2185
2273
|
this.cancelRequest(requestId);
|
|
2186
2274
|
}
|
|
2187
2275
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zkpassport/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Privacy-preserving identity verification using passports and ID cards",
|
|
5
5
|
"main": "./dist/cjs/index.js",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
@@ -42,8 +42,9 @@
|
|
|
42
42
|
"@noble/ciphers": "^1.2.1",
|
|
43
43
|
"@noble/hashes": "^1.7.2",
|
|
44
44
|
"@noble/secp256k1": "^2.2.3",
|
|
45
|
+
"@obsidion/bridge": "^0.9.0",
|
|
45
46
|
"@zkpassport/registry": "^0.1.9",
|
|
46
|
-
"@zkpassport/utils": "^0.
|
|
47
|
+
"@zkpassport/utils": "^0.9.4",
|
|
47
48
|
"buffer": "^6.0.3",
|
|
48
49
|
"i18n-iso-countries": "^7.12.0",
|
|
49
50
|
"pako": "^2.1.0",
|