@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/src/index.ts CHANGED
@@ -51,6 +51,33 @@ if (typeof globalThis.Buffer === "undefined") {
51
51
  }
52
52
  }
53
53
 
54
+ export type QueryResultError<T> = {
55
+ expected?: T
56
+ received?: T
57
+ message: string
58
+ }
59
+
60
+ export type QueryResultErrors = {
61
+ [key in
62
+ | IDCredential
63
+ | "sig_check_dsc"
64
+ | "sig_check_id_data"
65
+ | "data_check_integrity"
66
+ | "disclose"]: {
67
+ disclose?: QueryResultError<string | number | Date>
68
+ gte?: QueryResultError<number | Date>
69
+ lte?: QueryResultError<number | Date>
70
+ lt?: QueryResultError<number | Date>
71
+ range?: QueryResultError<[number | Date, number | Date]>
72
+ in?: QueryResultError<string[]>
73
+ out?: QueryResultError<string[]>
74
+ eq?: QueryResultError<string | number | Date>
75
+ commitment?: QueryResultError<string>
76
+ date?: QueryResultError<string>
77
+ certificate?: QueryResultError<string>
78
+ }
79
+ }
80
+
54
81
  registerLocale(i18en)
55
82
 
56
83
  function hasRequestedAccessToField(credentialsRequest: Query, field: IDCredential): boolean {
@@ -172,6 +199,7 @@ export type QueryBuilderResult = {
172
199
  uniqueIdentifier: string | undefined
173
200
  verified: boolean
174
201
  result: QueryResult
202
+ queryResultErrors?: QueryResultErrors
175
203
  }) => void,
176
204
  ) => void
177
205
  /**
@@ -260,6 +288,12 @@ export type QueryBuilder = {
260
288
  export class ZKPassport {
261
289
  private domain: string
262
290
  private topicToConfig: Record<string, Record<string, IDCredentialConfig>> = {}
291
+ private topicToLocalConfig: Record<
292
+ string,
293
+ {
294
+ validity: number
295
+ }
296
+ > = {}
263
297
  private topicToKeyPair: Record<string, { privateKey: Uint8Array; publicKey: Uint8Array }> = {}
264
298
  private topicToWebSocketClient: Record<string, WebSocketClient> = {}
265
299
  private topicToSharedSecret: Record<string, Uint8Array> = {}
@@ -270,6 +304,7 @@ export class ZKPassport {
270
304
  > = {}
271
305
  private topicToProofs: Record<string, Array<ProofResult>> = {}
272
306
  private topicToExpectedProofCount: Record<string, number> = {}
307
+ private topicToFailedProofCount: Record<string, number> = {}
273
308
  private topicToResults: Record<string, QueryResult> = {}
274
309
 
275
310
  private onRequestReceivedCallbacks: Record<string, Array<() => void>> = {}
@@ -283,6 +318,7 @@ export class ZKPassport {
283
318
  uniqueIdentifier: string | undefined
284
319
  verified: boolean
285
320
  result: QueryResult
321
+ queryResultErrors?: QueryResultErrors
286
322
  }) => void
287
323
  >
288
324
  > = {}
@@ -309,22 +345,27 @@ export class ZKPassport {
309
345
  // Clear the results straight away to avoid concurrency issues
310
346
  delete this.topicToResults[topic]
311
347
  // Verify the proofs and extract the unique identifier (aka nullifier) and the verification result
312
- const { uniqueIdentifier, verified } = await this.verify(
348
+ const { uniqueIdentifier, verified, queryResultErrors } = await this.verify(
313
349
  topic,
314
350
  this.topicToProofs[topic],
315
351
  result,
316
352
  )
353
+ const hasFailedProofs = this.topicToFailedProofCount[topic] > 0
317
354
  await Promise.all(
318
355
  this.onResultCallbacks[topic].map((callback) =>
319
356
  callback({
320
- uniqueIdentifier,
321
- verified,
357
+ // If there are failed proofs, we don't return the unique identifier
358
+ // and we set the verified result to false
359
+ uniqueIdentifier: hasFailedProofs ? undefined : uniqueIdentifier,
360
+ verified: hasFailedProofs ? false : verified,
322
361
  result,
362
+ queryResultErrors,
323
363
  }),
324
364
  ),
325
365
  )
326
- // Clear the expected proof count
366
+ // Clear the expected proof count and failed proof count
327
367
  delete this.topicToExpectedProofCount[topic]
368
+ delete this.topicToFailedProofCount[topic]
328
369
  }
329
370
 
330
371
  private setExpectedProofCount(topic: string) {
@@ -375,6 +416,7 @@ export class ZKPassport {
375
416
  // Each separate needed circuit adds 1 disclosure proof
376
417
  this.topicToExpectedProofCount[topic] =
377
418
  neededCircuits.length === 0 ? 4 : 3 + neededCircuits.length
419
+ this.topicToFailedProofCount[topic] = 0
378
420
  }
379
421
 
380
422
  /**
@@ -435,6 +477,7 @@ export class ZKPassport {
435
477
  // This means the user has an ID that is not supported yet
436
478
  // So we won't receive any proofs and we can handle the result now
437
479
  this.topicToExpectedProofCount[topic] = 0
480
+ this.topicToFailedProofCount[topic] += this.topicToExpectedProofCount[topic]
438
481
  if (this.topicToResults[topic]) {
439
482
  await this.handleResult(topic)
440
483
  }
@@ -442,6 +485,7 @@ export class ZKPassport {
442
485
  // This means one of the disclosure proofs failed to be generated
443
486
  // So we need to remove one from the expected proof count
444
487
  this.topicToExpectedProofCount[topic] -= 1
488
+ this.topicToFailedProofCount[topic] += 1
445
489
  // If the expected proof count is now equal to the number of proofs received
446
490
  // and the results were received, we can handle the result now
447
491
  if (
@@ -505,9 +549,6 @@ export class ZKPassport {
505
549
  }
506
550
  return this.getZkPassportRequest(topic)
507
551
  },
508
- /*checkAML: (country?: CountryName | Alpha2Code | Alpha3Code) => {
509
- return this.getZkPassportRequest(topic)
510
- },*/
511
552
  done: () => {
512
553
  const base64Config = Buffer.from(JSON.stringify(this.topicToConfig[topic])).toString(
513
554
  "base64",
@@ -533,6 +574,7 @@ export class ZKPassport {
533
574
  uniqueIdentifier: string | undefined
534
575
  verified: boolean
535
576
  result: QueryResult
577
+ queryResultErrors?: QueryResultErrors
536
578
  }) => void,
537
579
  ) => this.onResultCallbacks[topic].push(callback),
