@zkpassport/sdk 0.2.8 → 0.2.10

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
@@ -70,6 +70,7 @@ export class ZKPassport {
70
70
  this.topicToService = {};
71
71
  this.topicToProofs = {};
72
72
  this.topicToExpectedProofCount = {};
73
+ this.topicToFailedProofCount = {};
73
74
  this.topicToResults = {};
74
75
  this.onRequestReceivedCallbacks = {};
75
76
  this.onGeneratingProofCallbacks = {};
@@ -94,14 +95,19 @@ export class ZKPassport {
94
95
  // Clear the results straight away to avoid concurrency issues
95
96
  delete this.topicToResults[topic];
96
97
  // Verify the proofs and extract the unique identifier (aka nullifier) and the verification result
97
- 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;
98
100
  await Promise.all(this.onResultCallbacks[topic].map((callback) => callback({
99
- uniqueIdentifier,
100
- 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,
101
105
  result,
106
+ queryResultErrors,
102
107
  })));
103
- // Clear the expected proof count
108
+ // Clear the expected proof count and failed proof count
104
109
  delete this.topicToExpectedProofCount[topic];
110
+ delete this.topicToFailedProofCount[topic];
105
111
  }
106
112
  setExpectedProofCount(topic) {
107
113
  const fields = Object.keys(this.topicToConfig[topic]).filter((key) => hasRequestedAccessToField(this.topicToConfig[topic], key));
@@ -135,13 +141,23 @@ export class ZKPassport {
135
141
  }
136
142
  break;
137
143
  case "in":
138
- if (field === "nationality" && !neededCircuits.includes("inclusion_check_country")) {
139
- neededCircuits.push("inclusion_check_country");
144
+ if (field === "nationality" &&
145
+ !neededCircuits.includes("inclusion_check_nationality")) {
146
+ neededCircuits.push("inclusion_check_nationality");
147
+ }
148
+ else if (field === "issuing_country" &&
149
+ !neededCircuits.includes("inclusion_check_issuing_country")) {
150
+ neededCircuits.push("inclusion_check_issuing_country");
140
151
  }
141
152
  break;
142
153
  case "out":
143
- if (field === "nationality" && !neededCircuits.includes("exclusion_check_country")) {
144
- neededCircuits.push("exclusion_check_country");
154
+ if (field === "nationality" &&
155
+ !neededCircuits.includes("exclusion_check_nationality")) {
156
+ neededCircuits.push("exclusion_check_nationality");
157
+ }
158
+ else if (field === "issuing_country" &&
159
+ !neededCircuits.includes("exclusion_check_issuing_country")) {
160
+ neededCircuits.push("exclusion_check_issuing_country");
145
161
  }
146
162
  break;
147
163
  }
@@ -152,6 +168,7 @@ export class ZKPassport {
152
168
  // Each separate needed circuit adds 1 disclosure proof
153
169
  this.topicToExpectedProofCount[topic] =
154
170
  neededCircuits.length === 0 ? 4 : 3 + neededCircuits.length;
171
+ this.topicToFailedProofCount[topic] = 0;
155
172
  }
156
173
  /**
157
174
  * @notice Handle an encrypted message.
@@ -207,6 +224,7 @@ export class ZKPassport {
207
224
  // This means the user has an ID that is not supported yet
208
225
  // So we won't receive any proofs and we can handle the result now
209
226
  this.topicToExpectedProofCount[topic] = 0;
227
+ this.topicToFailedProofCount[topic] += this.topicToExpectedProofCount[topic];
210
228
  if (this.topicToResults[topic]) {
211
229
  await this.handleResult(topic);
212
230
  }
@@ -215,6 +233,7 @@ export class ZKPassport {
215
233
  // This means one of the disclosure proofs failed to be generated
216
234
  // So we need to remove one from the expected proof count
217
235
  this.topicToExpectedProofCount[topic] -= 1;
236
+ this.topicToFailedProofCount[topic] += 1;
218
237
  // If the expected proof count is now equal to the number of proofs received
219
238
  // and the results were received, we can handle the result now
220
239
  if (this.topicToResults[topic] &&
@@ -271,9 +290,6 @@ export class ZKPassport {
271
290
  };
272
291
  return this.getZkPassportRequest(topic);
273
292
  },
274
- /*checkAML: (country?: CountryName | Alpha2Code | Alpha3Code) => {
275
- return this.getZkPassportRequest(topic)
276
- },*/
277
293
  done: () => {
278
294
  const base64Config = Buffer.from(JSON.stringify(this.topicToConfig[topic])).toString("base64");
279
295
  const base64Service = Buffer.from(JSON.stringify(this.topicToService[topic])).toString("base64");
@@ -384,6 +400,23 @@ export class ZKPassport {
384
400
  const defaultDateValue = new Date(1111, 10, 11);
385
401
  const currentTime = new Date();
386
402
  const today = new Date(currentTime.getFullYear(), currentTime.getMonth(), currentTime.getDate(), 0, 0, 0, 0);
403
+ const queryResultErrors = {
404
+ sig_check_dsc: {},
405
+ sig_check_id_data: {},
406
+ data_check_integrity: {},
407
+ disclose: {},
408
+ age: {},
409
+ birthdate: {},
410
+ expiry_date: {},
411
+ document_type: {},
412
+ issuing_country: {},
413
+ gender: {},
414
+ nationality: {},
415
+ firstname: {},
416
+ lastname: {},
417
+ fullname: {},
418
+ document_number: {},
419
+ };
387
420
  // Since the order is important for the commitments, we need to sort the proofs
388
421
  // by their expected order: root signature check -> ID signature check -> integrity check -> disclosure
389
422
  const sortedProofs = proofs.sort((a, b) => {
@@ -395,8 +428,10 @@ export class ZKPassport {
395
428
  "compare_age",
396
429
  "compare_birthdate",
397
430
  "compare_expiry",
398
- "exclusion_check_country",
399
- "inclusion_check_country",
431
+ "exclusion_check_nationality",
432
+ "inclusion_check_nationality",
433
+ "exclusion_check_issuing_country",
434
+ "inclusion_check_issuing_country",
400
435
  ];
401
436
  const getIndex = (proof) => {
402
437
  const name = proof.name || "";
@@ -412,7 +447,11 @@ export class ZKPassport {
412
447
  if (merkleRoot !== expectedMerkleRoot) {
413
448
  console.warn("The ID was signed by an unrecognized root certificate");
414
449
  isCorrect = false;
415
- break;
450
+ queryResultErrors.sig_check_dsc.certificate = {
451
+ expected: `Certificate registry root: ${expectedMerkleRoot.toString()}`,
452
+ received: `Certificate registry root: ${merkleRoot.toString()}`,
453
+ message: "The ID was signed by an unrecognized root certificate",
454
+ };
416
455
  }
417
456
  }
418
457
  else if (proof.name?.startsWith("sig_check_id_data")) {
@@ -420,7 +459,11 @@ export class ZKPassport {
420
459
  if (commitmentIn !== commitmentOut) {
421
460
  console.warn("Failed to check the link between the certificate signature and ID signature");
422
461
  isCorrect = false;
423
- break;
462
+ queryResultErrors.sig_check_id_data.commitment = {
463
+ expected: `Commitment: ${commitmentOut?.toString() || "undefined"}`,
464
+ received: `Commitment: ${commitmentIn?.toString() || "undefined"}`,
465
+ message: "Failed to check the link between the certificate signature and ID signature",
466
+ };
424
467
  }
425
468
  commitmentOut = getCommitmentOutFromIDDataProof(proofData);
426
469
  }
@@ -429,7 +472,11 @@ export class ZKPassport {
429
472
  if (commitmentIn !== commitmentOut) {
430
473
  console.warn("Failed to check the link between the ID signature and the data signed");
431
474
  isCorrect = false;
432
- break;
475
+ queryResultErrors.data_check_integrity.commitment = {
476
+ expected: `Commitment: ${commitmentOut?.toString() || "undefined"}`,
477
+ received: `Commitment: ${commitmentIn?.toString() || "undefined"}`,
478
+ message: "Failed to check the link between the ID signature and the data signed",
479
+ };
433
480
  }
434
481
  commitmentOut = getCommitmentOutFromIntegrityProof(proofData);
435
482
  const currentDate = getCurrentDateFromIntegrityProof(proofData);
@@ -440,7 +487,11 @@ export class ZKPassport {
440
487
  if (todayToCurrentDate >= actualDifference) {
441
488
  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`);
442
489
  isCorrect = false;
443
- break;
490
+ queryResultErrors.data_check_integrity.date = {
491
+ expected: `Difference: ${this.topicToLocalConfig[topic]?.validity} days`,
492
+ received: `Difference: ${Math.round(todayToCurrentDate / 86400000)} days`,
493
+ message: "The date used to check the validity of the ID is older than the validity period",
494
+ };
444
495
  }
445
496
  }
446
497
  else if (proof.name === "disclose_bytes") {
@@ -448,7 +499,11 @@ export class ZKPassport {
448
499
  if (commitmentIn !== commitmentOut) {
449
500
  console.warn("Failed to check the link between the validity of the ID and the data to disclose");
450
501
  isCorrect = false;
451
- break;
502
+ queryResultErrors.disclose.commitment = {
503
+ expected: `Commitment: ${commitmentOut?.toString() || "undefined"}`,
504
+ received: `Commitment: ${commitmentIn?.toString() || "undefined"}`,
505
+ message: "Failed to check the link between the validity of the ID and the data to disclose",
506
+ };
452
507
  }
453
508
  // We can't be certain that the disclosed data is for a passport or an ID card
454
509
  // so we need to check both (unless the document type is revealed)
@@ -461,12 +516,20 @@ export class ZKPassport {
461
516
  queryResult.document_type.eq.expected !== disclosedDataPassport.documentType) {
462
517
  console.warn("Document type does not match the expected document type");
463
518
  isCorrect = false;
464
- break;
519
+ queryResultErrors.document_type.eq = {
520
+ expected: `${queryResult.document_type.eq.expected}`,
521
+ received: `${disclosedDataPassport.documentType ?? disclosedDataIDCard.documentType}`,
522
+ message: "Document type does not match the expected document type",
523
+ };
465
524
  }
466
525
  if (queryResult.document_type.disclose?.result !== disclosedDataIDCard.documentType) {
467
526
  console.warn("Document type does not match the disclosed document type in query result");
468
527
  isCorrect = false;
469
- break;
528
+ queryResultErrors.document_type.disclose = {
529
+ expected: `${queryResult.document_type.disclose?.result}`,
530
+ received: `${disclosedDataIDCard.documentType ?? disclosedDataPassport.documentType}`,
531
+ message: "Document type does not match the disclosed document type in query result",
532
+ };
470
533
  }
471
534
  }
472
535
  if (queryResult.birthdate) {
@@ -478,14 +541,22 @@ export class ZKPassport {
478
541
  queryResult.birthdate.eq.expected.getTime() !== birthdateIDCard.getTime()) {
479
542
  console.warn("Birthdate does not match the expected birthdate");
480
543
  isCorrect = false;
481
- break;
544
+ queryResultErrors.birthdate.eq = {
545
+ expected: `${queryResult.birthdate.eq.expected.toISOString()}`,
546
+ received: `${birthdatePassport?.toISOString() ?? birthdateIDCard?.toISOString()}`,
547
+ message: "Birthdate does not match the expected birthdate",
548
+ };
482
549
  }
483
550
  if (queryResult.birthdate.disclose &&
484
551
  queryResult.birthdate.disclose.result.getTime() !== birthdatePassport.getTime() &&
485
552
  queryResult.birthdate.disclose.result.getTime() !== birthdateIDCard.getTime()) {
486
553
  console.warn("Birthdate does not match the disclosed birthdate in query result");
487
554
  isCorrect = false;
488
- break;
555
+ queryResultErrors.birthdate.disclose = {
556
+ expected: `${queryResult.birthdate.disclose.result.toISOString()}`,
557
+ received: `${birthdatePassport?.toISOString() ?? birthdateIDCard?.toISOString()}`,
558
+ message: "Birthdate does not match the disclosed birthdate in query result",
559
+ };
489
560
  }
490
561
  }
491
562
  if (queryResult.expiry_date) {
@@ -497,14 +568,22 @@ export class ZKPassport {
497
568
  queryResult.expiry_date.eq.expected.getTime() !== expiryDateIDCard.getTime()) {
498
569
  console.warn("Expiry date does not match the expected expiry date");
499
570
  isCorrect = false;
500
- break;
571
+ queryResultErrors.expiry_date.eq = {
572
+ expected: `${queryResult.expiry_date.eq.expected.toISOString()}`,
573
+ received: `${expiryDatePassport?.toISOString() ?? expiryDateIDCard?.toISOString()}`,
574
+ message: "Expiry date does not match the expected expiry date",
575
+ };
501
576
  }
502
577
  if (queryResult.expiry_date.disclose &&
503
578
  queryResult.expiry_date.disclose.result.getTime() !== expiryDatePassport.getTime() &&
504
579
  queryResult.expiry_date.disclose.result.getTime() !== expiryDateIDCard.getTime()) {
505
580
  console.warn("Expiry date does not match the disclosed expiry date in query result");
506
581
  isCorrect = false;
507
- break;
582
+ queryResultErrors.expiry_date.disclose = {
583
+ expected: `${queryResult.expiry_date.disclose.result.toISOString()}`,
584
+ received: `${expiryDatePassport?.toISOString() ?? expiryDateIDCard?.toISOString()}`,
585
+ message: "Expiry date does not match the disclosed expiry date in query result",
586
+ };
508
587
  }
509
588
  }
510
589
  if (queryResult.nationality) {
@@ -516,14 +595,22 @@ export class ZKPassport {
516
595
  queryResult.nationality.eq.expected !== nationalityIDCard) {
517
596
  console.warn("Nationality does not match the expected nationality");
518
597
  isCorrect = false;
519
- break;
598
+ queryResultErrors.nationality.eq = {
599
+ expected: `${queryResult.nationality.eq.expected}`,
600
+ received: `${nationalityPassport ?? nationalityIDCard}`,
601
+ message: "Nationality does not match the expected nationality",
602
+ };
520
603
  }
521
604
  if (queryResult.nationality.disclose &&
522
605
  queryResult.nationality.disclose.result !== nationalityPassport &&
523
606
  queryResult.nationality.disclose.result !== nationalityIDCard) {
524
607
  console.warn("Nationality does not match the disclosed nationality in query result");
525
608
  isCorrect = false;
526
- break;
609
+ queryResultErrors.nationality.disclose = {
610
+ expected: `${queryResult.nationality.disclose.result}`,
611
+ received: `${nationalityPassport ?? nationalityIDCard}`,
612
+ message: "Nationality does not match the disclosed nationality in query result",
613
+ };
527
614
  }
528
615
  }
529
616
  if (queryResult.document_number) {
@@ -535,14 +622,22 @@ export class ZKPassport {
535
622
  queryResult.document_number.eq.expected !== documentNumberIDCard) {
536
623
  console.warn("Document number does not match the expected document number");
537
624
  isCorrect = false;
538
- break;
625
+ queryResultErrors.document_number.eq = {
626
+ expected: `${queryResult.document_number.eq.expected}`,
627
+ received: `${documentNumberPassport ?? documentNumberIDCard}`,
628
+ message: "Document number does not match the expected document number",
629
+ };
539
630
  }
540
631
  if (queryResult.document_number.disclose &&
541
632
  queryResult.document_number.disclose.result !== documentNumberPassport &&
542
633
  queryResult.document_number.disclose.result !== documentNumberIDCard) {
543
634
  console.warn("Document number does not match the disclosed document number in query result");
544
635
  isCorrect = false;
545
- break;
636
+ queryResultErrors.document_number.disclose = {
637
+ expected: `${queryResult.document_number.disclose.result}`,
638
+ received: `${documentNumberPassport ?? documentNumberIDCard}`,
639
+ message: "Document number does not match the disclosed document number in query result",
640
+ };
546
641
  }
547
642
  }
548
643
  if (queryResult.gender) {
@@ -554,14 +649,22 @@ export class ZKPassport {
554
649
  queryResult.gender.eq.expected !== genderIDCard) {
555
650
  console.warn("Gender does not match the expected gender");
556
651
  isCorrect = false;
557
- break;
652
+ queryResultErrors.gender.eq = {
653
+ expected: `${queryResult.gender.eq.expected}`,
654
+ received: `${genderPassport ?? genderIDCard}`,
655
+ message: "Gender does not match the expected gender",
656
+ };
558
657
  }
559
658
  if (queryResult.gender.disclose &&
560
659
  queryResult.gender.disclose.result !== genderPassport &&
561
660
  queryResult.gender.disclose.result !== genderIDCard) {
562
661
  console.warn("Gender does not match the disclosed gender in query result");
563
662
  isCorrect = false;
564
- break;
663
+ queryResultErrors.gender.disclose = {
664
+ expected: `${queryResult.gender.disclose.result}`,
665
+ received: `${genderPassport ?? genderIDCard}`,
666
+ message: "Gender does not match the disclosed gender in query result",
667
+ };
565
668
  }
566
669
  }
567
670
  if (queryResult.issuing_country) {
@@ -573,14 +676,22 @@ export class ZKPassport {
573
676
  queryResult.issuing_country.eq.expected !== issuingCountryIDCard) {
574
677
  console.warn("Issuing country does not match the expected issuing country");
575
678
  isCorrect = false;
576
- break;
679
+ queryResultErrors.issuing_country.eq = {
680
+ expected: `${queryResult.issuing_country.eq.expected}`,
681
+ received: `${issuingCountryPassport ?? issuingCountryIDCard}`,
682
+ message: "Issuing country does not match the expected issuing country",
683
+ };
577
684
  }
578
685
  if (queryResult.issuing_country.disclose &&
579
686
  queryResult.issuing_country.disclose.result !== issuingCountryPassport &&
580
687
  queryResult.issuing_country.disclose.result !== issuingCountryIDCard) {
581
688
  console.warn("Issuing country does not match the disclosed issuing country in query result");
582
689
  isCorrect = false;
583
- break;
690
+ queryResultErrors.issuing_country.disclose = {
691
+ expected: `${queryResult.issuing_country.disclose.result}`,
692
+ received: `${issuingCountryPassport ?? issuingCountryIDCard}`,
693
+ message: "Issuing country does not match the disclosed issuing country in query result",
694
+ };
584
695
  }
585
696
  }
586
697
  if (queryResult.fullname) {
@@ -594,7 +705,11 @@ export class ZKPassport {
594
705
  fullnameIDCard.toLowerCase()) {
595
706
  console.warn("Fullname does not match the expected fullname");
596
707
  isCorrect = false;
597
- break;
708
+ queryResultErrors.fullname.eq = {
709
+ expected: `${queryResult.fullname.eq.expected}`,
710
+ received: `${fullnamePassport ?? fullnameIDCard}`,
711
+ message: "Fullname does not match the expected fullname",
712
+ };
598
713
  }
599
714
  if (queryResult.fullname.disclose &&
600
715
  formatName(queryResult.fullname.disclose.result).toLowerCase() !==
@@ -603,7 +718,11 @@ export class ZKPassport {
603
718
  fullnameIDCard.toLowerCase()) {
604
719
  console.warn("Fullname does not match the disclosed fullname in query result");
605
720
  isCorrect = false;
606
- break;
721
+ queryResultErrors.fullname.disclose = {
722
+ expected: `${queryResult.fullname.disclose.result}`,
723
+ received: `${fullnamePassport ?? fullnameIDCard}`,
724
+ message: "Fullname does not match the disclosed fullname in query result",
725
+ };
607
726
  }
608
727
  }
609
728
  if (queryResult.firstname) {
@@ -622,7 +741,11 @@ export class ZKPassport {
622
741
  firstnameIDCard.toLowerCase()) {
623
742
  console.warn("Firstname does not match the expected firstname");
624
743
  isCorrect = false;
625
- break;
744
+ queryResultErrors.firstname.eq = {
745
+ expected: `${queryResult.firstname.eq.expected}`,
746
+ received: `${firstnamePassport ?? firstnameIDCard}`,
747
+ message: "Firstname does not match the expected firstname",
748
+ };
626
749
  }
627
750
  if (queryResult.firstname.disclose &&
628
751
  formatName(queryResult.firstname.disclose.result).toLowerCase() !==
@@ -631,7 +754,11 @@ export class ZKPassport {
631
754
  firstnameIDCard.toLowerCase()) {
632
755
  console.warn("Firstname does not match the disclosed firstname in query result");
633
756
  isCorrect = false;
634
- break;
757
+ queryResultErrors.firstname.disclose = {
758
+ expected: `${queryResult.firstname.disclose.result}`,
759
+ received: `${firstnamePassport ?? firstnameIDCard}`,
760
+ message: "Firstname does not match the disclosed firstname in query result",
761
+ };
635
762
  }
636
763
  }
637
764
  if (queryResult.lastname) {
@@ -650,7 +777,11 @@ export class ZKPassport {
650
777
  lastnameIDCard.toLowerCase()) {
651
778
  console.warn("Lastname does not match the expected lastname");
652
779
  isCorrect = false;
653
- break;
780
+ queryResultErrors.lastname.eq = {
781
+ expected: `${queryResult.lastname.eq.expected}`,
782
+ received: `${lastnamePassport ?? lastnameIDCard}`,
783
+ message: "Lastname does not match the expected lastname",
784
+ };
654
785
  }
655
786
  if (queryResult.lastname.disclose &&
656
787
  formatName(queryResult.lastname.disclose.result).toLowerCase() !==
@@ -659,7 +790,11 @@ export class ZKPassport {
659
790
  lastnameIDCard.toLowerCase()) {
660
791
  console.warn("Lastname does not match the disclosed lastname in query result");
661
792
  isCorrect = false;
662
- break;
793
+ queryResultErrors.lastname.disclose = {
794
+ expected: `${queryResult.lastname.disclose.result}`,
795
+ received: `${lastnamePassport ?? lastnameIDCard}`,
796
+ message: "Lastname does not match the disclosed lastname in query result",
797
+ };
663
798
  }
664
799
  }
665
800
  uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10);
@@ -669,7 +804,11 @@ export class ZKPassport {
669
804
  if (commitmentIn !== commitmentOut) {
670
805
  console.warn("Failed to check the link between the validity of the ID and the age derived from it");
671
806
  isCorrect = false;
672
- break;
807
+ queryResultErrors.age.commitment = {
808
+ expected: `Commitment: ${commitmentOut}`,
809
+ received: `Commitment: ${commitmentIn}`,
810
+ message: "Failed to check the link between the validity of the ID and the age derived from it",
811
+ };
673
812
  }
674
813
  const minAge = getMinAgeFromProof(proofData);
675
814
  const maxAge = getMaxAgeFromProof(proofData);
@@ -679,14 +818,22 @@ export class ZKPassport {
679
818
  minAge < queryResult.age.gte.expected) {
680
819
  console.warn("Age is not greater than or equal to the expected age");
681
820
  isCorrect = false;
682
- break;
821
+ queryResultErrors.age.gte = {
822
+ expected: queryResult.age.gte.expected,
823
+ received: minAge,
824
+ message: "Age is not greater than or equal to the expected age",
825
+ };
683
826
  }
684
827
  if (queryResult.age.lt &&
685
828
  queryResult.age.lt.result &&
686
829
  maxAge >= queryResult.age.lt.expected) {
687
830
  console.warn("Age is not less than the expected age");
688
831
  isCorrect = false;
689
- break;
832
+ queryResultErrors.age.lt = {
833
+ expected: queryResult.age.lt.expected,
834
+ received: maxAge,
835
+ message: "Age is not less than the expected age",
836
+ };
690
837
  }
691
838
  if (queryResult.age.range) {
692
839
  if (queryResult.age.range.result &&
@@ -694,38 +841,60 @@ export class ZKPassport {
694
841
  maxAge >= queryResult.age.range.expected[1])) {
695
842
  console.warn("Age is not in the expected range");
696
843
  isCorrect = false;
697
- break;
844
+ queryResultErrors.age.range = {
845
+ expected: queryResult.age.range.expected,
846
+ received: [minAge, maxAge],
847
+ message: "Age is not in the expected range",
848
+ };
698
849
  }
699
850
  }
700
851
  if (!queryResult.age.lt && !queryResult.age.range && maxAge != 0) {
701
852
  console.warn("Maximum age should be equal to 0");
702
853
  isCorrect = false;
703
- break;
854
+ queryResultErrors.age.disclose = {
855
+ expected: 0,
856
+ received: maxAge,
857
+ message: "Maximum age should be equal to 0",
858
+ };
704
859
  }
705
860
  if (!queryResult.age.gte && !queryResult.age.range && minAge != 0) {
706
861
  console.warn("Minimum age should be equal to 0");
707
862
  isCorrect = false;
708
- break;
863
+ queryResultErrors.age.disclose = {
864
+ expected: 0,
865
+ received: minAge,
866
+ message: "Minimum age should be equal to 0",
867
+ };
709
868
  }
710
869
  if (queryResult.age.disclose &&
711
870
  (queryResult.age.disclose.result !== minAge ||
712
871
  queryResult.age.disclose.result !== maxAge)) {
713
872
  console.warn("Age does not match the disclosed age in query result");
714
873
  isCorrect = false;
715
- break;
874
+ queryResultErrors.age.disclose = {
875
+ expected: `${minAge}`,
876
+ received: `${queryResult.age.disclose.result}`,
877
+ message: "Age does not match the disclosed age in query result",
878
+ };
716
879
  }
717
880
  }
718
881
  else {
719
882
  console.warn("Age is not set in the query result");
720
883
  isCorrect = false;
721
- break;
884
+ queryResultErrors.age.disclose = {
885
+ message: "Age is not set in the query result",
886
+ };
722
887
  }
723
888
  const currentDate = getCurrentDateFromAgeProof(proofData);
724
889
  if (currentDate.getTime() !== today.getTime() &&
725
890
  currentDate.getTime() !== today.getTime() - 86400000) {
726
891
  console.warn("Current date in the proof is too old");
727
892
  isCorrect = false;
728
- break;
893
+ queryResultErrors.age.disclose = {
894
+ expected: `${today.toISOString()}`,
895
+ received: `${currentDate.toISOString()}`,
896
+ message: "Current date in the proof is too old",
897
+ };
729
898
  }
730
899
  uniqueIdentifier = getCommitmentInFromDisclosureProof(proofData).toString(10);
731
900
  }
@@ -734,7 +903,11 @@ export class ZKPassport {
734
903
  if (commitmentIn !== commitmentOut) {
735
904
  console.warn("Failed to check the link between the validity of the ID and the birthdate derived from it");
736
905
  isCorrect = false;
737
- break;
906
+ queryResultErrors.birthdate.commitment = {
907
+ expected: `Commitment: ${commitmentOut}`,
908
+ received: `Commitment: ${commitmentIn}`,
909
+ message: "Failed to check the link between the validity of the ID and the birthdate derived from it",
910
+ };
738
911
  }
739
912
  const minDate = getMinDateFromProof(proofData);
740
913
  const maxDate = getMaxDateFromProof(proofData);
@@ -744,14 +917,22 @@ export class ZKPassport {
744
917
  minDate < queryResult.birthdate.gte.expected) {
745
918
  console.warn("Birthdate is not greater than or equal to the expected birthdate");
746
919
  isCorrect = false;
747
- break;
920
+ queryResultErrors.birthdate.gte = {
921
+ expected: queryResult.birthdate.gte.expected,
922
+ received: minDate,
923
+ message: "Birthdate is not greater than or equal to the expected birthdate",
924
+ };
748
925
  }
749
926
  if (queryResult.birthdate.lte &&
750
927
  queryResult.birthdate.lte.result &&
751
928
  maxDate > queryResult.birthdate.lte.expected) {
752
929
  console.warn("Birthdate is not less than the expected birthdate");
753
930
  isCorrect = false;
754
- break;
931
+ queryResultErrors.birthdate.lte = {
932
+ expected: queryResult.birthdate.lte.expected,
933
+ received: maxDate,
934
+ message: "Birthdate is not less than the expected birthdate",
935
+ };
755
936
  }
756
937
  if (queryResult.birthdate.range) {
757
938
  if (queryResult.birthdate.range.result &&
@@ -759,7 +940,11 @@ export class ZKPassport {
759
940
  maxDate > queryResult.birthdate.range.expected[1])) {
760
941
  console.warn("Birthdate is not in the expected range");
761
942
  isCorrect = false;
762
- break;
943
+ queryResultErrors.birthdate.range = {
944
+ expected: queryResult.birthdate.range.expected,
945
+ received: [minDate, maxDate],
946
+ message: "Birthdate is not in the expected range",
947
+ };
763
948
  }
764
949
  }
765
950
  if (!queryResult.birthdate.lte &&
@@ -767,20 +952,30 @@ export class ZKPassport {
767
952
  maxDate.getTime() != defaultDateValue.getTime()) {
768
953
  console.warn("Maximum birthdate should be equal to default date value");
769
954
  isCorrect = false;
770
- break;
955
+ queryResultErrors.birthdate.disclose = {
956
+ expected: `${defaultDateValue.toISOString()}`,
957
+ received: `${maxDate.toISOString()}`,
958
+ message: "Maximum birthdate should be equal to default date value",
959
+ };
771
960
  }
772
961
  if (!queryResult.birthdate.gte &&
773
962
  !queryResult.birthdate.range &&
774
963
  minDate.getTime() != defaultDateValue.getTime()) {
775
964
  console.warn("Minimum birthdate should be equal to default date value");
776
965
  isCorrect = false;
777
- break;
966
+ queryResultErrors.birthdate.disclose = {
967
+ expected: `${defaultDateValue.toISOString()}`,
968
+ received: `${minDate.toISOString()}`,
969
+ message: "Minimum birthdate should be equal to default date value",
970
+ };
778
971
  }
779
972
  }
780
973
  else {
781
974
  console.warn("Birthdate is not set in the query result");
782
975
  isCorrect = false;
783
- break;
976
+ queryResultErrors.birthdate.disclose = {
977
+ message: "Birthdate is not set in the query result",
978
+ };
784
979
  }
785
980
  uniqueIdentifier = getCommitmentInFromDisclosureProof(proofData).toString(10);
786
981
  }
@@ -789,7 +984,11 @@ export class ZKPassport {
789
984
  if (commitmentIn !== commitmentOut) {
790
985
  console.warn("Failed to check the link between the validity of the ID and its expiry date");
791
986
  isCorrect = false;
792
- break;
987
+ queryResultErrors.expiry_date.commitment = {
988
+ expected: `Commitment: ${commitmentOut}`,
989
+ received: `Commitment: ${commitmentIn}`,
990
+ message: "Failed to check the link between the validity of the ID and its expiry date",
991
+ };
793
992
  }
794
993
  const minDate = getMinDateFromProof(proofData);
795
994
  const maxDate = getMaxDateFromProof(proofData);
@@ -799,14 +998,22 @@ export class ZKPassport {
799
998
  minDate < queryResult.expiry_date.gte.expected) {
800
999
  console.warn("Expiry date is not greater than or equal to the expected expiry date");
801
1000
  isCorrect = false;
802
- break;
1001
+ queryResultErrors.expiry_date.gte = {
1002
+ expected: queryResult.expiry_date.gte.expected,
1003
+ received: minDate,
1004
+ message: "Expiry date is not greater than or equal to the expected expiry date",
1005
+ };
803
1006
  }
804
1007
  if (queryResult.expiry_date.lte &&
805
1008
  queryResult.expiry_date.lte.result &&
806
1009
  maxDate > queryResult.expiry_date.lte.expected) {
807
1010
  console.warn("Expiry date is not less than the expected expiry date");
808
1011
  isCorrect = false;
809
- break;
1012
+ queryResultErrors.expiry_date.lte = {
1013
+ expected: queryResult.expiry_date.lte.expected,
1014
+ received: maxDate,
1015
+ message: "Expiry date is not less than the expected expiry date",
1016
+ };
810
1017
  }
811
1018
  if (queryResult.expiry_date.range) {
812
1019
  if (queryResult.expiry_date.range.result &&
@@ -814,7 +1021,11 @@ export class ZKPassport {
814
1021
  maxDate > queryResult.expiry_date.range.expected[1])) {
815
1022
  console.warn("Expiry date is not in the expected range");
816
1023
  isCorrect = false;
817
- break;
1024
+ queryResultErrors.expiry_date.range = {
1025
+ expected: queryResult.expiry_date.range.expected,
1026
+ received: [minDate, maxDate],
1027
+ message: "Expiry date is not in the expected range",
1028
+ };
818
1029
  }
819
1030
  }
820
1031
  if (!queryResult.expiry_date.lte &&
@@ -822,44 +1033,64 @@ export class ZKPassport {
822
1033
  maxDate.getTime() != defaultDateValue.getTime()) {
823
1034
  console.warn("Maximum expiry date should be equal to default date value");
824
1035
  isCorrect = false;
825
- break;
1036
+ queryResultErrors.expiry_date.disclose = {
1037
+ expected: `${defaultDateValue.toISOString()}`,
1038
+ received: `${maxDate.toISOString()}`,
1039
+ message: "Maximum expiry date should be equal to default date value",
1040
+ };
826
1041
  }
827
1042
  if (!queryResult.expiry_date.gte &&
828
1043
  !queryResult.expiry_date.range &&
829
1044
  minDate.getTime() != defaultDateValue.getTime()) {
830
1045
  console.warn("Minimum expiry date should be equal to default date value");
831
1046
  isCorrect = false;
832
- break;
1047
+ queryResultErrors.expiry_date.disclose = {
1048
+ expected: `${defaultDateValue.toISOString()}`,
1049
+ received: `${minDate.toISOString()}`,
1050
+ message: "Minimum expiry date should be equal to default date value",
1051
+ };
833
1052
  }
834
1053
  }
835
1054
  else {
836
1055
  console.warn("Expiry date is not set in the query result");
837
1056
  isCorrect = false;
838
- break;
1057
+ queryResultErrors.expiry_date.disclose = {
1058
+ message: "Expiry date is not set in the query result",
1059
+ };
839
1060
  }
840
1061
  uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10);
841
1062
  }
842
- else if (proof.name === "exclusion_check_country") {
1063
+ else if (proof.name === "exclusion_check_nationality") {
843
1064
  commitmentIn = getCommitmentInFromDisclosureProof(proofData);
844
1065
  if (commitmentIn !== commitmentOut) {
845
- console.warn("Failed to check the link between the validity of the ID and the country exclusion check");
1066
+ console.warn("Failed to check the link between the validity of the ID and the nationality exclusion check");
846
1067
  isCorrect = false;
847
- break;
1068
+ queryResultErrors.nationality.commitment = {
1069
+ expected: `Commitment: ${commitmentOut}`,
1070
+ received: `Commitment: ${commitmentIn}`,
1071
+ message: "Failed to check the link between the validity of the ID and the nationality exclusion check",
1072
+ };
848
1073
  }
849
1074
  const countryList = getCountryListFromExclusionProof(proofData);
850
1075
  if (queryResult.nationality &&
851
1076
  queryResult.nationality.out &&
852
1077
  queryResult.nationality.out.result) {
853
1078
  if (!queryResult.nationality.out.expected?.every((country) => countryList.includes(country))) {
854
- console.warn("Country exclusion list does not match the one from the query results");
1079
+ console.warn("Nationality exclusion list does not match the one from the query results");
855
1080
  isCorrect = false;
856
- break;
1081
+ queryResultErrors.nationality.out = {
1082
+ expected: queryResult.nationality.out.expected,
1083
+ received: countryList,
1084
+ message: "Nationality exclusion list does not match the one from the query results",
1085
+ };
857
1086
  }
858
1087
  }
859
1088
  else if (!queryResult.nationality || !queryResult.nationality.out) {
860
1089
  console.warn("Nationality exclusion is not set in the query result");
861
1090
  isCorrect = false;
862
- break;
1091
+ queryResultErrors.nationality.out = {
1092
+ message: "Nationality exclusion is not set in the query result",
1093
+ };
863
1094
  }
864
1095
  // Check the countryList is in ascending order
865
1096
  // If the prover doesn't use a sorted list then the proof cannot be trusted
@@ -868,37 +1099,129 @@ export class ZKPassport {
868
1099
  if (countryList[i] < countryList[i - 1]) {
869
1100
  console.warn("The nationality exclusion list has not been sorted, and thus the proof cannot be trusted");
870
1101
  isCorrect = false;
871
- break;
1102
+ queryResultErrors.nationality.out = {
1103
+ message: "The nationality exclusion list has not been sorted, and thus the proof cannot be trusted",
1104
+ };
872
1105
  }
873
1106
  }
874
1107
  uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10);
875
1108
  }
876
- else if (proof.name === "inclusion_check_country") {
1109
+ else if (proof.name === "exclusion_check_issuing_country") {
877
1110
  commitmentIn = getCommitmentInFromDisclosureProof(proofData);
878
1111
  if (commitmentIn !== commitmentOut) {
879
- console.warn("Failed to check the link between the validity of the ID and the country inclusion check");
1112
+ console.warn("Failed to check the link between the validity of the ID and the issuing country exclusion check");
880
1113
  isCorrect = false;
881
- break;
1114
+ queryResultErrors.nationality.commitment = {
1115
+ expected: `Commitment: ${commitmentOut}`,
1116
+ received: `Commitment: ${commitmentIn}`,
1117
+ message: "Failed to check the link between the validity of the ID and the issuing country exclusion check",
1118
+ };
1119
+ }
1120
+ const countryList = getCountryListFromExclusionProof(proofData);
1121
+ if (queryResult.issuing_country &&
1122
+ queryResult.issuing_country.out &&
1123
+ queryResult.issuing_country.out.result) {
1124
+ if (!queryResult.issuing_country.out.expected?.every((country) => countryList.includes(country))) {
1125
+ console.warn("Issuing country exclusion list does not match the one from the query results");
1126
+ isCorrect = false;
1127
+ queryResultErrors.issuing_country.out = {
1128
+ expected: queryResult.issuing_country.out.expected,
1129
+ received: countryList,
1130
+ message: "Issuing country exclusion list does not match the one from the query results",
1131
+ };
1132
+ }
1133
+ }
1134
+ else if (!queryResult.issuing_country || !queryResult.issuing_country.out) {
1135
+ console.warn("Issuing country exclusion is not set in the query result");
1136
+ isCorrect = false;
1137
+ queryResultErrors.issuing_country.out = {
1138
+ message: "Issuing country exclusion is not set in the query result",
1139
+ };
1140
+ }
1141
+ // Check the countryList is in ascending order
1142
+ // If the prover doesn't use a sorted list then the proof cannot be trusted
1143
+ // as it is requirement in the circuit for the exclusion check to work
1144
+ for (let i = 1; i < countryList.length; i++) {
1145
+ if (countryList[i] < countryList[i - 1]) {
1146
+ console.warn("The issuing country exclusion list has not been sorted, and thus the proof cannot be trusted");
1147
+ isCorrect = false;
1148
+ queryResultErrors.issuing_country.out = {
1149
+ message: "The issuing country exclusion list has not been sorted, and thus the proof cannot be trusted",
1150
+ };
1151
+ }
1152
+ }
1153
+ uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10);
1154
+ }
1155
+ else if (proof.name === "inclusion_check_nationality") {
1156
+ commitmentIn = getCommitmentInFromDisclosureProof(proofData);
1157
+ if (commitmentIn !== commitmentOut) {
1158
+ console.warn("Failed to check the link between the validity of the ID and the nationality inclusion check");
1159
+ isCorrect = false;
1160
+ queryResultErrors.nationality.commitment = {
1161
+ expected: `Commitment: ${commitmentOut}`,
1162
+ received: `Commitment: ${commitmentIn}`,
1163
+ message: "Failed to check the link between the validity of the ID and the nationality inclusion check",
1164
+ };
882
1165
  }
883
1166
  const countryList = getCountryListFromInclusionProof(proofData);
884
1167
  if (queryResult.nationality &&
885
1168
  queryResult.nationality.in &&
886
1169
  queryResult.nationality.in.result) {
887
1170
  if (!queryResult.nationality.in.expected?.every((country) => countryList.includes(country))) {
888
- console.warn("Country inclusion list does not match the one from the query results");
1171
+ console.warn("Nationality inclusion list does not match the one from the query results");
889
1172
  isCorrect = false;
890
- break;
1173
+ queryResultErrors.nationality.in = {
1174
+ expected: queryResult.nationality.in.expected,
1175
+ received: countryList,
1176
+ message: "Nationality inclusion list does not match the one from the query results",
1177
+ };
891
1178
  }
892
1179
  }
893
1180
  else if (!queryResult.nationality || !queryResult.nationality.in) {
894
1181
  console.warn("Nationality inclusion is not set in the query result");
895
1182
  isCorrect = false;
896
- break;
1183
+ queryResultErrors.nationality.in = {
1184
+ message: "Nationality inclusion is not set in the query result",
1185
+ };
1186
+ }
1187
+ uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10);
1188
+ }
1189
+ else if (proof.name === "inclusion_check_issuing_country") {
1190
+ commitmentIn = getCommitmentInFromDisclosureProof(proofData);
1191
+ if (commitmentIn !== commitmentOut) {
1192
+ console.warn("Failed to check the link between the validity of the ID and the issuing country inclusion check");
1193
+ isCorrect = false;
1194
+ queryResultErrors.nationality.commitment = {
1195
+ expected: `Commitment: ${commitmentOut}`,
1196
+ received: `Commitment: ${commitmentIn}`,
1197
+ message: "Failed to check the link between the validity of the ID and the issuing country inclusion check",
1198
+ };
1199
+ }
1200
+ const countryList = getCountryListFromInclusionProof(proofData);
1201
+ if (queryResult.issuing_country &&
1202
+ queryResult.issuing_country.in &&
1203
+ queryResult.issuing_country.in.result) {
1204
+ if (!queryResult.issuing_country.in.expected?.every((country) => countryList.includes(country))) {
1205
+ console.warn("Issuing country inclusion list does not match the one from the query results");
1206
+ isCorrect = false;
1207
+ queryResultErrors.issuing_country.in = {
1208
+ expected: queryResult.issuing_country.in.expected,
1209
+ received: countryList,
1210
+ message: "Issuing country inclusion list does not match the one from the query results",
1211
+ };
1212
+ }
1213
+ }
1214
+ else if (!queryResult.issuing_country || !queryResult.issuing_country.in) {
1215
+ console.warn("Issuing country inclusion is not set in the query result");
1216
+ isCorrect = false;
1217
+ queryResultErrors.issuing_country.in = {
1218
+ message: "Issuing country inclusion is not set in the query result",
1219
+ };
897
1220
  }
898
1221
  uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10);
899
1222
  }
900
1223
  }
901
- return { isCorrect, uniqueIdentifier };
1224
+ return { isCorrect, uniqueIdentifier, queryResultErrors };
902
1225
  }
903
1226
  /**
904
1227
  * @notice Verify the proofs received from the mobile app.
@@ -913,12 +1236,6 @@ export class ZKPassport {
913
1236
  // There is a minimum of 4 subproofs to make a complete proof
914
1237
  if (!proofs || proofs.length < 4) {
915
1238
  proofsToVerify = this.topicToProofs[requestId];
916
- if (!proofsToVerify || proofsToVerify.length < 4) {
917
- // It may happen that a request returns a result without proofs
918
- // Meaning the ID is not supported yet by ZKPassport circuits,
919
- // so the results has to be trusted and cannot be independently verified
920
- return { uniqueIdentifier: undefined, verified: false };
921
- }
922
1239
  }
923
1240
  const { BarretenbergVerifier } = await import("@aztec/bb.js");
924
1241
  const verifier = new BarretenbergVerifier();
@@ -927,13 +1244,15 @@ export class ZKPassport {
927
1244
  }*/
928
1245
  let verified = true;
929
1246
  let uniqueIdentifier;
1247
+ let queryResultErrors;
930
1248
  if (queryResult) {
931
- const { isCorrect, uniqueIdentifier: uniqueIdentifierFromPublicInputs } = await this.checkPublicInputs(proofsToVerify, queryResult, requestId);
1249
+ const { isCorrect, uniqueIdentifier: uniqueIdentifierFromPublicInputs, queryResultErrors: queryResultErrorsFromPublicInputs, } = await this.checkPublicInputs(proofsToVerify, queryResult, requestId);
932
1250
  uniqueIdentifier = uniqueIdentifierFromPublicInputs;
933
1251
  verified = isCorrect;
1252
+ queryResultErrors = isCorrect ? undefined : queryResultErrorsFromPublicInputs;
934
1253
  }
935
1254
  // Only proceed with the proof verification if the public inputs are correct
936
- if (verified) {
1255
+ if (verified && queryResult) {
937
1256
  for (const proof of proofsToVerify) {
938
1257
  const proofData = getProofData(proof.proof, true);
939
1258
  const hostedPackagedCircuit = await getHostedPackagedCircuitByName(proof.version, proof.name);
@@ -953,7 +1272,7 @@ export class ZKPassport {
953
1272
  }
954
1273
  }
955
1274
  this.topicToProofs[requestId] = [];
956
- return { uniqueIdentifier, verified };
1275
+ return { uniqueIdentifier, verified, queryResultErrors };
957
1276
  }
958
1277
  /**
959
1278
  * @notice Returns the URL of the request.
@@ -971,14 +1290,17 @@ export class ZKPassport {
971
1290
  * @param requestId The request ID.
972
1291
  */
973
1292
  cancelRequest(requestId) {
974
- this.topicToWebSocketClient[requestId].close();
975
- delete this.topicToWebSocketClient[requestId];
1293
+ if (this.topicToWebSocketClient[requestId]) {
1294
+ this.topicToWebSocketClient[requestId].close();
1295
+ delete this.topicToWebSocketClient[requestId];
1296
+ }
976
1297
  delete this.topicToKeyPair[requestId];
977
1298
  delete this.topicToConfig[requestId];
978
1299
  delete this.topicToLocalConfig[requestId];
979
1300
  delete this.topicToSharedSecret[requestId];
980
1301
  delete this.topicToProofs[requestId];
981
1302
  delete this.topicToExpectedProofCount[requestId];
1303
+ delete this.topicToFailedProofCount[requestId];
982
1304
  delete this.topicToResults[requestId];
983
1305
  this.onRequestReceivedCallbacks[requestId] = [];
984
1306
  this.onGeneratingProofCallbacks[requestId] = [];
@@ -987,4 +1309,12 @@ export class ZKPassport {
987
1309
  this.onRejectCallbacks[requestId] = [];
988
1310
  this.onErrorCallbacks[requestId] = [];
989
1311
  }
1312
+ /**
1313
+ * @notice Clears all requests.
1314
+ */
1315
+ clearAllRequests() {
1316
+ for (const requestId in this.topicToWebSocketClient) {
1317
+ this.cancelRequest(requestId);
1318
+ }
1319
+ }
990
1320
  }