@zkpassport/sdk 0.1.2 → 0.2.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.
Files changed (54) hide show
  1. package/README.md +80 -25
  2. package/dist/{encryption.d.ts → cjs/encryption.d.ts} +4 -4
  3. package/dist/cjs/encryption.js +79 -0
  4. package/dist/cjs/index.d.ts +191 -0
  5. package/dist/cjs/index.js +857 -0
  6. package/dist/{json-rpc.d.ts → cjs/json-rpc.d.ts} +1 -1
  7. package/dist/cjs/json-rpc.js +48 -0
  8. package/dist/{logger.js → cjs/logger.js} +5 -38
  9. package/dist/cjs/mobile.js +131 -0
  10. package/dist/{websocket.js → cjs/websocket.js} +2 -2
  11. package/dist/esm/encryption.d.ts +7 -0
  12. package/dist/esm/encryption.js +40 -0
  13. package/dist/esm/index.d.ts +191 -0
  14. package/dist/esm/index.js +846 -0
  15. package/dist/esm/json-rpc.d.ts +6 -0
  16. package/dist/esm/json-rpc.js +41 -0
  17. package/dist/esm/logger.d.ts +7 -0
  18. package/dist/esm/logger.js +37 -0
  19. package/dist/esm/mobile.d.ts +39 -0
  20. package/dist/esm/mobile.js +126 -0
  21. package/dist/esm/websocket.d.ts +2 -0
  22. package/dist/esm/websocket.js +15 -0
  23. package/package.json +16 -8
  24. package/src/index.ts +974 -63
  25. package/src/json-rpc.ts +6 -2
  26. package/src/mobile.ts +14 -5
  27. package/tsconfig.json +14 -9
  28. package/dist/constants/index.d.ts +0 -13
  29. package/dist/constants/index.js +0 -52
  30. package/dist/encryption.js +0 -126
  31. package/dist/index.d.ts +0 -79
  32. package/dist/index.js +0 -384
  33. package/dist/json-rpc.js +0 -105
  34. package/dist/mobile.js +0 -253
  35. package/dist/types/countries.d.ts +0 -1
  36. package/dist/types/countries.js +0 -2
  37. package/dist/types/credentials.d.ts +0 -17
  38. package/dist/types/credentials.js +0 -2
  39. package/dist/types/index.d.ts +0 -4
  40. package/dist/types/index.js +0 -2
  41. package/dist/types/json-rpc.d.ts +0 -12
  42. package/dist/types/json-rpc.js +0 -2
  43. package/dist/types/query-result.d.ts +0 -46
  44. package/dist/types/query-result.js +0 -2
  45. package/src/circuits/proof_age.json +0 -1
  46. package/src/constants/index.ts +0 -54
  47. package/src/types/countries.ts +0 -278
  48. package/src/types/credentials.ts +0 -40
  49. package/src/types/index.ts +0 -13
  50. package/src/types/json-rpc.ts +0 -13
  51. package/src/types/query-result.ts +0 -49
  52. /package/dist/{logger.d.ts → cjs/logger.d.ts} +0 -0
  53. /package/dist/{mobile.d.ts → cjs/mobile.d.ts} +0 -0
  54. /package/dist/{websocket.d.ts → cjs/websocket.d.ts} +0 -0
package/src/index.ts CHANGED
@@ -1,21 +1,44 @@
1
1
  import { randomBytes } from 'crypto'
2
2
  import { Alpha3Code, getAlpha3Code, registerLocale } from 'i18n-iso-countries'
3
3
  import {
4
- DisclosableIDCredential,
5
- IDCredential,
6
- IDCredentialConfig,
7
- IDCredentialValue,
8
- NumericalIDCredential,
9
- } from './types/credentials'
10
- import { ProofResult } from './types/query-result'
11
- import { CountryName } from './types/countries'
12
- //import { UltraHonkBackend, ProofData, CompiledCircuit } from '@noir-lang/backend_barretenberg'
4
+ type DisclosableIDCredential,
5
+ type IDCredential,
6
+ type IDCredentialConfig,
7
+ type IDCredentialValue,
8
+ type NumericalIDCredential,
9
+ type ProofResult,
10
+ type QueryResult,
11
+ type CountryName,
12
+ type JsonRpcRequest,
13
+ getProofData,
14
+ getCommitmentFromDSCProof,
15
+ getCommitmentInFromIDDataProof,
16
+ getCommitmentOutFromIDDataProof,
17
+ getNullifierFromDisclosureProof,
18
+ getCommitmentInFromIntegrityProof,
19
+ getCommitmentOutFromIntegrityProof,
20
+ getCommitmentInFromDisclosureProof,
21
+ getMerkleRootFromDSCProof,
22
+ getCurrentDateFromIntegrityProof,
23
+ getMaxAgeFromProof,
24
+ getMinAgeFromProof,
25
+ getCurrentDateFromAgeProof,
26
+ getMinDateFromProof,
27
+ getMaxDateFromProof,
28
+ getCountryListFromExclusionProof,
29
+ getCountryListFromInclusionProof,
30
+ DisclosedData,
31
+ formatName,
32
+ getHostedPackagedCircuitByName,
33
+ } from '@zkpassport/utils'
13
34
  import { bytesToHex } from '@noble/ciphers/utils'
14
35
  import { getWebSocketClient, WebSocketClient } from './websocket'
15
36
  import { createEncryptedJsonRpcRequest } from './json-rpc'
16
37
  import { decrypt, generateECDHKeyPair, getSharedSecret } from './encryption'
17
- import { JsonRpcRequest } from './types/json-rpc'
18
38
  import logger from './logger'
39
+ import { ungzip } from 'node-gzip'
40
+ //import initNoirC from '@noir-lang/noirc_abi'
41
+ //import initACVM from '@noir-lang/acvm_js'
19
42
 
20
43
  registerLocale(require('i18n-iso-countries/langs/en.json'))
21
44
 
@@ -64,24 +87,178 @@ function generalCompare(
64
87
  }
65
88
  }
66
89
 
67
- export * from './constants'
68
- export * from './types'
90
+ export type * from '@zkpassport/utils'
91
+ export {
92
+ SANCTIONED_COUNTRIES,
93
+ EU_COUNTRIES,
94
+ EEA_COUNTRIES,
95
+ SCHENGEN_COUNTRIES,
96
+ ASEAN_COUNTRIES,
97
+ MERCOSUR_COUNTRIES,
98
+ } from '@zkpassport/utils'
69
99
 