538
580
  onReject: (callback: () => void) => this.onRejectCallbacks[topic].push(callback),
@@ -546,7 +588,12 @@ export class ZKPassport {
546
588
  }
547
589
 
548
590
  /**
549
- * @notice Create a new request.
591
+ * @notice Create a new request
592
+ * @param name Your service name
593
+ * @param logo The logo of your service
594
+ * @param purpose To explain what you want to do with the user's data
595
+ * @param scope Scope this request to a specific use case
596
+ * @param validity How many days ago should have the ID been last scanned by the user?
550
597
  * @returns The query builder object.
551
598
  */
552
599
  public async request({
@@ -554,6 +601,7 @@ export class ZKPassport {
554
601
  logo,
555
602
  purpose,
556
603
  scope,
604
+ validity,
557
605
  topicOverride,
558
606
  keyPairOverride,
559
607
  }: {
@@ -561,6 +609,7 @@ export class ZKPassport {
561
609
  logo: string
562
610
  purpose: string
563
611
  scope?: string
612
+ validity?: number
564
613
  topicOverride?: string
565
614
  keyPairOverride?: { privateKey: Uint8Array; publicKey: Uint8Array }
566
615
  }): Promise<QueryBuilder> {
@@ -576,6 +625,10 @@ export class ZKPassport {
576
625
  this.topicToService[topic] = { name, logo, purpose, scope }
577
626
  this.topicToProofs[topic] = []
578
627
  this.topicToExpectedProofCount[topic] = 0
628
+ this.topicToLocalConfig[topic] = {
629
+ // Default to 6 months
630
+ validity: validity || 6 * 30,
631
+ }
579
632
 
580
633
  this.onRequestReceivedCallbacks[topic] = []
581
634
  this.onGeneratingProofCallbacks[topic] = []
@@ -650,7 +703,11 @@ export class ZKPassport {
650
703
  return this.getZkPassportRequest(topic)
651
704
  }
652
705
 
653
- private async checkPublicInputs(proofs: Array<ProofResult>, queryResult: QueryResult) {
706
+ private async checkPublicInputs(
707
+ proofs: Array<ProofResult>,
708
+ queryResult: QueryResult,
709
+ topic: string,
710
+ ) {
654
711
  let commitmentIn: bigint | undefined
655
712
  let commitmentOut: bigint | undefined
656
713
  let isCorrect = true
@@ -669,6 +726,23 @@ export class ZKPassport {
669
726
  0,
670
727
  0,
671
728
  )
729
+ const queryResultErrors: QueryResultErrors = {
730
+ sig_check_dsc: {},
731
+ sig_check_id_data: {},
732
+ data_check_integrity: {},
733
+ disclose: {},
734
+ age: {},
735
+ birthdate: {},
736
+ expiry_date: {},
737
+ document_type: {},
738
+ issuing_country: {},
739
+ gender: {},
740
+ nationality: {},
741
+ firstname: {},
742
+ lastname: {},
743
+ fullname: {},
744
+ document_number: {},
745
+ }
672
746
 
673
747
  // Since the order is important for the commitments, we need to sort the proofs
674
748
  // by their expected order: root signature check -> ID signature check -> integrity check -> disclosure
@@ -699,7 +773,11 @@ export class ZKPassport {
699
773
  if (merkleRoot !== expectedMerkleRoot) {
700
774
  console.warn("The ID was signed by an unrecognized root certificate")
701
775
  isCorrect = false
702
- break
776
+ queryResultErrors.sig_check_dsc.certificate = {
777
+ expected: `Certificate registry root: ${expectedMerkleRoot.toString()}`,
778
+ received: `Certificate registry root: ${merkleRoot.toString()}`,
779
+ message: "The ID was signed by an unrecognized root certificate",
780
+ }
703
781
  }
704
782
  } else if (proof.name?.startsWith("sig_check_id_data")) {
705
783
  commitmentIn = getCommitmentInFromIDDataProof(proofData)
@@ -708,7 +786,11 @@ export class ZKPassport {
708
786
  "Failed to check the link between the certificate signature and ID signature",
709
787
  )
710
788
  isCorrect = false
711
- break
789
+ queryResultErrors.sig_check_id_data.commitment = {
790
+ expected: `Commitment: ${commitmentOut?.toString() || "undefined"}`,
791
+ received: `Commitment: ${commitmentIn?.toString() || "undefined"}`,
792
+ message: "Failed to check the link between the certificate signature and ID signature",
793
+ }
712
794
  }
713
795
  commitmentOut = getCommitmentOutFromIDDataProof(proofData)
714
796
  } else if (proof.name?.startsWith("data_check_integrity")) {
@@ -716,19 +798,29 @@ export class ZKPassport {
716
798
  if (commitmentIn !== commitmentOut) {
717
799
  console.warn("Failed to check the link between the ID signature and the data signed")
718
800
  isCorrect = false
719
- break
801
+ queryResultErrors.data_check_integrity.commitment = {
802
+ expected: `Commitment: ${commitmentOut?.toString() || "undefined"}`,
803
+ received: `Commitment: ${commitmentIn?.toString() || "undefined"}`,
804
+ message: "Failed to check the link between the ID signature and the data signed",
805
+ }
720
806
  }
721
807
  commitmentOut = getCommitmentOutFromIntegrityProof(proofData)
722
808
  const currentDate = getCurrentDateFromIntegrityProof(proofData)
723
- // The date should be today or yesterday
724
- // (if the proof request was requested just before midnight and is finalized after)
725
- if (
726
- currentDate.getTime() !== today.getTime() &&
727
- currentDate.getTime() !== today.getTime() - 86400000
728
- ) {
729
- console.warn("Current date used to check the validity of the ID is too old")
809
+ const todayToCurrentDate = today.getTime() - currentDate.getTime()
810
+ const expectedDifference = this.topicToLocalConfig[topic]?.validity * 86400000
811
+ const actualDifference = today.getTime() - (today.getTime() - expectedDifference)
812
+ // The ID should not expire within the next 6 months (or whatever the custom value is)
813
+ if (todayToCurrentDate >= actualDifference) {
814
+ console.warn(
815
+ `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`,
816
+ )
730
817
  isCorrect = false
731
- break
818
+ queryResultErrors.data_check_integrity.date = {
819
+ expected: `Difference: ${this.topicToLocalConfig[topic]?.validity} days`,
820
+ received: `Difference: ${Math.round(todayToCurrentDate / 86400000)} days`,
821
+ message:
822
+ "The date used to check the validity of the ID is older than the validity period",
823
+ }
732
824
  }
733
825
  } else if (proof.name === "disclose_bytes") {
734
826
  commitmentIn = getCommitmentInFromDisclosureProof(proofData)
@@ -737,7 +829,12 @@ export class ZKPassport {
737
829
  "Failed to check the link between the validity of the ID and the data to disclose",
738
830
  )
739
831
  isCorrect = false
740
- break
832
+ queryResultErrors.disclose.commitment = {
833
+ expected: `Commitment: ${commitmentOut?.toString() || "undefined"}`,
834
+ received: `Commitment: ${commitmentIn?.toString() || "undefined"}`,
835
+ message:
836
+ "Failed to check the link between the validity of the ID and the data to disclose",
837
+ }
741
838
  }
742
839
  // We can't be certain that the disclosed data is for a passport or an ID card
743
840
  // so we need to check both (unless the document type is revealed)
@@ -752,12 +849,20 @@ export class ZKPassport {
752
849
  ) {
753
850
  console.warn("Document type does not match the expected document type")
754
851
  isCorrect = false
755
- break
852
+ queryResultErrors.document_type.eq = {
853
+ expected: `${queryResult.document_type.eq.expected}`,
854
+ received: `${disclosedDataPassport.documentType}`,
855
+ message: "Document type does not match the expected document type",
856
+ }
756
857
  }
757
858
  if (queryResult.document_type.disclose?.result !== disclosedDataIDCard.documentType) {
758
859
  console.warn("Document type does not match the disclosed document type in query result")
759
860
  isCorrect = false
760
- break
861
+ queryResultErrors.document_type.disclose = {
862
+ expected: `${queryResult.document_type.disclose?.result}`,
863
+ received: `${disclosedDataIDCard.documentType}`,
864
+ message: "Document type does not match the disclosed document type in query result",
865
+ }
761
866
  }
762
867
  }
763
868
  if (queryResult.birthdate) {
@@ -771,7 +876,11 @@ export class ZKPassport {
771
876
  ) {
772
877
  console.warn("Birthdate does not match the expected birthdate")
773
878
  isCorrect = false
774
- break
879
+ queryResultErrors.birthdate.eq = {
880
+ expected: `${queryResult.birthdate.eq.expected.toISOString()}`,
881
+ received: `${birthdatePassport.toISOString()}`,
882
+ message: "Birthdate does not match the expected birthdate",
883
+ }
775
884
  }
776
885
  if (
777
886
  queryResult.birthdate.disclose &&
@@ -780,7 +889,11 @@ export class ZKPassport {
780
889
  ) {
781
890
  console.warn("Birthdate does not match the disclosed birthdate in query result")
782
891
  isCorrect = false
783
- break
892
+ queryResultErrors.birthdate.disclose = {
893
+ expected: `${queryResult.birthdate.disclose.result.toISOString()}`,
894
+ received: `${birthdatePassport.toISOString()}`,
895
+ message: "Birthdate does not match the disclosed birthdate in query result",
896
+ }
784
897
  }
785
898
  }
786
899
  if (queryResult.expiry_date) {
@@ -794,7 +907,11 @@ export class ZKPassport {
794
907
  ) {
795
908
  console.warn("Expiry date does not match the expected expiry date")
796
909
  isCorrect = false
797
- break
910
+ queryResultErrors.expiry_date.eq = {
911
+ expected: `${queryResult.expiry_date.eq.expected.toISOString()}`,
912
+ received: `${expiryDatePassport.toISOString()}`,
913
+ message: "Expiry date does not match the expected expiry date",
914
+ }
798
915
  }
799
916
  if (
800
917
  queryResult.expiry_date.disclose &&
@@ -803,7 +920,11 @@ export class ZKPassport {
803
920
  ) {
804
921
  console.warn("Expiry date does not match the disclosed expiry date in query result")
805
922
  isCorrect = false
806
- break
923
+ queryResultErrors.expiry_date.disclose = {
924
+ expected: `${queryResult.expiry_date.disclose.result.toISOString()}`,
925
+ received: `${expiryDatePassport.toISOString()}`,
926
+ message: "Expiry date does not match the disclosed expiry date in query result",
927
+ }
807
928
  }
808
929
  }
809
930
  if (queryResult.nationality) {
@@ -817,7 +938,11 @@ export class ZKPassport {
817
938
  ) {
818
939
  console.warn("Nationality does not match the expected nationality")
819
940
  isCorrect = false
820
- break
941
+ queryResultErrors.nationality.eq = {
942
+ expected: `${queryResult.nationality.eq.expected}`,
943
+ received: `${nationalityPassport}`,
944
+ message: "Nationality does not match the expected nationality",
945
+ }
821
946
  }
822
947
  if (
823
948
  queryResult.nationality.disclose &&
@@ -826,7 +951,11 @@ export class ZKPassport {
826
951
  ) {
827
952
  console.warn("Nationality does not match the disclosed nationality in query result")
828
953
  isCorrect = false
829
- break
954
+ queryResultErrors.nationality.disclose = {
955
+ expected: `${queryResult.nationality.disclose.result}`,
956
+ received: `${nationalityPassport}`,
957
+ message: "Nationality does not match the disclosed nationality in query result",
958
+ }
830
959
  }
831
960
  }
832
961
  if (queryResult.document_number) {
@@ -840,7 +969,11 @@ export class ZKPassport {
840
969
  ) {
841
970
  console.warn("Document number does not match the expected document number")
842
971
  isCorrect = false
843
- break
972
+ queryResultErrors.document_number.eq = {
973
+ expected: `${queryResult.document_number.eq.expected}`,
974
+ received: `${documentNumberPassport}`,
975
+ message: "Document number does not match the expected document number",
976
+ }
844
977
  }
845
978
  if (
846
979
  queryResult.document_number.disclose &&
@@ -851,7 +984,12 @@ export class ZKPassport {
851
984
  "Document number does not match the disclosed document number in query result",
852
985
  )
853
986
  isCorrect = false
854
- break
987
+ queryResultErrors.document_number.disclose = {
988
+ expected: `${queryResult.document_number.disclose.result}`,
989
+ received: `${documentNumberPassport}`,
990
+ message:
991
+ "Document number does not match the disclosed document number in query result",
992
+ }
855
993
  }
856
994
  }
857
995
  if (queryResult.gender) {
@@ -865,7 +1003,11 @@ export class ZKPassport {
865
1003
  ) {
866
1004
  console.warn("Gender does not match the expected gender")
867
1005
  isCorrect = false
868
- break
1006
+ queryResultErrors.gender.eq = {
1007
+ expected: `${queryResult.gender.eq.expected}`,
1008
+ received: `${genderPassport}`,
1009
+ message: "Gender does not match the expected gender",
1010
+ }
869
1011
  }
870
1012
  if (
871
1013
  queryResult.gender.disclose &&
@@ -874,7 +1016,11 @@ export class ZKPassport {
874
1016
  ) {
875
1017
  console.warn("Gender does not match the disclosed gender in query result")
876
1018
  isCorrect = false
877
- break
1019
+ queryResultErrors.gender.disclose = {
1020
+ expected: `${queryResult.gender.disclose.result}`,
1021
+ received: `${genderPassport}`,
1022
+ message: "Gender does not match the disclosed gender in query result",
1023
+ }
878
1024
  }
879
1025
  }
880
1026
  if (queryResult.issuing_country) {
@@ -888,7 +1034,11 @@ export class ZKPassport {
888
1034
  ) {
889
1035
  console.warn("Issuing country does not match the expected issuing country")
890
1036
  isCorrect = false
891
- break
1037
+ queryResultErrors.issuing_country.eq = {
1038
+ expected: `${queryResult.issuing_country.eq.expected}`,
1039
+ received: `${issuingCountryPassport}`,
1040
+ message: "Issuing country does not match the expected issuing country",
1041
+ }
892
1042
  }
893
1043
  if (
894
1044
  queryResult.issuing_country.disclose &&
@@ -899,7 +1049,12 @@ export class ZKPassport {
899
1049
  "Issuing country does not match the disclosed issuing country in query result",
900
1050
  )
901
1051
  isCorrect = false
902
- break
1052
+ queryResultErrors.issuing_country.disclose = {
1053
+ expected: `${queryResult.issuing_country.disclose.result}`,
1054
+ received: `${issuingCountryPassport}`,
1055
+ message:
1056
+ "Issuing country does not match the disclosed issuing country in query result",
1057
+ }
903
1058
  }
904
1059
  }
905
1060
  if (queryResult.fullname) {
@@ -915,7 +1070,11 @@ export class ZKPassport {
915
1070
  ) {
916
1071
  console.warn("Fullname does not match the expected fullname")
917
1072
  isCorrect = false
918
- break
1073
+ queryResultErrors.fullname.eq = {
1074
+ expected: `${queryResult.fullname.eq.expected}`,
1075
+ received: `${fullnamePassport}`,
1076
+ message: "Fullname does not match the expected fullname",
1077
+ }
919
1078
  }
920
1079
  if (
921
1080
  queryResult.fullname.disclose &&
@@ -926,7 +1085,11 @@ export class ZKPassport {
926
1085
  ) {
927
1086
  console.warn("Fullname does not match the disclosed fullname in query result")
928
1087
  isCorrect = false
929
- break
1088
+ queryResultErrors.fullname.disclose = {
1089
+ expected: `${queryResult.fullname.disclose.result}`,
1090
+ received: `${fullnamePassport}`,
1091
+ message: "Fullname does not match the disclosed fullname in query result",
1092
+ }
930
1093
  }
931
1094
  }
932
1095
  if (queryResult.firstname) {
@@ -949,7 +1112,11 @@ export class ZKPassport {
949
1112
  ) {
950
1113
  console.warn("Firstname does not match the expected firstname")
951
1114
  isCorrect = false
952
- break
1115
+ queryResultErrors.firstname.eq = {
1116
+ expected: `${queryResult.firstname.eq.expected}`,
1117
+ received: `${firstnamePassport}`,
1118
+ message: "Firstname does not match the expected firstname",
1119
+ }
953
1120
  }
954
1121
  if (
955
1122
  queryResult.firstname.disclose &&
@@ -960,7 +1127,11 @@ export class ZKPassport {
960
1127
  ) {
961
1128
  console.warn("Firstname does not match the disclosed firstname in query result")
962
1129
  isCorrect = false
963
- break
1130
+ queryResultErrors.firstname.disclose = {
1131
+ expected: `${queryResult.firstname.disclose.result}`,
1132
+ received: `${firstnamePassport}`,
1133
+ message: "Firstname does not match the disclosed firstname in query result",
1134
+ }
964
1135
  }
965
1136
  }
966
1137
  if (queryResult.lastname) {
@@ -983,7 +1154,11 @@ export class ZKPassport {
983
1154
  ) {
984
1155
  console.warn("Lastname does not match the expected lastname")
985
1156
  isCorrect = false
986
- break
1157
+ queryResultErrors.lastname.eq = {
1158
+ expected: `${queryResult.lastname.eq.expected}`,
1159
+ received: `${lastnamePassport}`,
1160
+ message: "Lastname does not match the expected lastname",
1161
+ }
987
1162
  }
988
1163
  if (
989
1164
  queryResult.lastname.disclose &&
@@ -994,7 +1169,11 @@ export class ZKPassport {
994
1169
  ) {
995
1170
  console.warn("Lastname does not match the disclosed lastname in query result")
996
1171
  isCorrect = false
997
- break
1172
+ queryResultErrors.lastname.disclose = {
1173
+ expected: `${queryResult.lastname.disclose.result}`,
1174
+ received: `${lastnamePassport}`,
1175
+ message: "Lastname does not match the disclosed lastname in query result",
1176
+ }
998
1177
  }
999
1178
  }
1000
1179
  uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10)
@@ -1005,7 +1184,12 @@ export class ZKPassport {
1005
1184
  "Failed to check the link between the validity of the ID and the age derived from it",
1006
1185
  )
1007
1186
  isCorrect = false
1008
- break
1187
+ queryResultErrors.age.commitment = {
1188
+ expected: `Commitment: ${commitmentOut}`,
1189
+ received: `Commitment: ${commitmentIn}`,
1190
+ message:
1191
+ "Failed to check the link between the validity of the ID and the age derived from it",
1192
+ }
1009
1193
  }
1010
1194
  const minAge = getMinAgeFromProof(proofData)
1011
1195
  const maxAge = getMaxAgeFromProof(proofData)
@@ -1017,7 +1201,11 @@ export class ZKPassport {
1017
1201
  ) {
1018
1202
  console.warn("Age is not greater than or equal to the expected age")
1019
1203
  isCorrect = false
1020
- break
1204
+ queryResultErrors.age.gte = {
1205
+ expected: queryResult.age.gte.expected,
1206
+ received: minAge,
1207
+ message: "Age is not greater than or equal to the expected age",
1208
+ }
1021
1209
  }
1022
1210
  if (
1023
1211
  queryResult.age.lt &&
@@ -1026,7 +1214,11 @@ export class ZKPassport {
1026
1214
  ) {
1027
1215
  console.warn("Age is not less than the expected age")
1028
1216
  isCorrect = false
1029
- break
1217
+ queryResultErrors.age.lt = {
1218
+ expected: queryResult.age.lt.expected,
1219
+ received: maxAge,
1220
+ message: "Age is not less than the expected age",
1221
+ }
1030
1222
  }
1031
1223
  if (queryResult.age.range) {
1032
1224
  if (
@@ -1036,18 +1228,30 @@ export class ZKPassport {
1036
1228
  ) {
1037
1229
  console.warn("Age is not in the expected range")
1038
1230
  isCorrect = false
1039
- break
1231
+ queryResultErrors.age.range = {
1232
+ expected: queryResult.age.range.expected,
1233
+ received: [minAge, maxAge],
1234
+ message: "Age is not in the expected range",
1235
+ }
1040
1236
  }
1041
1237
  }
1042
1238
  if (!queryResult.age.lt && !queryResult.age.range && maxAge != 0) {
1043
1239
  console.warn("Maximum age should be equal to 0")
1044
1240
  isCorrect = false
1045
- break
1241
+ queryResultErrors.age.disclose = {
1242
+ expected: 0,
1243
+ received: maxAge,
1244
+ message: "Maximum age should be equal to 0",
1245
+ }
1046
1246
  }
1047
1247
  if (!queryResult.age.gte && !queryResult.age.range && minAge != 0) {
1048
1248
  console.warn("Minimum age should be equal to 0")
1049
1249
  isCorrect = false
1050
- break
1250
+ queryResultErrors.age.disclose = {
1251
+ expected: 0,
1252
+ received: minAge,
1253
+ message: "Minimum age should be equal to 0",
1254
+ }
1051
1255
  }
1052
1256
  if (
1053
1257
  queryResult.age.disclose &&
@@ -1056,12 +1260,18 @@ export class ZKPassport {
1056
1260
  ) {
1057
1261
  console.warn("Age does not match the disclosed age in query result")
1058
1262
  isCorrect = false
1059
- break
1263
+ queryResultErrors.age.disclose = {
1264
+ expected: `${minAge}`,
1265
+ received: `${queryResult.age.disclose.result}`,
1266
+ message: "Age does not match the disclosed age in query result",
1267
+ }
1060
1268
  }
1061
1269
  } else {
1062
1270
  console.warn("Age is not set in the query result")
1063
1271
  isCorrect = false
1064
- break
1272
+ queryResultErrors.age.disclose = {
1273
+ message: "Age is not set in the query result",
1274
+ }
1065
1275
  }
1066
1276
  const currentDate = getCurrentDateFromAgeProof(proofData)
1067
1277
  if (
@@ -1070,7 +1280,11 @@ export class ZKPassport {
1070
1280
  ) {
1071
1281
  console.warn("Current date in the proof is too old")
1072
1282
  isCorrect = false
1073
- break
1283
+ queryResultErrors.age.disclose = {
1284
+ expected: `${today.toISOString()}`,
1285
+ received: `${currentDate.toISOString()}`,
1286
+ message: "Current date in the proof is too old",
1287
+ }
1074
1288
  }
1075
1289
  uniqueIdentifier = getCommitmentInFromDisclosureProof(proofData).toString(10)
1076
1290
  } else if (proof.name === "compare_birthdate") {
@@ -1080,7 +1294,12 @@ export class ZKPassport {
1080
1294
  "Failed to check the link between the validity of the ID and the birthdate derived from it",
1081
1295
  )
1082
1296
  isCorrect = false
1083
- break
1297
+ queryResultErrors.birthdate.commitment = {
1298
+ expected: `Commitment: ${commitmentOut}`,
1299
+ received: `Commitment: ${commitmentIn}`,
1300
+ message:
1301
+ "Failed to check the link between the validity of the ID and the birthdate derived from it",
1302
+ }
1084
1303
  }
1085
1304
  const minDate = getMinDateFromProof(proofData)
1086
1305
  const maxDate = getMaxDateFromProof(proofData)
@@ -1092,7 +1311,11 @@ export class ZKPassport {
1092
1311
  ) {
1093
1312
  console.warn("Birthdate is not greater than or equal to the expected birthdate")
1094
1313
  isCorrect = false
1095
- break
1314
+ queryResultErrors.birthdate.gte = {
1315
+ expected: queryResult.birthdate.gte.expected,
1316
+ received: minDate,
1317
+ message: "Birthdate is not greater than or equal to the expected birthdate",
1318
+ }
1096
1319
  }
1097
1320
  if (
1098
1321
  queryResult.birthdate.lte &&
@@ -1101,7 +1324,11 @@ export class ZKPassport {
1101
1324
  ) {
1102
1325
  console.warn("Birthdate is not less than the expected birthdate")
1103
1326
  isCorrect = false
1104
- break
1327
+ queryResultErrors.birthdate.lte = {
1328
+ expected: queryResult.birthdate.lte.expected,
1329
+ received: maxDate,
1330
+ message: "Birthdate is not less than the expected birthdate",
1331
+ }
1105
1332
  }
1106
1333
  if (queryResult.birthdate.range) {
1107
1334
  if (
@@ -1111,7 +1338,11 @@ export class ZKPassport {
1111
1338
  ) {
1112
1339
  console.warn("Birthdate is not in the expected range")
1113
1340
  isCorrect = false
1114
- break
1341
+ queryResultErrors.birthdate.range = {
1342
+ expected: queryResult.birthdate.range.expected,
1343
+ received: [minDate, maxDate],
1344
+ message: "Birthdate is not in the expected range",
1345
+ }
1115
1346
  }
1116
1347
  }
1117
1348
  if (
@@ -1121,7 +1352,11 @@ export class ZKPassport {
1121
1352
  ) {
1122
1353
  console.warn("Maximum birthdate should be equal to default date value")
1123
1354
  isCorrect = false
1124
- break
1355
+ queryResultErrors.birthdate.disclose = {
1356
+ expected: `${defaultDateValue.toISOString()}`,
1357
+ received: `${maxDate.toISOString()}`,
1358
+ message: "Maximum birthdate should be equal to default date value",
1359
+ }
1125
1360
  }
1126
1361
  if (
1127
1362
  !queryResult.birthdate.gte &&
@@ -1130,12 +1365,18 @@ export class ZKPassport {
1130
1365
  ) {
1131
1366
  console.warn("Minimum birthdate should be equal to default date value")
1132
1367
  isCorrect = false
1133
- break
1368
+ queryResultErrors.birthdate.disclose = {
1369
+ expected: `${defaultDateValue.toISOString()}`,
1370
+ received: `${minDate.toISOString()}`,
1371
+ message: "Minimum birthdate should be equal to default date value",
1372
+ }
1134
1373
  }
1135
1374
  } else {
1136
1375
  console.warn("Birthdate is not set in the query result")
1137
1376
  isCorrect = false
1138
- break
1377
+ queryResultErrors.birthdate.disclose = {
1378
+ message: "Birthdate is not set in the query result",
1379
+ }
1139
1380
  }
1140
1381
  uniqueIdentifier = getCommitmentInFromDisclosureProof(proofData).toString(10)
1141
1382
  } else if (proof.name === "compare_expiry") {
@@ -1145,7 +1386,11 @@ export class ZKPassport {
1145
1386
  "Failed to check the link between the validity of the ID and its expiry date",
1146
1387
  )
1147
1388
  isCorrect = false
1148
- break
1389
+ queryResultErrors.expiry_date.commitment = {
1390
+ expected: `Commitment: ${commitmentOut}`,
1391
+ received: `Commitment: ${commitmentIn}`,
1392
+ message: "Failed to check the link between the validity of the ID and its expiry date",
1393
+ }
1149
1394
  }
1150
1395
  const minDate = getMinDateFromProof(proofData)
1151
1396
  const maxDate = getMaxDateFromProof(proofData)
@@ -1157,7 +1402,11 @@ export class ZKPassport {
1157
1402
  ) {
1158
1403
  console.warn("Expiry date is not greater than or equal to the expected expiry date")
1159
1404
  isCorrect = false
1160
- break
1405
+ queryResultErrors.expiry_date.gte = {
1406
+ expected: queryResult.expiry_date.gte.expected,
1407
+ received: minDate,
1408
+ message: "Expiry date is not greater than or equal to the expected expiry date",
1409
+ }
1161
1410
  }
1162
1411
  if (
1163
1412
  queryResult.expiry_date.lte &&
@@ -1166,7 +1415,11 @@ export class ZKPassport {
1166
1415
  ) {
1167
1416
  console.warn("Expiry date is not less than the expected expiry date")
1168
1417
  isCorrect = false
1169
- break
1418
+ queryResultErrors.expiry_date.lte = {
1419
+ expected: queryResult.expiry_date.lte.expected,
1420
+ received: maxDate,
1421
+ message: "Expiry date is not less than the expected expiry date",
1422
+ }
1170
1423
  }
1171
1424
  if (queryResult.expiry_date.range) {
1172
1425
  if (
@@ -1176,7 +1429,11 @@ export class ZKPassport {
1176
1429
  ) {
1177
1430
  console.warn("Expiry date is not in the expected range")
1178
1431
  isCorrect = false
1179
- break
1432
+ queryResultErrors.expiry_date.range = {
1433
+ expected: queryResult.expiry_date.range.expected,
1434
+ received: [minDate, maxDate],
1435
+ message: "Expiry date is not in the expected range",
1436
+ }
1180
1437
  }
1181
1438
  }
1182
1439
  if (
@@ -1186,7 +1443,11 @@ export class ZKPassport {
1186
1443
  ) {
1187
1444
  console.warn("Maximum expiry date should be equal to default date value")
1188
1445
  isCorrect = false
1189
- break
1446
+ queryResultErrors.expiry_date.disclose = {
1447
+ expected: `${defaultDateValue.toISOString()}`,
1448
+ received: `${maxDate.toISOString()}`,
1449
+ message: "Maximum expiry date should be equal to default date value",
1450
+ }
1190
1451
  }
1191
1452
  if (
1192
1453
  !queryResult.expiry_date.gte &&
@@ -1195,12 +1456,18 @@ export class ZKPassport {
1195
1456
  ) {
1196
1457
  console.warn("Minimum expiry date should be equal to default date value")
1197
1458
  isCorrect = false
1198
- break
1459
+ queryResultErrors.expiry_date.disclose = {
1460
+ expected: `${defaultDateValue.toISOString()}`,
1461
+ received: `${minDate.toISOString()}`,
1462
+ message: "Minimum expiry date should be equal to default date value",
1463
+ }
1199
1464
  }
1200
1465
  } else {
1201
1466
  console.warn("Expiry date is not set in the query result")
1202
1467
  isCorrect = false
1203
- break
1468
+ queryResultErrors.expiry_date.disclose = {
1469
+ message: "Expiry date is not set in the query result",
1470
+ }
1204
1471
  }
1205
1472
  uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10)
1206
1473
  } else if (proof.name === "exclusion_check_country") {
@@ -1210,7 +1477,12 @@ export class ZKPassport {
1210
1477
  "Failed to check the link between the validity of the ID and the country exclusion check",
1211
1478
  )
1212
1479
  isCorrect = false
1213
- break
1480
+ queryResultErrors.nationality.commitment = {
1481
+ expected: `Commitment: ${commitmentOut}`,
1482
+ received: `Commitment: ${commitmentIn}`,
1483
+ message:
1484
+ "Failed to check the link between the validity of the ID and the country exclusion check",
1485
+ }
1214
1486
  }
1215
1487
  const countryList = getCountryListFromExclusionProof(proofData)
1216
1488
  if (
@@ -1223,12 +1495,18 @@ export class ZKPassport {
1223
1495
  ) {
1224
1496
  console.warn("Country exclusion list does not match the one from the query results")
1225
1497
  isCorrect = false
1226
- break
1498
+ queryResultErrors.nationality.out = {
1499
+ expected: queryResult.nationality.out.expected,
1500
+ received: countryList,
1501
+ message: "Country exclusion list does not match the one from the query results",
1502
+ }
1227
1503
  }
1228
1504
  } else if (!queryResult.nationality || !queryResult.nationality.out) {
1229
1505
  console.warn("Nationality exclusion is not set in the query result")
1230
1506
  isCorrect = false
1231
- break
1507
+ queryResultErrors.nationality.out = {
1508
+ message: "Nationality exclusion is not set in the query result",
1509
+ }
1232
1510
  }
1233
1511
  // Check the countryList is in ascending order
1234
1512
  // If the prover doesn't use a sorted list then the proof cannot be trusted
@@ -1239,7 +1517,10 @@ export class ZKPassport {
1239
1517
  "The nationality exclusion list has not been sorted, and thus the proof cannot be trusted",
1240
1518
  )
1241
1519
  isCorrect = false
1242
- break
1520
+ queryResultErrors.nationality.out = {
1521
+ message:
1522
+ "The nationality exclusion list has not been sorted, and thus the proof cannot be trusted",
1523
+ }
1243
1524
  }
1244
1525
  }
1245
1526
  uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10)
@@ -1250,7 +1531,12 @@ export class ZKPassport {
1250
1531
  "Failed to check the link between the validity of the ID and the country inclusion check",
1251
1532
  )
1252
1533
  isCorrect = false
1253
- break
1534
+ queryResultErrors.nationality.commitment = {
1535
+ expected: `Commitment: ${commitmentOut}`,
1536
+ received: `Commitment: ${commitmentIn}`,
1537
+ message:
1538
+ "Failed to check the link between the validity of the ID and the country inclusion check",
1539
+ }
1254
1540
  }
1255
1541
  const countryList = getCountryListFromInclusionProof(proofData)
1256
1542
  if (
@@ -1263,17 +1549,23 @@ export class ZKPassport {
1263
1549
  ) {
1264
1550
  console.warn("Country inclusion list does not match the one from the query results")
1265
1551
  isCorrect = false
1266
- break
1552
+ queryResultErrors.nationality.in = {
1553
+ expected: queryResult.nationality.in.expected,
1554
+ received: countryList,
1555
+ message: "Country inclusion list does not match the one from the query results",
1556
+ }
1267
1557
  }
1268
1558
  } else if (!queryResult.nationality || !queryResult.nationality.in) {
1269
1559
  console.warn("Nationality inclusion is not set in the query result")
1270
1560
  isCorrect = false
1271
- break
1561
+ queryResultErrors.nationality.in = {
1562
+ message: "Nationality inclusion is not set in the query result",
1563
+ }
1272
1564
  }
1273
1565
  uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10)
1274
1566
  }
1275
1567
  }
1276
- return { isCorrect, uniqueIdentifier }
1568
+ return { isCorrect, uniqueIdentifier, queryResultErrors }
1277
1569
  }
1278
1570
 
1279
1571
  /**
@@ -1288,17 +1580,15 @@ export class ZKPassport {
1288
1580
  requestId: string,
1289
1581
  proofs?: Array<ProofResult>,
1290
1582
  queryResult?: QueryResult,
1291
- ): Promise<{ uniqueIdentifier: string | undefined; verified: boolean }> {
1583
+ ): Promise<{
1584
+ uniqueIdentifier: string | undefined
1585
+ verified: boolean
1586
+ queryResultErrors?: QueryResultErrors
1587
+ }> {
1292
1588
  let proofsToVerify = proofs
1293
1589
  // There is a minimum of 4 subproofs to make a complete proof
1294
1590
  if (!proofs || proofs.length < 4) {
1295
1591
  proofsToVerify = this.topicToProofs[requestId]
1296
- if (!proofsToVerify || proofsToVerify.length < 4) {
1297
- // It may happen that a request returns a result without proofs
1298
- // Meaning the ID is not supported yet by ZKPassport circuits,
1299
- // so the results has to be trusted and cannot be independently verified
1300
- return { uniqueIdentifier: undefined, verified: false }
1301
- }
1302
1592
  }
1303
1593
  const { BarretenbergVerifier } = await import("@aztec/bb.js")
1304
1594
  const verifier = new BarretenbergVerifier()
@@ -1307,14 +1597,19 @@ export class ZKPassport {
1307
1597
  }*/
1308
1598
  let verified = true
1309
1599
  let uniqueIdentifier: string | undefined
1600
+ let queryResultErrors: QueryResultErrors | undefined
1310
1601
  if (queryResult) {
1311
- const { isCorrect, uniqueIdentifier: uniqueIdentifierFromPublicInputs } =
1312
- await this.checkPublicInputs(proofsToVerify!, queryResult!)
1602
+ const {
1603
+ isCorrect,
1604
+ uniqueIdentifier: uniqueIdentifierFromPublicInputs,
1605
+ queryResultErrors: queryResultErrorsFromPublicInputs,
1606
+ } = await this.checkPublicInputs(proofsToVerify!, queryResult!, requestId)
1313
1607
  uniqueIdentifier = uniqueIdentifierFromPublicInputs
1314
1608
  verified = isCorrect
1609
+ queryResultErrors = isCorrect ? undefined : queryResultErrorsFromPublicInputs
1315
1610
  }
1316
1611
  // Only proceed with the proof verification if the public inputs are correct
1317
- if (verified) {
1612
+ if (verified && queryResult) {
1318
1613
  for (const proof of proofsToVerify!) {
1319
1614
  const proofData = getProofData(proof.proof as string, true)
1320
1615
  const hostedPackagedCircuit = await getHostedPackagedCircuitByName(
@@ -1336,7 +1631,7 @@ export class ZKPassport {
1336
1631
  }
1337
1632
  }
1338
1633
  this.topicToProofs[requestId] = []
1339
- return { uniqueIdentifier, verified }
1634
+ return { uniqueIdentifier, verified, queryResultErrors }
1340
1635
  }
1341
1636
 
1342
1637
  /**
@@ -1360,13 +1655,17 @@ export class ZKPassport {
1360
1655
  * @param requestId The request ID.
1361
1656
  */
1362
1657
  public cancelRequest(requestId: string) {
1363
- this.topicToWebSocketClient[requestId].close()
1364
- delete this.topicToWebSocketClient[requestId]
1658
+ if (this.topicToWebSocketClient[requestId]) {
1659
+ this.topicToWebSocketClient[requestId].close()
1660
+ delete this.topicToWebSocketClient[requestId]
1661
+ }
1365
1662
  delete this.topicToKeyPair[requestId]
1366
1663
  delete this.topicToConfig[requestId]
1664
+ delete this.topicToLocalConfig[requestId]
1367
1665
  delete this.topicToSharedSecret[requestId]
1368
1666
  delete this.topicToProofs[requestId]
1369
1667
  delete this.topicToExpectedProofCount[requestId]
1668
+ delete this.topicToFailedProofCount[requestId]
1370
1669
  delete this.topicToResults[requestId]
1371
1670
  this.onRequestReceivedCallbacks[requestId] = []
1372
1671
  this.onGeneratingProofCallbacks[requestId] = []
@@ -1375,4 +1674,13 @@ export class ZKPassport {
1375
1674
  this.onRejectCallbacks[requestId] = []
1376
1675
  this.onErrorCallbacks[requestId] = []
1377
1676
  }
1677
+
1678
+ /**
1679
+ * @notice Clears all requests.
1680
+ */
1681
+ public clearAllRequests() {
1682
+ for (const requestId in this.topicToWebSocketClient) {
1683
+ this.cancelRequest(requestId)
1684
+ }
1685
+ }
1378
1686
  }