@zkpassport/sdk 0.5.6 → 0.5.7-beta.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 DELETED
@@ -1,3174 +0,0 @@
1
- import { Alpha3Code, getAlpha3Code, registerLocale } from "i18n-iso-countries"
2
- import {
3
- type DisclosableIDCredential,
4
- type IDCredential,
5
- type IDCredentialConfig,
6
- type IDCredentialValue,
7
- type NumericalIDCredential,
8
- type ProofResult,
9
- type QueryResult,
10
- type CountryName,
11
- type JsonRpcRequest,
12
- getProofData,
13
- getCommitmentFromDSCProof,
14
- getCommitmentInFromIDDataProof,
15
- getCommitmentOutFromIDDataProof,
16
- getNullifierFromDisclosureProof,
17
- getCommitmentInFromIntegrityProof,
18
- getCommitmentOutFromIntegrityProof,
19
- getCommitmentInFromDisclosureProof,
20
- getMerkleRootFromDSCProof,
21
- getCurrentDateFromIntegrityProof,
22
- DisclosedData,
23
- formatName,
24
- getHostedPackagedCircuitByName,
25
- Query,
26
- getNumberOfPublicInputs,
27
- getParameterCommitmentFromDisclosureProof,
28
- getCountryParameterCommitment,
29
- getDiscloseParameterCommitment,
30
- getDateParameterCommitment,
31
- getFormattedDate,
32
- getCertificateRegistryRootFromOuterProof,
33
- getParamCommitmentsFromOuterProof,
34
- AgeCommittedInputs,
35
- DiscloseCommittedInputs,
36
- getCurrentDateFromCommittedInputs,
37
- getMinAgeFromCommittedInputs,
38
- getMaxAgeFromCommittedInputs,
39
- getAgeParameterCommitment,
40
- DateCommittedInputs,
41
- CountryCommittedInputs,
42
- getMinDateFromCommittedInputs,
43
- getMaxDateFromCommittedInputs,
44
- getCurrentDateFromOuterProof,
45
- getNullifierFromOuterProof,
46
- DisclosureCircuitName,
47
- getAgeEVMParameterCommitment,
48
- getDateEVMParameterCommitment,
49
- getDiscloseEVMParameterCommitment,
50
- getCountryEVMParameterCommitment,
51
- rightPadArrayWithZeros,
52
- getCommittedInputCount,
53
- ProofMode,
54
- ProofType,
55
- getScopeHash,
56
- ProofData,
57
- getScopeFromOuterProof,
58
- getSubscopeFromOuterProof,
59
- getServiceScopeHash,
60
- BoundData,
61
- BindCommittedInputs,
62
- getBindEVMParameterCommitment,
63
- getBindParameterCommitment,
64
- formatBoundData,
65
- Service,
66
- CircuitManifest,
67
- getCircuitRegistryRootFromOuterProof,
68
- } from "@zkpassport/utils"
69
- import { bytesToHex } from "@noble/ciphers/utils"
70
- import { noLogger as logger } from "./logger"
71
- import i18en from "i18n-iso-countries/langs/en.json"
72
- import { Buffer } from "buffer/"
73
- import { sha256 } from "@noble/hashes/sha2"
74
- import { hexToBytes } from "@noble/hashes/utils"
75
- import ZKPassportVerifierAbi from "./assets/abi/ZKPassportVerifier.json"
76
- import { RegistryClient } from "@zkpassport/registry"
77
- import { Bridge, BridgeInterface } from "@obsidion/bridge"
78
-
79
- const VERSION = "0.5.5"
80
-
81
- const DEFAULT_DATE_VALUE = new Date(1111, 10, 11)
82
-
83
- // If Buffer is not defined, then we use the Buffer from the buffer package
84
- if (typeof globalThis.Buffer === "undefined") {
85
- globalThis.Buffer = Buffer as any
86
- if (typeof window !== "undefined") {
87
- window.Buffer = Buffer as any
88
- }
89
- }
90
-
91
- export type QueryResultError<T> = {
92
- expected?: T
93
- received?: T
94
- message: string
95
- }
96
-
97
- export type QueryResultErrors = {
98
- [key in
99
- | IDCredential
100
- | "sig_check_dsc"
101
- | "sig_check_id_data"
102
- | "data_check_integrity"
103
- | "outer"
104
- | "disclose"
105
- | "bind"]: {
106
- disclose?: QueryResultError<string | number | Date>
107
- gte?: QueryResultError<number | Date>
108
- lte?: QueryResultError<number | Date>
109
- lt?: QueryResultError<number | Date>
110
- range?: QueryResultError<[number | Date, number | Date]>
111
- in?: QueryResultError<string[]>
112
- out?: QueryResultError<string[]>
113
- eq?: QueryResultError<string | number | Date>
114
- commitment?: QueryResultError<string>
115
- date?: QueryResultError<string>
116
- certificate?: QueryResultError<string>
117
- scope?: QueryResultError<string>
118
- }
119
- }
120
-
121
- export type SolidityVerifierParameters = {
122
- vkeyHash: string
123
- proof: string
124
- publicInputs: string[]
125
- committedInputs: string
126
- committedInputCounts: number[]
127
- validityPeriodInDays: number
128
- domain: string
129
- scope: string
130
- devMode: boolean
131
- }
132
-
133
- export type EVMChain = "ethereum_sepolia" | "local_anvil"
134
-
135
- function getChainIdFromEVMChain(chain: EVMChain): number {
136
- if (chain === "ethereum_sepolia") {
137
- return 11155111
138
- } else if (chain === "local_anvil") {
139
- return 31337
140
- }
141
- throw new Error(`Unsupported chain: ${chain}`)
142
- }
143
-
144
- function getEVMChainFromChainId(chainId: number): EVMChain {
145
- if (chainId === 11155111) {
146
- return "ethereum_sepolia"
147
- } else if (chainId === 31337) {
148
- return "local_anvil"
149
- }
150
- throw new Error(`Unsupported chain ID: ${chainId}`)
151
- }
152
-
153
- registerLocale(i18en)
154
-
155
- function hasRequestedAccessToField(credentialsRequest: Query, field: IDCredential): boolean {
156
- const fieldValue = credentialsRequest[field as keyof Query]
157
- const isDefined = fieldValue !== undefined && fieldValue !== null
158
- if (!isDefined) {
159
- return false
160
- }
161
- for (const key in fieldValue) {
162
- if (
163
- fieldValue[key as keyof typeof fieldValue] !== undefined &&
164
- fieldValue[key as keyof typeof fieldValue] !== null
165
- ) {
166
- return true
167
- }
168
- }
169
- return false
170
- }
171
-
172
- function normalizeCountry(country: CountryName | Alpha3Code) {
173
- if (country === "Zero Knowledge Republic") {
174
- return "ZKR"
175
- }
176
- let normalizedCountry: Alpha3Code | "ZKR" | undefined
177
- const alpha3 = getAlpha3Code(country as CountryName, "en") as Alpha3Code | "ZKR" | undefined
178
- normalizedCountry = alpha3 || (country as Alpha3Code) || "ZKR"
179
- return normalizedCountry
180
- }
181
-
182
- function numericalCompare(
183
- fnName: "gte" | "gt" | "lte" | "lt",
184
- key: NumericalIDCredential,
185
- value: number | Date,
186
- requestId: string,
187
- requestIdToConfig: Record<string, Record<string, IDCredentialConfig>>,
188
- ) {
189
- requestIdToConfig[requestId][key] = {
190
- ...requestIdToConfig[requestId][key],
191
- [fnName]: value,
192
- }
193
- }
194
-
195
- function rangeCompare(
196
- key: NumericalIDCredential,
197
- value: [number | Date, number | Date],
198
- requestId: string,
199
- requestIdToConfig: Record<string, Record<string, IDCredentialConfig>>,
200
- ) {
201
- requestIdToConfig[requestId][key] = {
202
- ...requestIdToConfig[requestId][key],
203
- range: value,
204
- }
205
- }
206
-
207
- function generalCompare(
208
- fnName: "in" | "out" | "eq",
209
- key: IDCredential,
210
- value: any,
211
- requestId: string,
212
- requestIdToConfig: Record<string, Record<string, IDCredentialConfig>>,
213
- ) {
214
- requestIdToConfig[requestId][key] = {
215
- ...requestIdToConfig[requestId][key],
216
- [fnName]: value,
217
- }
218
- }
219
-
220
- export type * from "@zkpassport/utils"
221
- export {
222
- SANCTIONED_COUNTRIES,
223
- EU_COUNTRIES,
224
- EEA_COUNTRIES,
225
- SCHENGEN_COUNTRIES,
226
- ASEAN_COUNTRIES,
227
- MERCOSUR_COUNTRIES,
228
- } from "@zkpassport/utils"
229
-
230
- export type QueryBuilderResult = {
231
- /**
232
- * The URL of the request.
233
- *
234
- * You can either encode the URL in a QR code or let the user click the link
235
- * to this URL on your website if they're visiting your website on their phone.
236
- */
237
- url: string
238
- /**
239
- * The id of the request.
240
- */
241
- requestId: string
242
- /**
243
- * Called when the user has scanned the QR code or clicked the link to the request.
244
- *
245
- * This means the user is currently viewing the request popup with your website information
246
- * and the information requested from them.
247
- */
248
- onRequestReceived: (callback: () => void) => void
249
- /**
250
- * Called when the user has accepted the request and
251
- * started to generate the proof on their phone.
252
- */
253
- onGeneratingProof: (callback: () => void) => void
254
- /**
255
- * Called when the SDK successfully connects to the bridge with the mobile app.
256
- */
257
- onBridgeConnect: (callback: () => void) => void
258
- /**
259
- * Called when the user has generated a proof.
260
- *
261
- * There is a minimum of 4 proofs, but there can be more depending
262
- * on the type of information requested from the user.
263
- */
264
- onProofGenerated: (callback: (proof: ProofResult) => void) => void
265
- /**
266
- * Called when the user has sent the query result.
267
- *
268
- * The response contains the unique identifier associated to the user,
269
- * your domain name and chosen scope, along with the query result and whether
270
- * the proofs were successfully verified.
271
- */
272
- onResult: (
273
- callback: (response: {
274
- uniqueIdentifier: string | undefined
275
- verified: boolean
276
- result: QueryResult
277
- queryResultErrors?: QueryResultErrors
278
- }) => void,
279
- ) => void
280
- /**
281
- * Called when the user has rejected the request.
282
- */
283
- onReject: (callback: () => void) => void
284
- /**
285
- * Called when an error occurs, such as one of the requirements not being met
286
- * or a proof failing to be generated.
287
- */
288
- onError: (callback: (error: string) => void) => void
289
- /**
290
- * @returns true if the bridge with the mobile app is connected
291
- */
292
- isBridgeConnected: () => boolean
293
- /**
294
- * Get if the user has scanned the QR code or the link to this request
295
- * @returns true if the request has been received by the user on their phone
296
- */
297
- requestReceived: () => boolean
298
- }
299
-
300
- export type QueryBuilder = {
301
- /**
302
- * Requires this attribute to be equal to the provided value.
303
- * @param key The attribute to compare.
304
- * @param value The value of the attribute you require.
305
- */
306
- eq: <T extends IDCredential>(key: T, value: IDCredentialValue<T>) => QueryBuilder
307
- /**
308
- * Requires this attribute to be greater than or equal to the provided value.
309
- * @param key The attribute to compare.
310
- * @param value The value of the attribute you require.
311
- */
312
- gte: <T extends NumericalIDCredential>(key: T, value: IDCredentialValue<T>) => QueryBuilder
313
- /**
314
- * Requires this attribute to be less than or equal to the provided value.
315
- * @param key The attribute to compare.
316
- * @param value The value of the attribute you require.
317
- */
318
- lte: <T extends "birthdate" | "expiry_date">(key: T, value: IDCredentialValue<T>) => QueryBuilder
319
- /**
320
- * Requires this attribute to be less than the provided value.
321
- * @param key The attribute to compare.
322
- * @param value The value of the attribute you require.
323
- */
324
- lt: <T extends "age">(key: T, value: IDCredentialValue<T>) => QueryBuilder
325
- /**
326
- * Requires this attribute to be included in the provided range.
327
- * @param key The attribute to compare.
328
- * @param start The start of the range.
329
- * @param end The end of the range.
330
- */
331
- range: <T extends NumericalIDCredential>(
332
- key: T,
333
- start: IDCredentialValue<T>,
334
- end: IDCredentialValue<T>,
335
- ) => QueryBuilder
336
- /**
337
- * Requires this attribute to be included in the provided list.
338
- * @param key The attribute to compare.
339
- * @param value The list of values to check inclusion against.
340
- */
341
- in: <T extends "nationality" | "issuing_country">(
342
- key: T,
343
- value: IDCredentialValue<T>[],
344
- ) => QueryBuilder
345
- /**
346
- * Requires this attribute to be excluded from the provided list.
347
- * @param key The attribute to compare.
348
- * @param value The list of values to check exclusion against.
349
- */
350
- out: <T extends "nationality" | "issuing_country">(
351
- key: T,
352
- value: IDCredentialValue<T>[],
353
- ) => QueryBuilder
354
- /**
355
- * Requires this attribute to be disclosed.
356
- * @param key The attribute to disclose.
357
- */
358
- disclose: (key: DisclosableIDCredential) => QueryBuilder
359
- /**
360
- * Binds a value to the request.
361
- * @param key The key of the value to bind.
362
- * @param value The value to bind the request to.
363
- */
364
- bind: (key: keyof BoundData, value: BoundData[keyof BoundData]) => QueryBuilder
365
- /**
366
- * Builds the request.
367
- *
368
- * This will return the URL of the request, which you can either encode in a QR code
369
- * or provide as a link to the user if they're visiting your website on their phone.
370
- * It also returns all the callbacks you can use to handle the user's response.
371
- */
372
- done: () => QueryBuilderResult
373
- }
374
-
375
- export class ZKPassport {
376
- private domain: string
377
- private topicToConfig: Record<string, Record<string, IDCredentialConfig>> = {}
378
- private topicToLocalConfig: Record<
379
- string,
380
- {
381
- validity: number
382
- mode: ProofMode
383
- devMode: boolean
384
- }
385
- > = {}
386
- private topicToPublicKey: Record<string, string> = {}
387
- private topicToBridge: Record<string, BridgeInterface> = {}
388
- private topicToRequestReceived: Record<string, boolean> = {}
389
- private topicToService: Record<string, Service> = {}
390
- private topicToProofs: Record<string, Array<ProofResult>> = {}
391
- private topicToExpectedProofCount: Record<string, number> = {}
392
- private topicToFailedProofCount: Record<string, number> = {}
393
- private topicToResults: Record<string, QueryResult> = {}
394
-
395
- private onRequestReceivedCallbacks: Record<string, Array<() => void>> = {}
396
- private onGeneratingProofCallbacks: Record<string, Array<(topic: string) => void>> = {}
397
- private onBridgeConnectCallbacks: Record<string, Array<() => void>> = {}
398
- private onProofGeneratedCallbacks: Record<string, Array<(proof: ProofResult) => void>> = {}
399
- private onResultCallbacks: Record<
400
- string,
401
- Array<
402
- (response: {
403
- uniqueIdentifier: string | undefined
404
- verified: boolean
405
- result: QueryResult
406
- queryResultErrors?: QueryResultErrors
407
- }) => void
408
- >
409
- > = {}
410
- private onRejectCallbacks: Record<string, Array<() => void>> = {}
411
- private onErrorCallbacks: Record<string, Array<(topic: string) => void>> = {}
412
- //private wasmVerifierInit: boolean = false
413
-
414
- constructor(_domain?: string) {
415
- if (!_domain && typeof window === "undefined") {
416
- throw new Error("Domain argument is required in Node.js environment")
417
- }
418
- this.domain = _domain || window.location.hostname
419
- }
420
-
421
- private async handleResult(topic: string) {
422
- const result = this.topicToResults[topic]
423
- // Clear the results straight away to avoid concurrency issues
424
- delete this.topicToResults[topic]
425
- // Verify the proofs and extract the unique identifier (aka nullifier) and the verification result
426
- const { uniqueIdentifier, verified, queryResultErrors } = await this.verify({
427
- proofs: this.topicToProofs[topic],
428
- queryResult: result,
429
- validity: this.topicToLocalConfig[topic]?.validity,
430
- scope: this.topicToService[topic]?.scope,
431
- evmChain: this.topicToService[topic]?.chainId
432
- ? getEVMChainFromChainId(this.topicToService[topic]?.chainId)
433
- : undefined,
434
- devMode: this.topicToLocalConfig[topic]?.devMode,
435
- })
436
- delete this.topicToProofs[topic]
437
- const hasFailedProofs = this.topicToFailedProofCount[topic] > 0
438
- await Promise.all(
439
- this.onResultCallbacks[topic].map((callback) =>
440
- callback({
441
- // If there are failed proofs, we don't return the unique identifier
442
- // and we set the verified result to false
443
- uniqueIdentifier: hasFailedProofs ? undefined : uniqueIdentifier,
444
- verified: hasFailedProofs ? false : verified,
445
- result,
446
- queryResultErrors,
447
- }),
448
- ),
449
- )
450
- // Clear the expected proof count and failed proof count
451
- delete this.topicToExpectedProofCount[topic]
452
- delete this.topicToFailedProofCount[topic]
453
- }
454
-
455
- private setExpectedProofCount(topic: string) {
456
- // If the mode is not fast, we'll receive only 1 compressed proof
457
- if (this.topicToLocalConfig[topic].mode !== "fast") {
458
- this.topicToExpectedProofCount[topic] = 1
459
- return
460
- }
461
- const fields = Object.keys(this.topicToConfig[topic] as Query).filter((key) =>
462
- hasRequestedAccessToField(this.topicToConfig[topic] as Query, key as IDCredential),
463
- )
464
- const neededCircuits: string[] = []
465
- // Determine which circuits are needed based on the requested fields
466
- for (const field of fields) {
467
- for (const key in this.topicToConfig[topic][field as IDCredential]) {
468
- switch (key) {
469
- case "eq":
470
- case "disclose":
471
- if (field !== "age" && !neededCircuits.includes("disclose_bytes")) {
472
- neededCircuits.push("disclose_bytes")
473
- } else if (field === "age" && !neededCircuits.includes("compare_age")) {
474
- neededCircuits.push("compare_age")
475
- }
476
- break
477
- case "gte":
478
- case "gt":
479
- case "lte":
480
- case "lt":
481
- case "range":
482
- if (field === "age" && !neededCircuits.includes("compare_age")) {
483
- neededCircuits.push("compare_age")
484
- } else if (field === "expiry_date" && !neededCircuits.includes("compare_expiry")) {
485
- neededCircuits.push("compare_expiry")
486
- } else if (field === "birthdate" && !neededCircuits.includes("compare_birthdate")) {
487
- neededCircuits.push("compare_birthdate")
488
- }
489
- break
490
- case "in":
491
- if (
492
- field === "nationality" &&
493
- !neededCircuits.includes("inclusion_check_nationality")
494
- ) {
495
- neededCircuits.push("inclusion_check_nationality")
496
- } else if (
497
- field === "issuing_country" &&
498
- !neededCircuits.includes("inclusion_check_issuing_country")
499
- ) {
500
- neededCircuits.push("inclusion_check_issuing_country")
501
- }
502
- break
503
- case "out":
504
- if (
505
- field === "nationality" &&
506
- !neededCircuits.includes("exclusion_check_nationality")
507
- ) {
508
- neededCircuits.push("exclusion_check_nationality")
509
- } else if (
510
- field === "issuing_country" &&
511
- !neededCircuits.includes("exclusion_check_issuing_country")
512
- ) {
513
- neededCircuits.push("exclusion_check_issuing_country")
514
- }
515
- break
516
- }
517
- }
518
- }
519
- if ((this.topicToConfig[topic] as Query).bind) {
520
- neededCircuits.push("bind")
521
- }
522
- // From the circuits needed, determine the expected proof count
523
- // There are at least 4 proofs, 3 base proofs and 1 disclosure proof minimum
524
- // Each separate needed circuit adds 1 disclosure proof
525
- this.topicToExpectedProofCount[topic] =
526
- neededCircuits.length === 0 ? 4 : 3 + neededCircuits.length
527
- this.topicToFailedProofCount[topic] = 0
528
- }
529
-
530
- /**
531
- * @notice Handle an encrypted message.
532
- * @param request The request.
533
- * @param outerRequest The outer request.
534
- */
535
- private async handleEncryptedMessage(topic: string, request: JsonRpcRequest) {
536
- logger.debug("Received encrypted message:", request)
537
- if (request.method === "accept") {
538
- logger.debug(`User accepted the request and is generating a proof`)
539
- await Promise.all(this.onGeneratingProofCallbacks[topic].map((callback) => callback(topic)))
540
- } else if (request.method === "reject") {
541
- logger.debug(`User rejected the request`)
542
- await Promise.all(this.onRejectCallbacks[topic].map((callback) => callback()))
543
- } else if (request.method === "proof") {
544
- logger.debug(`User generated proof`)
545
- this.topicToProofs[topic].push(request.params)
546
- await Promise.all(
547
- this.onProofGeneratedCallbacks[topic].map((callback) => callback(request.params)),
548
- )
549
- // If the results were received before all the proofs were generated,
550
- // we can handle the result now
551
- if (
552
- this.topicToResults[topic] &&
553
- this.topicToExpectedProofCount[topic] === this.topicToProofs[topic].length
554
- ) {
555
- await this.handleResult(topic)
556
- }
557
- } else if (request.method === "done") {
558
- logger.debug(`User sent the query result`)
559
- const formattedResult: QueryResult = request.params
560
- // Make sure to reconvert the dates to Date objects
561
- if (formattedResult.birthdate && formattedResult.birthdate.disclose) {
562
- formattedResult.birthdate.disclose.result = new Date(
563
- formattedResult.birthdate.disclose.result,
564
- )
565
- }
566
- if (formattedResult.expiry_date && formattedResult.expiry_date.disclose) {
567
- formattedResult.expiry_date.disclose.result = new Date(
568
- formattedResult.expiry_date.disclose.result,
569
- )
570
- }
571
- this.topicToResults[topic] = formattedResult
572
- // Make sure all the proofs have been received, otherwise we'll handle the result later
573
- // once the proofs have all been received
574
- if (this.topicToExpectedProofCount[topic] === this.topicToProofs[topic].length) {
575
- await this.handleResult(topic)
576
- }
577
- } else if (request.method === "error") {
578
- const error = request.params.error
579
- if (error && error === "This ID is not supported yet") {
580
- // This means the user has an ID that is not supported yet
581
- // So we won't receive any proofs and we can handle the result now
582
- this.topicToExpectedProofCount[topic] = 0
583
- this.topicToFailedProofCount[topic] += this.topicToExpectedProofCount[topic]
584
- if (this.topicToResults[topic]) {
585
- await this.handleResult(topic)
586
- }
587
- } else if (error && error.startsWith("Cannot generate proof")) {
588
- // This means one of the disclosure proofs failed to be generated
589
- // So we need to remove one from the expected proof count
590
- this.topicToExpectedProofCount[topic] -= 1
591
- this.topicToFailedProofCount[topic] += 1
592
- // If the expected proof count is now equal to the number of proofs received
593
- // and the results were received, we can handle the result now
594
- if (
595
- this.topicToResults[topic] &&
596
- this.topicToExpectedProofCount[topic] === this.topicToProofs[topic].length
597
- ) {
598
- await this.handleResult(topic)
599
- }
600
- }
601
- await Promise.all(this.onErrorCallbacks[topic].map((callback) => callback(error)))
602
- }
603
- }
604
-
605
- private getZkPassportRequest(topic: string): QueryBuilder {
606
- return {
607
- eq: <T extends IDCredential>(key: T, value: IDCredentialValue<T>) => {
608
- if (key === "issuing_country" || key === "nationality") {
609
- value = normalizeCountry(value as CountryName) as IDCredentialValue<T>
610
- }
611
- generalCompare("eq", key, value, topic, this.topicToConfig)
612
- return this.getZkPassportRequest(topic)
613
- },
614
- gte: <T extends NumericalIDCredential>(key: T, value: IDCredentialValue<T>) => {
615
- numericalCompare("gte", key, value, topic, this.topicToConfig)
616
- if (key === "age" && ((value as number) < 1 || (value as number) >= 100)) {
617
- throw new Error("Age must be between 1 and 99 (inclusive)")
618
- }
619
- return this.getZkPassportRequest(topic)
620
- },
621
- /*gt: <T extends NumericalIDCredential>(key: T, value: IDCredentialValue<T>) => {
622
- numericalCompare('gt', key, value, topic, this.topicToConfig)
623
- return this.getZkPassportRequest(topic)
624
- },*/
625
- lte: <T extends "birthdate" | "expiry_date">(key: T, value: IDCredentialValue<T>) => {
626
- numericalCompare("lte", key, value, topic, this.topicToConfig)
627
- return this.getZkPassportRequest(topic)
628
- },
629
- lt: <T extends "age">(key: T, value: IDCredentialValue<T>) => {
630
- numericalCompare("lt", key, value, topic, this.topicToConfig)
631
- return this.getZkPassportRequest(topic)
632
- },
633
- range: <T extends NumericalIDCredential>(
634
- key: T,
635
- start: IDCredentialValue<T>,
636
- end: IDCredentialValue<T>,
637
- ) => {
638
- rangeCompare(key, [start, end], topic, this.topicToConfig)
639
- return this.getZkPassportRequest(topic)
640
- },
641
- in: <T extends "nationality" | "issuing_country">(key: T, value: IDCredentialValue<T>[]) => {
642
- value = value.map((v) => normalizeCountry(v as CountryName)) as IDCredentialValue<T>[]
643
- generalCompare("in", key, value, topic, this.topicToConfig)
644
- return this.getZkPassportRequest(topic)
645
- },
646
- out: <T extends "nationality" | "issuing_country">(key: T, value: IDCredentialValue<T>[]) => {
647
- value = value.map((v) => normalizeCountry(v as CountryName)) as IDCredentialValue<T>[]
648
- generalCompare("out", key, value, topic, this.topicToConfig)
649
- return this.getZkPassportRequest(topic)
650
- },
651
- disclose: (key: DisclosableIDCredential) => {
652
- this.topicToConfig[topic][key] = {
653
- ...this.topicToConfig[topic][key],
654
- disclose: true,
655
- }
656
- return this.getZkPassportRequest(topic)
657
- },
658
- bind: (key: keyof BoundData, value: BoundData[keyof BoundData]) => {
659
- this.topicToConfig[topic].bind = {
660
- ...this.topicToConfig[topic].bind,
661
- [key]: value,
662
- }
663
- return this.getZkPassportRequest(topic)
664
- },
665
- done: () => {
666
- this.setExpectedProofCount(topic)
667
- return {
668
- url: this._getUrl(topic),
669
- requestId: topic,
670
- onRequestReceived: (callback: () => void) =>
671
- this.onRequestReceivedCallbacks[topic].push(callback),
672
- onGeneratingProof: (callback: () => void) =>
673
- this.onGeneratingProofCallbacks[topic].push(callback),
674
- onBridgeConnect: (callback: () => void) =>
675
- this.onBridgeConnectCallbacks[topic].push(callback),
676
- onProofGenerated: (callback: (proof: ProofResult) => void) =>
677
- this.onProofGeneratedCallbacks[topic].push(callback),
678
- onResult: (
679
- callback: (response: {
680
- uniqueIdentifier: string | undefined
681
- verified: boolean
682
- result: QueryResult
683
- queryResultErrors?: QueryResultErrors
684
- }) => void,
685
- ) => this.onResultCallbacks[topic].push(callback),
686
- onReject: (callback: () => void) => this.onRejectCallbacks[topic].push(callback),
687
- onError: (callback: (error: string) => void) =>
688
- this.onErrorCallbacks[topic].push(callback),
689
- isBridgeConnected: () => this.topicToBridge[topic].isBridgeConnected(),
690
- requestReceived: () => this.topicToRequestReceived[topic] === true,
691
- }
692
- },
693
- }
694
- }
695
-
696
- /**
697
- * @notice Create a new request
698
- * @param name Your service name
699
- * @param logo The logo of your service
700
- * @param purpose To explain what you want to do with the user's data
701
- * @param scope Scope this request to a specific use case
702
- * @param validity How many days ago should have the ID been last scanned by the user?
703
- * @param devMode Whether to enable dev mode. This will allow you to verify mock proofs (i.e. from ZKR)
704
- * @param evmChain The EVM chain to use for the request (if using the proof onchain)
705
- * @returns The query builder object.
706
- */
707
- public async request({
708
- name,
709
- logo,
710
- purpose,
711
- scope,
712
- mode,
713
- evmChain,
714
- validity,
715
- devMode,
716
- topicOverride,
717
- keyPairOverride,
718
- cloudProverUrl,
719
- bridgeUrl,
720
- }: {
721
- name: string
722
- logo: string
723
- purpose: string
724
- scope?: string
725
- mode?: ProofMode
726
- evmChain?: EVMChain
727
- validity?: number
728
- devMode?: boolean
729
- topicOverride?: string
730
- keyPairOverride?: { privateKey: Uint8Array; publicKey: Uint8Array }
731
- cloudProverUrl?: string
732
- bridgeUrl?: string
733
- }): Promise<QueryBuilder> {
734
- const bridge = await Bridge.create({
735
- keyPair: keyPairOverride,
736
- bridgeId: topicOverride,
737
- bridgeUrl,
738
- })
739
-
740
- const topic = bridge.connection.getBridgeId()
741
-
742
- this.topicToConfig[topic] = {}
743
- this.topicToService[topic] = {
744
- name,
745
- logo,
746
- purpose,
747
- scope,
748
- chainId: evmChain ? getChainIdFromEVMChain(evmChain) : undefined,
749
- cloudProverUrl,
750
- bridgeUrl,
751
- }
752
- this.topicToProofs[topic] = []
753
- this.topicToExpectedProofCount[topic] = 0
754
- this.topicToLocalConfig[topic] = {
755
- // Default to 6 months
756
- validity: validity || 6 * 30,
757
- mode: mode || "fast",
758
- devMode: devMode || false,
759
- }
760
-
761
- this.onRequestReceivedCallbacks[topic] = []
762
- this.onGeneratingProofCallbacks[topic] = []
763
- this.onBridgeConnectCallbacks[topic] = []
764
- this.onProofGeneratedCallbacks[topic] = []
765
- this.onResultCallbacks[topic] = []
766
- this.onRejectCallbacks[topic] = []
767
- this.onErrorCallbacks[topic] = []
768
-
769
- this.topicToPublicKey[topic] = bridge.getPublicKey()
770
-
771
- this.topicToBridge[topic] = bridge
772
- bridge.onConnect(async (reconnection: boolean) => {
773
- logger.debug("Bridge connected")
774
- logger.debug("Is reconnection:", reconnection)
775
- await Promise.all(this.onBridgeConnectCallbacks[topic].map((callback) => callback()))
776
- })
777
- bridge.onSecureChannelEstablished(async () => {
778
- logger.debug("Secure channel established")
779
- await Promise.all(this.onRequestReceivedCallbacks[topic].map((callback) => callback()))
780
- })
781
- bridge.onSecureMessage(async (message: any) => {
782
- logger.debug("Received message:", message)
783
- this.handleEncryptedMessage(topic, message)
784
- })
785
- return this.getZkPassportRequest(topic)
786
- }
787
-
788
- private checkDiscloseBytesPublicInputs(proof: ProofResult, queryResult: QueryResult) {
789
- const queryResultErrors: QueryResultErrors = {
790
- sig_check_dsc: {},
791
- sig_check_id_data: {},
792
- data_check_integrity: {},
793
- disclose: {},
794
- age: {},
795
- birthdate: {},
796
- expiry_date: {},
797
- document_type: {},
798
- issuing_country: {},
799
- gender: {},
800
- nationality: {},
801
- firstname: {},
802
- lastname: {},
803
- fullname: {},
804
- document_number: {},
805
- outer: {},
806
- bind: {},
807
- }
808
- let isCorrect = true
809
- // We can't be certain that the disclosed data is for a passport or an ID card
810
- // so we need to check both (unless the document type is revealed)
811
- const disclosedDataPassport = DisclosedData.fromDisclosedBytes(
812
- (proof.committedInputs?.disclose_bytes as DiscloseCommittedInputs).disclosedBytes!,
813
- "passport",
814
- )
815
- const disclosedDataIDCard = DisclosedData.fromDisclosedBytes(
816
- (proof.committedInputs?.disclose_bytes as DiscloseCommittedInputs).disclosedBytes!,
817
- "id_card",
818
- )
819
- if (queryResult.document_type) {
820
- // Document type is always at the same index in the disclosed data
821
- if (
822
- queryResult.document_type.eq &&
823
- queryResult.document_type.eq.result &&
824
- queryResult.document_type.eq.expected !== disclosedDataPassport.documentType
825
- ) {
826
- console.warn("Document type does not match the expected document type")
827
- isCorrect = false
828
- queryResultErrors.document_type.eq = {
829
- expected: `${queryResult.document_type.eq.expected}`,
830
- received: `${disclosedDataPassport.documentType ?? disclosedDataIDCard.documentType}`,
831
- message: "Document type does not match the expected document type",
832
- }
833
- }
834
- if (queryResult.document_type.disclose?.result !== disclosedDataIDCard.documentType) {
835
- console.warn("Document type does not match the disclosed document type in query result")
836
- isCorrect = false
837
- queryResultErrors.document_type.disclose = {
838
- expected: `${queryResult.document_type.disclose?.result}`,
839
- received: `${disclosedDataIDCard.documentType ?? disclosedDataPassport.documentType}`,
840
- message: "Document type does not match the disclosed document type in query result",
841
- }
842
- }
843
- }
844
- if (queryResult.birthdate) {
845
- const birthdatePassport = disclosedDataPassport.dateOfBirth
846
- const birthdateIDCard = disclosedDataIDCard.dateOfBirth
847
- if (
848
- queryResult.birthdate.eq &&
849
- queryResult.birthdate.eq.result &&
850
- queryResult.birthdate.eq.expected.getTime() !== birthdatePassport.getTime() &&
851
- queryResult.birthdate.eq.expected.getTime() !== birthdateIDCard.getTime()
852
- ) {
853
- console.warn("Birthdate does not match the expected birthdate")
854
- isCorrect = false
855
- queryResultErrors.birthdate.eq = {
856
- expected: `${queryResult.birthdate.eq.expected.toISOString()}`,
857
- received: `${birthdatePassport?.toISOString() ?? birthdateIDCard?.toISOString()}`,
858
- message: "Birthdate does not match the expected birthdate",
859
- }
860
- }
861
- if (
862
- queryResult.birthdate.disclose &&
863
- queryResult.birthdate.disclose.result.getTime() !== birthdatePassport.getTime() &&
864
- queryResult.birthdate.disclose.result.getTime() !== birthdateIDCard.getTime()
865
- ) {
866
- console.warn("Birthdate does not match the disclosed birthdate in query result")
867
- isCorrect = false
868
- queryResultErrors.birthdate.disclose = {
869
- expected: `${queryResult.birthdate.disclose.result.toISOString()}`,
870
- received: `${birthdatePassport?.toISOString() ?? birthdateIDCard?.toISOString()}`,
871
- message: "Birthdate does not match the disclosed birthdate in query result",
872
- }
873
- }
874
- }
875
- if (queryResult.expiry_date) {
876
- const expiryDatePassport = disclosedDataPassport.dateOfExpiry
877
- const expiryDateIDCard = disclosedDataIDCard.dateOfExpiry
878
- if (
879
- queryResult.expiry_date.eq &&
880
- queryResult.expiry_date.eq.result &&
881
- queryResult.expiry_date.eq.expected.getTime() !== expiryDatePassport.getTime() &&
882
- queryResult.expiry_date.eq.expected.getTime() !== expiryDateIDCard.getTime()
883
- ) {
884
- console.warn("Expiry date does not match the expected expiry date")
885
- isCorrect = false
886
- queryResultErrors.expiry_date.eq = {
887
- expected: `${queryResult.expiry_date.eq.expected.toISOString()}`,
888
- received: `${expiryDatePassport?.toISOString() ?? expiryDateIDCard?.toISOString()}`,
889
- message: "Expiry date does not match the expected expiry date",
890
- }
891
- }
892
- if (
893
- queryResult.expiry_date.disclose &&
894
- queryResult.expiry_date.disclose.result.getTime() !== expiryDatePassport.getTime() &&
895
- queryResult.expiry_date.disclose.result.getTime() !== expiryDateIDCard.getTime()
896
- ) {
897
- console.warn("Expiry date does not match the disclosed expiry date in query result")
898
- isCorrect = false
899
- queryResultErrors.expiry_date.disclose = {
900
- expected: `${queryResult.expiry_date.disclose.result.toISOString()}`,
901
- received: `${expiryDatePassport?.toISOString() ?? expiryDateIDCard?.toISOString()}`,
902
- message: "Expiry date does not match the disclosed expiry date in query result",
903
- }
904
- }
905
- }
906
- if (queryResult.nationality) {
907
- const nationalityPassport = disclosedDataPassport.nationality
908
- const nationalityIDCard = disclosedDataIDCard.nationality
909
- if (
910
- queryResult.nationality.eq &&
911
- queryResult.nationality.eq.result &&
912
- queryResult.nationality.eq.expected !== nationalityPassport &&
913
- queryResult.nationality.eq.expected !== nationalityIDCard
914
- ) {
915
- console.warn("Nationality does not match the expected nationality")
916
- isCorrect = false
917
- queryResultErrors.nationality.eq = {
918
- expected: `${queryResult.nationality.eq.expected}`,
919
- received: `${nationalityPassport ?? nationalityIDCard}`,
920
- message: "Nationality does not match the expected nationality",
921
- }
922
- }
923
- if (
924
- queryResult.nationality.disclose &&
925
- queryResult.nationality.disclose.result !== nationalityPassport &&
926
- queryResult.nationality.disclose.result !== nationalityIDCard
927
- ) {
928
- console.warn("Nationality does not match the disclosed nationality in query result")
929
- isCorrect = false
930
- queryResultErrors.nationality.disclose = {
931
- expected: `${queryResult.nationality.disclose.result}`,
932
- received: `${nationalityPassport ?? nationalityIDCard}`,
933
- message: "Nationality does not match the disclosed nationality in query result",
934
- }
935
- }
936
- }
937
- if (queryResult.document_number) {
938
- const documentNumberPassport = disclosedDataPassport.documentNumber
939
- const documentNumberIDCard = disclosedDataIDCard.documentNumber
940
- if (
941
- queryResult.document_number.eq &&
942
- queryResult.document_number.eq.result &&
943
- queryResult.document_number.eq.expected !== documentNumberPassport &&
944
- queryResult.document_number.eq.expected !== documentNumberIDCard
945
- ) {
946
- console.warn("Document number does not match the expected document number")
947
- isCorrect = false
948
- queryResultErrors.document_number.eq = {
949
- expected: `${queryResult.document_number.eq.expected}`,
950
- received: `${documentNumberPassport ?? documentNumberIDCard}`,
951
- message: "Document number does not match the expected document number",
952
- }
953
- }
954
- if (
955
- queryResult.document_number.disclose &&
956
- queryResult.document_number.disclose.result !== documentNumberPassport &&
957
- queryResult.document_number.disclose.result !== documentNumberIDCard
958
- ) {
959
- console.warn("Document number does not match the disclosed document number in query result")
960
- isCorrect = false
961
- queryResultErrors.document_number.disclose = {
962
- expected: `${queryResult.document_number.disclose.result}`,
963
- received: `${documentNumberPassport ?? documentNumberIDCard}`,
964
- message: "Document number does not match the disclosed document number in query result",
965
- }
966
- }
967
- }
968
- if (queryResult.gender) {
969
- const genderPassport = disclosedDataPassport.gender
970
- const genderIDCard = disclosedDataIDCard.gender
971
- if (
972
- queryResult.gender.eq &&
973
- queryResult.gender.eq.result &&
974
- queryResult.gender.eq.expected !== genderPassport &&
975
- queryResult.gender.eq.expected !== genderIDCard
976
- ) {
977
- console.warn("Gender does not match the expected gender")
978
- isCorrect = false
979
- queryResultErrors.gender.eq = {
980
- expected: `${queryResult.gender.eq.expected}`,
981
- received: `${genderPassport ?? genderIDCard}`,
982
- message: "Gender does not match the expected gender",
983
- }
984
- }
985
- if (
986
- queryResult.gender.disclose &&
987
- queryResult.gender.disclose.result !== genderPassport &&
988
- queryResult.gender.disclose.result !== genderIDCard
989
- ) {
990
- console.warn("Gender does not match the disclosed gender in query result")
991
- isCorrect = false
992
- queryResultErrors.gender.disclose = {
993
- expected: `${queryResult.gender.disclose.result}`,
994
- received: `${genderPassport ?? genderIDCard}`,
995
- message: "Gender does not match the disclosed gender in query result",
996
- }
997
- }
998
- }
999
- if (queryResult.issuing_country) {
1000
- const issuingCountryPassport = disclosedDataPassport.issuingCountry
1001
- const issuingCountryIDCard = disclosedDataIDCard.issuingCountry
1002
- if (
1003
- queryResult.issuing_country.eq &&
1004
- queryResult.issuing_country.eq.result &&
1005
- queryResult.issuing_country.eq.expected !== issuingCountryPassport &&
1006
- queryResult.issuing_country.eq.expected !== issuingCountryIDCard
1007
- ) {
1008
- console.warn("Issuing country does not match the expected issuing country")
1009
- isCorrect = false
1010
- queryResultErrors.issuing_country.eq = {
1011
- expected: `${queryResult.issuing_country.eq.expected}`,
1012
- received: `${issuingCountryPassport ?? issuingCountryIDCard}`,
1013
- message: "Issuing country does not match the expected issuing country",
1014
- }
1015
- }
1016
- if (
1017
- queryResult.issuing_country.disclose &&
1018
- queryResult.issuing_country.disclose.result !== issuingCountryPassport &&
1019
- queryResult.issuing_country.disclose.result !== issuingCountryIDCard
1020
- ) {
1021
- console.warn("Issuing country does not match the disclosed issuing country in query result")
1022
- isCorrect = false
1023
- queryResultErrors.issuing_country.disclose = {
1024
- expected: `${queryResult.issuing_country.disclose.result}`,
1025
- received: `${issuingCountryPassport ?? issuingCountryIDCard}`,
1026
- message: "Issuing country does not match the disclosed issuing country in query result",
1027
- }
1028
- }
1029
- }
1030
- if (queryResult.fullname) {
1031
- const fullnamePassport = disclosedDataPassport.name
1032
- const fullnameIDCard = disclosedDataIDCard.name
1033
- if (
1034
- queryResult.fullname.eq &&
1035
- queryResult.fullname.eq.result &&
1036
- formatName(queryResult.fullname.eq.expected).toLowerCase() !==
1037
- fullnamePassport.toLowerCase() &&
1038
- formatName(queryResult.fullname.eq.expected).toLowerCase() !== fullnameIDCard.toLowerCase()
1039
- ) {
1040
- console.warn("Fullname does not match the expected fullname")
1041
- isCorrect = false
1042
- queryResultErrors.fullname.eq = {
1043
- expected: `${queryResult.fullname.eq.expected}`,
1044
- received: `${fullnamePassport ?? fullnameIDCard}`,
1045
- message: "Fullname does not match the expected fullname",
1046
- }
1047
- }
1048
- if (
1049
- queryResult.fullname.disclose &&
1050
- formatName(queryResult.fullname.disclose.result).toLowerCase() !==
1051
- fullnamePassport.toLowerCase() &&
1052
- formatName(queryResult.fullname.disclose.result).toLowerCase() !==
1053
- fullnameIDCard.toLowerCase()
1054
- ) {
1055
- console.warn("Fullname does not match the disclosed fullname in query result")
1056
- isCorrect = false
1057
- queryResultErrors.fullname.disclose = {
1058
- expected: `${queryResult.fullname.disclose.result}`,
1059
- received: `${fullnamePassport ?? fullnameIDCard}`,
1060
- message: "Fullname does not match the disclosed fullname in query result",
1061
- }
1062
- }
1063
- }
1064
- if (queryResult.firstname) {
1065
- // If fullname was not revealed, then the name could be either the first name or last name
1066
- const firstnamePassport =
1067
- disclosedDataPassport.firstName && disclosedDataPassport.firstName.length > 0
1068
- ? disclosedDataPassport.firstName
1069
- : disclosedDataPassport.name
1070
- const firstnameIDCard =
1071
- disclosedDataIDCard.firstName && disclosedDataIDCard.firstName.length > 0
1072
- ? disclosedDataIDCard.firstName
1073
- : disclosedDataIDCard.name
1074
- if (
1075
- queryResult.firstname.eq &&
1076
- queryResult.firstname.eq.result &&
1077
- formatName(queryResult.firstname.eq.expected).toLowerCase() !==
1078
- firstnamePassport.toLowerCase() &&
1079
- formatName(queryResult.firstname.eq.expected).toLowerCase() !==
1080
- firstnameIDCard.toLowerCase()
1081
- ) {
1082
- console.warn("Firstname does not match the expected firstname")
1083
- isCorrect = false
1084
- queryResultErrors.firstname.eq = {
1085
- expected: `${queryResult.firstname.eq.expected}`,
1086
- received: `${firstnamePassport ?? firstnameIDCard}`,
1087
- message: "Firstname does not match the expected firstname",
1088
- }
1089
- }
1090
- if (
1091
- queryResult.firstname.disclose &&
1092
- formatName(queryResult.firstname.disclose.result).toLowerCase() !==
1093
- firstnamePassport.toLowerCase() &&
1094
- formatName(queryResult.firstname.disclose.result).toLowerCase() !==
1095
- firstnameIDCard.toLowerCase()
1096
- ) {
1097
- console.warn("Firstname does not match the disclosed firstname in query result")
1098
- isCorrect = false
1099
- queryResultErrors.firstname.disclose = {
1100
- expected: `${queryResult.firstname.disclose.result}`,
1101
- received: `${firstnamePassport ?? firstnameIDCard}`,
1102
- message: "Firstname does not match the disclosed firstname in query result",
1103
- }
1104
- }
1105
- }
1106
- if (queryResult.lastname) {
1107
- // If fullname was not revealed, then the name could be either the first name or last name
1108
- const lastnamePassport =
1109
- disclosedDataPassport.lastName && disclosedDataPassport.lastName.length > 0
1110
- ? disclosedDataPassport.lastName
1111
- : disclosedDataPassport.name
1112
- const lastnameIDCard =
1113
- disclosedDataIDCard.lastName && disclosedDataIDCard.lastName.length > 0
1114
- ? disclosedDataIDCard.lastName
1115
- : disclosedDataIDCard.name
1116
- if (
1117
- queryResult.lastname.eq &&
1118
- queryResult.lastname.eq.result &&
1119
- formatName(queryResult.lastname.eq.expected).toLowerCase() !==
1120
- lastnamePassport.toLowerCase() &&
1121
- formatName(queryResult.lastname.eq.expected).toLowerCase() !== lastnameIDCard.toLowerCase()
1122
- ) {
1123
- console.warn("Lastname does not match the expected lastname")
1124
- isCorrect = false
1125
- queryResultErrors.lastname.eq = {
1126
- expected: `${queryResult.lastname.eq.expected}`,
1127
- received: `${lastnamePassport ?? lastnameIDCard}`,
1128
- message: "Lastname does not match the expected lastname",
1129
- }
1130
- }
1131
- if (
1132
- queryResult.lastname.disclose &&
1133
- formatName(queryResult.lastname.disclose.result).toLowerCase() !==
1134
- lastnamePassport.toLowerCase() &&
1135
- formatName(queryResult.lastname.disclose.result).toLowerCase() !==
1136
- lastnameIDCard.toLowerCase()
1137
- ) {
1138
- console.warn("Lastname does not match the disclosed lastname in query result")
1139
- isCorrect = false
1140
- queryResultErrors.lastname.disclose = {
1141
- expected: `${queryResult.lastname.disclose.result}`,
1142
- received: `${lastnamePassport ?? lastnameIDCard}`,
1143
- message: "Lastname does not match the disclosed lastname in query result",
1144
- }
1145
- }
1146
- }
1147
- return { isCorrect, queryResultErrors }
1148
- }
1149
-
1150
- private checkAgePublicInputs(proof: ProofResult, queryResult: QueryResult) {
1151
- const queryResultErrors: QueryResultErrors = {
1152
- sig_check_dsc: {},
1153
- sig_check_id_data: {},
1154
- data_check_integrity: {},
1155
- disclose: {},
1156
- age: {},
1157
- birthdate: {},
1158
- expiry_date: {},
1159
- document_type: {},
1160
- issuing_country: {},
1161
- gender: {},
1162
- nationality: {},
1163
- firstname: {},
1164
- lastname: {},
1165
- fullname: {},
1166
- document_number: {},
1167
- outer: {},
1168
- bind: {},
1169
- }
1170
- let isCorrect = true
1171
- const currentTime = new Date()
1172
- const today = new Date(
1173
- currentTime.getFullYear(),
1174
- currentTime.getMonth(),
1175
- currentTime.getDate(),
1176
- 0,
1177
- 0,
1178
- 0,
1179
- 0,
1180
- )
1181
- const minAge = getMinAgeFromCommittedInputs(
1182
- proof.committedInputs?.compare_age as AgeCommittedInputs,
1183
- )
1184
- const maxAge = getMaxAgeFromCommittedInputs(
1185
- proof.committedInputs?.compare_age as AgeCommittedInputs,
1186
- )
1187
- if (queryResult.age) {
1188
- if (
1189
- queryResult.age.gte &&
1190
- queryResult.age.gte.result &&
1191
- minAge !== (queryResult.age.gte.expected as number)
1192
- ) {
1193
- console.warn("Age is not greater than or equal to the expected age")
1194
- isCorrect = false
1195
- queryResultErrors.age.gte = {
1196
- expected: queryResult.age.gte.expected,
1197
- received: minAge,
1198
- message: "Age is not greater than or equal to the expected age",
1199
- }
1200
- }
1201
- if (
1202
- queryResult.age.lt &&
1203
- queryResult.age.lt.result &&
1204
- maxAge !== (queryResult.age.lt.expected as number)
1205
- ) {
1206
- console.warn("Age is not less than the expected age")
1207
- isCorrect = false
1208
- queryResultErrors.age.lt = {
1209
- expected: queryResult.age.lt.expected,
1210
- received: maxAge,
1211
- message: "Age is not less than the expected age",
1212
- }
1213
- }
1214
- if (queryResult.age.range) {
1215
- if (
1216
- queryResult.age.range.result &&
1217
- (minAge !== (queryResult.age.range.expected[0] as number) ||
1218
- maxAge !== (queryResult.age.range.expected[1] as number))
1219
- ) {
1220
- console.warn("Age is not in the expected range")
1221
- isCorrect = false
1222
- queryResultErrors.age.range = {
1223
- expected: queryResult.age.range.expected,
1224
- received: [minAge, maxAge],
1225
- message: "Age is not in the expected range",
1226
- }
1227
- }
1228
- }
1229
- if (!queryResult.age.lt && !queryResult.age.range && maxAge != 0) {
1230
- console.warn("Maximum age should be equal to 0")
1231
- isCorrect = false
1232
- queryResultErrors.age.disclose = {
1233
- expected: 0,
1234
- received: maxAge,
1235
- message: "Maximum age should be equal to 0",
1236
- }
1237
- }
1238
- if (!queryResult.age.gte && !queryResult.age.range && minAge != 0) {
1239
- console.warn("Minimum age should be equal to 0")
1240
- isCorrect = false
1241
- queryResultErrors.age.disclose = {
1242
- expected: 0,
1243
- received: minAge,
1244
- message: "Minimum age should be equal to 0",
1245
- }
1246
- }
1247
- if (
1248
- queryResult.age.disclose &&
1249
- (queryResult.age.disclose.result !== minAge || queryResult.age.disclose.result !== maxAge)
1250
- ) {
1251
- console.warn("Age does not match the disclosed age in query result")
1252
- isCorrect = false
1253
- queryResultErrors.age.disclose = {
1254
- expected: `${minAge}`,
1255
- received: `${queryResult.age.disclose.result}`,
1256
- message: "Age does not match the disclosed age in query result",
1257
- }
1258
- }
1259
- } else {
1260
- console.warn("Age is not set in the query result")
1261
- isCorrect = false
1262
- queryResultErrors.age.disclose = {
1263
- message: "Age is not set in the query result",
1264
- }
1265
- }
1266
- const currentDate = getCurrentDateFromCommittedInputs(
1267
- proof.committedInputs?.compare_age as AgeCommittedInputs,
1268
- )
1269
- if (
1270
- currentDate.getTime() !== today.getTime() &&
1271
- currentDate.getTime() !== today.getTime() - 86400000
1272
- ) {
1273
- console.warn("Current date in the proof is too old")
1274
- isCorrect = false
1275
- queryResultErrors.age.disclose = {
1276
- expected: `${today.toISOString()}`,
1277
- received: `${currentDate.toISOString()}`,
1278
- message: "Current date in the proof is too old",
1279
- }
1280
- }
1281
- return { isCorrect, queryResultErrors }
1282
- }
1283
-
1284
- private checkBirthdatePublicInputs(proof: ProofResult, queryResult: QueryResult) {
1285
- const queryResultErrors: QueryResultErrors = {
1286
- sig_check_dsc: {},
1287
- sig_check_id_data: {},
1288
- data_check_integrity: {},
1289
- disclose: {},
1290
- age: {},
1291
- birthdate: {},
1292
- expiry_date: {},
1293
- document_type: {},
1294
- issuing_country: {},
1295
- gender: {},
1296
- nationality: {},
1297
- firstname: {},
1298
- lastname: {},
1299
- fullname: {},
1300
- document_number: {},
1301
- outer: {},
1302
- bind: {},
1303
- }
1304
- let isCorrect = true
1305
- const currentTime = new Date()
1306
- const today = new Date(
1307
- currentTime.getFullYear(),
1308
- currentTime.getMonth(),
1309
- currentTime.getDate(),
1310
- 0,
1311
- 0,
1312
- 0,
1313
- )
1314
- const minDate = getMinDateFromCommittedInputs(
1315
- proof.committedInputs?.compare_birthdate as DateCommittedInputs,
1316
- )
1317
- const maxDate = getMaxDateFromCommittedInputs(
1318
- proof.committedInputs?.compare_birthdate as DateCommittedInputs,
1319
- )
1320
- const currentDate = getCurrentDateFromCommittedInputs(
1321
- proof.committedInputs?.compare_birthdate as DateCommittedInputs,
1322
- )
1323
- if (queryResult.birthdate) {
1324
- if (
1325
- queryResult.birthdate.gte &&
1326
- queryResult.birthdate.gte.result &&
1327
- minDate !== queryResult.birthdate.gte.expected
1328
- ) {
1329
- console.warn("Birthdate is not greater than or equal to the expected birthdate")
1330
- isCorrect = false
1331
- queryResultErrors.birthdate.gte = {
1332
- expected: queryResult.birthdate.gte.expected,
1333
- received: minDate,
1334
- message: "Birthdate is not greater than or equal to the expected birthdate",
1335
- }
1336
- }
1337
- if (
1338
- queryResult.birthdate.lte &&
1339
- queryResult.birthdate.lte.result &&
1340
- maxDate !== queryResult.birthdate.lte.expected
1341
- ) {
1342
- console.warn("Birthdate is not less than the expected birthdate")
1343
- isCorrect = false
1344
- queryResultErrors.birthdate.lte = {
1345
- expected: queryResult.birthdate.lte.expected,
1346
- received: maxDate,
1347
- message: "Birthdate is not less than the expected birthdate",
1348
- }
1349
- }
1350
- if (queryResult.birthdate.range) {
1351
- if (
1352
- queryResult.birthdate.range.result &&
1353
- (minDate !== queryResult.birthdate.range.expected[0] ||
1354
- maxDate !== queryResult.birthdate.range.expected[1])
1355
- ) {
1356
- console.warn("Birthdate is not in the expected range")
1357
- isCorrect = false
1358
- queryResultErrors.birthdate.range = {
1359
- expected: queryResult.birthdate.range.expected,
1360
- received: [minDate, maxDate],
1361
- message: "Birthdate is not in the expected range",
1362
- }
1363
- }
1364
- }
1365
- if (
1366
- !queryResult.birthdate.lte &&
1367
- !queryResult.birthdate.range &&
1368
- maxDate.getTime() != DEFAULT_DATE_VALUE.getTime()
1369
- ) {
1370
- console.warn("Maximum birthdate should be equal to default date value")
1371
- isCorrect = false
1372
- queryResultErrors.birthdate.disclose = {
1373
- expected: `${DEFAULT_DATE_VALUE.toISOString()}`,
1374
- received: `${maxDate.toISOString()}`,
1375
- message: "Maximum birthdate should be equal to default date value",
1376
- }
1377
- }
1378
- if (
1379
- !queryResult.birthdate.gte &&
1380
- !queryResult.birthdate.range &&
1381
- minDate.getTime() != DEFAULT_DATE_VALUE.getTime()
1382
- ) {
1383
- console.warn("Minimum birthdate should be equal to default date value")
1384
- isCorrect = false
1385
- queryResultErrors.birthdate.disclose = {
1386
- expected: `${DEFAULT_DATE_VALUE.toISOString()}`,
1387
- received: `${minDate.toISOString()}`,
1388
- message: "Minimum birthdate should be equal to default date value",
1389
- }
1390
- }
1391
- } else {
1392
- console.warn("Birthdate is not set in the query result")
1393
- isCorrect = false
1394
- queryResultErrors.birthdate.disclose = {
1395
- message: "Birthdate is not set in the query result",
1396
- }
1397
- }
1398
- if (
1399
- currentDate.getTime() !== today.getTime() &&
1400
- currentDate.getTime() !== today.getTime() - 86400000
1401
- ) {
1402
- console.warn("Current date in the proof is too old")
1403
- isCorrect = false
1404
- queryResultErrors.age.disclose = {
1405
- expected: `${today.toISOString()}`,
1406
- received: `${currentDate.toISOString()}`,
1407
- message: "Current date in the proof is too old",
1408
- }
1409
- }
1410
- return { isCorrect, queryResultErrors }
1411
- }
1412
-
1413
- private checkExpiryDatePublicInputs(proof: ProofResult, queryResult: QueryResult) {
1414
- const queryResultErrors: QueryResultErrors = {
1415
- sig_check_dsc: {},
1416
- sig_check_id_data: {},
1417
- data_check_integrity: {},
1418
- disclose: {},
1419
- age: {},
1420
- birthdate: {},
1421
- expiry_date: {},
1422
- document_type: {},
1423
- issuing_country: {},
1424
- gender: {},
1425
- nationality: {},
1426
- firstname: {},
1427
- lastname: {},
1428
- fullname: {},
1429
- document_number: {},
1430
- outer: {},
1431
- bind: {},
1432
- }
1433
- let isCorrect = true
1434
- const currentTime = new Date()
1435
- const today = new Date(
1436
- currentTime.getFullYear(),
1437
- currentTime.getMonth(),
1438
- currentTime.getDate(),
1439
- 0,
1440
- 0,
1441
- 0,
1442
- )
1443
- const minDate = getMinDateFromCommittedInputs(
1444
- proof.committedInputs?.compare_expiry as DateCommittedInputs,
1445
- )
1446
- const maxDate = getMaxDateFromCommittedInputs(
1447
- proof.committedInputs?.compare_expiry as DateCommittedInputs,
1448
- )
1449
- const currentDate = getCurrentDateFromCommittedInputs(
1450
- proof.committedInputs?.compare_expiry as DateCommittedInputs,
1451
- )
1452
- if (queryResult.expiry_date) {
1453
- if (
1454
- queryResult.expiry_date.gte &&
1455
- queryResult.expiry_date.gte.result &&
1456
- minDate !== queryResult.expiry_date.gte.expected
1457
- ) {
1458
- console.warn("Expiry date is not greater than or equal to the expected expiry date")
1459
- isCorrect = false
1460
- queryResultErrors.expiry_date.gte = {
1461
- expected: queryResult.expiry_date.gte.expected,
1462
- received: minDate,
1463
- message: "Expiry date is not greater than or equal to the expected expiry date",
1464
- }
1465
- }
1466
- if (
1467
- queryResult.expiry_date.lte &&
1468
- queryResult.expiry_date.lte.result &&
1469
- maxDate !== queryResult.expiry_date.lte.expected
1470
- ) {
1471
- console.warn("Expiry date is not less than the expected expiry date")
1472
- isCorrect = false
1473
- queryResultErrors.expiry_date.lte = {
1474
- expected: queryResult.expiry_date.lte.expected,
1475
- received: maxDate,
1476
- message: "Expiry date is not less than the expected expiry date",
1477
- }
1478
- }
1479
- if (queryResult.expiry_date.range) {
1480
- if (
1481
- queryResult.expiry_date.range.result &&
1482
- (minDate !== queryResult.expiry_date.range.expected[0] ||
1483
- maxDate !== queryResult.expiry_date.range.expected[1])
1484
- ) {
1485
- console.warn("Expiry date is not in the expected range")
1486
- isCorrect = false
1487
- queryResultErrors.expiry_date.range = {
1488
- expected: queryResult.expiry_date.range.expected,
1489
- received: [minDate, maxDate],
1490
- message: "Expiry date is not in the expected range",
1491
- }
1492
- }
1493
- }
1494
- if (
1495
- !queryResult.expiry_date.lte &&
1496
- !queryResult.expiry_date.range &&
1497
- maxDate.getTime() != DEFAULT_DATE_VALUE.getTime()
1498
- ) {
1499
- console.warn("Maximum expiry date should be equal to default date value")
1500
- isCorrect = false
1501
- queryResultErrors.expiry_date.disclose = {
1502
- expected: `${DEFAULT_DATE_VALUE.toISOString()}`,
1503
- received: `${maxDate.toISOString()}`,
1504
- message: "Maximum expiry date should be equal to default date value",
1505
- }
1506
- }
1507
- if (
1508
- !queryResult.expiry_date.gte &&
1509
- !queryResult.expiry_date.range &&
1510
- minDate.getTime() != DEFAULT_DATE_VALUE.getTime()
1511
- ) {
1512
- console.warn("Minimum expiry date should be equal to default date value")
1513
- isCorrect = false
1514
- queryResultErrors.expiry_date.disclose = {
1515
- expected: `${DEFAULT_DATE_VALUE.toISOString()}`,
1516
- received: `${minDate.toISOString()}`,
1517
- message: "Minimum expiry date should be equal to default date value",
1518
- }
1519
- }
1520
- } else {
1521
- console.warn("Expiry date is not set in the query result")
1522
- isCorrect = false
1523
- queryResultErrors.expiry_date.disclose = {
1524
- message: "Expiry date is not set in the query result",
1525
- }
1526
- }
1527
- if (
1528
- currentDate.getTime() !== today.getTime() &&
1529
- currentDate.getTime() !== today.getTime() - 86400000
1530
- ) {
1531
- console.warn("Current date in the proof is too old")
1532
- isCorrect = false
1533
- queryResultErrors.age.disclose = {
1534
- expected: `${today.toISOString()}`,
1535
- received: `${currentDate.toISOString()}`,
1536
- message: "Current date in the proof is too old",
1537
- }
1538
- }
1539
- return { isCorrect, queryResultErrors }
1540
- }
1541
-
1542
- private checkNationalityExclusionPublicInputs(queryResult: QueryResult, countryList: string[]) {
1543
- const queryResultErrors: QueryResultErrors = {
1544
- sig_check_dsc: {},
1545
- sig_check_id_data: {},
1546
- data_check_integrity: {},
1547
- disclose: {},
1548
- age: {},
1549
- birthdate: {},
1550
- expiry_date: {},
1551
- document_type: {},
1552
- issuing_country: {},
1553
- gender: {},
1554
- nationality: {},
1555
- firstname: {},
1556
- lastname: {},
1557
- fullname: {},
1558
- document_number: {},
1559
- outer: {},
1560
- bind: {},
1561
- }
1562
- let isCorrect = true
1563
- if (
1564
- queryResult.nationality &&
1565
- queryResult.nationality.out &&
1566
- queryResult.nationality.out.result
1567
- ) {
1568
- if (
1569
- !queryResult.nationality.out.expected?.every((country) => countryList.includes(country))
1570
- ) {
1571
- console.warn("Nationality exclusion list does not match the one from the query results")
1572
- isCorrect = false
1573
- queryResultErrors.nationality.out = {
1574
- expected: queryResult.nationality.out.expected,
1575
- received: countryList,
1576
- message: "Nationality exclusion list does not match the one from the query results",
1577
- }
1578
- }
1579
- } else if (!queryResult.nationality || !queryResult.nationality.out) {
1580
- console.warn("Nationality exclusion is not set in the query result")
1581
- isCorrect = false
1582
- queryResultErrors.nationality.out = {
1583
- message: "Nationality exclusion is not set in the query result",
1584
- }
1585
- }
1586
- // Check the countryList is in ascending order
1587
- // If the prover doesn't use a sorted list then the proof cannot be trusted
1588
- // as it is requirement in the circuit for the exclusion check to work
1589
- for (let i = 1; i < countryList.length; i++) {
1590
- if (countryList[i] < countryList[i - 1]) {
1591
- console.warn(
1592
- "The nationality exclusion list has not been sorted, and thus the proof cannot be trusted",
1593
- )
1594
- isCorrect = false
1595
- queryResultErrors.nationality.out = {
1596
- message:
1597
- "The nationality exclusion list has not been sorted, and thus the proof cannot be trusted",
1598
- }
1599
- }
1600
- }
1601
- return { isCorrect, queryResultErrors }
1602
- }
1603
-
1604
- private checkIssuingCountryExclusionPublicInputs(
1605
- queryResult: QueryResult,
1606
- countryList: string[],
1607
- ) {
1608
- const queryResultErrors: QueryResultErrors = {
1609
- sig_check_dsc: {},
1610
- sig_check_id_data: {},
1611
- data_check_integrity: {},
1612
- disclose: {},
1613
- age: {},
1614
- birthdate: {},
1615
- expiry_date: {},
1616
- document_type: {},
1617
- issuing_country: {},
1618
- gender: {},
1619
- nationality: {},
1620
- firstname: {},
1621
- lastname: {},
1622
- fullname: {},
1623
- document_number: {},
1624
- outer: {},
1625
- bind: {},
1626
- }
1627
- let isCorrect = true
1628
-
1629
- if (
1630
- queryResult.issuing_country &&
1631
- queryResult.issuing_country.out &&
1632
- queryResult.issuing_country.out.result
1633
- ) {
1634
- if (
1635
- !queryResult.issuing_country.out.expected?.every((country) => countryList.includes(country))
1636
- ) {
1637
- console.warn("Issuing country exclusion list does not match the one from the query results")
1638
- isCorrect = false
1639
- queryResultErrors.issuing_country.out = {
1640
- expected: queryResult.issuing_country.out.expected,
1641
- received: countryList,
1642
- message: "Issuing country exclusion list does not match the one from the query results",
1643
- }
1644
- }
1645
- } else if (!queryResult.issuing_country || !queryResult.issuing_country.out) {
1646
- console.warn("Issuing country exclusion is not set in the query result")
1647
- isCorrect = false
1648
- queryResultErrors.issuing_country.out = {
1649
- message: "Issuing country exclusion is not set in the query result",
1650
- }
1651
- }
1652
- // Check the countryList is in ascending order
1653
- // If the prover doesn't use a sorted list then the proof cannot be trusted
1654
- // as it is requirement in the circuit for the exclusion check to work
1655
- for (let i = 1; i < countryList.length; i++) {
1656
- if (countryList[i] < countryList[i - 1]) {
1657
- console.warn(
1658
- "The issuing country exclusion list has not been sorted, and thus the proof cannot be trusted",
1659
- )
1660
- isCorrect = false
1661
- queryResultErrors.issuing_country.out = {
1662
- message:
1663
- "The issuing country exclusion list has not been sorted, and thus the proof cannot be trusted",
1664
- }
1665
- }
1666
- }
1667
- return { isCorrect, queryResultErrors }
1668
- }
1669
-
1670
- private checkNationalityInclusionPublicInputs(queryResult: QueryResult, countryList: string[]) {
1671
- const queryResultErrors: QueryResultErrors = {
1672
- sig_check_dsc: {},
1673
- sig_check_id_data: {},
1674
- data_check_integrity: {},
1675
- disclose: {},
1676
- age: {},
1677
- birthdate: {},
1678
- expiry_date: {},
1679
- document_type: {},
1680
- issuing_country: {},
1681
- gender: {},
1682
- nationality: {},
1683
- firstname: {},
1684
- lastname: {},
1685
- fullname: {},
1686
- document_number: {},
1687
- outer: {},
1688
- bind: {},
1689
- }
1690
- let isCorrect = true
1691
- if (
1692
- queryResult.nationality &&
1693
- queryResult.nationality.in &&
1694
- queryResult.nationality.in.result
1695
- ) {
1696
- if (!queryResult.nationality.in.expected?.every((country) => countryList.includes(country))) {
1697
- console.warn("Nationality inclusion list does not match the one from the query results")
1698
- isCorrect = false
1699
- queryResultErrors.nationality.in = {
1700
- expected: queryResult.nationality.in.expected,
1701
- received: countryList,
1702
- message: "Nationality inclusion list does not match the one from the query results",
1703
- }
1704
- }
1705
- } else if (!queryResult.nationality || !queryResult.nationality.in) {
1706
- console.warn("Nationality inclusion is not set in the query result")
1707
- isCorrect = false
1708
- queryResultErrors.nationality.in = {
1709
- message: "Nationality inclusion is not set in the query result",
1710
- }
1711
- }
1712
- return { isCorrect, queryResultErrors }
1713
- }
1714
-
1715
- private checkIssuingCountryInclusionPublicInputs(
1716
- queryResult: QueryResult,
1717
- countryList: string[],
1718
- ) {
1719
- const queryResultErrors: QueryResultErrors = {
1720
- sig_check_dsc: {},
1721
- sig_check_id_data: {},
1722
- data_check_integrity: {},
1723
- disclose: {},
1724
- age: {},
1725
- birthdate: {},
1726
- expiry_date: {},
1727
- document_type: {},
1728
- issuing_country: {},
1729
- gender: {},
1730
- nationality: {},
1731
- firstname: {},
1732
- lastname: {},
1733
- fullname: {},
1734
- document_number: {},
1735
- outer: {},
1736
- bind: {},
1737
- }
1738
- let isCorrect = true
1739
-
1740
- if (
1741
- queryResult.issuing_country &&
1742
- queryResult.issuing_country.in &&
1743
- queryResult.issuing_country.in.result
1744
- ) {
1745
- if (
1746
- !queryResult.issuing_country.in.expected?.every((country) => countryList.includes(country))
1747
- ) {
1748
- console.warn("Issuing country inclusion list does not match the one from the query results")
1749
- isCorrect = false
1750
- queryResultErrors.issuing_country.in = {
1751
- expected: queryResult.issuing_country.in.expected,
1752
- received: countryList,
1753
- message: "Issuing country inclusion list does not match the one from the query results",
1754
- }
1755
- }
1756
- } else if (!queryResult.issuing_country || !queryResult.issuing_country.in) {
1757
- console.warn("Issuing country inclusion is not set in the query result")
1758
- isCorrect = false
1759
- queryResultErrors.issuing_country.in = {
1760
- message: "Issuing country inclusion is not set in the query result",
1761
- }
1762
- }
1763
- return { isCorrect, queryResultErrors }
1764
- }
1765
-
1766
- private checkScopeFromDisclosureProof(
1767
- proofData: ProofData,
1768
- queryResultErrors: QueryResultErrors,
1769
- key: string,
1770
- scope?: string,
1771
- chainId?: number,
1772
- ) {
1773
- let isCorrect = true
1774
- if (
1775
- this.domain &&
1776
- getServiceScopeHash(this.domain, chainId) !== BigInt(proofData.publicInputs[1])
1777
- ) {
1778
- console.warn("The proof comes from a different domain than the one expected")
1779
- isCorrect = false
1780
- queryResultErrors[key as keyof QueryResultErrors].scope = {
1781
- expected: `Scope: ${getServiceScopeHash(this.domain, chainId).toString()}`,
1782
- received: `Scope: ${BigInt(proofData.publicInputs[1]).toString()}`,
1783
- message: "The proof comes from a different domain than the one expected",
1784
- }
1785
- }
1786
- if (scope && getScopeHash(scope) !== BigInt(proofData.publicInputs[2])) {
1787
- console.warn("The proof uses a different scope than the one expected")
1788
- isCorrect = false
1789
- queryResultErrors[key as keyof QueryResultErrors].scope = {
1790
- expected: `Scope: ${getScopeHash(scope).toString()}`,
1791
- received: `Scope: ${BigInt(proofData.publicInputs[2]).toString()}`,
1792
- message: "The proof uses a different scope than the one expected",
1793
- }
1794
- }
1795
- return { isCorrect, queryResultErrors }
1796
- }
1797
-
1798
- private async checkCertificateRegistryRoot(
1799
- root: string,
1800
- queryResultErrors: any,
1801
- outer?: boolean,
1802
- ) {
1803
- let isCorrect = true
1804
- try {
1805
- // Maintained certificate registry settled onchain
1806
- // Here we use Ethereum Sepolia
1807
- const registryClient = new RegistryClient({ chainId: 11155111 })
1808
- const isValid = await registryClient.isCertificateRootValid(root)
1809
- if (!isValid) {
1810
- console.warn("The ID was signed by an unrecognized root certificate")
1811
- isCorrect = false
1812
- queryResultErrors[outer ? "outer" : "sig_check_dsc"].certificate = {
1813
- expected: `A valid root from ZKPassport Registry`,
1814
- received: `Got invalid certificate registry root: ${root}`,
1815
- message: "The ID was signed by an unrecognized root certificate",
1816
- }
1817
- }
1818
- } catch (error) {
1819
- console.warn(error)
1820
- console.warn("The ID was signed by an unrecognized root certificate")
1821
- isCorrect = false
1822
- queryResultErrors[outer ? "outer" : "sig_check_dsc"].certificate = {
1823
- expected: `A valid root from ZKPassport Registry`,
1824
- received: `Got invalid certificate registry root: ${root}`,
1825
- message: "The ID was signed by an unrecognized root certificate",
1826
- }
1827
- }
1828
- return { isCorrect, queryResultErrors }
1829
- }
1830
-
1831
- private async checkCircuitRegistryRoot(root: string, queryResultErrors: any) {
1832
- let isCorrect = true
1833
- try {
1834
- const registryClient = new RegistryClient({ chainId: 11155111 })
1835
- const isValid = await registryClient.isCircuitRootValid(root)
1836
- if (!isValid) {
1837
- console.warn("The proof uses unrecognized circuits")
1838
- isCorrect = false
1839
- queryResultErrors.outer.circuit = {
1840
- expected: `A valid circuit from ZKPassport Registry`,
1841
- received: `Got invalid circuit registry root: ${root}`,
1842
- message: "The proof uses an unrecognized circuit",
1843
- }
1844
- }
1845
- } catch (error) {
1846
- console.warn(error)
1847
- console.warn("The proof uses unrecognized circuits")
1848
- isCorrect = false
1849
- queryResultErrors.outer.circuit = {
1850
- expected: `A valid circuit from ZKPassport Registry`,
1851
- received: `Got invalid circuit registry root: ${root}`,
1852
- message: "The proof uses an unrecognized circuit",
1853
- }
1854
- }
1855
- return { isCorrect, queryResultErrors }
1856
- }
1857
-
1858
- private checkBindPublicInputs(queryResult: QueryResult, boundData: BoundData) {
1859
- const queryResultErrors: QueryResultErrors = {
1860
- sig_check_dsc: {},
1861
- sig_check_id_data: {},
1862
- data_check_integrity: {},
1863
- disclose: {},
1864
- age: {},
1865
- birthdate: {},
1866
- expiry_date: {},
1867
- document_type: {},
1868
- issuing_country: {},
1869
- gender: {},
1870
- nationality: {},
1871
- firstname: {},
1872
- lastname: {},
1873
- fullname: {},
1874
- document_number: {},
1875
- outer: {},
1876
- bind: {},
1877
- }
1878
- let isCorrect = true
1879
-
1880
- if (queryResult.bind) {
1881
- if (
1882
- queryResult.bind.user_address?.toLowerCase().replace("0x", "") !==
1883
- boundData.user_address?.toLowerCase().replace("0x", "")
1884
- ) {
1885
- console.warn("Bound user address does not match the one from the query results")
1886
- isCorrect = false
1887
- queryResultErrors.bind.eq = {
1888
- expected: queryResult.bind.user_address,
1889
- received: boundData.user_address,
1890
- message: "Bound user address does not match the one from the query results",
1891
- }
1892
- }
1893
- if (
1894
- queryResult.bind.custom_data?.trim().toLowerCase() !==
1895
- boundData.custom_data?.trim().toLowerCase()
1896
- ) {
1897
- console.warn("Bound custom data does not match the one from the query results")
1898
- isCorrect = false
1899
- queryResultErrors.bind.eq = {
1900
- expected: queryResult.bind.custom_data,
1901
- received: boundData.custom_data,
1902
- message: "Bound custom data does not match the one from the query results",
1903
- }
1904
- }
1905
- }
1906
- return { isCorrect, queryResultErrors }
1907
- }
1908
-
1909
- private async checkPublicInputs(
1910
- proofs: Array<ProofResult>,
1911
- queryResult: QueryResult,
1912
- validity?: number,
1913
- scope?: string,
1914
- chainId?: number,
1915
- ) {
1916
- let commitmentIn: bigint | undefined
1917
- let commitmentOut: bigint | undefined
1918
- let isCorrect = true
1919
- let uniqueIdentifier: string | undefined
1920
- const currentTime = new Date()
1921
- const today = new Date(
1922
- currentTime.getFullYear(),
1923
- currentTime.getMonth(),
1924
- currentTime.getDate(),
1925
- 0,
1926
- 0,
1927
- 0,
1928
- 0,
1929
- )
1930
- let queryResultErrors: QueryResultErrors = {
1931
- sig_check_dsc: {},
1932
- sig_check_id_data: {},
1933
- data_check_integrity: {},
1934
- disclose: {},
1935
- age: {},
1936
- birthdate: {},
1937
- expiry_date: {},
1938
- document_type: {},
1939
- issuing_country: {},
1940
- gender: {},
1941
- nationality: {},
1942
- firstname: {},
1943
- lastname: {},
1944
- fullname: {},
1945
- document_number: {},
1946
- outer: {},
1947
- bind: {},
1948
- }
1949
-
1950
- // Since the order is important for the commitments, we need to sort the proofs
1951
- // by their expected order: root signature check -> ID signature check -> integrity check -> disclosure
1952
- const sortedProofs = proofs.sort((a, b) => {
1953
- const proofOrder = [
1954
- "sig_check_dsc",
1955
- "sig_check_id_data",
1956
- "data_check_integrity",
1957
- "disclose_bytes",
1958
- "compare_age",
1959
- "compare_birthdate",
1960
- "compare_expiry",
1961
- "exclusion_check_nationality",
1962
- "inclusion_check_nationality",
1963
- "exclusion_check_issuing_country",
1964
- "inclusion_check_issuing_country",
1965
- "bind",
1966
- ]
1967
- const getIndex = (proof: ProofResult) => {
1968
- const name = proof.name || ""
1969
- return proofOrder.findIndex((p) => name.startsWith(p))
1970
- }
1971
- return getIndex(a) - getIndex(b)
1972
- })
1973
-
1974
- for (const proof of sortedProofs!) {
1975
- const proofData = getProofData(proof.proof as string, getNumberOfPublicInputs(proof.name!))
1976
- if (proof.name?.startsWith("outer")) {
1977
- const isForEVM = proof.name?.startsWith("outer_evm")
1978
- const certificateRegistryRoot = getCertificateRegistryRootFromOuterProof(proofData)
1979
- const {
1980
- isCorrect: isCorrectCertificateRegistryRoot,
1981
- queryResultErrors: queryResultErrorsCertificateRegistryRoot,
1982
- } = await this.checkCertificateRegistryRoot(
1983
- certificateRegistryRoot.toString(16),
1984
- queryResultErrors,
1985
- true,
1986
- )
1987
- isCorrect = isCorrect && isCorrectCertificateRegistryRoot
1988
- queryResultErrors = {
1989
- ...queryResultErrors,
1990
- ...queryResultErrorsCertificateRegistryRoot,
1991
- }
1992
-
1993
- const circuitRegistryRoot = getCircuitRegistryRootFromOuterProof(proofData)
1994
- const {
1995
- isCorrect: isCorrectCircuitRegistryRoot,
1996
- queryResultErrors: queryResultErrorsCircuitRegistryRoot,
1997
- } = await this.checkCircuitRegistryRoot(circuitRegistryRoot.toString(16), queryResultErrors)
1998
- isCorrect = isCorrect && isCorrectCircuitRegistryRoot
1999
- queryResultErrors = {
2000
- ...queryResultErrors,
2001
- ...queryResultErrorsCircuitRegistryRoot,
2002
- }
2003
-
2004
- const currentDate = getCurrentDateFromOuterProof(proofData)
2005
- const todayToCurrentDate = today.getTime() - currentDate.getTime()
2006
- const differenceInDays = validity ?? 180
2007
- const expectedDifference = differenceInDays * 86400000
2008
- const actualDifference = today.getTime() - (today.getTime() - expectedDifference)
2009
- // The ID should not expire within the next 6 months (or whatever the custom value is)
2010
- if (todayToCurrentDate >= actualDifference) {
2011
- console.warn(
2012
- `The date used to check the validity of the ID is older than ${differenceInDays} days. You can ask the user to rescan their ID or ask them to disclose their expiry date`,
2013
- )
2014
- isCorrect = false
2015
- queryResultErrors.outer.date = {
2016
- expected: `Difference: ${differenceInDays} days`,
2017
- received: `Difference: ${Math.round(todayToCurrentDate / 86400000)} days`,
2018
- message:
2019
- "The date used to check the validity of the ID is older than the validity period",
2020
- }
2021
- }
2022
- const paramCommitments = getParamCommitmentsFromOuterProof(proofData)
2023
- const committedInputs = proof.committedInputs
2024
- const keysInCommittedInputs = Object.keys(committedInputs || {})
2025
- if (keysInCommittedInputs.length !== paramCommitments.length) {
2026
- console.warn("The proof does not verify all the requested conditions and information")
2027
- isCorrect = false
2028
- queryResultErrors.outer.commitment = {
2029
- expected: `Number of parameter commitments: ${paramCommitments.length}`,
2030
- received: `Number of disclosure proofs provided: ${keysInCommittedInputs.length}`,
2031
- message: "The proof does not verify all the requested conditions and information",
2032
- }
2033
- }
2034
- if (
2035
- this.domain &&
2036
- getServiceScopeHash(this.domain, chainId) !== getScopeFromOuterProof(proofData)
2037
- ) {
2038
- console.warn("The proof comes from a different domain than the one expected")
2039
- isCorrect = false
2040
- queryResultErrors.outer.scope = {
2041
- expected: `Scope: ${getServiceScopeHash(this.domain, chainId).toString()}`,
2042
- received: `Scope: ${getScopeFromOuterProof(proofData).toString()}`,
2043
- message: "The proof comes from a different domain than the one expected",
2044
- }
2045
- }
2046
- if (scope && getScopeHash(scope) !== getSubscopeFromOuterProof(proofData)) {
2047
- console.warn("The proof uses a different scope than the one expected")
2048
- isCorrect = false
2049
- queryResultErrors.outer.scope = {
2050
- expected: `Scope: ${getScopeHash(scope).toString()}`,
2051
- received: `Scope: ${getSubscopeFromOuterProof(proofData).toString()}`,
2052
- message: "The proof uses a different scope than the one expected",
2053
- }
2054
- }
2055
- if (!!committedInputs?.compare_age) {
2056
- const ageCommittedInputs = committedInputs?.compare_age as AgeCommittedInputs
2057
- const ageParameterCommitment = isForEVM
2058
- ? await getAgeEVMParameterCommitment(
2059
- ageCommittedInputs.currentDate,
2060
- ageCommittedInputs.minAge,
2061
- ageCommittedInputs.maxAge,
2062
- )
2063
- : await getAgeParameterCommitment(
2064
- ageCommittedInputs.currentDate,
2065
- ageCommittedInputs.minAge,
2066
- ageCommittedInputs.maxAge,
2067
- )
2068
- if (!paramCommitments.includes(ageParameterCommitment)) {
2069
- console.warn("This proof does not verify the age")
2070
- isCorrect = false
2071
- queryResultErrors.age.commitment = {
2072
- expected: `Age parameter commitment: ${ageParameterCommitment.toString()}`,
2073
- received: `Parameter commitments included: ${paramCommitments.join(", ")}`,
2074
- message: "This proof does not verify the age",
2075
- }
2076
- }
2077
- const { isCorrect: isCorrectAge, queryResultErrors: queryResultErrorsAge } =
2078
- this.checkAgePublicInputs(proof, queryResult)
2079
- isCorrect = isCorrect && isCorrectAge
2080
- queryResultErrors = {
2081
- ...queryResultErrors,
2082
- ...queryResultErrorsAge,
2083
- }
2084
- } else if (!!committedInputs?.compare_birthdate) {
2085
- const birthdateCommittedInputs = committedInputs?.compare_birthdate as DateCommittedInputs
2086
- const birthdateParameterCommitment = isForEVM
2087
- ? await getDateEVMParameterCommitment(
2088
- ProofType.BIRTHDATE,
2089
- birthdateCommittedInputs.currentDate,
2090
- birthdateCommittedInputs.minDate,
2091
- birthdateCommittedInputs.maxDate,
2092
- )
2093
- : await getDateParameterCommitment(
2094
- ProofType.BIRTHDATE,
2095
- birthdateCommittedInputs.currentDate,
2096
- birthdateCommittedInputs.minDate,
2097
- birthdateCommittedInputs.maxDate,
2098
- )
2099
- if (!paramCommitments.includes(birthdateParameterCommitment)) {
2100
- console.warn("This proof does not verify the birthdate")
2101
- isCorrect = false
2102
- queryResultErrors.birthdate.commitment = {
2103
- expected: `Birthdate parameter commitment: ${birthdateParameterCommitment.toString()}`,
2104
- received: `Parameter commitments included: ${paramCommitments.join(", ")}`,
2105
- message: "This proof does not verify the birthdate",
2106
- }
2107
- }
2108
- const { isCorrect: isCorrectBirthdate, queryResultErrors: queryResultErrorsBirthdate } =
2109
- this.checkBirthdatePublicInputs(proof, queryResult)
2110
- isCorrect = isCorrect && isCorrectBirthdate
2111
- queryResultErrors = {
2112
- ...queryResultErrors,
2113
- ...queryResultErrorsBirthdate,
2114
- }
2115
- } else if (!!committedInputs?.compare_expiry) {
2116
- const expiryCommittedInputs = committedInputs?.compare_expiry as DateCommittedInputs
2117
- const expiryParameterCommitment = isForEVM
2118
- ? await getDateEVMParameterCommitment(
2119
- ProofType.EXPIRY_DATE,
2120
- expiryCommittedInputs.currentDate,
2121
- expiryCommittedInputs.minDate,
2122
- expiryCommittedInputs.maxDate,
2123
- )
2124
- : await getDateParameterCommitment(
2125
- ProofType.EXPIRY_DATE,
2126
- expiryCommittedInputs.currentDate,
2127
- expiryCommittedInputs.minDate,
2128
- expiryCommittedInputs.maxDate,
2129
- )
2130
- if (!paramCommitments.includes(expiryParameterCommitment)) {
2131
- console.warn("This proof does not verify the expiry date")
2132
- isCorrect = false
2133
- queryResultErrors.expiry_date.commitment = {
2134
- expected: `Expiry date parameter commitment: ${expiryParameterCommitment.toString()}`,
2135
- received: `Parameter commitments included: ${paramCommitments.join(", ")}`,
2136
- message: "This proof does not verify the expiry date",
2137
- }
2138
- }
2139
- const { isCorrect: isCorrectExpiryDate, queryResultErrors: queryResultErrorsExpiryDate } =
2140
- this.checkExpiryDatePublicInputs(proof, queryResult)
2141
- isCorrect = isCorrect && isCorrectExpiryDate
2142
- queryResultErrors = {
2143
- ...queryResultErrors,
2144
- ...queryResultErrorsExpiryDate,
2145
- }
2146
- } else if (!!committedInputs?.disclose_bytes) {
2147
- const discloseCommittedInputs = committedInputs?.disclose_bytes as DiscloseCommittedInputs
2148
- const discloseParameterCommitment = isForEVM
2149
- ? await getDiscloseEVMParameterCommitment(
2150
- discloseCommittedInputs.discloseMask,
2151
- discloseCommittedInputs.disclosedBytes,
2152
- )
2153
- : await getDiscloseParameterCommitment(
2154
- discloseCommittedInputs.discloseMask,
2155
- discloseCommittedInputs.disclosedBytes,
2156
- )
2157
- if (!paramCommitments.includes(discloseParameterCommitment)) {
2158
- console.warn("This proof does not verify any of the data disclosed")
2159
- isCorrect = false
2160
- queryResultErrors.disclose.commitment = {
2161
- expected: `Disclosure parameter commitment: ${discloseParameterCommitment.toString()}`,
2162
- received: `Parameter commitments included: ${paramCommitments.join(", ")}`,
2163
- message: "This proof does not verify any of the data disclosed",
2164
- }
2165
- }
2166
- const { isCorrect: isCorrectDisclose, queryResultErrors: queryResultErrorsDisclose } =
2167
- this.checkDiscloseBytesPublicInputs(proof, queryResult)
2168
- isCorrect = isCorrect && isCorrectDisclose
2169
- queryResultErrors = {
2170
- ...queryResultErrors,
2171
- ...queryResultErrorsDisclose,
2172
- }
2173
- } else if (!!committedInputs?.inclusion_check_nationality) {
2174
- const inclusionCheckNationalityCommittedInputs =
2175
- committedInputs?.inclusion_check_nationality as CountryCommittedInputs
2176
- const inclusionCheckNationalityParameterCommitment = isForEVM
2177
- ? await getCountryEVMParameterCommitment(
2178
- ProofType.NATIONALITY_INCLUSION,
2179
- inclusionCheckNationalityCommittedInputs.countries,
2180
- )
2181
- : await getCountryParameterCommitment(
2182
- ProofType.NATIONALITY_INCLUSION,
2183
- inclusionCheckNationalityCommittedInputs.countries,
2184
- )
2185
- if (!paramCommitments.includes(inclusionCheckNationalityParameterCommitment)) {
2186
- console.warn("This proof does not verify the inclusion of the nationality")
2187
- isCorrect = false
2188
- queryResultErrors.nationality.commitment = {
2189
- expected: `Nationality parameter commitment: ${inclusionCheckNationalityParameterCommitment.toString()}`,
2190
- received: `Parameter commitments included: ${paramCommitments.join(", ")}`,
2191
- message: "This proof does not verify the inclusion of the nationality",
2192
- }
2193
- }
2194
- const countryList = inclusionCheckNationalityCommittedInputs.countries
2195
- const {
2196
- isCorrect: isCorrectNationalityInclusion,
2197
- queryResultErrors: queryResultErrorsNationalityInclusion,
2198
- } = this.checkNationalityInclusionPublicInputs(queryResult, countryList)
2199
- isCorrect = isCorrect && isCorrectNationalityInclusion
2200
- queryResultErrors = {
2201
- ...queryResultErrors,
2202
- ...queryResultErrorsNationalityInclusion,
2203
- }
2204
- } else if (!!committedInputs?.inclusion_check_issuing_country) {
2205
- const inclusionCheckIssuingCountryCommittedInputs =
2206
- committedInputs?.inclusion_check_issuing_country as CountryCommittedInputs
2207
- const inclusionCheckIssuingCountryParameterCommitment = isForEVM
2208
- ? await getCountryEVMParameterCommitment(
2209
- ProofType.ISSUING_COUNTRY_INCLUSION,
2210
- inclusionCheckIssuingCountryCommittedInputs.countries,
2211
- )
2212
- : await getCountryParameterCommitment(
2213
- ProofType.ISSUING_COUNTRY_INCLUSION,
2214
- inclusionCheckIssuingCountryCommittedInputs.countries,
2215
- )
2216
- if (!paramCommitments.includes(inclusionCheckIssuingCountryParameterCommitment)) {
2217
- console.warn("This proof does not verify the inclusion of the issuing country")
2218
- isCorrect = false
2219
- queryResultErrors.issuing_country.commitment = {
2220
- expected: `Issuing country parameter commitment: ${inclusionCheckIssuingCountryParameterCommitment.toString()}`,
2221
- received: `Parameter commitments included: ${paramCommitments.join(", ")}`,
2222
- message: "This proof does not verify the inclusion of the issuing country",
2223
- }
2224
- }
2225
- const countryList = inclusionCheckIssuingCountryCommittedInputs.countries
2226
- const {
2227
- isCorrect: isCorrectIssuingCountryInclusion,
2228
- queryResultErrors: queryResultErrorsIssuingCountryInclusion,
2229
- } = this.checkIssuingCountryInclusionPublicInputs(queryResult, countryList)
2230
- isCorrect = isCorrect && isCorrectIssuingCountryInclusion
2231
- queryResultErrors = {
2232
- ...queryResultErrors,
2233
- ...queryResultErrorsIssuingCountryInclusion,
2234
- }
2235
- } else if (!!committedInputs?.exclusion_check_nationality) {
2236
- const exclusionCheckNationalityCommittedInputs =
2237
- committedInputs?.exclusion_check_nationality as CountryCommittedInputs
2238
- const exclusionCheckNationalityParameterCommitment = isForEVM
2239
- ? await getCountryEVMParameterCommitment(
2240
- ProofType.NATIONALITY_EXCLUSION,
2241
- exclusionCheckNationalityCommittedInputs.countries,
2242
- )
2243
- : await getCountryParameterCommitment(
2244
- ProofType.NATIONALITY_EXCLUSION,
2245
- exclusionCheckNationalityCommittedInputs.countries,
2246
- )
2247
- if (!paramCommitments.includes(exclusionCheckNationalityParameterCommitment)) {
2248
- console.warn("This proof does not verify the exclusion of the nationality")
2249
- isCorrect = false
2250
- queryResultErrors.nationality.commitment = {
2251
- expected: `Nationality parameter commitment: ${exclusionCheckNationalityParameterCommitment.toString()}`,
2252
- received: `Parameter commitments included: ${paramCommitments.join(", ")}`,
2253
- message: "This proof does not verify the exclusion of the nationality",
2254
- }
2255
- }
2256
- const countryList = exclusionCheckNationalityCommittedInputs.countries
2257
- const {
2258
- isCorrect: isCorrectNationalityExclusion,
2259
- queryResultErrors: queryResultErrorsNationalityExclusion,
2260
- } = this.checkNationalityExclusionPublicInputs(queryResult, countryList)
2261
- isCorrect = isCorrect && isCorrectNationalityExclusion
2262
- queryResultErrors = {
2263
- ...queryResultErrors,
2264
- ...queryResultErrorsNationalityExclusion,
2265
- }
2266
- } else if (!!committedInputs?.exclusion_check_issuing_country) {
2267
- const exclusionCheckIssuingCountryCommittedInputs =
2268
- committedInputs?.exclusion_check_issuing_country as CountryCommittedInputs
2269
- const exclusionCheckIssuingCountryParameterCommitment = isForEVM
2270
- ? await getCountryEVMParameterCommitment(
2271
- ProofType.ISSUING_COUNTRY_EXCLUSION,
2272
- exclusionCheckIssuingCountryCommittedInputs.countries,
2273
- )
2274
- : await getCountryParameterCommitment(
2275
- ProofType.ISSUING_COUNTRY_EXCLUSION,
2276
- exclusionCheckIssuingCountryCommittedInputs.countries,
2277
- )
2278
- if (!paramCommitments.includes(exclusionCheckIssuingCountryParameterCommitment)) {
2279
- console.warn("This proof does not verify the exclusion of the issuing country")
2280
- isCorrect = false
2281
- queryResultErrors.issuing_country.commitment = {
2282
- expected: `Issuing country parameter commitment: ${exclusionCheckIssuingCountryParameterCommitment.toString()}`,
2283
- received: `Parameter commitments included: ${paramCommitments.join(", ")}`,
2284
- message: "This proof does not verify the exclusion of the issuing country",
2285
- }
2286
- }
2287
- const countryList = exclusionCheckIssuingCountryCommittedInputs.countries
2288
- const {
2289
- isCorrect: isCorrectIssuingCountryExclusion,
2290
- queryResultErrors: queryResultErrorsIssuingCountryExclusion,
2291
- } = this.checkIssuingCountryExclusionPublicInputs(queryResult, countryList)
2292
- isCorrect = isCorrect && isCorrectIssuingCountryExclusion
2293
- queryResultErrors = {
2294
- ...queryResultErrors,
2295
- ...queryResultErrorsIssuingCountryExclusion,
2296
- }
2297
- } else if (!!committedInputs?.bind) {
2298
- const bindCommittedInputs = committedInputs?.bind as BindCommittedInputs
2299
- const bindParameterCommitment = isForEVM
2300
- ? await getBindEVMParameterCommitment(formatBoundData(bindCommittedInputs.data))
2301
- : await getBindParameterCommitment(formatBoundData(bindCommittedInputs.data))
2302
- if (!paramCommitments.includes(bindParameterCommitment)) {
2303
- console.warn("This proof does not verify the bound data")
2304
- isCorrect = false
2305
- queryResultErrors.bind.commitment = {
2306
- expected: `Bind parameter commitment: ${bindParameterCommitment.toString()}`,
2307
- received: `Parameter commitments included: ${paramCommitments.join(", ")}`,
2308
- message: "This proof does not verify the bound data",
2309
- }
2310
- }
2311
- const { isCorrect: isCorrectBind, queryResultErrors: queryResultErrorsBind } =
2312
- this.checkBindPublicInputs(queryResult, bindCommittedInputs.data)
2313
- isCorrect = isCorrect && isCorrectBind
2314
- queryResultErrors = {
2315
- ...queryResultErrors,
2316
- ...queryResultErrorsBind,
2317
- }
2318
- }
2319
- uniqueIdentifier = getNullifierFromOuterProof(proofData).toString(10)
2320
- } else if (proof.name?.startsWith("sig_check_dsc")) {
2321
- commitmentOut = getCommitmentFromDSCProof(proofData)
2322
- const merkleRoot = getMerkleRootFromDSCProof(proofData)
2323
- const {
2324
- isCorrect: isCorrectCertificateRegistryRoot,
2325
- queryResultErrors: queryResultErrorsCertificateRegistryRoot,
2326
- } = await this.checkCertificateRegistryRoot(
2327
- merkleRoot.toString(16),
2328
- queryResultErrors,
2329
- false,
2330
- )
2331
- isCorrect = isCorrect && isCorrectCertificateRegistryRoot
2332
- queryResultErrors = {
2333
- ...queryResultErrors,
2334
- ...queryResultErrorsCertificateRegistryRoot,
2335
- }
2336
- } else if (proof.name?.startsWith("sig_check_id_data")) {
2337
- commitmentIn = getCommitmentInFromIDDataProof(proofData)
2338
- if (commitmentIn !== commitmentOut) {
2339
- console.warn(
2340
- "Failed to check the link between the certificate signature and ID signature",
2341
- )
2342
- isCorrect = false
2343
- queryResultErrors.sig_check_id_data.commitment = {
2344
- expected: `Commitment: ${commitmentOut?.toString() || "undefined"}`,
2345
- received: `Commitment: ${commitmentIn?.toString() || "undefined"}`,
2346
- message: "Failed to check the link between the certificate signature and ID signature",
2347
- }
2348
- }
2349
- commitmentOut = getCommitmentOutFromIDDataProof(proofData)
2350
- } else if (proof.name?.startsWith("data_check_integrity")) {
2351
- commitmentIn = getCommitmentInFromIntegrityProof(proofData)
2352
- if (commitmentIn !== commitmentOut) {
2353
- console.warn("Failed to check the link between the ID signature and the data signed")
2354
- isCorrect = false
2355
- queryResultErrors.data_check_integrity.commitment = {
2356
- expected: `Commitment: ${commitmentOut?.toString() || "undefined"}`,
2357
- received: `Commitment: ${commitmentIn?.toString() || "undefined"}`,
2358
- message: "Failed to check the link between the ID signature and the data signed",
2359
- }
2360
- }
2361
- commitmentOut = getCommitmentOutFromIntegrityProof(proofData)
2362
- const currentDate = getCurrentDateFromIntegrityProof(proofData)
2363
- const todayToCurrentDate = today.getTime() - currentDate.getTime()
2364
- const differenceInDays = validity ?? 180
2365
- const expectedDifference = differenceInDays * 86400000
2366
- const actualDifference = today.getTime() - (today.getTime() - expectedDifference)
2367
- // The ID should not expire within the next 6 months (or whatever the custom value is)
2368
- if (todayToCurrentDate >= actualDifference) {
2369
- console.warn(
2370
- `The date used to check the validity of the ID is older than ${differenceInDays} days. You can ask the user to rescan their ID or ask them to disclose their expiry date`,
2371
- )
2372
- isCorrect = false
2373
- queryResultErrors.data_check_integrity.date = {
2374
- expected: `Difference: ${differenceInDays} days`,
2375
- received: `Difference: ${Math.round(todayToCurrentDate / 86400000)} days`,
2376
- message:
2377
- "The date used to check the validity of the ID is older than the validity period",
2378
- }
2379
- }
2380
- } else if (proof.name === "disclose_bytes") {
2381
- commitmentIn = getCommitmentInFromDisclosureProof(proofData)
2382
- if (commitmentIn !== commitmentOut) {
2383
- console.warn(
2384
- "Failed to check the link between the validity of the ID and the data to disclose",
2385
- )
2386
- isCorrect = false
2387
- queryResultErrors.disclose.commitment = {
2388
- expected: `Commitment: ${commitmentOut?.toString() || "undefined"}`,
2389
- received: `Commitment: ${commitmentIn?.toString() || "undefined"}`,
2390
- message:
2391
- "Failed to check the link between the validity of the ID and the data to disclose",
2392
- }
2393
- }
2394
- const paramCommitment = getParameterCommitmentFromDisclosureProof(proofData)
2395
- const calculatedParamCommitment = await getDiscloseParameterCommitment(
2396
- (proof.committedInputs?.disclose_bytes as DiscloseCommittedInputs).discloseMask!,
2397
- (proof.committedInputs?.disclose_bytes as DiscloseCommittedInputs).disclosedBytes!,
2398
- )
2399
- if (paramCommitment !== calculatedParamCommitment) {
2400
- console.warn("The disclosed data does not match the data committed by the proof")
2401
- isCorrect = false
2402
- queryResultErrors.disclose.commitment = {
2403
- expected: `Commitment: ${calculatedParamCommitment}`,
2404
- received: `Commitment: ${paramCommitment}`,
2405
- message: "The disclosed data does not match the data committed by the proof",
2406
- }
2407
- }
2408
- const { isCorrect: isCorrectScope, queryResultErrors: queryResultErrorsScope } =
2409
- this.checkScopeFromDisclosureProof(proofData, queryResultErrors, "disclose", scope)
2410
- isCorrect = isCorrect && isCorrectScope
2411
- queryResultErrors = {
2412
- ...queryResultErrors,
2413
- ...queryResultErrorsScope,
2414
- }
2415
- const { isCorrect: isCorrectDisclose, queryResultErrors: queryResultErrorsDisclose } =
2416
- this.checkDiscloseBytesPublicInputs(proof, queryResult)
2417
- isCorrect = isCorrect && isCorrectDisclose && isCorrectScope
2418
- queryResultErrors = {
2419
- ...queryResultErrors,
2420
- ...queryResultErrorsDisclose,
2421
- ...queryResultErrorsScope,
2422
- }
2423
- uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10)
2424
- } else if (proof.name === "compare_age") {
2425
- commitmentIn = getCommitmentInFromDisclosureProof(proofData)
2426
- if (commitmentIn !== commitmentOut) {
2427
- console.warn(
2428
- "Failed to check the link between the validity of the ID and the age derived from it",
2429
- )
2430
- isCorrect = false
2431
- queryResultErrors.age.commitment = {
2432
- expected: `Commitment: ${commitmentOut}`,
2433
- received: `Commitment: ${commitmentIn}`,
2434
- message:
2435
- "Failed to check the link between the validity of the ID and the age derived from it",
2436
- }
2437
- }
2438
- const paramCommitment = getParameterCommitmentFromDisclosureProof(proofData)
2439
- const committedInputs = proof.committedInputs?.compare_age as AgeCommittedInputs
2440
- const calculatedParamCommitment = await getAgeParameterCommitment(
2441
- committedInputs.currentDate,
2442
- committedInputs.minAge,
2443
- committedInputs.maxAge,
2444
- )
2445
- if (paramCommitment !== calculatedParamCommitment) {
2446
- console.warn(
2447
- "The conditions for the age check do not match the conditions checked by the proof",
2448
- )
2449
- isCorrect = false
2450
- queryResultErrors.age.commitment = {
2451
- expected: `Commitment: ${calculatedParamCommitment}`,
2452
- received: `Commitment: ${paramCommitment}`,
2453
- message:
2454
- "The conditions for the age check do not match the conditions checked by the proof",
2455
- }
2456
- }
2457
- const { isCorrect: isCorrectScope, queryResultErrors: queryResultErrorsScope } =
2458
- this.checkScopeFromDisclosureProof(proofData, queryResultErrors, "age", scope)
2459
- const { isCorrect: isCorrectAge, queryResultErrors: queryResultErrorsAge } =
2460
- this.checkAgePublicInputs(proof, queryResult)
2461
- isCorrect = isCorrect && isCorrectAge && isCorrectScope
2462
- queryResultErrors = {
2463
- ...queryResultErrors,
2464
- ...queryResultErrorsAge,
2465
- ...queryResultErrorsScope,
2466
- }
2467
- uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10)
2468
- } else if (proof.name === "compare_birthdate") {
2469
- commitmentIn = getCommitmentInFromDisclosureProof(proofData)
2470
- if (commitmentIn !== commitmentOut) {
2471
- console.warn(
2472
- "Failed to check the link between the validity of the ID and the birthdate derived from it",
2473
- )
2474
- isCorrect = false
2475
- queryResultErrors.birthdate.commitment = {
2476
- expected: `Commitment: ${commitmentOut}`,
2477
- received: `Commitment: ${commitmentIn}`,
2478
- message:
2479
- "Failed to check the link between the validity of the ID and the birthdate derived from it",
2480
- }
2481
- }
2482
- const paramCommitment = getParameterCommitmentFromDisclosureProof(proofData)
2483
- const committedInputs = proof.committedInputs?.compare_birthdate as DateCommittedInputs
2484
- const calculatedParamCommitment = await getDateParameterCommitment(
2485
- ProofType.BIRTHDATE,
2486
- committedInputs.currentDate,
2487
- committedInputs.minDate,
2488
- committedInputs.maxDate,
2489
- )
2490
- if (paramCommitment !== calculatedParamCommitment) {
2491
- console.warn(
2492
- "The conditions for the birthdate check do not match the conditions checked by the proof",
2493
- )
2494
- isCorrect = false
2495
- queryResultErrors.birthdate.commitment = {
2496
- expected: `Commitment: ${calculatedParamCommitment}`,
2497
- received: `Commitment: ${paramCommitment}`,
2498
- message:
2499
- "The conditions for the birthdate check do not match the conditions checked by the proof",
2500
- }
2501
- }
2502
- const { isCorrect: isCorrectScope, queryResultErrors: queryResultErrorsScope } =
2503
- this.checkScopeFromDisclosureProof(proofData, queryResultErrors, "birthdate", scope)
2504
- const { isCorrect: isCorrectBirthdate, queryResultErrors: queryResultErrorsBirthdate } =
2505
- this.checkBirthdatePublicInputs(proof, queryResult)
2506
- isCorrect = isCorrect && isCorrectBirthdate && isCorrectScope
2507
- queryResultErrors = {
2508
- ...queryResultErrors,
2509
- ...queryResultErrorsBirthdate,
2510
- ...queryResultErrorsScope,
2511
- }
2512
- uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10)
2513
- } else if (proof.name === "compare_expiry") {
2514
- commitmentIn = getCommitmentInFromDisclosureProof(proofData)
2515
- if (commitmentIn !== commitmentOut) {
2516
- console.warn(
2517
- "Failed to check the link between the validity of the ID and its expiry date",
2518
- )
2519
- isCorrect = false
2520
- queryResultErrors.expiry_date.commitment = {
2521
- expected: `Commitment: ${commitmentOut}`,
2522
- received: `Commitment: ${commitmentIn}`,
2523
- message: "Failed to check the link between the validity of the ID and its expiry date",
2524
- }
2525
- }
2526
- const paramCommitment = getParameterCommitmentFromDisclosureProof(proofData)
2527
- const committedInputs = proof.committedInputs?.compare_expiry as DateCommittedInputs
2528
- const calculatedParamCommitment = await getDateParameterCommitment(
2529
- ProofType.EXPIRY_DATE,
2530
- committedInputs.currentDate,
2531
- committedInputs.minDate,
2532
- committedInputs.maxDate,
2533
- )
2534
- if (paramCommitment !== calculatedParamCommitment) {
2535
- console.warn(
2536
- "The conditions for the expiry date check do not match the conditions checked by the proof",
2537
- )
2538
- isCorrect = false
2539
- queryResultErrors.expiry_date.commitment = {
2540
- expected: `Commitment: ${calculatedParamCommitment}`,
2541
- received: `Commitment: ${paramCommitment}`,
2542
- message:
2543
- "The conditions for the expiry date check do not match the conditions checked by the proof",
2544
- }
2545
- }
2546
- const { isCorrect: isCorrectScope, queryResultErrors: queryResultErrorsScope } =
2547
- this.checkScopeFromDisclosureProof(proofData, queryResultErrors, "expiry_date", scope)
2548
- const { isCorrect: isCorrectExpiryDate, queryResultErrors: queryResultErrorsExpiryDate } =
2549
- this.checkExpiryDatePublicInputs(proof, queryResult)
2550
- isCorrect = isCorrect && isCorrectExpiryDate && isCorrectScope
2551
- queryResultErrors = {
2552
- ...queryResultErrors,
2553
- ...queryResultErrorsExpiryDate,
2554
- ...queryResultErrorsScope,
2555
- }
2556
- uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10)
2557
- } else if (proof.name === "exclusion_check_nationality") {
2558
- commitmentIn = getCommitmentInFromDisclosureProof(proofData)
2559
- if (commitmentIn !== commitmentOut) {
2560
- console.warn(
2561
- "Failed to check the link between the validity of the ID and the nationality exclusion check",
2562
- )
2563
- isCorrect = false
2564
- queryResultErrors.nationality.commitment = {
2565
- expected: `Commitment: ${commitmentOut}`,
2566
- received: `Commitment: ${commitmentIn}`,
2567
- message:
2568
- "Failed to check the link between the validity of the ID and the nationality exclusion check",
2569
- }
2570
- }
2571
- const countryList = (
2572
- proof.committedInputs?.exclusion_check_nationality as CountryCommittedInputs
2573
- ).countries
2574
- const paramCommittment = getParameterCommitmentFromDisclosureProof(proofData)
2575
- const calculatedParamCommitment = await getCountryParameterCommitment(
2576
- ProofType.NATIONALITY_EXCLUSION,
2577
- countryList,
2578
- true,
2579
- )
2580
- if (paramCommittment !== calculatedParamCommitment) {
2581
- console.warn(
2582
- "The committed country list for the exclusion check does not match the one from the proof",
2583
- )
2584
- isCorrect = false
2585
- queryResultErrors.nationality.commitment = {
2586
- expected: `Commitment: ${calculatedParamCommitment}`,
2587
- received: `Commitment: ${paramCommittment}`,
2588
- message:
2589
- "The committed country list for the exclusion check does not match the one from the proof",
2590
- }
2591
- }
2592
- const { isCorrect: isCorrectScope, queryResultErrors: queryResultErrorsScope } =
2593
- this.checkScopeFromDisclosureProof(proofData, queryResultErrors, "nationality", scope)
2594
- const {
2595
- isCorrect: isCorrectNationalityExclusion,
2596
- queryResultErrors: queryResultErrorsNationalityExclusion,
2597
- } = this.checkNationalityExclusionPublicInputs(queryResult, countryList)
2598
- isCorrect = isCorrect && isCorrectNationalityExclusion && isCorrectScope
2599
- queryResultErrors = {
2600
- ...queryResultErrors,
2601
- ...queryResultErrorsNationalityExclusion,
2602
- ...queryResultErrorsScope,
2603
- }
2604
- uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10)
2605
- } else if (proof.name === "exclusion_check_issuing_country") {
2606
- commitmentIn = getCommitmentInFromDisclosureProof(proofData)
2607
- if (commitmentIn !== commitmentOut) {
2608
- console.warn(
2609
- "Failed to check the link between the validity of the ID and the issuing country exclusion check",
2610
- )
2611
- isCorrect = false
2612
- queryResultErrors.nationality.commitment = {
2613
- expected: `Commitment: ${commitmentOut}`,
2614
- received: `Commitment: ${commitmentIn}`,
2615
- message:
2616
- "Failed to check the link between the validity of the ID and the issuing country exclusion check",
2617
- }
2618
- }
2619
- const countryList = (
2620
- proof.committedInputs?.exclusion_check_issuing_country as CountryCommittedInputs
2621
- ).countries
2622
- const paramCommittment = getParameterCommitmentFromDisclosureProof(proofData)
2623
- const calculatedParamCommitment = await getCountryParameterCommitment(
2624
- ProofType.ISSUING_COUNTRY_EXCLUSION,
2625
- countryList,
2626
- true,
2627
- )
2628
- if (paramCommittment !== calculatedParamCommitment) {
2629
- console.warn(
2630
- "The committed country list for the issuing country exclusion check does not match the one from the proof",
2631
- )
2632
- isCorrect = false
2633
- queryResultErrors.issuing_country.commitment = {
2634
- expected: `Commitment: ${calculatedParamCommitment}`,
2635
- received: `Commitment: ${paramCommittment}`,
2636
- message:
2637
- "The committed country list for the issuing country exclusion check does not match the one from the proof",
2638
- }
2639
- }
2640
- const { isCorrect: isCorrectScope, queryResultErrors: queryResultErrorsScope } =
2641
- this.checkScopeFromDisclosureProof(proofData, queryResultErrors, "nationality", scope)
2642
- const {
2643
- isCorrect: isCorrectIssuingCountryExclusion,
2644
- queryResultErrors: queryResultErrorsIssuingCountryExclusion,
2645
- } = this.checkIssuingCountryExclusionPublicInputs(queryResult, countryList)
2646
- isCorrect = isCorrect && isCorrectIssuingCountryExclusion && isCorrectScope
2647
- queryResultErrors = {
2648
- ...queryResultErrors,
2649
- ...queryResultErrorsIssuingCountryExclusion,
2650
- ...queryResultErrorsScope,
2651
- }
2652
- uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10)
2653
- } else if (proof.name === "inclusion_check_nationality") {
2654
- commitmentIn = getCommitmentInFromDisclosureProof(proofData)
2655
- if (commitmentIn !== commitmentOut) {
2656
- console.warn(
2657
- "Failed to check the link between the validity of the ID and the nationality inclusion check",
2658
- )
2659
- isCorrect = false
2660
- queryResultErrors.nationality.commitment = {
2661
- expected: `Commitment: ${commitmentOut}`,
2662
- received: `Commitment: ${commitmentIn}`,
2663
- message:
2664
- "Failed to check the link between the validity of the ID and the nationality inclusion check",
2665
- }
2666
- }
2667
- const countryList = (
2668
- proof.committedInputs?.inclusion_check_nationality as CountryCommittedInputs
2669
- ).countries
2670
- const paramCommittment = getParameterCommitmentFromDisclosureProof(proofData)
2671
- const calculatedParamCommitment = await getCountryParameterCommitment(
2672
- ProofType.NATIONALITY_INCLUSION,
2673
- countryList,
2674
- false,
2675
- )
2676
- if (paramCommittment !== calculatedParamCommitment) {
2677
- console.warn(
2678
- "The committed country list for the nationality inclusion check does not match the one from the proof",
2679
- )
2680
- isCorrect = false
2681
- queryResultErrors.nationality.commitment = {
2682
- expected: `Commitment: ${calculatedParamCommitment}`,
2683
- received: `Commitment: ${paramCommittment}`,
2684
- message:
2685
- "The committed country list for the nationality inclusion check does not match the one from the proof",
2686
- }
2687
- }
2688
- const { isCorrect: isCorrectScope, queryResultErrors: queryResultErrorsScope } =
2689
- this.checkScopeFromDisclosureProof(proofData, queryResultErrors, "nationality", scope)
2690
- const {
2691
- isCorrect: isCorrectNationalityInclusion,
2692
- queryResultErrors: queryResultErrorsNationalityInclusion,
2693
- } = this.checkNationalityInclusionPublicInputs(queryResult, countryList)
2694
- isCorrect = isCorrect && isCorrectNationalityInclusion && isCorrectScope
2695
- queryResultErrors = {
2696
- ...queryResultErrors,
2697
- ...queryResultErrorsNationalityInclusion,
2698
- ...queryResultErrorsScope,
2699
- }
2700
- uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10)
2701
- } else if (proof.name === "inclusion_check_issuing_country") {
2702
- commitmentIn = getCommitmentInFromDisclosureProof(proofData)
2703
- if (commitmentIn !== commitmentOut) {
2704
- console.warn(
2705
- "Failed to check the link between the validity of the ID and the issuing country inclusion check",
2706
- )
2707
- isCorrect = false
2708
- queryResultErrors.nationality.commitment = {
2709
- expected: `Commitment: ${commitmentOut}`,
2710
- received: `Commitment: ${commitmentIn}`,
2711
- message:
2712
- "Failed to check the link between the validity of the ID and the issuing country inclusion check",
2713
- }
2714
- }
2715
- const countryList = (
2716
- proof.committedInputs?.inclusion_check_issuing_country as CountryCommittedInputs
2717
- ).countries
2718
- const paramCommittment = getParameterCommitmentFromDisclosureProof(proofData)
2719
- const calculatedParamCommitment = await getCountryParameterCommitment(
2720
- ProofType.ISSUING_COUNTRY_INCLUSION,
2721
- countryList,
2722
- false,
2723
- )
2724
- if (paramCommittment !== calculatedParamCommitment) {
2725
- console.warn(
2726
- "The committed country list for the issuing country inclusion check does not match the one from the proof",
2727
- )
2728
- isCorrect = false
2729
- queryResultErrors.issuing_country.commitment = {
2730
- expected: `Commitment: ${calculatedParamCommitment}`,
2731
- received: `Commitment: ${paramCommittment}`,
2732
- message:
2733
- "The committed country list for the issuing country inclusion check does not match the one from the proof",
2734
- }
2735
- }
2736
- const { isCorrect: isCorrectScope, queryResultErrors: queryResultErrorsScope } =
2737
- this.checkScopeFromDisclosureProof(proofData, queryResultErrors, "nationality", scope)
2738
- const {
2739
- isCorrect: isCorrectIssuingCountryInclusion,
2740
- queryResultErrors: queryResultErrorsIssuingCountryInclusion,
2741
- } = this.checkIssuingCountryInclusionPublicInputs(queryResult, countryList)
2742
- isCorrect = isCorrect && isCorrectIssuingCountryInclusion && isCorrectScope
2743
- queryResultErrors = {
2744
- ...queryResultErrors,
2745
- ...queryResultErrorsIssuingCountryInclusion,
2746
- ...queryResultErrorsScope,
2747
- }
2748
- uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10)
2749
- } else if (proof.name === "bind") {
2750
- const bindCommittedInputs = proof.committedInputs?.bind as BindCommittedInputs
2751
- const paramCommittment = getParameterCommitmentFromDisclosureProof(proofData)
2752
- const calculatedParamCommitment = await getBindParameterCommitment(
2753
- formatBoundData(bindCommittedInputs.data),
2754
- )
2755
- if (paramCommittment !== calculatedParamCommitment) {
2756
- console.warn("The bound data does not match the one from the proof")
2757
- isCorrect = false
2758
- queryResultErrors.bind.commitment = {
2759
- expected: `Commitment: ${calculatedParamCommitment}`,
2760
- received: `Commitment: ${paramCommittment}`,
2761
- message: "The bound data does not match the one from the proof",
2762
- }
2763
- }
2764
- const { isCorrect: isCorrectBind, queryResultErrors: queryResultErrorsBind } =
2765
- this.checkBindPublicInputs(queryResult, bindCommittedInputs.data)
2766
- isCorrect = isCorrect && isCorrectBind
2767
- queryResultErrors = {
2768
- ...queryResultErrors,
2769
- ...queryResultErrorsBind,
2770
- }
2771
- uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10)
2772
- }
2773
- }
2774
- return { isCorrect, uniqueIdentifier, queryResultErrors }
2775
- }
2776
-
2777
- /**
2778
- * @notice Verify the proofs received from the mobile app.
2779
- * @param proofs The proofs to verify.
2780
- * @param queryResult The query result to verify against
2781
- * @param validity How many days ago should have the ID been last scanned by the user?
2782
- * @param scope Scope this request to a specific use case
2783
- * @param evmChain The EVM chain to use for the verification (if using the proof onchain)
2784
- * @param devMode Whether to enable dev mode. This will allow you to verify mock proofs (i.e. from ZKR)
2785
- * @param writingDirectory The directory (e.g. `./tmp`) where the necessary temporary artifacts for verification are written to.
2786
- * It should only be needed when running the `verify` function on a server with restricted write access (e.g. Vercel)
2787
- * @returns An object containing the unique identifier associated to the user
2788
- * and a boolean indicating whether the proofs were successfully verified.
2789
- */
2790
- public async verify({
2791
- proofs,
2792
- queryResult,
2793
- validity,
2794
- scope,
2795
- evmChain,
2796
- devMode = false,
2797
- writingDirectory,
2798
- }: {
2799
- proofs: Array<ProofResult>
2800
- queryResult: QueryResult
2801
- validity?: number
2802
- scope?: string
2803
- evmChain?: EVMChain
2804
- devMode?: boolean
2805
- writingDirectory?: string
2806
- }): Promise<{
2807
- uniqueIdentifier: string | undefined
2808
- verified: boolean
2809
- queryResultErrors?: QueryResultErrors
2810
- }> {
2811
- // If no proofs were generated, the results can't be trusted.
2812
- // We still return it but verified will be false
2813
- if (!proofs || proofs.length === 0) {
2814
- return {
2815
- uniqueIdentifier: undefined,
2816
- verified: false,
2817
- }
2818
- }
2819
- const formattedResult: QueryResult = queryResult
2820
- // Make sure to reconvert the dates to Date objects
2821
- if (formattedResult.birthdate && formattedResult.birthdate.disclose) {
2822
- formattedResult.birthdate.disclose.result = new Date(
2823
- formattedResult.birthdate.disclose.result,
2824
- )
2825
- }
2826
- if (formattedResult.expiry_date && formattedResult.expiry_date.disclose) {
2827
- formattedResult.expiry_date.disclose.result = new Date(
2828
- formattedResult.expiry_date.disclose.result,
2829
- )
2830
- }
2831
-
2832
- const { BarretenbergVerifier } = await import("@aztec/bb.js")
2833
- // Automatically set the writing directory to `/tmp` if it is not provided
2834
- // and the code is not running in the browser
2835
- if (typeof window === "undefined" && !writingDirectory) {
2836
- writingDirectory = "/tmp"
2837
- }
2838
- const verifier = new BarretenbergVerifier({
2839
- crsPath: writingDirectory ? writingDirectory + "/.bb-crs" : undefined,
2840
- })
2841
- let verified = true
2842
- let uniqueIdentifier: string | undefined
2843
- let queryResultErrors: QueryResultErrors | undefined
2844
- const chainId = evmChain ? getChainIdFromEVMChain(evmChain) : undefined
2845
- const {
2846
- isCorrect,
2847
- uniqueIdentifier: uniqueIdentifierFromPublicInputs,
2848
- queryResultErrors: queryResultErrorsFromPublicInputs,
2849
- } = await this.checkPublicInputs(proofs, formattedResult, validity, scope, chainId)
2850
- uniqueIdentifier = uniqueIdentifierFromPublicInputs
2851
- verified = isCorrect
2852
- queryResultErrors = isCorrect ? undefined : queryResultErrorsFromPublicInputs
2853
- if (
2854
- uniqueIdentifier &&
2855
- (BigInt(uniqueIdentifier) === BigInt(1) || BigInt(uniqueIdentifier) === BigInt(0)) &&
2856
- !devMode
2857
- ) {
2858
- // If the unique identifier is 0 (old ZKR proofs) or 1 (new ZKR proofs) and it is not in dev mode,
2859
- // the proofs are considered invalid as these are mock proofs only meant
2860
- // for testing purposes
2861
- verified = false
2862
- console.warn(
2863
- "You are trying to verify a mock proof. This is only allowed in dev mode. To enable dev mode, set the `devMode` parameter to `true` in the request function parameters.",
2864
- )
2865
- }
2866
- // Only proceed with the proof verification if the public inputs are correct
2867
- if (verified) {
2868
- const registryClient = new RegistryClient({ chainId: 11155111 })
2869
- const circuitManifest = await registryClient.getCircuitManifest(undefined, {
2870
- // We assume all proofs have the same version
2871
- version: proofs[0].version,
2872
- })
2873
- for (const proof of proofs) {
2874
- const proofData = getProofData(proof.proof as string, getNumberOfPublicInputs(proof.name!))
2875
- const hostedPackagedCircuit = await registryClient.getPackagedCircuit(
2876
- proof.name!,
2877
- circuitManifest,
2878
- )
2879
- if (proof.name?.startsWith("outer_evm")) {
2880
- try {
2881
- const { createPublicClient, http } = await import("viem")
2882
- const { sepolia } = await import("viem/chains")
2883
- const { address, abi, functionName } =
2884
- this.getSolidityVerifierDetails("ethereum_sepolia")
2885
- const client = createPublicClient({
2886
- chain: sepolia,
2887
- transport: http("https://ethereum-sepolia-rpc.publicnode.com"),
2888
- })
2889
- const params = this.getSolidityVerifierParameters({
2890
- proof,
2891
- validityPeriodInDays: validity,
2892
- domain: this.domain,
2893
- scope,
2894
- devMode,
2895
- })
2896
- const result = await client.readContract({
2897
- address,
2898
- abi,
2899
- functionName,
2900
- args: [params],
2901
- })
2902
- const isVerified = Array.isArray(result) ? Boolean(result[0]) : false
2903
- verified = isVerified
2904
- } catch (error) {
2905
- console.warn("Error verifying proof", error)
2906
- verified = false
2907
- }
2908
- } else {
2909
- const vkeyBytes = Buffer.from(hostedPackagedCircuit.vkey, "base64")
2910
- try {
2911
- verified = await verifier.verifyUltraHonkProof(
2912
- {
2913
- proof: Buffer.from(proofData.proof.join(""), "hex"),
2914
- publicInputs: proofData.publicInputs,
2915
- },
2916
- new Uint8Array(vkeyBytes),
2917
- )
2918
- } catch (e) {
2919
- console.warn("Error verifying proof", e)
2920
- verified = false
2921
- }
2922
- }
2923
- if (!verified) {
2924
- // Break the loop if the proof is not valid
2925
- // and don't bother checking the other proofs
2926
- break
2927
- }
2928
- }
2929
- }
2930
- // If the proofs are not verified, we don't return the unique identifier
2931
- uniqueIdentifier = verified ? uniqueIdentifier : undefined
2932
- return { uniqueIdentifier, verified, queryResultErrors }
2933
- }
2934
-
2935
- public getSolidityVerifierDetails(network: EVMChain): {
2936
- address: `0x${string}`
2937
- functionName: string
2938
- abi: {
2939
- type: "function" | "event" | "constructor"
2940
- name: string
2941
- inputs: { name: string; type: string; internalType: string }[]
2942
- outputs: { name: string; type: string; internalType: string }[]
2943
- }[]
2944
- } {
2945
- const baseConfig = {
2946
- functionName: "verifyProof",
2947
- abi: ZKPassportVerifierAbi.abi as any,
2948
- }
2949
- if (network === "ethereum_sepolia") {
2950
- return {
2951
- ...baseConfig,
2952
- address: "0xDDeFf76024052D26B78A7Fac66FFbd6fbc5bd9Ad",
2953
- }
2954
- } else if (network === "local_anvil") {
2955
- return {
2956
- ...baseConfig,
2957
- address: "0x0",
2958
- }
2959
- }
2960
- throw new Error(`Unsupported network: ${network}`)
2961
- }
2962
-
2963
- public getSolidityVerifierParameters({
2964
- proof,
2965
- validityPeriodInDays = 7,
2966
- domain,
2967
- scope,
2968
- devMode = false,
2969
- }: {
2970
- proof: ProofResult
2971
- validityPeriodInDays?: number
2972
- domain?: string
2973
- scope?: string
2974
- devMode?: boolean
2975
- }) {
2976
- if (!proof.name?.startsWith("outer_evm")) {
2977
- throw new Error(
2978
- "This proof cannot be verified on an EVM chain. Please make sure to use the `compressed-evm` mode.",
2979
- )
2980
- }
2981
- const proofData = getProofData(proof.proof as string, getNumberOfPublicInputs(proof.name!))
2982
- // For EVM optimised proofs, the first 16 bytes of the proof are the aggregation object
2983
- // and should be moved at the end of the public inputs
2984
- const actualProof = proofData.proof.slice(16)
2985
- const actualPublicInputs = proofData.publicInputs.concat(
2986
- proofData.proof.slice(0, 16).map((x) => `0x${x}`),
2987
- )
2988
- let committedInputCounts: { circuitName: DisclosureCircuitName; count: number }[] = []
2989
- let committedInputs: { circuitName: DisclosureCircuitName; inputs: string }[] = []
2990
- for (const key in proof.committedInputs) {
2991
- const committedInputCount = getCommittedInputCount(key as DisclosureCircuitName)
2992
- const circuitName = key as DisclosureCircuitName
2993
- committedInputCounts.push({ circuitName, count: committedInputCount })
2994
- let compressedCommittedInputs = ""
2995
- if (
2996
- circuitName === "inclusion_check_issuing_country_evm" ||
2997
- circuitName === "inclusion_check_nationality_evm" ||
2998
- circuitName === "exclusion_check_issuing_country_evm" ||
2999
- circuitName === "exclusion_check_nationality_evm"
3000
- ) {
3001
- const value = proof.committedInputs[circuitName] as CountryCommittedInputs
3002
- const formattedCountries = value.countries
3003
- if (
3004
- circuitName === "exclusion_check_issuing_country_evm" ||
3005
- circuitName === "exclusion_check_nationality_evm"
3006
- ) {
3007
- formattedCountries.sort((a, b) => a.localeCompare(b))
3008
- }
3009
- const proofType = (() => {
3010
- switch (circuitName) {
3011
- case "exclusion_check_issuing_country_evm":
3012
- return ProofType.ISSUING_COUNTRY_EXCLUSION
3013
- case "exclusion_check_nationality_evm":
3014
- return ProofType.NATIONALITY_EXCLUSION
3015
- case "inclusion_check_issuing_country_evm":
3016
- return ProofType.ISSUING_COUNTRY_INCLUSION
3017
- case "inclusion_check_nationality_evm":
3018
- return ProofType.NATIONALITY_INCLUSION
3019
- }
3020
- })()
3021
- compressedCommittedInputs =
3022
- proofType.toString(16).padStart(2, "0") +
3023
- rightPadArrayWithZeros(
3024
- formattedCountries.map((c) => Array.from(new TextEncoder().encode(c))).flat(),
3025
- 600,
3026
- )
3027
- .map((x) => x.toString(16).padStart(2, "0"))
3028
- .join("")
3029
- } else if (circuitName === "compare_age_evm") {
3030
- const value = proof.committedInputs[circuitName] as AgeCommittedInputs
3031
- const currentDateBytes = Array.from(new TextEncoder().encode(value.currentDate))
3032
- compressedCommittedInputs =
3033
- ProofType.AGE.toString(16).padStart(2, "0") +
3034
- currentDateBytes.map((x) => x.toString(16).padStart(2, "0")).join("") +
3035
- value.minAge.toString(16).padStart(2, "0") +
3036
- value.maxAge.toString(16).padStart(2, "0")
3037
- } else if (circuitName === "compare_birthdate_evm") {
3038
- const value = proof.committedInputs[circuitName] as DateCommittedInputs
3039
- const currentDateBytes = Array.from(new TextEncoder().encode(value.currentDate))
3040
- const minDateBytes = Array.from(new TextEncoder().encode(value.minDate))
3041
- const maxDateBytes = Array.from(new TextEncoder().encode(value.maxDate))
3042
- compressedCommittedInputs =
3043
- ProofType.BIRTHDATE.toString(16).padStart(2, "0") +
3044
- currentDateBytes.map((x) => x.toString(16).padStart(2, "0")).join("") +
3045
- minDateBytes.map((x) => x.toString(16).padStart(2, "0")).join("") +
3046
- maxDateBytes.map((x) => x.toString(16).padStart(2, "0")).join("")
3047
- } else if (circuitName === "compare_expiry_evm") {
3048
- const value = proof.committedInputs[circuitName] as DateCommittedInputs
3049
- const currentDateBytes = Array.from(new TextEncoder().encode(value.currentDate))
3050
- const minDateBytes = Array.from(new TextEncoder().encode(value.minDate))
3051
- const maxDateBytes = Array.from(new TextEncoder().encode(value.maxDate))
3052
- compressedCommittedInputs =
3053
- ProofType.EXPIRY_DATE.toString(16).padStart(2, "0") +
3054
- currentDateBytes.map((x) => x.toString(16).padStart(2, "0")).join("") +
3055
- minDateBytes.map((x) => x.toString(16).padStart(2, "0")).join("") +
3056
- maxDateBytes.map((x) => x.toString(16).padStart(2, "0")).join("")
3057
- } else if (circuitName === "disclose_bytes_evm") {
3058
- const value = proof.committedInputs[circuitName] as DiscloseCommittedInputs
3059
- compressedCommittedInputs =
3060
- ProofType.DISCLOSE.toString(16).padStart(2, "0") +
3061
- value.discloseMask.map((x) => x.toString(16).padStart(2, "0")).join("") +
3062
- value.disclosedBytes.map((x) => x.toString(16).padStart(2, "0")).join("")
3063
- } else if (circuitName === "bind_evm") {
3064
- const value = proof.committedInputs[circuitName] as BindCommittedInputs
3065
- compressedCommittedInputs =
3066
- ProofType.BIND.toString(16).padStart(2, "0") +
3067
- rightPadArrayWithZeros(formatBoundData(value.data), 500)
3068
- .map((x) => x.toString(16).padStart(2, "0"))
3069
- .join("")
3070
- } else {
3071
- throw new Error(`Unsupported circuit for EVM verification: ${circuitName}`)
3072
- }
3073
- committedInputs.push({ circuitName, inputs: compressedCommittedInputs })
3074
- }
3075
- const parameterCommitments = proofData.publicInputs.slice(12, proofData.publicInputs.length - 1)
3076
- let compressedCommittedInputs = ""
3077
- let committedInputCountsArray = []
3078
- for (const commitment of parameterCommitments) {
3079
- const committedInput = committedInputs.find((x) => {
3080
- const rawHashedInputs = sha256(hexToBytes(x.inputs))
3081
- // Shift the hash 8 bits to the right (1 byte)
3082
- // as one byte is dropped in the circuit to fit in the 254-bit field size
3083
- const hashedInputs = new Uint8Array(rawHashedInputs.length)
3084
- // Move each byte 1 position to the right (shifting 8 bits)
3085
- for (let i = 0; i < rawHashedInputs.length - 1; i++) {
3086
- hashedInputs[i + 1] = rawHashedInputs[i]
3087
- }
3088
- // First byte becomes 0 (since we're shifting right)
3089
- hashedInputs[0] = 0
3090
-
3091
- return bytesToHex(hashedInputs) === commitment.replace("0x", "")
3092
- })
3093
- if (committedInput) {
3094
- const count = committedInputCounts.find(
3095
- (x) => x.circuitName === committedInput.circuitName,
3096
- )?.count
3097
- if (count) {
3098
- committedInputCountsArray.push(count)
3099
- compressedCommittedInputs += committedInput.inputs
3100
- } else {
3101
- throw new Error(`Unknown circuit name: ${committedInput.circuitName}`)
3102
- }
3103
- } else {
3104
- throw new Error(`Invalid commitment: ${commitment}`)
3105
- }
3106
- }
3107
- const params: SolidityVerifierParameters = {
3108
- // Make sure the vkeyHash is 32 bytes
3109
- vkeyHash: `0x${proof.vkeyHash!.replace("0x", "").padStart(64, "0")}`,
3110
- proof: `0x${actualProof.join("")}`,
3111
- publicInputs: actualPublicInputs,
3112
- committedInputs: `0x${compressedCommittedInputs}`,
3113
- committedInputCounts: committedInputCountsArray,
3114
- validityPeriodInDays,
3115
- domain: domain ?? this.domain,
3116
- scope: scope ?? "",
3117
- devMode,
3118
- }
3119
- return params
3120
- }
3121
-
3122
- private _getUrl(requestId: string) {
3123
- const base64Config = Buffer.from(JSON.stringify(this.topicToConfig[requestId])).toString(
3124
- "base64",
3125
- )
3126
- const base64Service = Buffer.from(JSON.stringify(this.topicToService[requestId])).toString(
3127
- "base64",
3128
- )
3129
- const pubkey = this.topicToPublicKey[requestId]
3130
- return `https://zkpassport.id/r?d=${this.domain}&t=${requestId}&c=${base64Config}&s=${base64Service}&p=${pubkey}&m=${this.topicToLocalConfig[requestId].mode}&v=${VERSION}`
3131
- }
3132
-
3133
- /**
3134
- * @notice Returns the URL of the request.
3135
- * @param requestId The request ID.
3136
- * @returns The URL of the request.
3137
- */
3138
- public getUrl(requestId: string) {
3139
- return this._getUrl(requestId)
3140
- }
3141
-
3142
- /**
3143
- * @notice Cancels a request by closing the WebSocket connection and deleting the associated data.
3144
- * @param requestId The request ID.
3145
- */
3146
- public cancelRequest(requestId: string) {
3147
- if (this.topicToBridge[requestId]) {
3148
- this.topicToBridge[requestId].close()
3149
- delete this.topicToBridge[requestId]
3150
- }
3151
- delete this.topicToPublicKey[requestId]
3152
- delete this.topicToConfig[requestId]
3153
- delete this.topicToLocalConfig[requestId]
3154
- delete this.topicToProofs[requestId]
3155
- delete this.topicToExpectedProofCount[requestId]
3156
- delete this.topicToFailedProofCount[requestId]
3157
- delete this.topicToResults[requestId]
3158
- this.onRequestReceivedCallbacks[requestId] = []
3159
- this.onGeneratingProofCallbacks[requestId] = []
3160
- this.onBridgeConnectCallbacks[requestId] = []
3161
- this.onProofGeneratedCallbacks[requestId] = []
3162
- this.onRejectCallbacks[requestId] = []
3163
- this.onErrorCallbacks[requestId] = []
3164
- }
3165
-
3166
- /**
3167
- * @notice Clears all requests.
3168
- */
3169
- public clearAllRequests() {
3170
- for (const requestId in this.topicToBridge) {
3171
- this.cancelRequest(requestId)
3172
- }
3173
- }
3174
- }