@zkpassport/sdk 0.2.7 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/esm/index.js CHANGED
@@ -62,6 +62,7 @@ export class ZKPassport {
62
62
  //private wasmVerifierInit: boolean = false
63
63
  constructor(_domain) {
64
64
  this.topicToConfig = {};
65
+ this.topicToLocalConfig = {};
65
66
  this.topicToKeyPair = {};
66
67
  this.topicToWebSocketClient = {};
67
68
  this.topicToSharedSecret = {};
@@ -69,6 +70,7 @@ export class ZKPassport {
69
70
  this.topicToService = {};
70
71
  this.topicToProofs = {};
71
72
  this.topicToExpectedProofCount = {};
73
+ this.topicToFailedProofCount = {};
72
74
  this.topicToResults = {};
73
75
  this.onRequestReceivedCallbacks = {};
74
76
  this.onGeneratingProofCallbacks = {};
@@ -93,14 +95,19 @@ export class ZKPassport {
93
95
  // Clear the results straight away to avoid concurrency issues
94
96
  delete this.topicToResults[topic];
95
97
  // Verify the proofs and extract the unique identifier (aka nullifier) and the verification result
96
- const { uniqueIdentifier, verified } = await this.verify(topic, this.topicToProofs[topic], result);
98
+ const { uniqueIdentifier, verified, queryResultErrors } = await this.verify(topic, this.topicToProofs[topic], result);
99
+ const hasFailedProofs = this.topicToFailedProofCount[topic] > 0;
97
100
  await Promise.all(this.onResultCallbacks[topic].map((callback) => callback({
98
- uniqueIdentifier,
99
- verified,
101
+ // If there are failed proofs, we don't return the unique identifier
102
+ // and we set the verified result to false
103
+ uniqueIdentifier: hasFailedProofs ? undefined : uniqueIdentifier,
104
+ verified: hasFailedProofs ? false : verified,
100
105
  result,
106
+ queryResultErrors,
101
107
  })));
102
- // Clear the expected proof count
108
+ // Clear the expected proof count and failed proof count
103
109
  delete this.topicToExpectedProofCount[topic];
110
+ delete this.topicToFailedProofCount[topic];
104
111
  }
105
112
  setExpectedProofCount(topic) {
106
113
  const fields = Object.keys(this.topicToConfig[topic]).filter((key) => hasRequestedAccessToField(this.topicToConfig[topic], key));
@@ -151,6 +158,7 @@ export class ZKPassport {
151
158
  // Each separate needed circuit adds 1 disclosure proof
152
159
  this.topicToExpectedProofCount[topic] =
153
160
  neededCircuits.length === 0 ? 4 : 3 + neededCircuits.length;
161
+ this.topicToFailedProofCount[topic] = 0;
154
162
  }
155
163
  /**
156
164
  * @notice Handle an encrypted message.
@@ -206,6 +214,7 @@ export class ZKPassport {
206
214
  // This means the user has an ID that is not supported yet
207
215
  // So we won't receive any proofs and we can handle the result now
208
216
  this.topicToExpectedProofCount[topic] = 0;
217
+ this.topicToFailedProofCount[topic] += this.topicToExpectedProofCount[topic];
209
218
  if (this.topicToResults[topic]) {
210
219
  await this.handleResult(topic);
211
220
  }
@@ -214,6 +223,7 @@ export class ZKPassport {
214
223
  // This means one of the disclosure proofs failed to be generated
215
224
  // So we need to remove one from the expected proof count
216
225
  this.topicToExpectedProofCount[topic] -= 1;
226
+ this.topicToFailedProofCount[topic] += 1;
217
227
  // If the expected proof count is now equal to the number of proofs received
218
228
  // and the results were received, we can handle the result now
219
229
  if (this.topicToResults[topic] &&
@@ -270,9 +280,6 @@ export class ZKPassport {
270
280
  };
271
281
  return this.getZkPassportRequest(topic);
272
282
  },
273
- /*checkAML: (country?: CountryName | Alpha2Code | Alpha3Code) => {
274
- return this.getZkPassportRequest(topic)
275
- },*/
276
283
  done: () => {
277
284
  const base64Config = Buffer.from(JSON.stringify(this.topicToConfig[topic])).toString("base64");
278
285
  const base64Service = Buffer.from(JSON.stringify(this.topicToService[topic])).toString("base64");
@@ -295,10 +302,15 @@ export class ZKPassport {
295
302
  };
296
303
  }
297
304
  /**
298
- * @notice Create a new request.
305
+ * @notice Create a new request
306
+ * @param name Your service name
307
+ * @param logo The logo of your service
308
+ * @param purpose To explain what you want to do with the user's data
309
+ * @param scope Scope this request to a specific use case
310
+ * @param validity How many days ago should have the ID been last scanned by the user?
299
311
  * @returns The query builder object.
300
312
  */
301
- async request({ name, logo, purpose, scope, topicOverride, keyPairOverride, }) {
313
+ async request({ name, logo, purpose, scope, validity, topicOverride, keyPairOverride, }) {
302
314
  const topic = topicOverride || randomBytes(16).toString("hex");
303
315
  const keyPair = keyPairOverride || (await generateECDHKeyPair());
304
316
  this.topicToKeyPair[topic] = {
@@ -309,6 +321,10 @@ export class ZKPassport {
309
321
  this.topicToService[topic] = { name, logo, purpose, scope };
310
322
  this.topicToProofs[topic] = [];
311
323
  this.topicToExpectedProofCount[topic] = 0;
324
+ this.topicToLocalConfig[topic] = {
325
+ // Default to 6 months
326
+ validity: validity || 6 * 30,
327
+ };
312
328
  this.onRequestReceivedCallbacks[topic] = [];
313
329
  this.onGeneratingProofCallbacks[topic] = [];
314
330
  this.onBridgeConnectCallbacks[topic] = [];
@@ -365,7 +381,7 @@ export class ZKPassport {
365
381
  };
366
382
  return this.getZkPassportRequest(topic);
367
383
  }
368
- async checkPublicInputs(proofs, queryResult) {
384
+ async checkPublicInputs(proofs, queryResult, topic) {
369
385
  let commitmentIn;
370
386
  let commitmentOut;
371
387
  let isCorrect = true;
@@ -374,6 +390,23 @@ export class ZKPassport {
374
390
  const defaultDateValue = new Date(1111, 10, 11);
375
391
  const currentTime = new Date();
376
392
  const today = new Date(currentTime.getFullYear(), currentTime.getMonth(), currentTime.getDate(), 0, 0, 0, 0);
393
+ const queryResultErrors = {
394
+ sig_check_dsc: {},
395
+ sig_check_id_data: {},
396
+ data_check_integrity: {},
397
+ disclose: {},
398
+ age: {},
399
+ birthdate: {},
400
+ expiry_date: {},
401
+ document_type: {},
402
+ issuing_country: {},
403
+ gender: {},
404
+ nationality: {},
405
+ firstname: {},
406
+ lastname: {},
407
+ fullname: {},
408
+ document_number: {},
409
+ };
377
410
  // Since the order is important for the commitments, we need to sort the proofs
378
411
  // by their expected order: root signature check -> ID signature check -> integrity check -> disclosure
379
412
  const sortedProofs = proofs.sort((a, b) => {
@@ -402,7 +435,11 @@ export class ZKPassport {
402
435
  if (merkleRoot !== expectedMerkleRoot) {
403
436
  console.warn("The ID was signed by an unrecognized root certificate");
404
437
  isCorrect = false;
405
- break;
438
+ queryResultErrors.sig_check_dsc.certificate = {
439
+ expected: `Certificate registry root: ${expectedMerkleRoot.toString()}`,
440
+ received: `Certificate registry root: ${merkleRoot.toString()}`,
441
+ message: "The ID was signed by an unrecognized root certificate",
442
+ };
406
443
  }
407
444
  }
408
445
  else if (proof.name?.startsWith("sig_check_id_data")) {
@@ -410,7 +447,11 @@ export class ZKPassport {
410
447
  if (commitmentIn !== commitmentOut) {
411
448
  console.warn("Failed to check the link between the certificate signature and ID signature");
412
449
  isCorrect = false;
413
- break;
450
+ queryResultErrors.sig_check_id_data.commitment = {
451
+ expected: `Commitment: ${commitmentOut?.toString() || "undefined"}`,
452
+ received: `Commitment: ${commitmentIn?.toString() || "undefined"}`,
453
+ message: "Failed to check the link between the certificate signature and ID signature",
454
+ };
414
455
  }
415
456
  commitmentOut = getCommitmentOutFromIDDataProof(proofData);
416
457
  }
@@ -419,17 +460,26 @@ export class ZKPassport {
419
460
  if (commitmentIn !== commitmentOut) {
420
461
  console.warn("Failed to check the link between the ID signature and the data signed");
421
462
  isCorrect = false;
422
- break;
463
+ queryResultErrors.data_check_integrity.commitment = {
464
+ expected: `Commitment: ${commitmentOut?.toString() || "undefined"}`,
465
+ received: `Commitment: ${commitmentIn?.toString() || "undefined"}`,
466
+ message: "Failed to check the link between the ID signature and the data signed",
467
+ };
423
468
  }
424
469
  commitmentOut = getCommitmentOutFromIntegrityProof(proofData);
425
470
  const currentDate = getCurrentDateFromIntegrityProof(proofData);
426
- // The date should be today or yesterday
427
- // (if the proof request was requested just before midnight and is finalized after)
428
- if (currentDate.getTime() !== today.getTime() &&
429
- currentDate.getTime() !== today.getTime() - 86400000) {
430
- console.warn("Current date used to check the validity of the ID is too old");
471
+ const todayToCurrentDate = today.getTime() - currentDate.getTime();
472
+ const expectedDifference = this.topicToLocalConfig[topic]?.validity * 86400000;
473
+ const actualDifference = today.getTime() - (today.getTime() - expectedDifference);
474
+ // The ID should not expire within the next 6 months (or whatever the custom value is)
475
+ if (todayToCurrentDate >= actualDifference) {
476
+ console.warn(`The date used to check the validity of the ID is older than ${this.topicToLocalConfig[topic]?.validity} days. You can ask the user to rescan their ID or ask them to disclose their expiry date`);
431
477
  isCorrect = false;
432
- break;
478
+ queryResultErrors.data_check_integrity.date = {
479
+ expected: `Difference: ${this.topicToLocalConfig[topic]?.validity} days`,
480
+ received: `Difference: ${Math.round(todayToCurrentDate / 86400000)} days`,
481
+ message: "The date used to check the validity of the ID is older than the validity period",
482
+ };
433
483
  }
434
484
  }
435
485
  else if (proof.name === "disclose_bytes") {
@@ -437,7 +487,11 @@ export class ZKPassport {
437
487
  if (commitmentIn !== commitmentOut) {
438
488
  console.warn("Failed to check the link between the validity of the ID and the data to disclose");
439
489
  isCorrect = false;
440
- break;
490
+ queryResultErrors.disclose.commitment = {
491
+ expected: `Commitment: ${commitmentOut?.toString() || "undefined"}`,
492
+ received: `Commitment: ${commitmentIn?.toString() || "undefined"}`,
493
+ message: "Failed to check the link between the validity of the ID and the data to disclose",
494
+ };
441
495
  }
442
496
  // We can't be certain that the disclosed data is for a passport or an ID card
443
497
  // so we need to check both (unless the document type is revealed)
@@ -450,12 +504,20 @@ export class ZKPassport {
450
504
  queryResult.document_type.eq.expected !== disclosedDataPassport.documentType) {
451
505
  console.warn("Document type does not match the expected document type");
452
506
  isCorrect = false;
453
- break;
507
+ queryResultErrors.document_type.eq = {
508
+ expected: `${queryResult.document_type.eq.expected}`,
509
+ received: `${disclosedDataPassport.documentType}`,
510
+ message: "Document type does not match the expected document type",
511
+ };
454
512
  }
455
513
  if (queryResult.document_type.disclose?.result !== disclosedDataIDCard.documentType) {
456
514
  console.warn("Document type does not match the disclosed document type in query result");
457
515
  isCorrect = false;
458
- break;
516
+ queryResultErrors.document_type.disclose = {
517
+ expected: `${queryResult.document_type.disclose?.result}`,
518
+ received: `${disclosedDataIDCard.documentType}`,
519
+ message: "Document type does not match the disclosed document type in query result",
520
+ };
459
521
  }
460
522
  }
461
523
  if (queryResult.birthdate) {
@@ -467,14 +529,22 @@ export class ZKPassport {
467
529
  queryResult.birthdate.eq.expected.getTime() !== birthdateIDCard.getTime()) {
468
530
  console.warn("Birthdate does not match the expected birthdate");
469
531
  isCorrect = false;
470
- break;
532
+ queryResultErrors.birthdate.eq = {
533
+ expected: `${queryResult.birthdate.eq.expected.toISOString()}`,
534
+ received: `${birthdatePassport.toISOString()}`,
535
+ message: "Birthdate does not match the expected birthdate",
536
+ };
471
537
  }
472
538
  if (queryResult.birthdate.disclose &&
473
539
  queryResult.birthdate.disclose.result.getTime() !== birthdatePassport.getTime() &&
474
540
  queryResult.birthdate.disclose.result.getTime() !== birthdateIDCard.getTime()) {
475
541
  console.warn("Birthdate does not match the disclosed birthdate in query result");
476
542
  isCorrect = false;
477
- break;
543
+ queryResultErrors.birthdate.disclose = {
544
+ expected: `${queryResult.birthdate.disclose.result.toISOString()}`,
545
+ received: `${birthdatePassport.toISOString()}`,
546
+ message: "Birthdate does not match the disclosed birthdate in query result",
547
+ };
478
548
  }
479
549
  }
480
550
  if (queryResult.expiry_date) {
@@ -486,14 +556,22 @@ export class ZKPassport {
486
556
  queryResult.expiry_date.eq.expected.getTime() !== expiryDateIDCard.getTime()) {
487
557
  console.warn("Expiry date does not match the expected expiry date");
488
558
  isCorrect = false;
489
- break;
559
+ queryResultErrors.expiry_date.eq = {
560
+ expected: `${queryResult.expiry_date.eq.expected.toISOString()}`,
561
+ received: `${expiryDatePassport.toISOString()}`,
562
+ message: "Expiry date does not match the expected expiry date",
563
+ };
490
564
  }
491
565
  if (queryResult.expiry_date.disclose &&
492
566
  queryResult.expiry_date.disclose.result.getTime() !== expiryDatePassport.getTime() &&
493
567
  queryResult.expiry_date.disclose.result.getTime() !== expiryDateIDCard.getTime()) {
494
568
  console.warn("Expiry date does not match the disclosed expiry date in query result");
495
569
  isCorrect = false;
496
- break;
570
+ queryResultErrors.expiry_date.disclose = {
571
+ expected: `${queryResult.expiry_date.disclose.result.toISOString()}`,
572
+ received: `${expiryDatePassport.toISOString()}`,
573
+ message: "Expiry date does not match the disclosed expiry date in query result",
574
+ };
497
575
  }
498
576
  }
499
577
  if (queryResult.nationality) {
@@ -505,14 +583,22 @@ export class ZKPassport {
505
583
  queryResult.nationality.eq.expected !== nationalityIDCard) {
506
584
  console.warn("Nationality does not match the expected nationality");
507
585
  isCorrect = false;
508
- break;
586
+ queryResultErrors.nationality.eq = {
587
+ expected: `${queryResult.nationality.eq.expected}`,
588
+ received: `${nationalityPassport}`,
589
+ message: "Nationality does not match the expected nationality",
590
+ };
509
591
  }
510
592
  if (queryResult.nationality.disclose &&
511
593
  queryResult.nationality.disclose.result !== nationalityPassport &&
512
594
  queryResult.nationality.disclose.result !== nationalityIDCard) {
513
595
  console.warn("Nationality does not match the disclosed nationality in query result");
514
596
  isCorrect = false;
515
- break;
597
+ queryResultErrors.nationality.disclose = {
598
+ expected: `${queryResult.nationality.disclose.result}`,
599
+ received: `${nationalityPassport}`,
600
+ message: "Nationality does not match the disclosed nationality in query result",
601
+ };
516
602
  }
517
603
  }
518
604
  if (queryResult.document_number) {
@@ -524,14 +610,22 @@ export class ZKPassport {
524
610
  queryResult.document_number.eq.expected !== documentNumberIDCard) {
525
611
  console.warn("Document number does not match the expected document number");
526
612
  isCorrect = false;
527
- break;
613
+ queryResultErrors.document_number.eq = {
614
+ expected: `${queryResult.document_number.eq.expected}`,
615
+ received: `${documentNumberPassport}`,
616
+ message: "Document number does not match the expected document number",
617
+ };
528
618
  }
529
619
  if (queryResult.document_number.disclose &&
530
620
  queryResult.document_number.disclose.result !== documentNumberPassport &&
531
621
  queryResult.document_number.disclose.result !== documentNumberIDCard) {
532
622
  console.warn("Document number does not match the disclosed document number in query result");
533
623
  isCorrect = false;
534
- break;
624
+ queryResultErrors.document_number.disclose = {
625
+ expected: `${queryResult.document_number.disclose.result}`,
626
+ received: `${documentNumberPassport}`,
627
+ message: "Document number does not match the disclosed document number in query result",
628
+ };
535
629
  }
536
630
  }
537
631
  if (queryResult.gender) {
@@ -543,14 +637,22 @@ export class ZKPassport {
543
637
  queryResult.gender.eq.expected !== genderIDCard) {
544
638
  console.warn("Gender does not match the expected gender");
545
639
  isCorrect = false;
546
- break;
640
+ queryResultErrors.gender.eq = {
641
+ expected: `${queryResult.gender.eq.expected}`,
642
+ received: `${genderPassport}`,
643
+ message: "Gender does not match the expected gender",
644
+ };
547
645
  }
548
646
  if (queryResult.gender.disclose &&
549
647
  queryResult.gender.disclose.result !== genderPassport &&
550
648
  queryResult.gender.disclose.result !== genderIDCard) {
551
649
  console.warn("Gender does not match the disclosed gender in query result");
552
650
  isCorrect = false;
553
- break;
651
+ queryResultErrors.gender.disclose = {
652
+ expected: `${queryResult.gender.disclose.result}`,
653
+ received: `${genderPassport}`,
654
+ message: "Gender does not match the disclosed gender in query result",
655
+ };
554
656
  }
555
657
  }
556
658
  if (queryResult.issuing_country) {
@@ -562,14 +664,22 @@ export class ZKPassport {
562
664
  queryResult.issuing_country.eq.expected !== issuingCountryIDCard) {
563
665
  console.warn("Issuing country does not match the expected issuing country");
564
666
  isCorrect = false;
565
- break;
667
+ queryResultErrors.issuing_country.eq = {
668
+ expected: `${queryResult.issuing_country.eq.expected}`,
669
+ received: `${issuingCountryPassport}`,
670
+ message: "Issuing country does not match the expected issuing country",
671
+ };
566
672
  }
567
673
  if (queryResult.issuing_country.disclose &&
568
674
  queryResult.issuing_country.disclose.result !== issuingCountryPassport &&
569
675
  queryResult.issuing_country.disclose.result !== issuingCountryIDCard) {
570
676
  console.warn("Issuing country does not match the disclosed issuing country in query result");
571
677
  isCorrect = false;
572
- break;
678
+ queryResultErrors.issuing_country.disclose = {
679
+ expected: `${queryResult.issuing_country.disclose.result}`,
680
+ received: `${issuingCountryPassport}`,
681
+ message: "Issuing country does not match the disclosed issuing country in query result",
682
+ };
573
683
  }
574
684
  }
575
685
  if (queryResult.fullname) {
@@ -583,7 +693,11 @@ export class ZKPassport {
583
693
  fullnameIDCard.toLowerCase()) {
584
694
  console.warn("Fullname does not match the expected fullname");
585
695
  isCorrect = false;
586
- break;
696
+ queryResultErrors.fullname.eq = {
697
+ expected: `${queryResult.fullname.eq.expected}`,
698
+ received: `${fullnamePassport}`,
699
+ message: "Fullname does not match the expected fullname",
700
+ };
587
701
  }
588
702
  if (queryResult.fullname.disclose &&
589
703
  formatName(queryResult.fullname.disclose.result).toLowerCase() !==
@@ -592,7 +706,11 @@ export class ZKPassport {
592
706
  fullnameIDCard.toLowerCase()) {
593
707
  console.warn("Fullname does not match the disclosed fullname in query result");
594
708
  isCorrect = false;
595
- break;
709
+ queryResultErrors.fullname.disclose = {
710
+ expected: `${queryResult.fullname.disclose.result}`,
711
+ received: `${fullnamePassport}`,
712
+ message: "Fullname does not match the disclosed fullname in query result",
713
+ };
596
714
  }
597
715
  }
598
716
  if (queryResult.firstname) {
@@ -611,7 +729,11 @@ export class ZKPassport {
611
729
  firstnameIDCard.toLowerCase()) {
612
730
  console.warn("Firstname does not match the expected firstname");
613
731
  isCorrect = false;
614
- break;
732
+ queryResultErrors.firstname.eq = {
733
+ expected: `${queryResult.firstname.eq.expected}`,
734
+ received: `${firstnamePassport}`,
735
+ message: "Firstname does not match the expected firstname",
736
+ };
615
737
  }
616
738
  if (queryResult.firstname.disclose &&
617
739
  formatName(queryResult.firstname.disclose.result).toLowerCase() !==
@@ -620,7 +742,11 @@ export class ZKPassport {
620
742
  firstnameIDCard.toLowerCase()) {
621
743
  console.warn("Firstname does not match the disclosed firstname in query result");
622
744
  isCorrect = false;
623
- break;
745
+ queryResultErrors.firstname.disclose = {
746
+ expected: `${queryResult.firstname.disclose.result}`,
747
+ received: `${firstnamePassport}`,
748
+ message: "Firstname does not match the disclosed firstname in query result",
749
+ };
624
750
  }
625
751
  }
626
752
  if (queryResult.lastname) {
@@ -639,7 +765,11 @@ export class ZKPassport {
639
765
  lastnameIDCard.toLowerCase()) {
640
766
  console.warn("Lastname does not match the expected lastname");
641
767
  isCorrect = false;
642
- break;
768
+ queryResultErrors.lastname.eq = {
769
+ expected: `${queryResult.lastname.eq.expected}`,
770
+ received: `${lastnamePassport}`,
771
+ message: "Lastname does not match the expected lastname",
772
+ };
643
773
  }
644
774
  if (queryResult.lastname.disclose &&
645
775
  formatName(queryResult.lastname.disclose.result).toLowerCase() !==
@@ -648,7 +778,11 @@ export class ZKPassport {
648
778
  lastnameIDCard.toLowerCase()) {
649
779
  console.warn("Lastname does not match the disclosed lastname in query result");
650
780
  isCorrect = false;
651
- break;
781
+ queryResultErrors.lastname.disclose = {
782
+ expected: `${queryResult.lastname.disclose.result}`,
783
+ received: `${lastnamePassport}`,
784
+ message: "Lastname does not match the disclosed lastname in query result",
785
+ };
652
786
  }
653
787
  }
654
788
  uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10);
@@ -658,7 +792,11 @@ export class ZKPassport {
658
792
  if (commitmentIn !== commitmentOut) {
659
793
  console.warn("Failed to check the link between the validity of the ID and the age derived from it");
660
794
  isCorrect = false;
661
- break;
795
+ queryResultErrors.age.commitment = {
796
+ expected: `Commitment: ${commitmentOut}`,
797
+ received: `Commitment: ${commitmentIn}`,
798
+ message: "Failed to check the link between the validity of the ID and the age derived from it",
799
+ };
662
800
  }
663
801
  const minAge = getMinAgeFromProof(proofData);
664
802
  const maxAge = getMaxAgeFromProof(proofData);
@@ -668,14 +806,22 @@ export class ZKPassport {
668
806
  minAge < queryResult.age.gte.expected) {
669
807
  console.warn("Age is not greater than or equal to the expected age");
670
808
  isCorrect = false;
671
- break;
809
+ queryResultErrors.age.gte = {
810
+ expected: queryResult.age.gte.expected,
811
+ received: minAge,
812
+ message: "Age is not greater than or equal to the expected age",
813
+ };
672
814
  }
673
815
  if (queryResult.age.lt &&
674
816
  queryResult.age.lt.result &&
675
817
  maxAge >= queryResult.age.lt.expected) {
676
818
  console.warn("Age is not less than the expected age");
677
819
  isCorrect = false;
678
- break;
820
+ queryResultErrors.age.lt = {
821
+ expected: queryResult.age.lt.expected,
822
+ received: maxAge,
823
+ message: "Age is not less than the expected age",
824
+ };
679
825
  }
680
826
  if (queryResult.age.range) {
681
827
  if (queryResult.age.range.result &&
@@ -683,38 +829,60 @@ export class ZKPassport {
683
829
  maxAge >= queryResult.age.range.expected[1])) {
684
830
  console.warn("Age is not in the expected range");
685
831
  isCorrect = false;
686
- break;
832
+ queryResultErrors.age.range = {
833
+ expected: queryResult.age.range.expected,
834
+ received: [minAge, maxAge],
835
+ message: "Age is not in the expected range",
836
+ };
687
837
  }
688
838
  }
689
839
  if (!queryResult.age.lt && !queryResult.age.range && maxAge != 0) {
690
840
  console.warn("Maximum age should be equal to 0");
691
841
  isCorrect = false;
692
- break;
842
+ queryResultErrors.age.disclose = {
843
+ expected: 0,
844
+ received: maxAge,
845
+ message: "Maximum age should be equal to 0",
846
+ };
693
847
  }
694
848
  if (!queryResult.age.gte && !queryResult.age.range && minAge != 0) {
695
849
  console.warn("Minimum age should be equal to 0");
696
850
  isCorrect = false;
697
- break;
851
+ queryResultErrors.age.disclose = {
852
+ expected: 0,
853
+ received: minAge,
854
+ message: "Minimum age should be equal to 0",
855
+ };
698
856
  }
699
857
  if (queryResult.age.disclose &&
700
858
  (queryResult.age.disclose.result !== minAge ||
701
859
  queryResult.age.disclose.result !== maxAge)) {
702
860
  console.warn("Age does not match the disclosed age in query result");
703
861
  isCorrect = false;
704
- break;
862
+ queryResultErrors.age.disclose = {
863
+ expected: `${minAge}`,
864
+ received: `${queryResult.age.disclose.result}`,
865
+ message: "Age does not match the disclosed age in query result",
866
+ };
705
867
  }
706
868
  }
707
869
  else {
708
870
  console.warn("Age is not set in the query result");
709
871
  isCorrect = false;
710
- break;
872
+ queryResultErrors.age.disclose = {
873
+ message: "Age is not set in the query result",
874
+ };
711
875
  }
712
876
  const currentDate = getCurrentDateFromAgeProof(proofData);
713
877
  if (currentDate.getTime() !== today.getTime() &&
714
878
  currentDate.getTime() !== today.getTime() - 86400000) {
715
879
  console.warn("Current date in the proof is too old");
716
880
  isCorrect = false;
717
- break;
881
+ queryResultErrors.age.disclose = {
882
+ expected: `${today.toISOString()}`,
883
+ received: `${currentDate.toISOString()}`,
884
+ message: "Current date in the proof is too old",
885
+ };
718
886
  }
719
887
  uniqueIdentifier = getCommitmentInFromDisclosureProof(proofData).toString(10);
720
888
  }
@@ -723,7 +891,11 @@ export class ZKPassport {
723
891
  if (commitmentIn !== commitmentOut) {
724
892
  console.warn("Failed to check the link between the validity of the ID and the birthdate derived from it");
725
893
  isCorrect = false;
726
- break;
894
+ queryResultErrors.birthdate.commitment = {
895
+ expected: `Commitment: ${commitmentOut}`,
896
+ received: `Commitment: ${commitmentIn}`,
897
+ message: "Failed to check the link between the validity of the ID and the birthdate derived from it",
898
+ };
727
899
  }
728
900
  const minDate = getMinDateFromProof(proofData);
729
901
  const maxDate = getMaxDateFromProof(proofData);
@@ -733,14 +905,22 @@ export class ZKPassport {
733
905
  minDate < queryResult.birthdate.gte.expected) {
734
906
  console.warn("Birthdate is not greater than or equal to the expected birthdate");
735
907
  isCorrect = false;
736
- break;
908
+ queryResultErrors.birthdate.gte = {
909
+ expected: queryResult.birthdate.gte.expected,
910
+ received: minDate,
911
+ message: "Birthdate is not greater than or equal to the expected birthdate",
912
+ };
737
913
  }
738
914
  if (queryResult.birthdate.lte &&
739
915
  queryResult.birthdate.lte.result &&
740
916
  maxDate > queryResult.birthdate.lte.expected) {
741
917
  console.warn("Birthdate is not less than the expected birthdate");
742
918
  isCorrect = false;
743
- break;
919
+ queryResultErrors.birthdate.lte = {
920
+ expected: queryResult.birthdate.lte.expected,
921
+ received: maxDate,
922
+ message: "Birthdate is not less than the expected birthdate",
923
+ };
744
924
  }
745
925
  if (queryResult.birthdate.range) {
746
926
  if (queryResult.birthdate.range.result &&
@@ -748,7 +928,11 @@ export class ZKPassport {
748
928
  maxDate > queryResult.birthdate.range.expected[1])) {
749
929
  console.warn("Birthdate is not in the expected range");
750
930
  isCorrect = false;
751
- break;
931
+ queryResultErrors.birthdate.range = {
932
+ expected: queryResult.birthdate.range.expected,
933
+ received: [minDate, maxDate],
934
+ message: "Birthdate is not in the expected range",
935
+ };
752
936
  }
753
937
  }
754
938
  if (!queryResult.birthdate.lte &&
@@ -756,20 +940,30 @@ export class ZKPassport {
756
940
  maxDate.getTime() != defaultDateValue.getTime()) {
757
941
  console.warn("Maximum birthdate should be equal to default date value");
758
942
  isCorrect = false;
759
- break;
943
+ queryResultErrors.birthdate.disclose = {
944
+ expected: `${defaultDateValue.toISOString()}`,
945
+ received: `${maxDate.toISOString()}`,
946
+ message: "Maximum birthdate should be equal to default date value",
947
+ };
760
948
  }
761
949
  if (!queryResult.birthdate.gte &&
762
950
  !queryResult.birthdate.range &&
763
951
  minDate.getTime() != defaultDateValue.getTime()) {
764
952
  console.warn("Minimum birthdate should be equal to default date value");
765
953
  isCorrect = false;
766
- break;
954
+ queryResultErrors.birthdate.disclose = {
955
+ expected: `${defaultDateValue.toISOString()}`,
956
+ received: `${minDate.toISOString()}`,
957
+ message: "Minimum birthdate should be equal to default date value",
958
+ };
767
959
  }
768
960
  }
769
961
  else {
770
962
  console.warn("Birthdate is not set in the query result");
771
963
  isCorrect = false;
772
- break;
964
+ queryResultErrors.birthdate.disclose = {
965
+ message: "Birthdate is not set in the query result",
966
+ };
773
967
  }
774
968
  uniqueIdentifier = getCommitmentInFromDisclosureProof(proofData).toString(10);
775
969
  }
@@ -778,7 +972,11 @@ export class ZKPassport {
778
972
  if (commitmentIn !== commitmentOut) {
779
973
  console.warn("Failed to check the link between the validity of the ID and its expiry date");
780
974
  isCorrect = false;
781
- break;
975
+ queryResultErrors.expiry_date.commitment = {
976
+ expected: `Commitment: ${commitmentOut}`,
977
+ received: `Commitment: ${commitmentIn}`,
978
+ message: "Failed to check the link between the validity of the ID and its expiry date",
979
+ };
782
980
  }
783
981
  const minDate = getMinDateFromProof(proofData);
784
982
  const maxDate = getMaxDateFromProof(proofData);
@@ -788,14 +986,22 @@ export class ZKPassport {
788
986
  minDate < queryResult.expiry_date.gte.expected) {
789
987
  console.warn("Expiry date is not greater than or equal to the expected expiry date");
790
988
  isCorrect = false;
791
- break;
989
+ queryResultErrors.expiry_date.gte = {
990
+ expected: queryResult.expiry_date.gte.expected,
991
+ received: minDate,
992
+ message: "Expiry date is not greater than or equal to the expected expiry date",
993
+ };
792
994
  }
793
995
  if (queryResult.expiry_date.lte &&
794
996
  queryResult.expiry_date.lte.result &&
795
997
  maxDate > queryResult.expiry_date.lte.expected) {
796
998
  console.warn("Expiry date is not less than the expected expiry date");
797
999
  isCorrect = false;
798
- break;
1000
+ queryResultErrors.expiry_date.lte = {
1001
+ expected: queryResult.expiry_date.lte.expected,
1002
+ received: maxDate,
1003
+ message: "Expiry date is not less than the expected expiry date",
1004
+ };
799
1005
  }
800
1006
  if (queryResult.expiry_date.range) {
801
1007
  if (queryResult.expiry_date.range.result &&
@@ -803,7 +1009,11 @@ export class ZKPassport {
803
1009
  maxDate > queryResult.expiry_date.range.expected[1])) {
804
1010
  console.warn("Expiry date is not in the expected range");
805
1011
  isCorrect = false;
806
- break;
1012
+ queryResultErrors.expiry_date.range = {
1013
+ expected: queryResult.expiry_date.range.expected,
1014
+ received: [minDate, maxDate],
1015
+ message: "Expiry date is not in the expected range",
1016
+ };
807
1017
  }
808
1018
  }
809
1019
  if (!queryResult.expiry_date.lte &&
@@ -811,20 +1021,30 @@ export class ZKPassport {
811
1021
  maxDate.getTime() != defaultDateValue.getTime()) {
812
1022
  console.warn("Maximum expiry date should be equal to default date value");
813
1023
  isCorrect = false;
814
- break;
1024
+ queryResultErrors.expiry_date.disclose = {
1025
+ expected: `${defaultDateValue.toISOString()}`,
1026
+ received: `${maxDate.toISOString()}`,
1027
+ message: "Maximum expiry date should be equal to default date value",
1028
+ };
815
1029
  }
816
1030
  if (!queryResult.expiry_date.gte &&
817
1031
  !queryResult.expiry_date.range &&
818
1032
  minDate.getTime() != defaultDateValue.getTime()) {
819
1033
  console.warn("Minimum expiry date should be equal to default date value");
820
1034
  isCorrect = false;
821
- break;
1035
+ queryResultErrors.expiry_date.disclose = {
1036
+ expected: `${defaultDateValue.toISOString()}`,
1037
+ received: `${minDate.toISOString()}`,
1038
+ message: "Minimum expiry date should be equal to default date value",
1039
+ };
822
1040
  }
823
1041
  }
824
1042
  else {
825
1043
  console.warn("Expiry date is not set in the query result");
826
1044
  isCorrect = false;
827
- break;
1045
+ queryResultErrors.expiry_date.disclose = {
1046
+ message: "Expiry date is not set in the query result",
1047
+ };
828
1048
  }
829
1049
  uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10);
830
1050
  }
@@ -833,7 +1053,11 @@ export class ZKPassport {
833
1053
  if (commitmentIn !== commitmentOut) {
834
1054
  console.warn("Failed to check the link between the validity of the ID and the country exclusion check");
835
1055
  isCorrect = false;
836
- break;
1056
+ queryResultErrors.nationality.commitment = {
1057
+ expected: `Commitment: ${commitmentOut}`,
1058
+ received: `Commitment: ${commitmentIn}`,
1059
+ message: "Failed to check the link between the validity of the ID and the country exclusion check",
1060
+ };
837
1061
  }
838
1062
  const countryList = getCountryListFromExclusionProof(proofData);
839
1063
  if (queryResult.nationality &&
@@ -842,13 +1066,19 @@ export class ZKPassport {
842
1066
  if (!queryResult.nationality.out.expected?.every((country) => countryList.includes(country))) {
843
1067
  console.warn("Country exclusion list does not match the one from the query results");
844
1068
  isCorrect = false;
845
- break;
1069
+ queryResultErrors.nationality.out = {
1070
+ expected: queryResult.nationality.out.expected,
1071
+ received: countryList,
1072
+ message: "Country exclusion list does not match the one from the query results",
1073
+ };
846
1074
  }
847
1075
  }
848
1076
  else if (!queryResult.nationality || !queryResult.nationality.out) {
849
1077
  console.warn("Nationality exclusion is not set in the query result");
850
1078
  isCorrect = false;
851
- break;
1079
+ queryResultErrors.nationality.out = {
1080
+ message: "Nationality exclusion is not set in the query result",
1081
+ };
852
1082
  }
853
1083
  // Check the countryList is in ascending order
854
1084
  // If the prover doesn't use a sorted list then the proof cannot be trusted
@@ -857,7 +1087,9 @@ export class ZKPassport {
857
1087
  if (countryList[i] < countryList[i - 1]) {
858
1088
  console.warn("The nationality exclusion list has not been sorted, and thus the proof cannot be trusted");
859
1089
  isCorrect = false;
860
- break;
1090
+ queryResultErrors.nationality.out = {
1091
+ message: "The nationality exclusion list has not been sorted, and thus the proof cannot be trusted",
1092
+ };
861
1093
  }
862
1094
  }
863
1095
  uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10);
@@ -867,7 +1099,11 @@ export class ZKPassport {
867
1099
  if (commitmentIn !== commitmentOut) {
868
1100
  console.warn("Failed to check the link between the validity of the ID and the country inclusion check");
869
1101
  isCorrect = false;
870
- break;
1102
+ queryResultErrors.nationality.commitment = {
1103
+ expected: `Commitment: ${commitmentOut}`,
1104
+ received: `Commitment: ${commitmentIn}`,
1105
+ message: "Failed to check the link between the validity of the ID and the country inclusion check",
1106
+ };
871
1107
  }
872
1108
  const countryList = getCountryListFromInclusionProof(proofData);
873
1109
  if (queryResult.nationality &&
@@ -876,18 +1112,24 @@ export class ZKPassport {
876
1112
  if (!queryResult.nationality.in.expected?.every((country) => countryList.includes(country))) {
877
1113
  console.warn("Country inclusion list does not match the one from the query results");
878
1114
  isCorrect = false;
879
- break;
1115
+ queryResultErrors.nationality.in = {
1116
+ expected: queryResult.nationality.in.expected,
1117
+ received: countryList,
1118
+ message: "Country inclusion list does not match the one from the query results",
1119
+ };
880
1120
  }
881
1121
  }
882
1122
  else if (!queryResult.nationality || !queryResult.nationality.in) {
883
1123
  console.warn("Nationality inclusion is not set in the query result");
884
1124
  isCorrect = false;
885
- break;
1125
+ queryResultErrors.nationality.in = {
1126
+ message: "Nationality inclusion is not set in the query result",
1127
+ };
886
1128
  }
887
1129
  uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10);
888
1130
  }
889
1131
  }
890
- return { isCorrect, uniqueIdentifier };
1132
+ return { isCorrect, uniqueIdentifier, queryResultErrors };
891
1133
  }
892
1134
  /**
893
1135
  * @notice Verify the proofs received from the mobile app.
@@ -902,12 +1144,6 @@ export class ZKPassport {
902
1144
  // There is a minimum of 4 subproofs to make a complete proof
903
1145
  if (!proofs || proofs.length < 4) {
904
1146
  proofsToVerify = this.topicToProofs[requestId];
905
- if (!proofsToVerify || proofsToVerify.length < 4) {
906
- // It may happen that a request returns a result without proofs
907
- // Meaning the ID is not supported yet by ZKPassport circuits,
908
- // so the results has to be trusted and cannot be independently verified
909
- return { uniqueIdentifier: undefined, verified: false };
910
- }
911
1147
  }
912
1148
  const { BarretenbergVerifier } = await import("@aztec/bb.js");
913
1149
  const verifier = new BarretenbergVerifier();
@@ -916,13 +1152,15 @@ export class ZKPassport {
916
1152
  }*/
917
1153
  let verified = true;
918
1154
  let uniqueIdentifier;
1155
+ let queryResultErrors;
919
1156
  if (queryResult) {
920
- const { isCorrect, uniqueIdentifier: uniqueIdentifierFromPublicInputs } = await this.checkPublicInputs(proofsToVerify, queryResult);
1157
+ const { isCorrect, uniqueIdentifier: uniqueIdentifierFromPublicInputs, queryResultErrors: queryResultErrorsFromPublicInputs, } = await this.checkPublicInputs(proofsToVerify, queryResult, requestId);
921
1158
  uniqueIdentifier = uniqueIdentifierFromPublicInputs;
922
1159
  verified = isCorrect;
1160
+ queryResultErrors = isCorrect ? undefined : queryResultErrorsFromPublicInputs;
923
1161
  }
924
1162
  // Only proceed with the proof verification if the public inputs are correct
925
- if (verified) {
1163
+ if (verified && queryResult) {
926
1164
  for (const proof of proofsToVerify) {
927
1165
  const proofData = getProofData(proof.proof, true);
928
1166
  const hostedPackagedCircuit = await getHostedPackagedCircuitByName(proof.version, proof.name);
@@ -942,7 +1180,7 @@ export class ZKPassport {
942
1180
  }
943
1181
  }
944
1182
  this.topicToProofs[requestId] = [];
945
- return { uniqueIdentifier, verified };
1183
+ return { uniqueIdentifier, verified, queryResultErrors };
946
1184
  }
947
1185
  /**
948
1186
  * @notice Returns the URL of the request.
@@ -960,13 +1198,17 @@ export class ZKPassport {
960
1198
  * @param requestId The request ID.
961
1199
  */
962
1200
  cancelRequest(requestId) {
963
- this.topicToWebSocketClient[requestId].close();
964
- delete this.topicToWebSocketClient[requestId];
1201
+ if (this.topicToWebSocketClient[requestId]) {
1202
+ this.topicToWebSocketClient[requestId].close();
1203
+ delete this.topicToWebSocketClient[requestId];
1204
+ }
965
1205
  delete this.topicToKeyPair[requestId];
966
1206
  delete this.topicToConfig[requestId];
1207
+ delete this.topicToLocalConfig[requestId];
967
1208
  delete this.topicToSharedSecret[requestId];
968
1209
  delete this.topicToProofs[requestId];
969
1210
  delete this.topicToExpectedProofCount[requestId];
1211
+ delete this.topicToFailedProofCount[requestId];
970
1212
  delete this.topicToResults[requestId];
971
1213
  this.onRequestReceivedCallbacks[requestId] = [];
972
1214
  this.onGeneratingProofCallbacks[requestId] = [];
@@ -975,4 +1217,12 @@ export class ZKPassport {
975
1217
  this.onRejectCallbacks[requestId] = [];
976
1218
  this.onErrorCallbacks[requestId] = [];
977
1219
  }
1220
+ /**
1221
+ * @notice Clears all requests.
1222
+ */
1223
+ clearAllRequests() {
1224
+ for (const requestId in this.topicToWebSocketClient) {
1225
+ this.cancelRequest(requestId);
1226
+ }
1227
+ }
978
1228
  }