@zkpassport/sdk 0.3.4 → 0.4.0

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/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, } 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];
@@ -187,7 +203,7 @@ export class ZKPassport {
187
203
  * @param request The request.
188
204
  * @param outerRequest The outer request.
189
205
  */
190
- async handleEncryptedMessage(topic, request, outerRequest) {
206
+ async handleEncryptedMessage(topic, request) {
191
207
  logger.debug("Received encrypted message:", request);
192
208
  if (request.method === "accept") {
193
209
  logger.debug(`User accepted the request and is generating a proof`);
@@ -199,30 +215,8 @@ export class ZKPassport {
199
215
  }
200
216
  else if (request.method === "proof") {
201
217
  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)));
218
+ this.topicToProofs[topic].push(request.params);
219
+ await Promise.all(this.onProofGeneratedCallbacks[topic].map((callback) => callback(request.params)));
226
220
  // If the results were received before all the proofs were generated,
227
221
  // we can handle the result now
228
222
  if (this.topicToResults[topic] &&
@@ -322,7 +316,7 @@ export class ZKPassport {
322
316
  done: () => {
323
317
  const base64Config = Buffer.from(JSON.stringify(this.topicToConfig[topic])).toString("base64");
324
318
  const base64Service = Buffer.from(JSON.stringify(this.topicToService[topic])).toString("base64");
325
- const pubkey = bytesToHex(this.topicToKeyPair[topic].publicKey);
319
+ const pubkey = this.topicToPublicKey[topic];
326
320
  this.setExpectedProofCount(topic);
327
321
  return {
328
322
  url: `https://zkpassport.id/r?d=${this.domain}&t=${topic}&c=${base64Config}&s=${base64Service}&p=${pubkey}&m=${this.topicToLocalConfig[topic].mode}`,
@@ -334,7 +328,7 @@ export class ZKPassport {
334
328
  onResult: (callback) => this.onResultCallbacks[topic].push(callback),
335
329
  onReject: (callback) => this.onRejectCallbacks[topic].push(callback),
336
330
  onError: (callback) => this.onErrorCallbacks[topic].push(callback),
337
- isBridgeConnected: () => this.topicToWebSocketClient[topic].readyState === WebSocket.OPEN,
331
+ isBridgeConnected: () => this.topicToBridge[topic].isBridgeConnected(),
338
332
  requestReceived: () => this.topicToRequestReceived[topic] === true,
339
333
  };
340
334
  },
@@ -348,17 +342,23 @@ export class ZKPassport {
348
342
  * @param scope Scope this request to a specific use case
349
343
  * @param validity How many days ago should have the ID been last scanned by the user?
350
344
  * @param devMode Whether to enable dev mode. This will allow you to verify mock proofs (i.e. from ZKR)
345
+ * @param evmChain The EVM chain to use for the request (if using the proof onchain)
351
346
  * @returns The query builder object.
352
347
  */
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
- };
348
+ async request({ name, logo, purpose, scope, mode, evmChain, validity, devMode, topicOverride, keyPairOverride, }) {
349
+ const bridge = await Bridge.create({
350
+ keyPair: keyPairOverride,
351
+ bridgeId: topicOverride,
352
+ });
353
+ const topic = bridge.connection.getBridgeId();
360
354
  this.topicToConfig[topic] = {};
361
- this.topicToService[topic] = { name, logo, purpose, scope };
355
+ this.topicToService[topic] = {
356
+ name,
357
+ logo,
358
+ purpose,
359
+ scope,
360
+ chainId: evmChain ? getChainIdFromEVMChain(evmChain) : undefined,
361
+ };
362
362
  this.topicToProofs[topic] = [];
363
363
  this.topicToExpectedProofCount[topic] = 0;
364
364
  this.topicToLocalConfig[topic] = {
@@ -374,53 +374,21 @@ export class ZKPassport {
374
374
  this.onResultCallbacks[topic] = [];
375
375
  this.onRejectCallbacks[topic] = [];
376
376
  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");
377
+ this.topicToPublicKey[topic] = bridge.getPublicKey();
378
+ this.topicToBridge[topic] = bridge;
379
+ bridge.onConnect(async (reconnection) => {
380
+ logger.debug("Bridge connected");
381
+ logger.debug("Is reconnection:", reconnection);
381
382
  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
383
  });
421
- wsClient.onerror = (error) => {
422
- logger.error("[frontend] WebSocket error:", error);
423
- };
384
+ bridge.onSecureChannelEstablished(async () => {
385
+ logger.debug("Secure channel established");
386
+ await Promise.all(this.onRequestReceivedCallbacks[topic].map((callback) => callback()));
387
+ });
388
+ bridge.onSecureMessage(async (message) => {
389
+ logger.debug("Received message:", message);
390
+ this.handleEncryptedMessage(topic, message);
391
+ });
424
392
  return this.getZkPassportRequest(topic);
425
393
  }
426
394
  checkDiscloseBytesPublicInputs(proof, queryResult) {
@@ -1242,13 +1210,14 @@ export class ZKPassport {
1242
1210
  }
1243
1211
  return { isCorrect, queryResultErrors };
1244
1212
  }
1245
- checkScopeFromDisclosureProof(proofData, queryResultErrors, key, scope) {
1213
+ checkScopeFromDisclosureProof(proofData, queryResultErrors, key, scope, chainId) {
1246
1214
  let isCorrect = true;
1247
- if (this.domain && getScopeHash(this.domain) !== BigInt(proofData.publicInputs[1])) {
1215
+ if (this.domain &&
1216
+ getServiceScopeHash(this.domain, chainId) !== BigInt(proofData.publicInputs[1])) {
1248
1217
  console.warn("The proof comes from a different domain than the one expected");
1249
1218
  isCorrect = false;
1250
1219
  queryResultErrors[key].scope = {
1251
- expected: `Scope: ${getScopeHash(this.domain).toString()}`,
1220
+ expected: `Scope: ${getServiceScopeHash(this.domain, chainId).toString()}`,
1252
1221
  received: `Scope: ${BigInt(proofData.publicInputs[1]).toString()}`,
1253
1222
  message: "The proof comes from a different domain than the one expected",
1254
1223
  };
@@ -1292,7 +1261,7 @@ export class ZKPassport {
1292
1261
  }
1293
1262
  return { isCorrect, queryResultErrors };
1294
1263
  }
1295
- async checkPublicInputs(proofs, queryResult, validity, scope) {
1264
+ async checkPublicInputs(proofs, queryResult, validity, scope, chainId) {
1296
1265
  let commitmentIn;
1297
1266
  let commitmentOut;
1298
1267
  let isCorrect = true;
@@ -1377,11 +1346,12 @@ export class ZKPassport {
1377
1346
  message: "The proof does not verify all the requested conditions and information",
1378
1347
  };
1379
1348
  }
1380
- if (this.domain && getScopeHash(this.domain) !== getScopeFromOuterProof(proofData)) {
1349
+ if (this.domain &&
1350
+ getServiceScopeHash(this.domain, chainId) !== getScopeFromOuterProof(proofData)) {
1381
1351
  console.warn("The proof comes from a different domain than the one expected");
1382
1352
  isCorrect = false;
1383
1353
  queryResultErrors.outer.scope = {
1384
- expected: `Scope: ${getScopeHash(this.domain).toString()}`,
1354
+ expected: `Scope: ${getServiceScopeHash(this.domain, chainId).toString()}`,
1385
1355
  received: `Scope: ${getScopeFromOuterProof(proofData).toString()}`,
1386
1356
  message: "The proof comes from a different domain than the one expected",
1387
1357
  };
@@ -1896,10 +1866,13 @@ export class ZKPassport {
1896
1866
  * @param proofs The proofs to verify.
1897
1867
  * @param queryResult The query result to verify against
1898
1868
  * @param validity How many days ago should have the ID been last scanned by the user?
1869
+ * @param scope Scope this request to a specific use case
1870
+ * @param evmChain The EVM chain to use for the verification (if using the proof onchain)
1871
+ * @param devMode Whether to enable dev mode. This will allow you to verify mock proofs (i.e. from ZKR)
1899
1872
  * @returns An object containing the unique identifier associated to the user
1900
1873
  * and a boolean indicating whether the proofs were successfully verified.
1901
1874
  */
1902
- async verify({ proofs, queryResult, validity, scope, devMode = false, }) {
1875
+ async verify({ proofs, queryResult, validity, scope, evmChain, devMode = false, }) {
1903
1876
  // If no proofs were generated, the results can't be trusted.
1904
1877
  // We still return it but verified will be false
1905
1878
  if (!proofs || proofs.length === 0) {
@@ -1921,7 +1894,8 @@ export class ZKPassport {
1921
1894
  let verified = true;
1922
1895
  let uniqueIdentifier;
1923
1896
  let queryResultErrors;
1924
- const { isCorrect, uniqueIdentifier: uniqueIdentifierFromPublicInputs, queryResultErrors: queryResultErrorsFromPublicInputs, } = await this.checkPublicInputs(proofs, formattedResult, validity, scope);
1897
+ const chainId = evmChain ? getChainIdFromEVMChain(evmChain) : undefined;
1898
+ const { isCorrect, uniqueIdentifier: uniqueIdentifierFromPublicInputs, queryResultErrors: queryResultErrorsFromPublicInputs, } = await this.checkPublicInputs(proofs, formattedResult, validity, scope, chainId);
1925
1899
  uniqueIdentifier = uniqueIdentifierFromPublicInputs;
1926
1900
  verified = isCorrect;
1927
1901
  queryResultErrors = isCorrect ? undefined : queryResultErrorsFromPublicInputs;
@@ -1999,7 +1973,7 @@ export class ZKPassport {
1999
1973
  if (network === "ethereum_sepolia") {
2000
1974
  return {
2001
1975
  ...baseConfig,
2002
- address: "0x8c6982D77f7a8f60aE3133cA9b2FAA6f3e78c394",
1976
+ address: "0xDfE02DFd5c208854884B58bFf6522De5c42F73E3",
2003
1977
  };
2004
1978
  }
2005
1979
  else if (network === "local_anvil") {
@@ -2148,7 +2122,7 @@ export class ZKPassport {
2148
2122
  * @returns The URL of the request.
2149
2123
  */
2150
2124
  getUrl(requestId) {
2151
- const pubkey = bytesToHex(this.topicToKeyPair[requestId].publicKey);
2125
+ const pubkey = this.topicToPublicKey[requestId];
2152
2126
  const base64Config = Buffer.from(JSON.stringify(this.topicToConfig[requestId])).toString("base64");
2153
2127
  const base64Service = Buffer.from(JSON.stringify(this.topicToService[requestId])).toString("base64");
2154
2128
  return `https://zkpassport.id/r?d=${this.domain}&t=${requestId}&c=${base64Config}&s=${base64Service}&p=${pubkey}&m=${this.topicToLocalConfig[requestId].mode}`;
@@ -2158,14 +2132,13 @@ export class ZKPassport {
2158
2132
  * @param requestId The request ID.
2159
2133
  */
2160
2134
  cancelRequest(requestId) {
2161
- if (this.topicToWebSocketClient[requestId]) {
2162
- this.topicToWebSocketClient[requestId].close();
2163
- delete this.topicToWebSocketClient[requestId];
2135
+ if (this.topicToBridge[requestId]) {
2136
+ this.topicToBridge[requestId].close();
2137
+ delete this.topicToBridge[requestId];
2164
2138
  }
2165
- delete this.topicToKeyPair[requestId];
2139
+ delete this.topicToPublicKey[requestId];
2166
2140
  delete this.topicToConfig[requestId];
2167
2141
  delete this.topicToLocalConfig[requestId];
2168
- delete this.topicToSharedSecret[requestId];
2169
2142
  delete this.topicToProofs[requestId];
2170
2143
  delete this.topicToExpectedProofCount[requestId];
2171
2144
  delete this.topicToFailedProofCount[requestId];
@@ -2181,7 +2154,7 @@ export class ZKPassport {
2181
2154
  * @notice Clears all requests.
2182
2155
  */
2183
2156
  clearAllRequests() {
2184
- for (const requestId in this.topicToWebSocketClient) {
2157
+ for (const requestId in this.topicToBridge) {
2185
2158
  this.cancelRequest(requestId);
2186
2159
  }
2187
2160
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zkpassport/sdk",
3
- "version": "0.3.4",
3
+ "version": "0.4.0",
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.8.2",
47
48
  "buffer": "^6.0.3",
48
49
  "i18n-iso-countries": "^7.12.0",
49
50
  "pako": "^2.1.0",