@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/src/index.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { randomBytes } from "crypto"
2
1
  import { Alpha3Code, getAlpha3Code, registerLocale } from "i18n-iso-countries"
3
2
  import {
4
3
  type DisclosableIDCredential,
@@ -57,11 +56,14 @@ import {
57
56
  ProofData,
58
57
  getScopeFromOuterProof,
59
58
  getSubscopeFromOuterProof,
59
+ getServiceScopeHash,
60
+ BoundData,
61
+ BindCommittedInputs,
62
+ getBindEVMParameterCommitment,
63
+ getBindParameterCommitment,
64
+ formatBoundData,
60
65
  } from "@zkpassport/utils"
61
66
  import { bytesToHex } from "@noble/ciphers/utils"
62
- import { getWebSocketClient, WebSocketClient } from "./websocket"
63
- import { createEncryptedJsonRpcRequest } from "./json-rpc"
64
- import { decrypt, generateECDHKeyPair, getSharedSecret } from "./encryption"
65
67
  import { noLogger as logger } from "./logger"
66
68
  import { inflate } from "pako"
67
69
  import i18en from "i18n-iso-countries/langs/en.json"
@@ -70,6 +72,7 @@ import { sha256 } from "@noble/hashes/sha2"
70
72
  import { hexToBytes } from "@noble/hashes/utils"
71
73
  import ZKPassportVerifierAbi from "./assets/abi/ZKPassportVerifier.json"
72
74
  import { RegistryClient } from "@zkpassport/registry"
75
+ import { Bridge, BridgeInterface } from "@obsidion/bridge"
73
76
 
74
77
  const DEFAULT_DATE_VALUE = new Date(1111, 10, 11)
75
78
 
@@ -94,7 +97,8 @@ export type QueryResultErrors = {
94
97
  | "sig_check_id_data"
95
98
  | "data_check_integrity"
96
99
  | "outer"
97
- | "disclose"]: {
100
+ | "disclose"
101
+ | "bind"]: {
98
102
  disclose?: QueryResultError<string | number | Date>
99
103
  gte?: QueryResultError<number | Date>
100
104
  lte?: QueryResultError<number | Date>
@@ -124,6 +128,24 @@ export type SolidityVerifierParameters = {
124
128
 
125
129
  export type EVMChain = "ethereum_sepolia" | "local_anvil"
126
130
 
131
+ function getChainIdFromEVMChain(chain: EVMChain): number {
132
+ if (chain === "ethereum_sepolia") {
133
+ return 11155111
134
+ } else if (chain === "local_anvil") {
135
+ return 31337
136
+ }
137
+ throw new Error(`Unsupported chain: ${chain}`)
138
+ }
139
+
140
+ function getEVMChainFromChainId(chainId: number): EVMChain {
141
+ if (chainId === 11155111) {
142
+ return "ethereum_sepolia"
143
+ } else if (chainId === 31337) {
144
+ return "local_anvil"
145
+ }
146
+ throw new Error(`Unsupported chain ID: ${chainId}`)
147
+ }
148
+
127
149
  registerLocale(i18en)
128
150
 
129
151
  function hasRequestedAccessToField(credentialsRequest: Query, field: IDCredential): boolean {
@@ -330,6 +352,12 @@ export type QueryBuilder = {
330
352
  * @param key The attribute to disclose.
331
353
  */
332
354
  disclose: (key: DisclosableIDCredential) => QueryBuilder
355
+ /**
356
+ * Binds a value to the request.
357
+ * @param key The key of the value to bind.
358
+ * @param value The value to bind the request to.
359
+ */
360
+ bind: (key: keyof BoundData, value: BoundData[keyof BoundData]) => QueryBuilder
333
361
  /**
334
362
  * Builds the request.
335
363
  *
@@ -351,13 +379,12 @@ export class ZKPassport {
351
379
  devMode: boolean
352
380
  }
353
381
  > = {}
354
- private topicToKeyPair: Record<string, { privateKey: Uint8Array; publicKey: Uint8Array }> = {}
355
- private topicToWebSocketClient: Record<string, WebSocketClient> = {}
356
- private topicToSharedSecret: Record<string, Uint8Array> = {}
382
+ private topicToPublicKey: Record<string, string> = {}
383
+ private topicToBridge: Record<string, BridgeInterface> = {}
357
384
  private topicToRequestReceived: Record<string, boolean> = {}
358
385
  private topicToService: Record<
359
386
  string,
360
- { name: string; logo: string; purpose: string; scope?: string }
387
+ { name: string; logo: string; purpose: string; scope?: string; chainId?: number }
361
388
  > = {}
362
389
  private topicToProofs: Record<string, Array<ProofResult>> = {}
363
390
  private topicToExpectedProofCount: Record<string, number> = {}
@@ -400,6 +427,9 @@ export class ZKPassport {
400
427
  queryResult: result,
401
428
  validity: this.topicToLocalConfig[topic]?.validity,
402
429
  scope: this.topicToService[topic]?.scope,
430
+ evmChain: this.topicToService[topic]?.chainId
431
+ ? getEVMChainFromChainId(this.topicToService[topic]?.chainId)
432
+ : undefined,
403
433
  devMode: this.topicToLocalConfig[topic]?.devMode,
404
434
  })
405
435
  delete this.topicToProofs[topic]
@@ -485,6 +515,9 @@ export class ZKPassport {
485
515
  }
486
516
  }
487
517
  }
518
+ if ((this.topicToConfig[topic] as Query).bind) {
519
+ neededCircuits.push("bind")
520
+ }
488
521
  // From the circuits needed, determine the expected proof count
489
522
  // There are at least 4 proofs, 3 base proofs and 1 disclosure proof minimum
490
523
  // Each separate needed circuit adds 1 disclosure proof
@@ -498,11 +531,7 @@ export class ZKPassport {
498
531
  * @param request The request.
499
532
  * @param outerRequest The outer request.
500
533
  */
501
- private async handleEncryptedMessage(
502
- topic: string,
503
- request: JsonRpcRequest,
504
- outerRequest: JsonRpcRequest,
505
- ) {
534
+ private async handleEncryptedMessage(topic: string, request: JsonRpcRequest) {
506
535
  logger.debug("Received encrypted message:", request)
507
536
  if (request.method === "accept") {
508
537
  logger.debug(`User accepted the request and is generating a proof`)
@@ -512,31 +541,9 @@ export class ZKPassport {
512
541
  await Promise.all(this.onRejectCallbacks[topic].map((callback) => callback()))
513
542
  } else if (request.method === "proof") {
514
543
  logger.debug(`User generated proof`)
515
- // Uncompress the proof and convert it to a hex string
516
- const bytesProof = Buffer.from(request.params.proof, "base64")
517
- const bytesCommittedInputs = request.params.committedInputs
518
- ? Buffer.from(request.params.committedInputs, "base64")
519
- : null
520
- const uncompressedProof = inflate(bytesProof)
521
- const uncompressedCommittedInputs = bytesCommittedInputs
522
- ? inflate(bytesCommittedInputs)
523
- : null
524
- // The gzip lib in the app compress the proof as ASCII
525
- // and since the app passes the proof as a hex string, we can
526
- // just decode the bytes as hex characters using the TextDecoder
527
- const hexProof = new TextDecoder().decode(uncompressedProof)
528
- const processedProof: ProofResult = {
529
- proof: hexProof,
530
- vkeyHash: request.params.vkeyHash,
531
- name: request.params.name,
532
- version: request.params.version,
533
- committedInputs: uncompressedCommittedInputs
534
- ? JSON.parse(new TextDecoder().decode(uncompressedCommittedInputs))
535
- : undefined,
536
- }
537
- this.topicToProofs[topic].push(processedProof)
544
+ this.topicToProofs[topic].push(request.params)
538
545
  await Promise.all(
539
- this.onProofGeneratedCallbacks[topic].map((callback) => callback(processedProof)),
546
+ this.onProofGeneratedCallbacks[topic].map((callback) => callback(request.params)),
540
547
  )
541
548
  // If the results were received before all the proofs were generated,
542
549
  // we can handle the result now
@@ -644,6 +651,13 @@ export class ZKPassport {
644
651
  }
645
652
  return this.getZkPassportRequest(topic)
646
653
  },
654
+ bind: (key: keyof BoundData, value: BoundData[keyof BoundData]) => {
655
+ this.topicToConfig[topic].bind = {
656
+ ...this.topicToConfig[topic].bind,
657
+ [key]: value,
658
+ }
659
+ return this.getZkPassportRequest(topic)
660
+ },
647
661
  done: () => {
648
662
  const base64Config = Buffer.from(JSON.stringify(this.topicToConfig[topic])).toString(
649
663
  "base64",
@@ -651,7 +665,7 @@ export class ZKPassport {
651
665
  const base64Service = Buffer.from(JSON.stringify(this.topicToService[topic])).toString(
652
666
  "base64",
653
667
  )
654
- const pubkey = bytesToHex(this.topicToKeyPair[topic].publicKey)
668
+ const pubkey = this.topicToPublicKey[topic]
655
669
  this.setExpectedProofCount(topic)
656
670
  return {
657
671
  url: `https://zkpassport.id/r?d=${this.domain}&t=${topic}&c=${base64Config}&s=${base64Service}&p=${pubkey}&m=${this.topicToLocalConfig[topic].mode}`,
@@ -675,7 +689,7 @@ export class ZKPassport {
675
689
  onReject: (callback: () => void) => this.onRejectCallbacks[topic].push(callback),
676
690
  onError: (callback: (error: string) => void) =>
677
691
  this.onErrorCallbacks[topic].push(callback),
678
- isBridgeConnected: () => this.topicToWebSocketClient[topic].readyState === WebSocket.OPEN,
692
+ isBridgeConnected: () => this.topicToBridge[topic].isBridgeConnected(),
679
693
  requestReceived: () => this.topicToRequestReceived[topic] === true,
680
694
  }
681
695
  },
@@ -690,6 +704,7 @@ export class ZKPassport {
690
704
  * @param scope Scope this request to a specific use case
691
705
  * @param validity How many days ago should have the ID been last scanned by the user?
692
706
  * @param devMode Whether to enable dev mode. This will allow you to verify mock proofs (i.e. from ZKR)
707
+ * @param evmChain The EVM chain to use for the request (if using the proof onchain)
693
708
  * @returns The query builder object.
694
709
  */
695
710
  public async request({
@@ -698,6 +713,7 @@ export class ZKPassport {
698
713
  purpose,
699
714
  scope,
700
715
  mode,
716
+ evmChain,
701
717
  validity,
702
718
  devMode,
703
719
  topicOverride,
@@ -708,21 +724,27 @@ export class ZKPassport {
708
724
  purpose: string
709
725
  scope?: string
710
726
  mode?: ProofMode
727
+ evmChain?: EVMChain
711
728
  validity?: number
712
729
  devMode?: boolean
713
730
  topicOverride?: string
714
731
  keyPairOverride?: { privateKey: Uint8Array; publicKey: Uint8Array }
715
732
  }): Promise<QueryBuilder> {
716
- const topic = topicOverride || randomBytes(16).toString("hex")
733
+ const bridge = await Bridge.create({
734
+ keyPair: keyPairOverride,
735
+ bridgeId: topicOverride,
736
+ })
717
737
 
718
- const keyPair = keyPairOverride || (await generateECDHKeyPair())
719
- this.topicToKeyPair[topic] = {
720
- privateKey: keyPair.privateKey,
721
- publicKey: keyPair.publicKey,
722
- }
738
+ const topic = bridge.connection.getBridgeId()
723
739
 
724
740
  this.topicToConfig[topic] = {}
725
- this.topicToService[topic] = { name, logo, purpose, scope }
741
+ this.topicToService[topic] = {
742
+ name,
743
+ logo,
744
+ purpose,
745
+ scope,
746
+ chainId: evmChain ? getChainIdFromEVMChain(evmChain) : undefined,
747
+ }
726
748
  this.topicToProofs[topic] = []
727
749
  this.topicToExpectedProofCount[topic] = 0
728
750
  this.topicToLocalConfig[topic] = {
@@ -740,68 +762,22 @@ export class ZKPassport {
740
762
  this.onRejectCallbacks[topic] = []
741
763
  this.onErrorCallbacks[topic] = []
742
764
 
743
- const wsClient = getWebSocketClient(`wss://bridge.zkpassport.id?topic=${topic}`, this.domain)
744
- this.topicToWebSocketClient[topic] = wsClient
745
- wsClient.onopen = async () => {
746
- logger.info("[frontend] WebSocket connection established")
747
- await Promise.all(this.onBridgeConnectCallbacks[topic].map((callback) => callback()))
748
- }
749
- wsClient.addEventListener("message", async (event: any) => {
750
- logger.debug("[frontend] Received message:", event.data)
751
- try {
752
- const data: JsonRpcRequest = JSON.parse(event.data)
753
- // Handshake happens when the mobile app scans the QR code and connects to the bridge
754
- if (data.method === "handshake") {
755
- logger.debug("[frontend] Received handshake:", event.data)
756
-
757
- this.topicToRequestReceived[topic] = true
758
- this.topicToSharedSecret[topic] = await getSharedSecret(
759
- bytesToHex(keyPair.privateKey),
760
- data.params.pubkey,
761
- )
762
- logger.debug(
763
- "[frontend] Shared secret:",
764
- Buffer.from(this.topicToSharedSecret[topic]).toString("hex"),
765
- )
766
-
767
- const encryptedMessage = await createEncryptedJsonRpcRequest(
768
- "hello",
769
- null,
770
- this.topicToSharedSecret[topic],
771
- topic,
772
- )
773
- logger.debug("[frontend] Sending encrypted message:", encryptedMessage)
774
- wsClient.send(JSON.stringify(encryptedMessage))
775
-
776
- await Promise.all(this.onRequestReceivedCallbacks[topic].map((callback) => callback()))
777
- return
778
- }
765
+ this.topicToPublicKey[topic] = bridge.getPublicKey()
779
766
 
780
- // Handle encrypted messages
781
- if (data.method === "encryptedMessage") {
782
- // Decode the payload from base64 to Uint8Array
783
- const payload = new Uint8Array(
784
- atob(data.params.payload)
785
- .split("")
786
- .map((c) => c.charCodeAt(0)),
787
- )
788
- try {
789
- // Decrypt the payload using the shared secret
790
- const decrypted = await decrypt(payload, this.topicToSharedSecret[topic], topic)
791
- const decryptedJson: JsonRpcRequest = JSON.parse(decrypted)
792
- this.handleEncryptedMessage(topic, decryptedJson, data)
793
- } catch (error) {
794
- logger.error("[frontend] Error decrypting message:", error)
795
- }
796
- return
797
- }
798
- } catch (error) {
799
- logger.error("[frontend] Error:", error)
800
- }
767
+ this.topicToBridge[topic] = bridge
768
+ bridge.onConnect(async (reconnection: boolean) => {
769
+ logger.debug("Bridge connected")
770
+ logger.debug("Is reconnection:", reconnection)
771
+ await Promise.all(this.onBridgeConnectCallbacks[topic].map((callback) => callback()))
772
+ })
773
+ bridge.onSecureChannelEstablished(async () => {
774
+ logger.debug("Secure channel established")
775
+ await Promise.all(this.onRequestReceivedCallbacks[topic].map((callback) => callback()))
776
+ })
777
+ bridge.onSecureMessage(async (message: any) => {
778
+ logger.debug("Received message:", message)
779
+ this.handleEncryptedMessage(topic, message)
801
780
  })
802
- wsClient.onerror = (error: Event) => {
803
- logger.error("[frontend] WebSocket error:", error)
804
- }
805
781
  return this.getZkPassportRequest(topic)
806
782
  }
807
783
 
@@ -823,6 +799,7 @@ export class ZKPassport {
823
799
  fullname: {},
824
800
  document_number: {},
825
801
  outer: {},
802
+ bind: {},
826
803
  }
827
804
  let isCorrect = true
828
805
  // We can't be certain that the disclosed data is for a passport or an ID card
@@ -1184,6 +1161,7 @@ export class ZKPassport {
1184
1161
  fullname: {},
1185
1162
  document_number: {},
1186
1163
  outer: {},
1164
+ bind: {},
1187
1165
  }
1188
1166
  let isCorrect = true
1189
1167
  const currentTime = new Date()
@@ -1317,6 +1295,7 @@ export class ZKPassport {
1317
1295
  fullname: {},
1318
1296
  document_number: {},
1319
1297
  outer: {},
1298
+ bind: {},
1320
1299
  }
1321
1300
  let isCorrect = true
1322
1301
  const currentTime = new Date()
@@ -1445,6 +1424,7 @@ export class ZKPassport {
1445
1424
  fullname: {},
1446
1425
  document_number: {},
1447
1426
  outer: {},
1427
+ bind: {},
1448
1428
  }
1449
1429
  let isCorrect = true
1450
1430
  const currentTime = new Date()
@@ -1573,6 +1553,7 @@ export class ZKPassport {
1573
1553
  fullname: {},
1574
1554
  document_number: {},
1575
1555
  outer: {},
1556
+ bind: {},
1576
1557
  }
1577
1558
  let isCorrect = true
1578
1559
  if (
@@ -1637,6 +1618,7 @@ export class ZKPassport {
1637
1618
  fullname: {},
1638
1619
  document_number: {},
1639
1620
  outer: {},
1621
+ bind: {},
1640
1622
  }
1641
1623
  let isCorrect = true
1642
1624
 
@@ -1699,6 +1681,7 @@ export class ZKPassport {
1699
1681
  fullname: {},
1700
1682
  document_number: {},
1701
1683
  outer: {},
1684
+ bind: {},
1702
1685
  }
1703
1686
  let isCorrect = true
1704
1687
  if (
@@ -1746,6 +1729,7 @@ export class ZKPassport {
1746
1729
  fullname: {},
1747
1730
  document_number: {},
1748
1731
  outer: {},
1732
+ bind: {},
1749
1733
  }
1750
1734
  let isCorrect = true
1751
1735
 
@@ -1780,13 +1764,17 @@ export class ZKPassport {
1780
1764
  queryResultErrors: QueryResultErrors,
1781
1765
  key: string,
1782
1766
  scope?: string,
1767
+ chainId?: number,
1783
1768
  ) {
1784
1769
  let isCorrect = true
1785
- if (this.domain && getScopeHash(this.domain) !== BigInt(proofData.publicInputs[1])) {
1770
+ if (
1771
+ this.domain &&
1772
+ getServiceScopeHash(this.domain, chainId) !== BigInt(proofData.publicInputs[1])
1773
+ ) {
1786
1774
  console.warn("The proof comes from a different domain than the one expected")
1787
1775
  isCorrect = false
1788
1776
  queryResultErrors[key as keyof QueryResultErrors].scope = {
1789
- expected: `Scope: ${getScopeHash(this.domain).toString()}`,
1777
+ expected: `Scope: ${getServiceScopeHash(this.domain, chainId).toString()}`,
1790
1778
  received: `Scope: ${BigInt(proofData.publicInputs[1]).toString()}`,
1791
1779
  message: "The proof comes from a different domain than the one expected",
1792
1780
  }
@@ -1835,11 +1823,63 @@ export class ZKPassport {
1835
1823
  return { isCorrect, queryResultErrors }
1836
1824
  }
1837
1825
 
1826
+ private checkBindPublicInputs(queryResult: QueryResult, boundData: BoundData) {
1827
+ const queryResultErrors: QueryResultErrors = {
1828
+ sig_check_dsc: {},
1829
+ sig_check_id_data: {},
1830
+ data_check_integrity: {},
1831
+ disclose: {},
1832
+ age: {},
1833
+ birthdate: {},
1834
+ expiry_date: {},
1835
+ document_type: {},
1836
+ issuing_country: {},
1837
+ gender: {},
1838
+ nationality: {},
1839
+ firstname: {},
1840
+ lastname: {},
1841
+ fullname: {},
1842
+ document_number: {},
1843
+ outer: {},
1844
+ bind: {},
1845
+ }
1846
+ let isCorrect = true
1847
+
1848
+ if (queryResult.bind) {
1849
+ if (
1850
+ queryResult.bind.user_address?.toLowerCase().replace("0x", "") !==
1851
+ boundData.user_address?.toLowerCase().replace("0x", "")
1852
+ ) {
1853
+ console.warn("Bound user address does not match the one from the query results")
1854
+ isCorrect = false
1855
+ queryResultErrors.bind.eq = {
1856
+ expected: queryResult.bind.user_address,
1857
+ received: boundData.user_address,
1858
+ message: "Bound user address does not match the one from the query results",
1859
+ }
1860
+ }
1861
+ if (
1862
+ queryResult.bind.custom_data?.trim().toLowerCase() !==
1863
+ boundData.custom_data?.trim().toLowerCase()
1864
+ ) {
1865
+ console.warn("Bound custom data does not match the one from the query results")
1866
+ isCorrect = false
1867
+ queryResultErrors.bind.eq = {
1868
+ expected: queryResult.bind.custom_data,
1869
+ received: boundData.custom_data,
1870
+ message: "Bound custom data does not match the one from the query results",
1871
+ }
1872
+ }
1873
+ }
1874
+ return { isCorrect, queryResultErrors }
1875
+ }
1876
+
1838
1877
  private async checkPublicInputs(
1839
1878
  proofs: Array<ProofResult>,
1840
1879
  queryResult: QueryResult,
1841
1880
  validity?: number,
1842
1881
  scope?: string,
1882
+ chainId?: number,
1843
1883
  ) {
1844
1884
  let commitmentIn: bigint | undefined
1845
1885
  let commitmentOut: bigint | undefined
@@ -1872,6 +1912,7 @@ export class ZKPassport {
1872
1912
  fullname: {},
1873
1913
  document_number: {},
1874
1914
  outer: {},
1915
+ bind: {},
1875
1916
  }
1876
1917
 
1877
1918
  // Since the order is important for the commitments, we need to sort the proofs
@@ -1889,6 +1930,7 @@ export class ZKPassport {
1889
1930
  "inclusion_check_nationality",
1890
1931
  "exclusion_check_issuing_country",
1891
1932
  "inclusion_check_issuing_country",
1933
+ "bind",
1892
1934
  ]
1893
1935
  const getIndex = (proof: ProofResult) => {
1894
1936
  const name = proof.name || ""
@@ -1945,11 +1987,14 @@ export class ZKPassport {
1945
1987
  message: "The proof does not verify all the requested conditions and information",
1946
1988
  }
1947
1989
  }
1948
- if (this.domain && getScopeHash(this.domain) !== getScopeFromOuterProof(proofData)) {
1990
+ if (
1991
+ this.domain &&
1992
+ getServiceScopeHash(this.domain, chainId) !== getScopeFromOuterProof(proofData)
1993
+ ) {
1949
1994
  console.warn("The proof comes from a different domain than the one expected")
1950
1995
  isCorrect = false
1951
1996
  queryResultErrors.outer.scope = {
1952
- expected: `Scope: ${getScopeHash(this.domain).toString()}`,
1997
+ expected: `Scope: ${getServiceScopeHash(this.domain, chainId).toString()}`,
1953
1998
  received: `Scope: ${getScopeFromOuterProof(proofData).toString()}`,
1954
1999
  message: "The proof comes from a different domain than the one expected",
1955
2000
  }
@@ -2205,6 +2250,27 @@ export class ZKPassport {
2205
2250
  ...queryResultErrors,
2206
2251
  ...queryResultErrorsIssuingCountryExclusion,
2207
2252
  }
2253
+ } else if (!!committedInputs?.bind) {
2254
+ const bindCommittedInputs = committedInputs?.bind as BindCommittedInputs
2255
+ const bindParameterCommitment = isForEVM
2256
+ ? await getBindEVMParameterCommitment(formatBoundData(bindCommittedInputs.data))
2257
+ : await getBindParameterCommitment(formatBoundData(bindCommittedInputs.data))
2258
+ if (!paramCommitments.includes(bindParameterCommitment)) {
2259
+ console.warn("This proof does not verify the bound data")
2260
+ isCorrect = false
2261
+ queryResultErrors.bind.commitment = {
2262
+ expected: `Bind parameter commitment: ${bindParameterCommitment.toString()}`,
2263
+ received: `Parameter commitments included: ${paramCommitments.join(", ")}`,
2264
+ message: "This proof does not verify the bound data",
2265
+ }
2266
+ }
2267
+ const { isCorrect: isCorrectBind, queryResultErrors: queryResultErrorsBind } =
2268
+ this.checkBindPublicInputs(queryResult, bindCommittedInputs.data)
2269
+ isCorrect = isCorrect && isCorrectBind
2270
+ queryResultErrors = {
2271
+ ...queryResultErrors,
2272
+ ...queryResultErrorsBind,
2273
+ }
2208
2274
  }
2209
2275
  uniqueIdentifier = getNullifierFromOuterProof(proofData).toString(10)
2210
2276
  } else if (proof.name?.startsWith("sig_check_dsc")) {
@@ -2636,6 +2702,29 @@ export class ZKPassport {
2636
2702
  ...queryResultErrorsScope,
2637
2703
  }
2638
2704
  uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10)
2705
+ } else if (proof.name === "bind") {
2706
+ const bindCommittedInputs = proof.committedInputs?.bind as BindCommittedInputs
2707
+ const paramCommittment = getParameterCommitmentFromDisclosureProof(proofData)
2708
+ const calculatedParamCommitment = await getBindParameterCommitment(
2709
+ formatBoundData(bindCommittedInputs.data),
2710
+ )
2711
+ if (paramCommittment !== calculatedParamCommitment) {
2712
+ console.warn("The bound data does not match the one from the proof")
2713
+ isCorrect = false
2714
+ queryResultErrors.bind.commitment = {
2715
+ expected: `Commitment: ${calculatedParamCommitment}`,
2716
+ received: `Commitment: ${paramCommittment}`,
2717
+ message: "The bound data does not match the one from the proof",
2718
+ }
2719
+ }
2720
+ const { isCorrect: isCorrectBind, queryResultErrors: queryResultErrorsBind } =
2721
+ this.checkBindPublicInputs(queryResult, bindCommittedInputs.data)
2722
+ isCorrect = isCorrect && isCorrectBind
2723
+ queryResultErrors = {
2724
+ ...queryResultErrors,
2725
+ ...queryResultErrorsBind,
2726
+ }
2727
+ uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10)
2639
2728
  }
2640
2729
  }
2641
2730
  return { isCorrect, uniqueIdentifier, queryResultErrors }
@@ -2646,6 +2735,9 @@ export class ZKPassport {
2646
2735
  * @param proofs The proofs to verify.
2647
2736
  * @param queryResult The query result to verify against
2648
2737
  * @param validity How many days ago should have the ID been last scanned by the user?
2738
+ * @param scope Scope this request to a specific use case
2739
+ * @param evmChain The EVM chain to use for the verification (if using the proof onchain)
2740
+ * @param devMode Whether to enable dev mode. This will allow you to verify mock proofs (i.e. from ZKR)
2649
2741
  * @returns An object containing the unique identifier associated to the user
2650
2742
  * and a boolean indicating whether the proofs were successfully verified.
2651
2743
  */
@@ -2654,12 +2746,14 @@ export class ZKPassport {
2654
2746
  queryResult,
2655
2747
  validity,
2656
2748
  scope,
2749
+ evmChain,
2657
2750
  devMode = false,
2658
2751
  }: {
2659
2752
  proofs: Array<ProofResult>
2660
2753
  queryResult: QueryResult
2661
2754
  validity?: number
2662
2755
  scope?: string
2756
+ evmChain?: EVMChain
2663
2757
  devMode?: boolean
2664
2758
  }): Promise<{
2665
2759
  uniqueIdentifier: string | undefined
@@ -2692,11 +2786,12 @@ export class ZKPassport {
2692
2786
  let verified = true
2693
2787
  let uniqueIdentifier: string | undefined
2694
2788
  let queryResultErrors: QueryResultErrors | undefined
2789
+ const chainId = evmChain ? getChainIdFromEVMChain(evmChain) : undefined
2695
2790
  const {
2696
2791
  isCorrect,
2697
2792
  uniqueIdentifier: uniqueIdentifierFromPublicInputs,
2698
2793
  queryResultErrors: queryResultErrorsFromPublicInputs,
2699
- } = await this.checkPublicInputs(proofs, formattedResult, validity, scope)
2794
+ } = await this.checkPublicInputs(proofs, formattedResult, validity, scope, chainId)
2700
2795
  uniqueIdentifier = uniqueIdentifierFromPublicInputs
2701
2796
  verified = isCorrect
2702
2797
  queryResultErrors = isCorrect ? undefined : queryResultErrorsFromPublicInputs
@@ -2790,7 +2885,7 @@ export class ZKPassport {
2790
2885
  if (network === "ethereum_sepolia") {
2791
2886
  return {
2792
2887
  ...baseConfig,
2793
- address: "0x8c6982D77f7a8f60aE3133cA9b2FAA6f3e78c394",
2888
+ address: "0x5e4B11F7B7995F5Cee0134692a422b045091112F",
2794
2889
  }
2795
2890
  } else if (network === "local_anvil") {
2796
2891
  return {
@@ -2901,6 +2996,13 @@ export class ZKPassport {
2901
2996
  ProofType.DISCLOSE.toString(16).padStart(2, "0") +
2902
2997
  value.discloseMask.map((x) => x.toString(16).padStart(2, "0")).join("") +
2903
2998
  value.disclosedBytes.map((x) => x.toString(16).padStart(2, "0")).join("")
2999
+ } else if (circuitName === "bind_evm") {
3000
+ const value = proof.committedInputs[circuitName] as BindCommittedInputs
3001
+ compressedCommittedInputs =
3002
+ ProofType.BIND.toString(16).padStart(2, "0") +
3003
+ rightPadArrayWithZeros(formatBoundData(value.data), 500)
3004
+ .map((x) => x.toString(16).padStart(2, "0"))
3005
+ .join("")
2904
3006
  } else {
2905
3007
  throw new Error(`Unsupported circuit for EVM verification: ${circuitName}`)
2906
3008
  }
@@ -2959,7 +3061,7 @@ export class ZKPassport {
2959
3061
  * @returns The URL of the request.
2960
3062
  */
2961
3063
  public getUrl(requestId: string) {
2962
- const pubkey = bytesToHex(this.topicToKeyPair[requestId].publicKey)
3064
+ const pubkey = this.topicToPublicKey[requestId]
2963
3065
  const base64Config = Buffer.from(JSON.stringify(this.topicToConfig[requestId])).toString(
2964
3066
  "base64",
2965
3067
  )
@@ -2974,14 +3076,13 @@ export class ZKPassport {
2974
3076
  * @param requestId The request ID.
2975
3077
  */
2976
3078
  public cancelRequest(requestId: string) {
2977
- if (this.topicToWebSocketClient[requestId]) {
2978
- this.topicToWebSocketClient[requestId].close()
2979
- delete this.topicToWebSocketClient[requestId]
3079
+ if (this.topicToBridge[requestId]) {
3080
+ this.topicToBridge[requestId].close()
3081
+ delete this.topicToBridge[requestId]
2980
3082
  }
2981
- delete this.topicToKeyPair[requestId]
3083
+ delete this.topicToPublicKey[requestId]
2982
3084
  delete this.topicToConfig[requestId]
2983
3085
  delete this.topicToLocalConfig[requestId]
2984
- delete this.topicToSharedSecret[requestId]
2985
3086
  delete this.topicToProofs[requestId]
2986
3087
  delete this.topicToExpectedProofCount[requestId]
2987
3088
  delete this.topicToFailedProofCount[requestId]
@@ -2998,7 +3099,7 @@ export class ZKPassport {
2998
3099
  * @notice Clears all requests.
2999
3100
  */
3000
3101
  public clearAllRequests() {
3001
- for (const requestId in this.topicToWebSocketClient) {
3102
+ for (const requestId in this.topicToBridge) {
3002
3103
  this.cancelRequest(requestId)
3003
3104
  }
3004
3105
  }