@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.
@@ -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 topicToKeyPair;
167
- private topicToWebSocketClient;
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.topicToKeyPair = {};
73
- this.topicToWebSocketClient = {};
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, outerRequest) {
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
- // Uncompress the proof and convert it to a hex string
203
- const bytesProof = Buffer.from(request.params.proof, "base64");
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 = bytesToHex(this.topicToKeyPair[topic].publicKey);
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.topicToWebSocketClient[topic].readyState === WebSocket.OPEN,
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 topic = topicOverride || randomBytes(16).toString("hex");
355
- const keyPair = keyPairOverride || (await generateECDHKeyPair());
356
- this.topicToKeyPair[topic] = {
357
- privateKey: keyPair.privateKey,
358
- publicKey: keyPair.publicKey,
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] = { name, logo, purpose, scope };
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
- const wsClient = getWebSocketClient(`wss://bridge.zkpassport.id?topic=${topic}`, this.domain);
378
- this.topicToWebSocketClient[topic] = wsClient;
379
- wsClient.onopen = async () => {
380
- logger.info("[frontend] WebSocket connection established");
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
- wsClient.onerror = (error) => {
422
- logger.error("[frontend] WebSocket error:", error);
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 && getScopeHash(this.domain) !== BigInt(proofData.publicInputs[1])) {
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: ${getScopeHash(this.domain).toString()}`,
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
- async checkPublicInputs(proofs, queryResult, validity, scope) {
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 && getScopeHash(this.domain) !== getScopeFromOuterProof(proofData)) {
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: ${getScopeHash(this.domain).toString()}`,
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 { isCorrect, uniqueIdentifier: uniqueIdentifierFromPublicInputs, queryResultErrors: queryResultErrorsFromPublicInputs, } = await this.checkPublicInputs(proofs, formattedResult, validity, scope);
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: "0x8c6982D77f7a8f60aE3133cA9b2FAA6f3e78c394",
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 = bytesToHex(this.topicToKeyPair[requestId].publicKey);
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.topicToWebSocketClient[requestId]) {
2162
- this.topicToWebSocketClient[requestId].close();
2163
- delete this.topicToWebSocketClient[requestId];
2250
+ if (this.topicToBridge[requestId]) {
2251
+ this.topicToBridge[requestId].close();
2252
+ delete this.topicToBridge[requestId];
2164
2253
  }
2165
- delete this.topicToKeyPair[requestId];
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.topicToWebSocketClient) {
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.4",
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.7.4",
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",