70
- export class ZkPassport {
100
+ export type QueryBuilderResult = {
101
+ /**
102
+ * The URL of the request.
103
+ *
104
+ * You can either encode the URL in a QR code or let the user click the link
105
+ * to this URL on your website if they're visiting your website on their phone.
106
+ */
107
+ url: string
108
+ /**
109
+ * The id of the request.
110
+ */
111
+ requestId: string
112
+ /**
113
+ * Called when the user has scanned the QR code or clicked the link to the request.
114
+ *
115
+ * This means the user is currently viewing the request popup with your website information
116
+ * and the information requested from them.
117
+ */
118
+ onRequestReceived: (callback: () => void) => void
119
+ /**
120
+ * Called when the user has accepted the request and
121
+ * started to generate the proof on their phone.
122
+ */
123
+ onGeneratingProof: (callback: () => void) => void
124
+ /**
125
+ * Called when the SDK successfully connects to the bridge with the mobile app.
126
+ */
127
+ onBridgeConnect: (callback: () => void) => void
128
+ /**
129
+ * Called when the user has generated a proof.
130
+ *
131
+ * There is a minimum of 4 proofs, but there can be more depending
132
+ * on the type of information requested from the user.
133
+ */
134
+ onProofGenerated: (callback: (proof: ProofResult) => void) => void
135
+ /**
136
+ * Called when the user has sent the query result.
137
+ *
138
+ * The response contains the unique identifier associated to the user,
139
+ * your domain name and chosen scope, along with the query result and whether
140
+ * the proofs were successfully verified.
141
+ */
142
+ onResult: (
143
+ callback: (response: {
144
+ uniqueIdentifier: string | undefined
145
+ verified: boolean
146
+ result: QueryResult
147
+ }) => void,
148
+ ) => void
149
+ /**
150
+ * Called when the user has rejected the request.
151
+ */
152
+ onReject: (callback: () => void) => void
153
+ /**
154
+ * Called when an error occurs, such as one of the requirements not being met
155
+ * or a proof failing to be generated.
156
+ */
157
+ onError: (callback: (error: string) => void) => void
158
+ /**
159
+ * @returns true if the bridge with the mobile app is connected
160
+ */
161
+ isBridgeConnected: () => boolean
162
+ /**
163
+ * Get if the user has scanned the QR code or the link to this request
164
+ * @returns true if the request has been received by the user on their phone
165
+ */
166
+ requestReceived: () => boolean
167
+ }
168
+
169
+ export type QueryBuilder = {
170
+ /**
171
+ * Requires this attribute to be equal to the provided value.
172
+ * @param key The attribute to compare.
173
+ * @param value The value of the attribute you require.
174
+ */
175
+ eq: <T extends IDCredential>(key: T, value: IDCredentialValue<T>) => QueryBuilder
176
+ /**
177
+ * Requires this attribute to be greater than or equal to the provided value.
178
+ * @param key The attribute to compare.
179
+ * @param value The value of the attribute you require.
180
+ */
181
+ gte: <T extends NumericalIDCredential>(key: T, value: IDCredentialValue<T>) => QueryBuilder
182
+ /**
183
+ * Requires this attribute to be less than or equal to the provided value.
184
+ * @param key The attribute to compare.
185
+ * @param value The value of the attribute you require.
186
+ */
187
+ lte: <T extends 'birthdate' | 'expiry_date'>(key: T, value: IDCredentialValue<T>) => QueryBuilder
188
+ /**
189
+ * Requires this attribute to be less than the provided value.
190
+ * @param key The attribute to compare.
191
+ * @param value The value of the attribute you require.
192
+ */
193
+ lt: <T extends 'age'>(key: T, value: IDCredentialValue<T>) => QueryBuilder
194
+ /**
195
+ * Requires this attribute to be included in the provided range.
196
+ * @param key The attribute to compare.
197
+ * @param start The start of the range.
198
+ * @param end The end of the range.
199
+ */
200
+ range: <T extends NumericalIDCredential>(
201
+ key: T,
202
+ start: IDCredentialValue<T>,
203
+ end: IDCredentialValue<T>,
204
+ ) => QueryBuilder
205
+ /**
206
+ * Requires this attribute to be included in the provided list.
207
+ * @param key The attribute to compare.
208
+ * @param value The list of values to check inclusion against.
209
+ */
210
+ in: <T extends 'nationality'>(key: T, value: IDCredentialValue<T>[]) => QueryBuilder
211
+ /**
212
+ * Requires this attribute to be excluded from the provided list.
213
+ * @param key The attribute to compare.
214
+ * @param value The list of values to check exclusion against.
215
+ */
216
+ out: <T extends 'nationality'>(key: T, value: IDCredentialValue<T>[]) => QueryBuilder
217
+ /**
218
+ * Requires this attribute to be disclosed.
219
+ * @param key The attribute to disclose.
220
+ */
221
+ disclose: (key: DisclosableIDCredential) => QueryBuilder
222
+ /**
223
+ * Builds the request.
224
+ *
225
+ * This will return the URL of the request, which you can either encode in a QR code
226
+ * or provide as a link to the user if they're visiting your website on their phone.
227
+ * It also returns all the callbacks you can use to handle the user's response.
228
+ */
229
+ done: () => QueryBuilderResult
230
+ }
231
+
232
+ export class ZKPassport {
71
233
  private domain: string
72
234
  private topicToConfig: Record<string, Record<string, IDCredentialConfig>> = {}
73
235
  private topicToKeyPair: Record<string, { privateKey: Uint8Array; publicKey: Uint8Array }> = {}
74
236
  private topicToWebSocketClient: Record<string, WebSocketClient> = {}
75
237
  private topicToSharedSecret: Record<string, Uint8Array> = {}
76
- private topicToQRCodeScanned: Record<string, boolean> = {}
238
+ private topicToRequestReceived: Record<string, boolean> = {}
239
+ private topicToService: Record<
240
+ string,
241
+ { name: string; logo: string; purpose: string; scope?: string }
242
+ > = {}
243
+ private topicToProofs: Record<string, Array<ProofResult>> = {}
77
244
 
78
- private onQRCodeScannedCallbacks: Record<string, Array<() => void>> = {}
245
+ private onRequestReceivedCallbacks: Record<string, Array<() => void>> = {}
79
246
  private onGeneratingProofCallbacks: Record<string, Array<(topic: string) => void>> = {}
80
247
  private onBridgeConnectCallbacks: Record<string, Array<() => void>> = {}
81
- private onProofGeneratedCallbacks: Record<string, Array<(result: ProofResult) => void>> = {}
248
+ private onProofGeneratedCallbacks: Record<string, Array<(proof: ProofResult) => void>> = {}
249
+ private onResultCallbacks: Record<
250
+ string,
251
+ Array<
252
+ (response: {
253
+ uniqueIdentifier: string | undefined
254
+ verified: boolean
255
+ result: QueryResult
256
+ }) => void
257
+ >
258
+ > = {}
82
259
  private onRejectCallbacks: Record<string, Array<() => void>> = {}
83
260
  private onErrorCallbacks: Record<string, Array<(topic: string) => void>> = {}
84
- private topicToService: Record<string, { name: string; logo: string; purpose: string }> = {}
261
+ //private wasmVerifierInit: boolean = false
85
262
 
86
263
  constructor(_domain?: string) {
87
264
  if (!_domain && typeof window === 'undefined') {
@@ -90,12 +267,23 @@ export class ZkPassport {
90
267
  this.domain = _domain || window.location.hostname
91
268
  }
92
269
 
270
+ /*private async initWasmVerifier() {
271
+ const acvm = await import('@noir-lang/acvm_js/web/acvm_js_bg.wasm')
272
+ const noirc = await import('@noir-lang/noirc_abi/web/noirc_abi_wasm_bg.wasm')
273
+ await Promise.all([initACVM(acvm), initNoirC(noirc)])
274
+ this.wasmVerifierInit = true
275
+ }*/
276
+
93
277
  /**
94
278
  * @notice Handle an encrypted message.
95
279
  * @param request The request.
96
280
  * @param outerRequest The outer request.
97
281
  */
98
- private async handleEncryptedMessage(topic: string, request: JsonRpcRequest, outerRequest: JsonRpcRequest) {
282
+ private async handleEncryptedMessage(
283
+ topic: string,
284
+ request: JsonRpcRequest,
285
+ outerRequest: JsonRpcRequest,
286
+ ) {
99
287
  logger.debug('Received encrypted message:', request)
100
288
  if (request.method === 'accept') {
101
289
  logger.debug(`User accepted the request and is generating a proof`)
@@ -103,15 +291,50 @@ export class ZkPassport {
103
291
  } else if (request.method === 'reject') {
104
292
  logger.debug(`User rejected the request`)
105
293
  await Promise.all(this.onRejectCallbacks[topic].map((callback) => callback()))
106
- } else if (request.method === 'done') {
294
+ } else if (request.method === 'proof') {
107
295
  logger.debug(`User generated proof`)
108
- await Promise.all(this.onProofGeneratedCallbacks[topic].map((callback) => callback(request.params.result)))
296
+ // Uncompress the proof and convert it to a hex string
297
+ const bytesProof = Buffer.from(request.params.proof, 'base64')
298
+ const uncompressedProof = await ungzip(bytesProof)
299
+ // The gzip lib in the app compress the proof as ASCII
300
+ // and since the app passes the proof as a hex string, we can
301
+ // just decode the bytes as hex characters using the TextDecoder
302
+ const hexProof = new TextDecoder().decode(uncompressedProof)
303
+ const processedProof: ProofResult = {
304
+ proof: hexProof,
305
+ vkeyHash: request.params.vkeyHash,
306
+ name: request.params.name,
307
+ version: request.params.version,
308
+ }
309
+ this.topicToProofs[topic].push(processedProof)
310
+ await Promise.all(
311
+ this.onProofGeneratedCallbacks[topic].map((callback) => callback(processedProof)),
312
+ )
313
+ } else if (request.method === 'done') {
314
+ logger.debug(`User sent the query result`)
315
+ // Verify the proofs and extract the unique identifier (aka nullifier) and the verification result
316
+ const { uniqueIdentifier, verified } = await this.verify(
317
+ topic,
318
+ this.topicToProofs[topic],
319
+ request.params,
320
+ )
321
+ await Promise.all(
322
+ this.onResultCallbacks[topic].map((callback) =>
323
+ callback({
324
+ uniqueIdentifier,
325
+ verified,
326
+ result: request.params,
327
+ }),
328
+ ),
329
+ )
109
330
  } else if (request.method === 'error') {
110
- await Promise.all(this.onErrorCallbacks[topic].map((callback) => callback(request.params.error)))
331
+ await Promise.all(
332
+ this.onErrorCallbacks[topic].map((callback) => callback(request.params.error)),
333
+ )
111
334
  }
112
335
  }
113
336
 
114
- private getZkPassportRequest(topic: string) {
337
+ private getZkPassportRequest(topic: string): QueryBuilder {
115
338
  return {
116
339
  eq: <T extends IDCredential>(key: T, value: IDCredentialValue<T>) => {
117
340
  if (key === 'issuing_country' || key === 'nationality') {
@@ -124,33 +347,33 @@ export class ZkPassport {
124
347
  numericalCompare('gte', key, value, topic, this.topicToConfig)
125
348
  return this.getZkPassportRequest(topic)
126
349
  },
127
- gt: <T extends NumericalIDCredential>(key: T, value: IDCredentialValue<T>) => {
350
+ /*gt: <T extends NumericalIDCredential>(key: T, value: IDCredentialValue<T>) => {
128
351
  numericalCompare('gt', key, value, topic, this.topicToConfig)
129
352
  return this.getZkPassportRequest(topic)
130
- },
131
- lte: <T extends NumericalIDCredential>(key: T, value: IDCredentialValue<T>) => {
353
+ },*/
354
+ lte: <T extends 'birthdate' | 'expiry_date'>(key: T, value: IDCredentialValue<T>) => {
132
355
  numericalCompare('lte', key, value, topic, this.topicToConfig)
133
356
  return this.getZkPassportRequest(topic)
134
357
  },
135
- lt: <T extends NumericalIDCredential>(key: T, value: IDCredentialValue<T>) => {
358
+ lt: <T extends 'age'>(key: T, value: IDCredentialValue<T>) => {
136
359
  numericalCompare('lt', key, value, topic, this.topicToConfig)
137
360
  return this.getZkPassportRequest(topic)
138
361
  },
139
- range: <T extends NumericalIDCredential>(key: T, start: IDCredentialValue<T>, end: IDCredentialValue<T>) => {
362
+ range: <T extends NumericalIDCredential>(
363
+ key: T,
364
+ start: IDCredentialValue<T>,
365
+ end: IDCredentialValue<T>,
366
+ ) => {
140
367
  rangeCompare(key, [start, end], topic, this.topicToConfig)
141
368
  return this.getZkPassportRequest(topic)
142
369
  },
143
- in: <T extends IDCredential>(key: T, value: IDCredentialValue<T>[]) => {
144
- if (key === 'issuing_country' || key === 'nationality') {
145
- value = value.map((v) => normalizeCountry(v as CountryName)) as IDCredentialValue<T>[]
146
- }
370
+ in: <T extends 'nationality'>(key: T, value: IDCredentialValue<T>[]) => {
371
+ value = value.map((v) => normalizeCountry(v as CountryName)) as IDCredentialValue<T>[]
147
372
  generalCompare('in', key, value, topic, this.topicToConfig)
148
373
  return this.getZkPassportRequest(topic)
149
374
  },
150
- out: <T extends IDCredential>(key: T, value: IDCredentialValue<T>[]) => {
151
- if (key === 'issuing_country' || key === 'nationality') {
152
- value = value.map((v) => normalizeCountry(v as CountryName)) as IDCredentialValue<T>[]
153
- }
375
+ out: <T extends 'nationality'>(key: T, value: IDCredentialValue<T>[]) => {
376
+ value = value.map((v) => normalizeCountry(v as CountryName)) as IDCredentialValue<T>[]
154
377
  generalCompare('out', key, value, topic, this.topicToConfig)
155
378
  return this.getZkPassportRequest(topic)
156
379
  },
@@ -165,21 +388,36 @@ export class ZkPassport {
165
388
  return this.getZkPassportRequest(topic)
166
389
  },*/
167
390
  done: () => {
168
- const base64Config = Buffer.from(JSON.stringify(this.topicToConfig[topic])).toString('base64')
169
- const base64Service = Buffer.from(JSON.stringify(this.topicToService[topic])).toString('base64')
391
+ const base64Config = Buffer.from(JSON.stringify(this.topicToConfig[topic])).toString(
392
+ 'base64',
393
+ )
394
+ const base64Service = Buffer.from(JSON.stringify(this.topicToService[topic])).toString(
395
+ 'base64',
396
+ )
170
397
  const pubkey = bytesToHex(this.topicToKeyPair[topic].publicKey)
171
398
  return {
172
399
  url: `https://zkpassport.id/r?d=${this.domain}&t=${topic}&c=${base64Config}&s=${base64Service}&p=${pubkey}`,
173
400
  requestId: topic,
174
- onQRCodeScanned: (callback: () => void) => this.onQRCodeScannedCallbacks[topic].push(callback),
175
- onGeneratingProof: (callback: () => void) => this.onGeneratingProofCallbacks[topic].push(callback),
176
- onBridgeConnect: (callback: () => void) => this.onBridgeConnectCallbacks[topic].push(callback),
177
- onProofGenerated: (callback: (result: ProofResult) => void) =>
401
+ onRequestReceived: (callback: () => void) =>
402
+ this.onRequestReceivedCallbacks[topic].push(callback),
403
+ onGeneratingProof: (callback: () => void) =>
404
+ this.onGeneratingProofCallbacks[topic].push(callback),
405
+ onBridgeConnect: (callback: () => void) =>
406
+ this.onBridgeConnectCallbacks[topic].push(callback),
407
+ onProofGenerated: (callback: (proof: ProofResult) => void) =>
178
408
  this.onProofGeneratedCallbacks[topic].push(callback),
409
+ onResult: (
410
+ callback: (response: {
411
+ uniqueIdentifier: string | undefined
412
+ verified: boolean
413
+ result: QueryResult
414
+ }) => void,
415
+ ) => this.onResultCallbacks[topic].push(callback),
179
416
  onReject: (callback: () => void) => this.onRejectCallbacks[topic].push(callback),
180
- onError: (callback: (error: string) => void) => this.onErrorCallbacks[topic].push(callback),
417
+ onError: (callback: (error: string) => void) =>
418
+ this.onErrorCallbacks[topic].push(callback),
181
419
  isBridgeConnected: () => this.topicToWebSocketClient[topic].readyState === WebSocket.OPEN,
182
- isQRCodeScanned: () => this.topicToQRCodeScanned[topic] === true,
420
+ requestReceived: () => this.topicToRequestReceived[topic] === true,
183
421
  }
184
422
  },
185
423
  }
@@ -193,12 +431,14 @@ export class ZkPassport {
193
431
  name,
194
432
  logo,
195
433
  purpose,
434
+ scope,
196
435
  topicOverride,
197
436
  keyPairOverride,
198
437
  }: {
199
438
  name: string
200
439
  logo: string
201
440
  purpose: string
441
+ scope?: string
202
442
  topicOverride?: string
203
443
  keyPairOverride?: { privateKey: Uint8Array; publicKey: Uint8Array }
204
444
  }) {
@@ -211,12 +451,14 @@ export class ZkPassport {
211
451
  }
212
452
 
213
453
  this.topicToConfig[topic] = {}
214
- this.topicToService[topic] = { name, logo, purpose }
454
+ this.topicToService[topic] = { name, logo, purpose, scope }
455
+ this.topicToProofs[topic] = []
215
456
 
216
- this.onQRCodeScannedCallbacks[topic] = []
457
+ this.onRequestReceivedCallbacks[topic] = []
217
458
  this.onGeneratingProofCallbacks[topic] = []
218
459
  this.onBridgeConnectCallbacks[topic] = []
219
460
  this.onProofGeneratedCallbacks[topic] = []
461
+ this.onResultCallbacks[topic] = []
220
462
  this.onRejectCallbacks[topic] = []
221
463
  this.onErrorCallbacks[topic] = []
222
464
 
@@ -234,9 +476,15 @@ export class ZkPassport {
234
476
  if (data.method === 'handshake') {
235
477
  logger.debug('[frontend] Received handshake:', event.data)
236
478
 
237
- this.topicToQRCodeScanned[topic] = true
238
- this.topicToSharedSecret[topic] = await getSharedSecret(bytesToHex(keyPair.privateKey), data.params.pubkey)
239
- logger.debug('[frontend] Shared secret:', Buffer.from(this.topicToSharedSecret[topic]).toString('hex'))
479
+ this.topicToRequestReceived[topic] = true
480
+ this.topicToSharedSecret[topic] = await getSharedSecret(
481
+ bytesToHex(keyPair.privateKey),
482
+ data.params.pubkey,
483
+ )
484
+ logger.debug(
485
+ '[frontend] Shared secret:',
486
+ Buffer.from(this.topicToSharedSecret[topic]).toString('hex'),
487
+ )
240
488
 
241
489
  const encryptedMessage = await createEncryptedJsonRpcRequest(
242
490
  'hello',
@@ -247,7 +495,7 @@ export class ZkPassport {
247
495
  logger.debug('[frontend] Sending encrypted message:', encryptedMessage)
248
496
  wsClient.send(JSON.stringify(encryptedMessage))
249
497
 
250
- await Promise.all(this.onQRCodeScannedCallbacks[topic].map((callback) => callback()))
498
+ await Promise.all(this.onRequestReceivedCallbacks[topic].map((callback) => callback()))
251
499
  return
252
500
  }
253
501
 
@@ -279,20 +527,678 @@ export class ZkPassport {
279
527
  return this.getZkPassportRequest(topic)
280
528
  }
281
529
 
530
+ private async checkPublicInputs(proofs: Array<ProofResult>, queryResult: QueryResult) {
531
+ let commitmentIn: bigint | undefined
532
+ let commitmentOut: bigint | undefined
533
+ let isCorrect = true
534
+ let uniqueIdentifier: string | undefined
535
+ const expectedMerkleRoot = BigInt(
536
+ '21301853597069384763054217328384418971999152625381818922211526730996340553696',
537
+ )
538
+ const defaultDateValue = new Date(1111, 10, 11)
539
+ const currentTime = new Date()
540
+ const today = new Date(
541
+ currentTime.getFullYear(),
542
+ currentTime.getMonth(),
543
+ currentTime.getDate(),
544
+ 0,
545
+ 0,
546
+ 0,
547
+ 0,
548
+ )
549
+
550
+ // Since the order is important for the commitments, we need to sort the proofs
551
+ // by their expected order: root signature check -> ID signature check -> integrity check -> disclosure
552
+ const sortedProofs = proofs.sort((a, b) => {
553
+ const proofOrder = [
554
+ 'sig_check_dsc',
555
+ 'sig_check_id_data',
556
+ 'data_check_integrity',
557
+ 'disclose_bytes',
558
+ 'compare_age',
559
+ 'compare_birthdate',
560
+ 'compare_expiry',
561
+ 'exclusion_check_country',
562
+ 'inclusion_check_country',
563
+ ]
564
+ const getIndex = (proof: ProofResult) => {
565
+ const name = proof.name || ''
566
+ return proofOrder.findIndex((p) => name.startsWith(p))
567
+ }
568
+ return getIndex(a) - getIndex(b)
569
+ })
570
+
571
+ for (const proof of sortedProofs!) {
572
+ const proofData = getProofData(proof.proof as string, true)
573
+ if (proof.name?.startsWith('sig_check_dsc')) {
574
+ commitmentOut = getCommitmentFromDSCProof(proofData)
575
+ const merkleRoot = getMerkleRootFromDSCProof(proofData)
576
+ if (merkleRoot !== expectedMerkleRoot) {
577
+ console.warn('The ID was signed by an unrecognized root certificate')
578
+ isCorrect = false
579
+ break
580
+ }
581
+ } else if (proof.name?.startsWith('sig_check_id_data')) {
582
+ commitmentIn = getCommitmentInFromIDDataProof(proofData)
583
+ if (commitmentIn !== commitmentOut) {
584
+ console.warn(
585
+ 'Failed to check the link between the certificate signature and ID signature',
586
+ )
587
+ isCorrect = false
588
+ break
589
+ }
590
+ commitmentOut = getCommitmentOutFromIDDataProof(proofData)
591
+ } else if (proof.name?.startsWith('data_check_integrity')) {
592
+ commitmentIn = getCommitmentInFromIntegrityProof(proofData)
593
+ if (commitmentIn !== commitmentOut) {
594
+ console.warn('Failed to check the link between the ID signature and the data signed')
595
+ isCorrect = false
596
+ break
597
+ }
598
+ commitmentOut = getCommitmentOutFromIntegrityProof(proofData)
599
+ const currentDate = getCurrentDateFromIntegrityProof(proofData)
600
+ // The date should be today or yesterday
601
+ // (if the proof request was requested just before midnight and is finalized after)
602
+ if (
603
+ currentDate.getTime() !== today.getTime() &&
604
+ currentDate.getTime() !== today.getTime() - 86400000
605
+ ) {
606
+ console.warn('Current date used to check the validity of the ID is too old')
607
+ isCorrect = false
608
+ break
609
+ }
610
+ } else if (proof.name === 'disclose_bytes') {
611
+ commitmentIn = getCommitmentInFromDisclosureProof(proofData)
612
+ if (commitmentIn !== commitmentOut) {
613
+ console.warn(
614
+ 'Failed to check the link between the validity of the ID and the data to disclose',
615
+ )
616
+ isCorrect = false
617
+ break
618
+ }
619
+ // We can't be certain that the disclosed data is for a passport or an ID card
620
+ // so we need to check both (unless the document type is revealed)
621
+ const disclosedDataPassport = DisclosedData.fromBytesProof(proofData, 'passport')
622
+ const disclosedDataIDCard = DisclosedData.fromBytesProof(proofData, 'id_card')
623
+ if (queryResult.document_type) {
624
+ // Document type is always at the same index in the disclosed data
625
+ if (
626
+ queryResult.document_type.eq &&
627
+ queryResult.document_type.eq.result &&
628
+ queryResult.document_type.eq.expected !== disclosedDataPassport.documentType
629
+ ) {
630
+ console.warn('Document type does not match the expected document type')
631
+ isCorrect = false
632
+ break
633
+ }
634
+ if (queryResult.document_type.disclose?.result !== disclosedDataIDCard.documentType) {
635
+ console.warn('Document type does not match the disclosed document type in query result')
636
+ isCorrect = false
637
+ break
638
+ }
639
+ }
640
+ if (queryResult.birthdate) {
641
+ const birthdatePassport = disclosedDataPassport.dateOfBirth
642
+ const birthdateIDCard = disclosedDataIDCard.dateOfBirth
643
+ if (
644
+ queryResult.birthdate.eq &&
645
+ queryResult.birthdate.eq.result &&
646
+ queryResult.birthdate.eq.expected.getTime() !== birthdatePassport.getTime() &&
647
+ queryResult.birthdate.eq.expected.getTime() !== birthdateIDCard.getTime()
648
+ ) {
649
+ console.warn('Birthdate does not match the expected birthdate')
650
+ isCorrect = false
651
+ break
652
+ }
653
+ if (
654
+ queryResult.birthdate.disclose &&
655
+ queryResult.birthdate.disclose.result.getTime() !== birthdatePassport.getTime() &&
656
+ queryResult.birthdate.disclose.result.getTime() !== birthdateIDCard.getTime()
657
+ ) {
658
+ console.warn('Birthdate does not match the disclosed birthdate in query result')
659
+ isCorrect = false
660
+ break
661
+ }
662
+ }
663
+ if (queryResult.expiry_date) {
664
+ const expiryDatePassport = disclosedDataPassport.dateOfExpiry
665
+ const expiryDateIDCard = disclosedDataIDCard.dateOfExpiry
666
+ if (
667
+ queryResult.expiry_date.eq &&
668
+ queryResult.expiry_date.eq.result &&
669
+ queryResult.expiry_date.eq.expected.getTime() !== expiryDatePassport.getTime() &&
670
+ queryResult.expiry_date.eq.expected.getTime() !== expiryDateIDCard.getTime()
671
+ ) {
672
+ console.warn('Expiry date does not match the expected expiry date')
673
+ isCorrect = false
674
+ break
675
+ }
676
+ if (
677
+ queryResult.expiry_date.disclose &&
678
+ queryResult.expiry_date.disclose.result.getTime() !== expiryDatePassport.getTime() &&
679
+ queryResult.expiry_date.disclose.result.getTime() !== expiryDateIDCard.getTime()
680
+ ) {
681
+ console.warn('Expiry date does not match the disclosed expiry date in query result')
682
+ isCorrect = false
683
+ break
684
+ }
685
+ }
686
+ if (queryResult.nationality) {
687
+ const nationalityPassport = disclosedDataPassport.nationality
688
+ const nationalityIDCard = disclosedDataIDCard.nationality
689
+ if (
690
+ queryResult.nationality.eq &&
691
+ queryResult.nationality.eq.result &&
692
+ queryResult.nationality.eq.expected !== nationalityPassport &&
693
+ queryResult.nationality.eq.expected !== nationalityIDCard
694
+ ) {
695
+ console.warn('Nationality does not match the expected nationality')
696
+ isCorrect = false
697
+ break
698
+ }
699
+ if (
700
+ queryResult.nationality.disclose &&
701
+ queryResult.nationality.disclose.result !== nationalityPassport &&
702
+ queryResult.nationality.disclose.result !== nationalityIDCard
703
+ ) {
704
+ console.warn('Nationality does not match the disclosed nationality in query result')
705
+ isCorrect = false
706
+ break
707
+ }
708
+ }
709
+ if (queryResult.document_number) {
710
+ const documentNumberPassport = disclosedDataPassport.documentNumber
711
+ const documentNumberIDCard = disclosedDataIDCard.documentNumber
712
+ if (
713
+ queryResult.document_number.eq &&
714
+ queryResult.document_number.eq.result &&
715
+ queryResult.document_number.eq.expected !== documentNumberPassport &&
716
+ queryResult.document_number.eq.expected !== documentNumberIDCard
717
+ ) {
718
+ console.warn('Document number does not match the expected document number')
719
+ isCorrect = false
720
+ break
721
+ }
722
+ if (
723
+ queryResult.document_number.disclose &&
724
+ queryResult.document_number.disclose.result !== documentNumberPassport &&
725
+ queryResult.document_number.disclose.result !== documentNumberIDCard
726
+ ) {
727
+ console.warn(
728
+ 'Document number does not match the disclosed document number in query result',
729
+ )
730
+ isCorrect = false
731
+ break
732
+ }
733
+ }
734
+ if (queryResult.gender) {
735
+ const genderPassport = disclosedDataPassport.gender
736
+ const genderIDCard = disclosedDataIDCard.gender
737
+ if (
738
+ queryResult.gender.eq &&
739
+ queryResult.gender.eq.result &&
740
+ queryResult.gender.eq.expected !== genderPassport &&
741
+ queryResult.gender.eq.expected !== genderIDCard
742
+ ) {
743
+ console.warn('Gender does not match the expected gender')
744
+ isCorrect = false
745
+ break
746
+ }
747
+ if (
748
+ queryResult.gender.disclose &&
749
+ queryResult.gender.disclose.result !== genderPassport &&
750
+ queryResult.gender.disclose.result !== genderIDCard
751
+ ) {
752
+ console.warn('Gender does not match the disclosed gender in query result')
753
+ isCorrect = false
754
+ break
755
+ }
756
+ }
757
+ if (queryResult.issuing_country) {
758
+ const issuingCountryPassport = disclosedDataPassport.issuingCountry
759
+ const issuingCountryIDCard = disclosedDataIDCard.issuingCountry
760
+ if (
761
+ queryResult.issuing_country.eq &&
762
+ queryResult.issuing_country.eq.result &&
763
+ queryResult.issuing_country.eq.expected !== issuingCountryPassport &&
764
+ queryResult.issuing_country.eq.expected !== issuingCountryIDCard
765
+ ) {
766
+ console.warn('Issuing country does not match the expected issuing country')
767
+ isCorrect = false
768
+ break
769
+ }
770
+ if (
771
+ queryResult.issuing_country.disclose &&
772
+ queryResult.issuing_country.disclose.result !== issuingCountryPassport &&
773
+ queryResult.issuing_country.disclose.result !== issuingCountryIDCard
774
+ ) {
775
+ console.warn(
776
+ 'Issuing country does not match the disclosed issuing country in query result',
777
+ )
778
+ isCorrect = false
779
+ break
780
+ }
781
+ }
782
+ if (queryResult.fullname) {
783
+ const fullnamePassport = disclosedDataPassport.name
784
+ const fullnameIDCard = disclosedDataIDCard.name
785
+ if (
786
+ queryResult.fullname.eq &&
787
+ queryResult.fullname.eq.result &&
788
+ formatName(queryResult.fullname.eq.expected).toLowerCase() !==
789
+ fullnamePassport.toLowerCase() &&
790
+ formatName(queryResult.fullname.eq.expected).toLowerCase() !==
791
+ fullnameIDCard.toLowerCase()
792
+ ) {
793
+ console.warn('Fullname does not match the expected fullname')
794
+ isCorrect = false
795
+ break
796
+ }
797
+ if (
798
+ queryResult.fullname.disclose &&
799
+ formatName(queryResult.fullname.disclose.result).toLowerCase() !==
800
+ fullnamePassport.toLowerCase() &&
801
+ formatName(queryResult.fullname.disclose.result).toLowerCase() !==
802
+ fullnameIDCard.toLowerCase()
803
+ ) {
804
+ console.warn('Fullname does not match the disclosed fullname in query result')
805
+ isCorrect = false
806
+ break
807
+ }
808
+ }
809
+ if (queryResult.firstname) {
810
+ // If fullname was not revealed, then the name could be either the first name or last name
811
+ const firstnamePassport =
812
+ disclosedDataPassport.firstName && disclosedDataPassport.firstName.length > 0
813
+ ? disclosedDataPassport.firstName
814
+ : disclosedDataPassport.name
815
+ const firstnameIDCard =
816
+ disclosedDataIDCard.firstName && disclosedDataIDCard.firstName.length > 0
817
+ ? disclosedDataIDCard.firstName
818
+ : disclosedDataIDCard.name
819
+ if (
820
+ queryResult.firstname.eq &&
821
+ queryResult.firstname.eq.result &&
822
+ formatName(queryResult.firstname.eq.expected).toLowerCase() !==
823
+ firstnamePassport.toLowerCase() &&
824
+ formatName(queryResult.firstname.eq.expected).toLowerCase() !==
825
+ firstnameIDCard.toLowerCase()
826
+ ) {
827
+ console.warn('Firstname does not match the expected firstname')
828
+ isCorrect = false
829
+ break
830
+ }
831
+ if (
832
+ queryResult.firstname.disclose &&
833
+ formatName(queryResult.firstname.disclose.result).toLowerCase() !==
834
+ firstnamePassport.toLowerCase() &&
835
+ formatName(queryResult.firstname.disclose.result).toLowerCase() !==
836
+ firstnameIDCard.toLowerCase()
837
+ ) {
838
+ console.warn('Firstname does not match the disclosed firstname in query result')
839
+ isCorrect = false
840
+ break
841
+ }
842
+ }
843
+ if (queryResult.lastname) {
844
+ // If fullname was not revealed, then the name could be either the first name or last name
845
+ const lastnamePassport =
846
+ disclosedDataPassport.lastName && disclosedDataPassport.lastName.length > 0
847
+ ? disclosedDataPassport.lastName
848
+ : disclosedDataPassport.name
849
+ const lastnameIDCard =
850
+ disclosedDataIDCard.lastName && disclosedDataIDCard.lastName.length > 0
851
+ ? disclosedDataIDCard.lastName
852
+ : disclosedDataIDCard.name
853
+ if (
854
+ queryResult.lastname.eq &&
855
+ queryResult.lastname.eq.result &&
856
+ formatName(queryResult.lastname.eq.expected).toLowerCase() !==
857
+ lastnamePassport.toLowerCase() &&
858
+ formatName(queryResult.lastname.eq.expected).toLowerCase() !==
859
+ lastnameIDCard.toLowerCase()
860
+ ) {
861
+ console.warn('Lastname does not match the expected lastname')
862
+ isCorrect = false
863
+ break
864
+ }
865
+ if (
866
+ queryResult.lastname.disclose &&
867
+ formatName(queryResult.lastname.disclose.result).toLowerCase() !==
868
+ lastnamePassport.toLowerCase() &&
869
+ formatName(queryResult.lastname.disclose.result).toLowerCase() !==
870
+ lastnameIDCard.toLowerCase()
871
+ ) {
872
+ console.warn('Lastname does not match the disclosed lastname in query result')
873
+ isCorrect = false
874
+ break
875
+ }
876
+ }
877
+ uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10)
878
+ } else if (proof.name === 'compare_age') {
879
+ commitmentIn = getCommitmentInFromDisclosureProof(proofData)
880
+ if (commitmentIn !== commitmentOut) {
881
+ console.warn(
882
+ 'Failed to check the link between the validity of the ID and the age derived from it',
883
+ )
884
+ isCorrect = false
885
+ break
886
+ }
887
+ const minAge = getMinAgeFromProof(proofData)
888
+ const maxAge = getMaxAgeFromProof(proofData)
889
+ if (queryResult.age) {
890
+ if (
891
+ queryResult.age.gte &&
892
+ queryResult.age.gte.result &&
893
+ minAge < (queryResult.age.gte.expected as number)
894
+ ) {
895
+ console.warn('Age is not greater than or equal to the expected age')
896
+ isCorrect = false
897
+ break
898
+ }
899
+ if (
900
+ queryResult.age.lt &&
901
+ queryResult.age.lt.result &&
902
+ maxAge >= (queryResult.age.lt.expected as number)
903
+ ) {
904
+ console.warn('Age is not less than the expected age')
905
+ isCorrect = false
906
+ break
907
+ }
908
+ if (queryResult.age.range) {
909
+ if (
910
+ queryResult.age.range.result &&
911
+ (minAge < (queryResult.age.range.expected[0] as number) ||
912
+ maxAge >= (queryResult.age.range.expected[1] as number))
913
+ ) {
914
+ console.warn('Age is not in the expected range')
915
+ isCorrect = false
916
+ break
917
+ }
918
+ }
919
+ if (!queryResult.age.lt && !queryResult.age.range && maxAge != 0) {
920
+ console.warn('Maximum age should be equal to 0')
921
+ isCorrect = false
922
+ break
923
+ }
924
+ if (!queryResult.age.gte && !queryResult.age.range && minAge != 0) {
925
+ console.warn('Minimum age should be equal to 0')
926
+ isCorrect = false
927
+ break
928
+ }
929
+ if (
930
+ queryResult.age.disclose &&
931
+ (queryResult.age.disclose.result !== minAge ||
932
+ queryResult.age.disclose.result !== maxAge)
933
+ ) {
934
+ console.warn('Age does not match the disclosed age in query result')
935
+ isCorrect = false
936
+ break
937
+ }
938
+ } else {
939
+ console.warn('Age is not set in the query result')
940
+ isCorrect = false
941
+ break
942
+ }
943
+ const currentDate = getCurrentDateFromAgeProof(proofData)
944
+ if (
945
+ currentDate.getTime() !== today.getTime() &&
946
+ currentDate.getTime() !== today.getTime() - 86400000
947
+ ) {
948
+ console.warn('Current date in the proof is too old')
949
+ isCorrect = false
950
+ break
951
+ }
952
+ uniqueIdentifier = getCommitmentInFromDisclosureProof(proofData).toString(10)
953
+ } else if (proof.name === 'compare_birthdate') {
954
+ commitmentIn = getCommitmentInFromDisclosureProof(proofData)
955
+ if (commitmentIn !== commitmentOut) {
956
+ console.warn(
957
+ 'Failed to check the link between the validity of the ID and the birthdate derived from it',
958
+ )
959
+ isCorrect = false
960
+ break
961
+ }
962
+ const minDate = getMinDateFromProof(proofData)
963
+ const maxDate = getMaxDateFromProof(proofData)
964
+ if (queryResult.birthdate) {
965
+ if (
966
+ queryResult.birthdate.gte &&
967
+ queryResult.birthdate.gte.result &&
968
+ minDate < queryResult.birthdate.gte.expected
969
+ ) {
970
+ console.warn('Birthdate is not greater than or equal to the expected birthdate')
971
+ isCorrect = false
972
+ break
973
+ }
974
+ if (
975
+ queryResult.birthdate.lte &&
976
+ queryResult.birthdate.lte.result &&
977
+ maxDate > queryResult.birthdate.lte.expected
978
+ ) {
979
+ console.warn('Birthdate is not less than the expected birthdate')
980
+ isCorrect = false
981
+ break
982
+ }
983
+ if (queryResult.birthdate.range) {
984
+ if (
985
+ queryResult.birthdate.range.result &&
986
+ (minDate < queryResult.birthdate.range.expected[0] ||
987
+ maxDate > queryResult.birthdate.range.expected[1])
988
+ ) {
989
+ console.warn('Birthdate is not in the expected range')
990
+ isCorrect = false
991
+ break
992
+ }
993
+ }
994
+ if (
995
+ !queryResult.birthdate.lte &&
996
+ !queryResult.birthdate.range &&
997
+ maxDate.getTime() != defaultDateValue.getTime()
998
+ ) {
999
+ console.warn('Maximum birthdate should be equal to default date value')
1000
+ isCorrect = false
1001
+ break
1002
+ }
1003
+ if (
1004
+ !queryResult.birthdate.gte &&
1005
+ !queryResult.birthdate.range &&
1006
+ minDate.getTime() != defaultDateValue.getTime()
1007
+ ) {
1008
+ console.warn('Minimum birthdate should be equal to default date value')
1009
+ isCorrect = false
1010
+ break
1011
+ }
1012
+ } else {
1013
+ console.warn('Birthdate is not set in the query result')
1014
+ isCorrect = false
1015
+ break
1016
+ }
1017
+ uniqueIdentifier = getCommitmentInFromDisclosureProof(proofData).toString(10)
1018
+ } else if (proof.name === 'compare_expiry') {
1019
+ commitmentIn = getCommitmentInFromDisclosureProof(proofData)
1020
+ if (commitmentIn !== commitmentOut) {
1021
+ console.warn(
1022
+ 'Failed to check the link between the validity of the ID and its expiry date',
1023
+ )
1024
+ isCorrect = false
1025
+ break
1026
+ }
1027
+ const minDate = getMinDateFromProof(proofData)
1028
+ const maxDate = getMaxDateFromProof(proofData)
1029
+ if (queryResult.expiry_date) {
1030
+ if (
1031
+ queryResult.expiry_date.gte &&
1032
+ queryResult.expiry_date.gte.result &&
1033
+ minDate < queryResult.expiry_date.gte.expected
1034
+ ) {
1035
+ console.warn('Expiry date is not greater than or equal to the expected expiry date')
1036
+ isCorrect = false
1037
+ break
1038
+ }
1039
+ if (
1040
+ queryResult.expiry_date.lte &&
1041
+ queryResult.expiry_date.lte.result &&
1042
+ maxDate > queryResult.expiry_date.lte.expected
1043
+ ) {
1044
+ console.warn('Expiry date is not less than the expected expiry date')
1045
+ isCorrect = false
1046
+ break
1047
+ }
1048
+ if (queryResult.expiry_date.range) {
1049
+ if (
1050
+ queryResult.expiry_date.range.result &&
1051
+ (minDate < queryResult.expiry_date.range.expected[0] ||
1052
+ maxDate > queryResult.expiry_date.range.expected[1])
1053
+ ) {
1054
+ console.warn('Expiry date is not in the expected range')
1055
+ isCorrect = false
1056
+ break
1057
+ }
1058
+ }
1059
+ if (
1060
+ !queryResult.expiry_date.lte &&
1061
+ !queryResult.expiry_date.range &&
1062
+ maxDate.getTime() != defaultDateValue.getTime()
1063
+ ) {
1064
+ console.warn('Maximum expiry date should be equal to default date value')
1065
+ isCorrect = false
1066
+ break
1067
+ }
1068
+ if (
1069
+ !queryResult.expiry_date.gte &&
1070
+ !queryResult.expiry_date.range &&
1071
+ minDate.getTime() != defaultDateValue.getTime()
1072
+ ) {
1073
+ console.warn('Minimum expiry date should be equal to default date value')
1074
+ isCorrect = false
1075
+ break
1076
+ }
1077
+ } else {
1078
+ console.warn('Expiry date is not set in the query result')
1079
+ isCorrect = false
1080
+ break
1081
+ }
1082
+ uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10)
1083
+ } else if (proof.name === 'exclusion_check_country') {
1084
+ commitmentIn = getCommitmentInFromDisclosureProof(proofData)
1085
+ if (commitmentIn !== commitmentOut) {
1086
+ console.warn(
1087
+ 'Failed to check the link between the validity of the ID and the country exclusion check',
1088
+ )
1089
+ isCorrect = false
1090
+ break
1091
+ }
1092
+ const countryList = getCountryListFromExclusionProof(proofData)
1093
+ if (
1094
+ queryResult.nationality &&
1095
+ queryResult.nationality.out &&
1096
+ queryResult.nationality.out.result
1097
+ ) {
1098
+ if (
1099
+ !queryResult.nationality.out.expected?.every((country) => countryList.includes(country))
1100
+ ) {
1101
+ console.warn('Country exclusion list does not match the one from the query results')
1102
+ isCorrect = false
1103
+ break
1104
+ }
1105
+ } else if (!queryResult.nationality || !queryResult.nationality.out) {
1106
+ console.warn('Nationality exclusion is not set in the query result')
1107
+ isCorrect = false
1108
+ break
1109
+ }
1110
+ uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10)
1111
+ } else if (proof.name === 'inclusion_check_country') {
1112
+ commitmentIn = getCommitmentInFromDisclosureProof(proofData)
1113
+ if (commitmentIn !== commitmentOut) {
1114
+ console.warn(
1115
+ 'Failed to check the link between the validity of the ID and the country inclusion check',
1116
+ )
1117
+ isCorrect = false
1118
+ break
1119
+ }
1120
+ const countryList = getCountryListFromInclusionProof(proofData)
1121
+ if (
1122
+ queryResult.nationality &&
1123
+ queryResult.nationality.in &&
1124
+ queryResult.nationality.in.result
1125
+ ) {
1126
+ if (
1127
+ !queryResult.nationality.in.expected?.every((country) => countryList.includes(country))
1128
+ ) {
1129
+ console.warn('Country inclusion list does not match the one from the query results')
1130
+ isCorrect = false
1131
+ break
1132
+ }
1133
+ } else if (!queryResult.nationality || !queryResult.nationality.in) {
1134
+ console.warn('Nationality inclusion is not set in the query result')
1135
+ isCorrect = false
1136
+ break
1137
+ }
1138
+ uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10)
1139
+ }
1140
+ }
1141
+ return { isCorrect, uniqueIdentifier }
1142
+ }
1143
+
282
1144
  /**
283
- * @notice Verifies a proof.
284
- * @param proof The proof to verify.
285
- * @returns True if the proof is valid, false otherwise.
1145
+ * @notice Verify the proofs received from the mobile app.
1146
+ * @param requestId The request ID.
1147
+ * @param proofs The proofs to verify.
1148
+ * @param queryResult The query result to verify against
1149
+ * @returns An object containing the unique identifier associated to the user
1150
+ * and a boolean indicating whether the proofs were successfully verified.
286
1151
  */
287
- /*public verify(result: ProofResult) {
288
- const backend = new UltraHonkBackend(proofOfAgeCircuit as CompiledCircuit)
289
- const proofData: ProofData = {
290
- proof: Buffer.from(result.proof as string, 'hex'),
291
- // TODO: extract the public inputs from the proof
292
- publicInputs: [],
1152
+ public async verify(
1153
+ requestId: string,
1154
+ proofs?: Array<ProofResult>,
1155
+ queryResult?: QueryResult,
1156
+ ): Promise<{ uniqueIdentifier: string | undefined; verified: boolean }> {
1157
+ let proofsToVerify = proofs
1158
+ if (!proofs) {
1159
+ proofsToVerify = this.topicToProofs[requestId]
1160
+ if (!proofsToVerify || proofsToVerify.length === 0) {
1161
+ throw new Error('No proofs to verify')
1162
+ }
293
1163
  }
294
- return backend.verifyProof(proofData)
295
- }*/
1164
+ const { BarretenbergVerifier } = await import('@aztec/bb.js')
1165
+ const verifier = new BarretenbergVerifier()
1166
+ /*if (!this.wasmVerifierInit) {
1167
+ await this.initWasmVerifier()
1168
+ }*/
1169
+ let verified = true
1170
+ let uniqueIdentifier: string | undefined
1171
+ if (queryResult) {
1172
+ const { isCorrect, uniqueIdentifier: uniqueIdentifierFromPublicInputs } =
1173
+ await this.checkPublicInputs(proofsToVerify!, queryResult!)
1174
+ uniqueIdentifier = uniqueIdentifierFromPublicInputs
1175
+ verified = isCorrect
1176
+ }
1177
+ // Only proceed with the proof verification if the public inputs are correct
1178
+ if (verified) {
1179
+ for (const proof of proofsToVerify!) {
1180
+ const proofData = getProofData(proof.proof as string, true)
1181
+ const hostedPackagedCircuit = await getHostedPackagedCircuitByName(
1182
+ proof.version as any,
1183
+ proof.name!,
1184
+ )
1185
+ const vkeyBytes = Buffer.from(hostedPackagedCircuit.vkey, 'base64')
1186
+ try {
1187
+ verified = await verifier.verifyUltraHonkProof(proofData, new Uint8Array(vkeyBytes))
1188
+ } catch (e) {
1189
+ console.warn('Error verifying proof', e)
1190
+ verified = false
1191
+ }
1192
+ if (!verified) {
1193
+ // Break the loop if the proof is not valid
1194
+ // and don't bother checking the other proofs
1195
+ break
1196
+ }
1197
+ }
1198
+ }
1199
+ this.topicToProofs[requestId] = []
1200
+ return { uniqueIdentifier, verified }
1201
+ }
296
1202
 
297
1203
  /**
298
1204
  * @notice Returns the URL of the request.
@@ -301,8 +1207,12 @@ export class ZkPassport {
301
1207
  */
302
1208
  public getUrl(requestId: string) {
303
1209
  const pubkey = bytesToHex(this.topicToKeyPair[requestId].publicKey)
304
- const base64Config = Buffer.from(JSON.stringify(this.topicToConfig[requestId])).toString('base64')
305
- const base64Service = Buffer.from(JSON.stringify(this.topicToService[requestId])).toString('base64')
1210
+ const base64Config = Buffer.from(JSON.stringify(this.topicToConfig[requestId])).toString(
1211
+ 'base64',
1212
+ )
1213
+ const base64Service = Buffer.from(JSON.stringify(this.topicToService[requestId])).toString(
1214
+ 'base64',
1215
+ )
306
1216
  return `https://zkpassport.id/r?d=${this.domain}&t=${requestId}&c=${base64Config}&s=${base64Service}&p=${pubkey}`
307
1217
  }
308
1218
 
@@ -316,7 +1226,8 @@ export class ZkPassport {
316
1226
  delete this.topicToKeyPair[requestId]
317
1227
  delete this.topicToConfig[requestId]
318
1228
  delete this.topicToSharedSecret[requestId]
319
- this.onQRCodeScannedCallbacks[requestId] = []
1229
+ delete this.topicToProofs[requestId]
1230
+ this.onRequestReceivedCallbacks[requestId] = []
320
1231
  this.onGeneratingProofCallbacks[requestId] = []
321
1232
  this.onBridgeConnectCallbacks[requestId] = []
322
1233
  this.onProofGeneratedCallbacks[requestId] = []