@zkpassport/sdk 0.2.15 → 0.3.0

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
@@ -1,6 +1,6 @@
1
1
  import { randomBytes } from "crypto";
2
2
  import { getAlpha3Code, registerLocale } from "i18n-iso-countries";
3
- import { getProofData, getCommitmentFromDSCProof, getCommitmentInFromIDDataProof, getCommitmentOutFromIDDataProof, getNullifierFromDisclosureProof, getCommitmentInFromIntegrityProof, getCommitmentOutFromIntegrityProof, getCommitmentInFromDisclosureProof, getMerkleRootFromDSCProof, getCurrentDateFromIntegrityProof, getMaxAgeFromProof, getMinAgeFromProof, getCurrentDateFromAgeProof, getMinDateFromProof, getMaxDateFromProof, getCountryListFromExclusionProof, getCountryListFromInclusionProof, DisclosedData, formatName, getHostedPackagedCircuitByName, } from "@zkpassport/utils";
3
+ import { getProofData, getCommitmentFromDSCProof, getCommitmentInFromIDDataProof, getCommitmentOutFromIDDataProof, getNullifierFromDisclosureProof, getCommitmentInFromIntegrityProof, getCommitmentOutFromIntegrityProof, getCommitmentInFromDisclosureProof, getMerkleRootFromDSCProof, getCurrentDateFromIntegrityProof, DisclosedData, formatName, getHostedPackagedCircuitByName, getNumberOfPublicInputs, getParameterCommitmentFromDisclosureProof, getCountryParameterCommitment, getDiscloseParameterCommitment, getDateParameterCommitment, getCertificateRegistryRootFromOuterProof, getParamCommitmentsFromOuterProof, getCurrentDateFromCommittedInputs, getMinAgeFromCommittedInputs, getMaxAgeFromCommittedInputs, getAgeParameterCommitment, getMinDateFromCommittedInputs, getMaxDateFromCommittedInputs, getCurrentDateFromOuterProof, getNullifierFromOuterProof, getAgeEVMParameterCommitment, getDateEVMParameterCommitment, getDiscloseEVMParameterCommitment, getCountryEVMParameterCommitment, rightPadArrayWithZeros, getCommittedInputCount, ProofType, } from "@zkpassport/utils";
4
4
  import { bytesToHex } from "@noble/ciphers/utils";
5
5
  import { getWebSocketClient } from "./websocket";
6
6
  import { createEncryptedJsonRpcRequest } from "./json-rpc";
@@ -9,6 +9,10 @@ import { noLogger as logger } from "./logger";
9
9
  import { inflate } from "pako";
10
10
  import i18en from "i18n-iso-countries/langs/en.json";
11
11
  import { Buffer } from "buffer/";
12
+ import { sha256 } from "@noble/hashes/sha256";
13
+ import { hexToBytes } from "@noble/hashes/utils";
14
+ import ZKPassportVerifierAbi from "./assets/abi/ZKPassportVerifier.json";
15
+ const DEFAULT_DATE_VALUE = new Date(1111, 10, 11);
12
16
  // If Buffer is not defined, then we use the Buffer from the buffer package
13
17
  if (typeof globalThis.Buffer === "undefined") {
14
18
  globalThis.Buffer = Buffer;
@@ -107,6 +111,11 @@ export class ZKPassport {
107
111
  delete this.topicToFailedProofCount[topic];
108
112
  }
109
113
  setExpectedProofCount(topic) {
114
+ // If the mode is not fast, we'll receive only 1 compressed proof
115
+ if (this.topicToLocalConfig[topic].mode !== "fast") {
116
+ this.topicToExpectedProofCount[topic] = 1;
117
+ return;
118
+ }
110
119
  const fields = Object.keys(this.topicToConfig[topic]).filter((key) => hasRequestedAccessToField(this.topicToConfig[topic], key));
111
120
  const neededCircuits = [];
112
121
  // Determine which circuits are needed based on the requested fields
@@ -186,7 +195,13 @@ export class ZKPassport {
186
195
  logger.debug(`User generated proof`);
187
196
  // Uncompress the proof and convert it to a hex string
188
197
  const bytesProof = Buffer.from(request.params.proof, "base64");
198
+ const bytesCommittedInputs = request.params.committedInputs
199
+ ? Buffer.from(request.params.committedInputs, "base64")
200
+ : null;
189
201
  const uncompressedProof = inflate(bytesProof);
202
+ const uncompressedCommittedInputs = bytesCommittedInputs
203
+ ? inflate(bytesCommittedInputs)
204
+ : null;
190
205
  // The gzip lib in the app compress the proof as ASCII
191
206
  // and since the app passes the proof as a hex string, we can
192
207
  // just decode the bytes as hex characters using the TextDecoder
@@ -196,6 +211,9 @@ export class ZKPassport {
196
211
  vkeyHash: request.params.vkeyHash,
197
212
  name: request.params.name,
198
213
  version: request.params.version,
214
+ committedInputs: uncompressedCommittedInputs
215
+ ? JSON.parse(new TextDecoder().decode(uncompressedCommittedInputs))
216
+ : undefined,
199
217
  };
200
218
  this.topicToProofs[topic].push(processedProof);
201
219
  await Promise.all(this.onProofGeneratedCallbacks[topic].map((callback) => callback(processedProof)));
@@ -301,7 +319,7 @@ export class ZKPassport {
301
319
  const pubkey = bytesToHex(this.topicToKeyPair[topic].publicKey);
302
320
  this.setExpectedProofCount(topic);
303
321
  return {
304
- url: `https://zkpassport.id/r?d=${this.domain}&t=${topic}&c=${base64Config}&s=${base64Service}&p=${pubkey}`,
322
+ url: `https://zkpassport.id/r?d=${this.domain}&t=${topic}&c=${base64Config}&s=${base64Service}&p=${pubkey}&m=${this.topicToLocalConfig[topic].mode}`,
305
323
  requestId: topic,
306
324
  onRequestReceived: (callback) => this.onRequestReceivedCallbacks[topic].push(callback),
307
325
  onGeneratingProof: (callback) => this.onGeneratingProofCallbacks[topic].push(callback),
@@ -325,7 +343,7 @@ export class ZKPassport {
325
343
  * @param validity How many days ago should have the ID been last scanned by the user?
326
344
  * @returns The query builder object.
327
345
  */
328
- async request({ name, logo, purpose, scope, validity, topicOverride, keyPairOverride, }) {
346
+ async request({ name, logo, purpose, scope, mode, validity, topicOverride, keyPairOverride, }) {
329
347
  const topic = topicOverride || randomBytes(16).toString("hex");
330
348
  const keyPair = keyPairOverride || (await generateECDHKeyPair());
331
349
  this.topicToKeyPair[topic] = {
@@ -339,6 +357,7 @@ export class ZKPassport {
339
357
  this.topicToLocalConfig[topic] = {
340
358
  // Default to 6 months
341
359
  validity: validity || 6 * 30,
360
+ mode: mode || "fast",
342
361
  };
343
362
  this.onRequestReceivedCallbacks[topic] = [];
344
363
  this.onGeneratingProofCallbacks[topic] = [];
@@ -396,19 +415,319 @@ export class ZKPassport {
396
415
  };
397
416
  return this.getZkPassportRequest(topic);
398
417
  }
399
- async checkPublicInputs(proofs, queryResult, validity) {
400
- let commitmentIn;
401
- let commitmentOut;
418
+ checkDiscloseBytesPublicInputs(proof, queryResult) {
419
+ const queryResultErrors = {
420
+ sig_check_dsc: {},
421
+ sig_check_id_data: {},
422
+ data_check_integrity: {},
423
+ disclose: {},
424
+ age: {},
425
+ birthdate: {},
426
+ expiry_date: {},
427
+ document_type: {},
428
+ issuing_country: {},
429
+ gender: {},
430
+ nationality: {},
431
+ firstname: {},
432
+ lastname: {},
433
+ fullname: {},
434
+ document_number: {},
435
+ outer: {},
436
+ };
402
437
  let isCorrect = true;
403
- let uniqueIdentifier;
404
- const VALID_CERTIFICATE_REGISTRY_ROOT = [
405
- BigInt("20192042006788880778219739574377003123593792072535937278552252195461520776494"),
406
- BigInt("21301853597069384763054217328384418971999152625381818922211526730996340553696"),
407
- BigInt("10839898448097753834842514286432152806152415606387598803678317315409344029817"),
408
- ];
409
- const defaultDateValue = new Date(1111, 10, 11);
410
- const currentTime = new Date();
411
- const today = new Date(currentTime.getFullYear(), currentTime.getMonth(), currentTime.getDate(), 0, 0, 0, 0);
438
+ // We can't be certain that the disclosed data is for a passport or an ID card
439
+ // so we need to check both (unless the document type is revealed)
440
+ const disclosedDataPassport = DisclosedData.fromDisclosedBytes((proof.committedInputs?.disclose_bytes).disclosedBytes, "passport");
441
+ const disclosedDataIDCard = DisclosedData.fromDisclosedBytes((proof.committedInputs?.disclose_bytes).disclosedBytes, "id_card");
442
+ if (queryResult.document_type) {
443
+ // Document type is always at the same index in the disclosed data
444
+ if (queryResult.document_type.eq &&
445
+ queryResult.document_type.eq.result &&
446
+ queryResult.document_type.eq.expected !== disclosedDataPassport.documentType) {
447
+ console.warn("Document type does not match the expected document type");
448
+ isCorrect = false;
449
+ queryResultErrors.document_type.eq = {
450
+ expected: `${queryResult.document_type.eq.expected}`,
451
+ received: `${disclosedDataPassport.documentType ?? disclosedDataIDCard.documentType}`,
452
+ message: "Document type does not match the expected document type",
453
+ };
454
+ }
455
+ if (queryResult.document_type.disclose?.result !== disclosedDataIDCard.documentType) {
456
+ console.warn("Document type does not match the disclosed document type in query result");
457
+ isCorrect = false;
458
+ queryResultErrors.document_type.disclose = {
459
+ expected: `${queryResult.document_type.disclose?.result}`,
460
+ received: `${disclosedDataIDCard.documentType ?? disclosedDataPassport.documentType}`,
461
+ message: "Document type does not match the disclosed document type in query result",
462
+ };
463
+ }
464
+ }
465
+ if (queryResult.birthdate) {
466
+ const birthdatePassport = disclosedDataPassport.dateOfBirth;
467
+ const birthdateIDCard = disclosedDataIDCard.dateOfBirth;
468
+ if (queryResult.birthdate.eq &&
469
+ queryResult.birthdate.eq.result &&
470
+ queryResult.birthdate.eq.expected.getTime() !== birthdatePassport.getTime() &&
471
+ queryResult.birthdate.eq.expected.getTime() !== birthdateIDCard.getTime()) {
472
+ console.warn("Birthdate does not match the expected birthdate");
473
+ isCorrect = false;
474
+ queryResultErrors.birthdate.eq = {
475
+ expected: `${queryResult.birthdate.eq.expected.toISOString()}`,
476
+ received: `${birthdatePassport?.toISOString() ?? birthdateIDCard?.toISOString()}`,
477
+ message: "Birthdate does not match the expected birthdate",
478
+ };
479
+ }
480
+ if (queryResult.birthdate.disclose &&
481
+ queryResult.birthdate.disclose.result.getTime() !== birthdatePassport.getTime() &&
482
+ queryResult.birthdate.disclose.result.getTime() !== birthdateIDCard.getTime()) {
483
+ console.warn("Birthdate does not match the disclosed birthdate in query result");
484
+ isCorrect = false;
485
+ queryResultErrors.birthdate.disclose = {
486
+ expected: `${queryResult.birthdate.disclose.result.toISOString()}`,
487
+ received: `${birthdatePassport?.toISOString() ?? birthdateIDCard?.toISOString()}`,
488
+ message: "Birthdate does not match the disclosed birthdate in query result",
489
+ };
490
+ }
491
+ }
492
+ if (queryResult.expiry_date) {
493
+ const expiryDatePassport = disclosedDataPassport.dateOfExpiry;
494
+ const expiryDateIDCard = disclosedDataIDCard.dateOfExpiry;
495
+ if (queryResult.expiry_date.eq &&
496
+ queryResult.expiry_date.eq.result &&
497
+ queryResult.expiry_date.eq.expected.getTime() !== expiryDatePassport.getTime() &&
498
+ queryResult.expiry_date.eq.expected.getTime() !== expiryDateIDCard.getTime()) {
499
+ console.warn("Expiry date does not match the expected expiry date");
500
+ isCorrect = false;
501
+ queryResultErrors.expiry_date.eq = {
502
+ expected: `${queryResult.expiry_date.eq.expected.toISOString()}`,
503
+ received: `${expiryDatePassport?.toISOString() ?? expiryDateIDCard?.toISOString()}`,
504
+ message: "Expiry date does not match the expected expiry date",
505
+ };
506
+ }
507
+ if (queryResult.expiry_date.disclose &&
508
+ queryResult.expiry_date.disclose.result.getTime() !== expiryDatePassport.getTime() &&
509
+ queryResult.expiry_date.disclose.result.getTime() !== expiryDateIDCard.getTime()) {
510
+ console.warn("Expiry date does not match the disclosed expiry date in query result");
511
+ isCorrect = false;
512
+ queryResultErrors.expiry_date.disclose = {
513
+ expected: `${queryResult.expiry_date.disclose.result.toISOString()}`,
514
+ received: `${expiryDatePassport?.toISOString() ?? expiryDateIDCard?.toISOString()}`,
515
+ message: "Expiry date does not match the disclosed expiry date in query result",
516
+ };
517
+ }
518
+ }
519
+ if (queryResult.nationality) {
520
+ const nationalityPassport = disclosedDataPassport.nationality;
521
+ const nationalityIDCard = disclosedDataIDCard.nationality;
522
+ if (queryResult.nationality.eq &&
523
+ queryResult.nationality.eq.result &&
524
+ queryResult.nationality.eq.expected !== nationalityPassport &&
525
+ queryResult.nationality.eq.expected !== nationalityIDCard) {
526
+ console.warn("Nationality does not match the expected nationality");
527
+ isCorrect = false;
528
+ queryResultErrors.nationality.eq = {
529
+ expected: `${queryResult.nationality.eq.expected}`,
530
+ received: `${nationalityPassport ?? nationalityIDCard}`,
531
+ message: "Nationality does not match the expected nationality",
532
+ };
533
+ }
534
+ if (queryResult.nationality.disclose &&
535
+ queryResult.nationality.disclose.result !== nationalityPassport &&
536
+ queryResult.nationality.disclose.result !== nationalityIDCard) {
537
+ console.warn("Nationality does not match the disclosed nationality in query result");
538
+ isCorrect = false;
539
+ queryResultErrors.nationality.disclose = {
540
+ expected: `${queryResult.nationality.disclose.result}`,
541
+ received: `${nationalityPassport ?? nationalityIDCard}`,
542
+ message: "Nationality does not match the disclosed nationality in query result",
543
+ };
544
+ }
545
+ }
546
+ if (queryResult.document_number) {
547
+ const documentNumberPassport = disclosedDataPassport.documentNumber;
548
+ const documentNumberIDCard = disclosedDataIDCard.documentNumber;
549
+ if (queryResult.document_number.eq &&
550
+ queryResult.document_number.eq.result &&
551
+ queryResult.document_number.eq.expected !== documentNumberPassport &&
552
+ queryResult.document_number.eq.expected !== documentNumberIDCard) {
553
+ console.warn("Document number does not match the expected document number");
554
+ isCorrect = false;
555
+ queryResultErrors.document_number.eq = {
556
+ expected: `${queryResult.document_number.eq.expected}`,
557
+ received: `${documentNumberPassport ?? documentNumberIDCard}`,
558
+ message: "Document number does not match the expected document number",
559
+ };
560
+ }
561
+ if (queryResult.document_number.disclose &&
562
+ queryResult.document_number.disclose.result !== documentNumberPassport &&
563
+ queryResult.document_number.disclose.result !== documentNumberIDCard) {
564
+ console.warn("Document number does not match the disclosed document number in query result");
565
+ isCorrect = false;
566
+ queryResultErrors.document_number.disclose = {
567
+ expected: `${queryResult.document_number.disclose.result}`,
568
+ received: `${documentNumberPassport ?? documentNumberIDCard}`,
569
+ message: "Document number does not match the disclosed document number in query result",
570
+ };
571
+ }
572
+ }
573
+ if (queryResult.gender) {
574
+ const genderPassport = disclosedDataPassport.gender;
575
+ const genderIDCard = disclosedDataIDCard.gender;
576
+ if (queryResult.gender.eq &&
577
+ queryResult.gender.eq.result &&
578
+ queryResult.gender.eq.expected !== genderPassport &&
579
+ queryResult.gender.eq.expected !== genderIDCard) {
580
+ console.warn("Gender does not match the expected gender");
581
+ isCorrect = false;
582
+ queryResultErrors.gender.eq = {
583
+ expected: `${queryResult.gender.eq.expected}`,
584
+ received: `${genderPassport ?? genderIDCard}`,
585
+ message: "Gender does not match the expected gender",
586
+ };
587
+ }
588
+ if (queryResult.gender.disclose &&
589
+ queryResult.gender.disclose.result !== genderPassport &&
590
+ queryResult.gender.disclose.result !== genderIDCard) {
591
+ console.warn("Gender does not match the disclosed gender in query result");
592
+ isCorrect = false;
593
+ queryResultErrors.gender.disclose = {
594
+ expected: `${queryResult.gender.disclose.result}`,
595
+ received: `${genderPassport ?? genderIDCard}`,
596
+ message: "Gender does not match the disclosed gender in query result",
597
+ };
598
+ }
599
+ }
600
+ if (queryResult.issuing_country) {
601
+ const issuingCountryPassport = disclosedDataPassport.issuingCountry;
602
+ const issuingCountryIDCard = disclosedDataIDCard.issuingCountry;
603
+ if (queryResult.issuing_country.eq &&
604
+ queryResult.issuing_country.eq.result &&
605
+ queryResult.issuing_country.eq.expected !== issuingCountryPassport &&
606
+ queryResult.issuing_country.eq.expected !== issuingCountryIDCard) {
607
+ console.warn("Issuing country does not match the expected issuing country");
608
+ isCorrect = false;
609
+ queryResultErrors.issuing_country.eq = {
610
+ expected: `${queryResult.issuing_country.eq.expected}`,
611
+ received: `${issuingCountryPassport ?? issuingCountryIDCard}`,
612
+ message: "Issuing country does not match the expected issuing country",
613
+ };
614
+ }
615
+ if (queryResult.issuing_country.disclose &&
616
+ queryResult.issuing_country.disclose.result !== issuingCountryPassport &&
617
+ queryResult.issuing_country.disclose.result !== issuingCountryIDCard) {
618
+ console.warn("Issuing country does not match the disclosed issuing country in query result");
619
+ isCorrect = false;
620
+ queryResultErrors.issuing_country.disclose = {
621
+ expected: `${queryResult.issuing_country.disclose.result}`,
622
+ received: `${issuingCountryPassport ?? issuingCountryIDCard}`,
623
+ message: "Issuing country does not match the disclosed issuing country in query result",
624
+ };
625
+ }
626
+ }
627
+ if (queryResult.fullname) {
628
+ const fullnamePassport = disclosedDataPassport.name;
629
+ const fullnameIDCard = disclosedDataIDCard.name;
630
+ if (queryResult.fullname.eq &&
631
+ queryResult.fullname.eq.result &&
632
+ formatName(queryResult.fullname.eq.expected).toLowerCase() !==
633
+ fullnamePassport.toLowerCase() &&
634
+ formatName(queryResult.fullname.eq.expected).toLowerCase() !== fullnameIDCard.toLowerCase()) {
635
+ console.warn("Fullname does not match the expected fullname");
636
+ isCorrect = false;
637
+ queryResultErrors.fullname.eq = {
638
+ expected: `${queryResult.fullname.eq.expected}`,
639
+ received: `${fullnamePassport ?? fullnameIDCard}`,
640
+ message: "Fullname does not match the expected fullname",
641
+ };
642
+ }
643
+ if (queryResult.fullname.disclose &&
644
+ formatName(queryResult.fullname.disclose.result).toLowerCase() !==
645
+ fullnamePassport.toLowerCase() &&
646
+ formatName(queryResult.fullname.disclose.result).toLowerCase() !==
647
+ fullnameIDCard.toLowerCase()) {
648
+ console.warn("Fullname does not match the disclosed fullname in query result");
649
+ isCorrect = false;
650
+ queryResultErrors.fullname.disclose = {
651
+ expected: `${queryResult.fullname.disclose.result}`,
652
+ received: `${fullnamePassport ?? fullnameIDCard}`,
653
+ message: "Fullname does not match the disclosed fullname in query result",
654
+ };
655
+ }
656
+ }
657
+ if (queryResult.firstname) {
658
+ // If fullname was not revealed, then the name could be either the first name or last name
659
+ const firstnamePassport = disclosedDataPassport.firstName && disclosedDataPassport.firstName.length > 0
660
+ ? disclosedDataPassport.firstName
661
+ : disclosedDataPassport.name;
662
+ const firstnameIDCard = disclosedDataIDCard.firstName && disclosedDataIDCard.firstName.length > 0
663
+ ? disclosedDataIDCard.firstName
664
+ : disclosedDataIDCard.name;
665
+ if (queryResult.firstname.eq &&
666
+ queryResult.firstname.eq.result &&
667
+ formatName(queryResult.firstname.eq.expected).toLowerCase() !==
668
+ firstnamePassport.toLowerCase() &&
669
+ formatName(queryResult.firstname.eq.expected).toLowerCase() !==
670
+ firstnameIDCard.toLowerCase()) {
671
+ console.warn("Firstname does not match the expected firstname");
672
+ isCorrect = false;
673
+ queryResultErrors.firstname.eq = {
674
+ expected: `${queryResult.firstname.eq.expected}`,
675
+ received: `${firstnamePassport ?? firstnameIDCard}`,
676
+ message: "Firstname does not match the expected firstname",
677
+ };
678
+ }
679
+ if (queryResult.firstname.disclose &&
680
+ formatName(queryResult.firstname.disclose.result).toLowerCase() !==
681
+ firstnamePassport.toLowerCase() &&
682
+ formatName(queryResult.firstname.disclose.result).toLowerCase() !==
683
+ firstnameIDCard.toLowerCase()) {
684
+ console.warn("Firstname does not match the disclosed firstname in query result");
685
+ isCorrect = false;
686
+ queryResultErrors.firstname.disclose = {
687
+ expected: `${queryResult.firstname.disclose.result}`,
688
+ received: `${firstnamePassport ?? firstnameIDCard}`,
689
+ message: "Firstname does not match the disclosed firstname in query result",
690
+ };
691
+ }
692
+ }
693
+ if (queryResult.lastname) {
694
+ // If fullname was not revealed, then the name could be either the first name or last name
695
+ const lastnamePassport = disclosedDataPassport.lastName && disclosedDataPassport.lastName.length > 0
696
+ ? disclosedDataPassport.lastName
697
+ : disclosedDataPassport.name;
698
+ const lastnameIDCard = disclosedDataIDCard.lastName && disclosedDataIDCard.lastName.length > 0
699
+ ? disclosedDataIDCard.lastName
700
+ : disclosedDataIDCard.name;
701
+ if (queryResult.lastname.eq &&
702
+ queryResult.lastname.eq.result &&
703
+ formatName(queryResult.lastname.eq.expected).toLowerCase() !==
704
+ lastnamePassport.toLowerCase() &&
705
+ formatName(queryResult.lastname.eq.expected).toLowerCase() !== lastnameIDCard.toLowerCase()) {
706
+ console.warn("Lastname does not match the expected lastname");
707
+ isCorrect = false;
708
+ queryResultErrors.lastname.eq = {
709
+ expected: `${queryResult.lastname.eq.expected}`,
710
+ received: `${lastnamePassport ?? lastnameIDCard}`,
711
+ message: "Lastname does not match the expected lastname",
712
+ };
713
+ }
714
+ if (queryResult.lastname.disclose &&
715
+ formatName(queryResult.lastname.disclose.result).toLowerCase() !==
716
+ lastnamePassport.toLowerCase() &&
717
+ formatName(queryResult.lastname.disclose.result).toLowerCase() !==
718
+ lastnameIDCard.toLowerCase()) {
719
+ console.warn("Lastname does not match the disclosed lastname in query result");
720
+ isCorrect = false;
721
+ queryResultErrors.lastname.disclose = {
722
+ expected: `${queryResult.lastname.disclose.result}`,
723
+ received: `${lastnamePassport ?? lastnameIDCard}`,
724
+ message: "Lastname does not match the disclosed lastname in query result",
725
+ };
726
+ }
727
+ }
728
+ return { isCorrect, queryResultErrors };
729
+ }
730
+ checkAgePublicInputs(proof, queryResult) {
412
731
  const queryResultErrors = {
413
732
  sig_check_dsc: {},
414
733
  sig_check_id_data: {},
@@ -425,388 +744,845 @@ export class ZKPassport {
425
744
  lastname: {},
426
745
  fullname: {},
427
746
  document_number: {},
747
+ outer: {},
428
748
  };
429
- // Since the order is important for the commitments, we need to sort the proofs
430
- // by their expected order: root signature check -> ID signature check -> integrity check -> disclosure
431
- const sortedProofs = proofs.sort((a, b) => {
432
- const proofOrder = [
433
- "sig_check_dsc",
434
- "sig_check_id_data",
435
- "data_check_integrity",
436
- "disclose_bytes",
437
- "compare_age",
438
- "compare_birthdate",
439
- "compare_expiry",
440
- "exclusion_check_nationality",
441
- "inclusion_check_nationality",
442
- "exclusion_check_issuing_country",
443
- "inclusion_check_issuing_country",
444
- ];
445
- const getIndex = (proof) => {
446
- const name = proof.name || "";
447
- return proofOrder.findIndex((p) => name.startsWith(p));
448
- };
449
- return getIndex(a) - getIndex(b);
450
- });
451
- for (const proof of sortedProofs) {
452
- const proofData = getProofData(proof.proof, true);
453
- if (proof.name?.startsWith("sig_check_dsc")) {
454
- commitmentOut = getCommitmentFromDSCProof(proofData);
455
- const merkleRoot = getMerkleRootFromDSCProof(proofData);
456
- if (!VALID_CERTIFICATE_REGISTRY_ROOT.includes(merkleRoot)) {
457
- console.warn("The ID was signed by an unrecognized root certificate");
458
- isCorrect = false;
459
- queryResultErrors.sig_check_dsc.certificate = {
460
- expected: `Certificate registry root: ${VALID_CERTIFICATE_REGISTRY_ROOT.join(", ")}`,
461
- received: `Certificate registry root: ${merkleRoot.toString()}`,
462
- message: "The ID was signed by an unrecognized root certificate",
463
- };
464
- }
749
+ let isCorrect = true;
750
+ const currentTime = new Date();
751
+ const today = new Date(currentTime.getFullYear(), currentTime.getMonth(), currentTime.getDate(), 0, 0, 0, 0);
752
+ const minAge = getMinAgeFromCommittedInputs(proof.committedInputs?.compare_age);
753
+ const maxAge = getMaxAgeFromCommittedInputs(proof.committedInputs?.compare_age);
754
+ if (queryResult.age) {
755
+ if (queryResult.age.gte &&
756
+ queryResult.age.gte.result &&
757
+ minAge < queryResult.age.gte.expected) {
758
+ console.warn("Age is not greater than or equal to the expected age");
759
+ isCorrect = false;
760
+ queryResultErrors.age.gte = {
761
+ expected: queryResult.age.gte.expected,
762
+ received: minAge,
763
+ message: "Age is not greater than or equal to the expected age",
764
+ };
465
765
  }
466
- else if (proof.name?.startsWith("sig_check_id_data")) {
467
- commitmentIn = getCommitmentInFromIDDataProof(proofData);
468
- if (commitmentIn !== commitmentOut) {
469
- console.warn("Failed to check the link between the certificate signature and ID signature");
470
- isCorrect = false;
471
- queryResultErrors.sig_check_id_data.commitment = {
472
- expected: `Commitment: ${commitmentOut?.toString() || "undefined"}`,
473
- received: `Commitment: ${commitmentIn?.toString() || "undefined"}`,
474
- message: "Failed to check the link between the certificate signature and ID signature",
475
- };
476
- }
477
- commitmentOut = getCommitmentOutFromIDDataProof(proofData);
766
+ if (queryResult.age.lt &&
767
+ queryResult.age.lt.result &&
768
+ maxAge >= queryResult.age.lt.expected) {
769
+ console.warn("Age is not less than the expected age");
770
+ isCorrect = false;
771
+ queryResultErrors.age.lt = {
772
+ expected: queryResult.age.lt.expected,
773
+ received: maxAge,
774
+ message: "Age is not less than the expected age",
775
+ };
478
776
  }
479
- else if (proof.name?.startsWith("data_check_integrity")) {
480
- commitmentIn = getCommitmentInFromIntegrityProof(proofData);
481
- if (commitmentIn !== commitmentOut) {
482
- console.warn("Failed to check the link between the ID signature and the data signed");
777
+ if (queryResult.age.range) {
778
+ if (queryResult.age.range.result &&
779
+ (minAge < queryResult.age.range.expected[0] ||
780
+ maxAge >= queryResult.age.range.expected[1])) {
781
+ console.warn("Age is not in the expected range");
483
782
  isCorrect = false;
484
- queryResultErrors.data_check_integrity.commitment = {
485
- expected: `Commitment: ${commitmentOut?.toString() || "undefined"}`,
486
- received: `Commitment: ${commitmentIn?.toString() || "undefined"}`,
487
- message: "Failed to check the link between the ID signature and the data signed",
783
+ queryResultErrors.age.range = {
784
+ expected: queryResult.age.range.expected,
785
+ received: [minAge, maxAge],
786
+ message: "Age is not in the expected range",
488
787
  };
489
788
  }
490
- commitmentOut = getCommitmentOutFromIntegrityProof(proofData);
491
- const currentDate = getCurrentDateFromIntegrityProof(proofData);
492
- const todayToCurrentDate = today.getTime() - currentDate.getTime();
493
- const differenceInDays = validity ?? 180;
494
- const expectedDifference = differenceInDays * 86400000;
495
- const actualDifference = today.getTime() - (today.getTime() - expectedDifference);
496
- // The ID should not expire within the next 6 months (or whatever the custom value is)
497
- if (todayToCurrentDate >= actualDifference) {
498
- console.warn(`The date used to check the validity of the ID is older than ${differenceInDays} days. You can ask the user to rescan their ID or ask them to disclose their expiry date`);
789
+ }
790
+ if (!queryResult.age.lt && !queryResult.age.range && maxAge != 0) {
791
+ console.warn("Maximum age should be equal to 0");
792
+ isCorrect = false;
793
+ queryResultErrors.age.disclose = {
794
+ expected: 0,
795
+ received: maxAge,
796
+ message: "Maximum age should be equal to 0",
797
+ };
798
+ }
799
+ if (!queryResult.age.gte && !queryResult.age.range && minAge != 0) {
800
+ console.warn("Minimum age should be equal to 0");
801
+ isCorrect = false;
802
+ queryResultErrors.age.disclose = {
803
+ expected: 0,
804
+ received: minAge,
805
+ message: "Minimum age should be equal to 0",
806
+ };
807
+ }
808
+ if (queryResult.age.disclose &&
809
+ (queryResult.age.disclose.result !== minAge || queryResult.age.disclose.result !== maxAge)) {
810
+ console.warn("Age does not match the disclosed age in query result");
811
+ isCorrect = false;
812
+ queryResultErrors.age.disclose = {
813
+ expected: `${minAge}`,
814
+ received: `${queryResult.age.disclose.result}`,
815
+ message: "Age does not match the disclosed age in query result",
816
+ };
817
+ }
818
+ }
819
+ else {
820
+ console.warn("Age is not set in the query result");
821
+ isCorrect = false;
822
+ queryResultErrors.age.disclose = {
823
+ message: "Age is not set in the query result",
824
+ };
825
+ }
826
+ const currentDate = getCurrentDateFromCommittedInputs(proof.committedInputs?.compare_age);
827
+ if (currentDate.getTime() !== today.getTime() &&
828
+ currentDate.getTime() !== today.getTime() - 86400000) {
829
+ console.warn("Current date in the proof is too old");
830
+ isCorrect = false;
831
+ queryResultErrors.age.disclose = {
832
+ expected: `${today.toISOString()}`,
833
+ received: `${currentDate.toISOString()}`,
834
+ message: "Current date in the proof is too old",
835
+ };
836
+ }
837
+ return { isCorrect, queryResultErrors };
838
+ }
839
+ checkBirthdatePublicInputs(proof, queryResult) {
840
+ const queryResultErrors = {
841
+ sig_check_dsc: {},
842
+ sig_check_id_data: {},
843
+ data_check_integrity: {},
844
+ disclose: {},
845
+ age: {},
846
+ birthdate: {},
847
+ expiry_date: {},
848
+ document_type: {},
849
+ issuing_country: {},
850
+ gender: {},
851
+ nationality: {},
852
+ firstname: {},
853
+ lastname: {},
854
+ fullname: {},
855
+ document_number: {},
856
+ outer: {},
857
+ };
858
+ let isCorrect = true;
859
+ const currentTime = new Date();
860
+ const today = new Date(currentTime.getFullYear(), currentTime.getMonth(), currentTime.getDate(), 0, 0, 0);
861
+ const minDate = getMinDateFromCommittedInputs(proof.committedInputs?.compare_birthdate);
862
+ const maxDate = getMaxDateFromCommittedInputs(proof.committedInputs?.compare_birthdate);
863
+ const currentDate = getCurrentDateFromCommittedInputs(proof.committedInputs?.compare_birthdate);
864
+ if (queryResult.birthdate) {
865
+ if (queryResult.birthdate.gte &&
866
+ queryResult.birthdate.gte.result &&
867
+ minDate < queryResult.birthdate.gte.expected) {
868
+ console.warn("Birthdate is not greater than or equal to the expected birthdate");
869
+ isCorrect = false;
870
+ queryResultErrors.birthdate.gte = {
871
+ expected: queryResult.birthdate.gte.expected,
872
+ received: minDate,
873
+ message: "Birthdate is not greater than or equal to the expected birthdate",
874
+ };
875
+ }
876
+ if (queryResult.birthdate.lte &&
877
+ queryResult.birthdate.lte.result &&
878
+ maxDate > queryResult.birthdate.lte.expected) {
879
+ console.warn("Birthdate is not less than the expected birthdate");
880
+ isCorrect = false;
881
+ queryResultErrors.birthdate.lte = {
882
+ expected: queryResult.birthdate.lte.expected,
883
+ received: maxDate,
884
+ message: "Birthdate is not less than the expected birthdate",
885
+ };
886
+ }
887
+ if (queryResult.birthdate.range) {
888
+ if (queryResult.birthdate.range.result &&
889
+ (minDate < queryResult.birthdate.range.expected[0] ||
890
+ maxDate > queryResult.birthdate.range.expected[1])) {
891
+ console.warn("Birthdate is not in the expected range");
499
892
  isCorrect = false;
500
- queryResultErrors.data_check_integrity.date = {
501
- expected: `Difference: ${differenceInDays} days`,
502
- received: `Difference: ${Math.round(todayToCurrentDate / 86400000)} days`,
503
- message: "The date used to check the validity of the ID is older than the validity period",
893
+ queryResultErrors.birthdate.range = {
894
+ expected: queryResult.birthdate.range.expected,
895
+ received: [minDate, maxDate],
896
+ message: "Birthdate is not in the expected range",
504
897
  };
505
898
  }
506
899
  }
507
- else if (proof.name === "disclose_bytes") {
508
- commitmentIn = getCommitmentInFromDisclosureProof(proofData);
509
- if (commitmentIn !== commitmentOut) {
510
- console.warn("Failed to check the link between the validity of the ID and the data to disclose");
900
+ if (!queryResult.birthdate.lte &&
901
+ !queryResult.birthdate.range &&
902
+ maxDate.getTime() != DEFAULT_DATE_VALUE.getTime()) {
903
+ console.warn("Maximum birthdate should be equal to default date value");
904
+ isCorrect = false;
905
+ queryResultErrors.birthdate.disclose = {
906
+ expected: `${DEFAULT_DATE_VALUE.toISOString()}`,
907
+ received: `${maxDate.toISOString()}`,
908
+ message: "Maximum birthdate should be equal to default date value",
909
+ };
910
+ }
911
+ if (!queryResult.birthdate.gte &&
912
+ !queryResult.birthdate.range &&
913
+ minDate.getTime() != DEFAULT_DATE_VALUE.getTime()) {
914
+ console.warn("Minimum birthdate should be equal to default date value");
915
+ isCorrect = false;
916
+ queryResultErrors.birthdate.disclose = {
917
+ expected: `${DEFAULT_DATE_VALUE.toISOString()}`,
918
+ received: `${minDate.toISOString()}`,
919
+ message: "Minimum birthdate should be equal to default date value",
920
+ };
921
+ }
922
+ }
923
+ else {
924
+ console.warn("Birthdate is not set in the query result");
925
+ isCorrect = false;
926
+ queryResultErrors.birthdate.disclose = {
927
+ message: "Birthdate is not set in the query result",
928
+ };
929
+ }
930
+ if (currentDate.getTime() !== today.getTime() &&
931
+ currentDate.getTime() !== today.getTime() - 86400000) {
932
+ console.warn("Current date in the proof is too old");
933
+ isCorrect = false;
934
+ queryResultErrors.age.disclose = {
935
+ expected: `${today.toISOString()}`,
936
+ received: `${currentDate.toISOString()}`,
937
+ message: "Current date in the proof is too old",
938
+ };
939
+ }
940
+ return { isCorrect, queryResultErrors };
941
+ }
942
+ checkExpiryDatePublicInputs(proof, queryResult) {
943
+ const queryResultErrors = {
944
+ sig_check_dsc: {},
945
+ sig_check_id_data: {},
946
+ data_check_integrity: {},
947
+ disclose: {},
948
+ age: {},
949
+ birthdate: {},
950
+ expiry_date: {},
951
+ document_type: {},
952
+ issuing_country: {},
953
+ gender: {},
954
+ nationality: {},
955
+ firstname: {},
956
+ lastname: {},
957
+ fullname: {},
958
+ document_number: {},
959
+ outer: {},
960
+ };
961
+ let isCorrect = true;
962
+ const currentTime = new Date();
963
+ const today = new Date(currentTime.getFullYear(), currentTime.getMonth(), currentTime.getDate(), 0, 0, 0);
964
+ const minDate = getMinDateFromCommittedInputs(proof.committedInputs?.compare_expiry);
965
+ const maxDate = getMaxDateFromCommittedInputs(proof.committedInputs?.compare_expiry);
966
+ const currentDate = getCurrentDateFromCommittedInputs(proof.committedInputs?.compare_expiry);
967
+ if (queryResult.expiry_date) {
968
+ if (queryResult.expiry_date.gte &&
969
+ queryResult.expiry_date.gte.result &&
970
+ minDate < queryResult.expiry_date.gte.expected) {
971
+ console.warn("Expiry date is not greater than or equal to the expected expiry date");
972
+ isCorrect = false;
973
+ queryResultErrors.expiry_date.gte = {
974
+ expected: queryResult.expiry_date.gte.expected,
975
+ received: minDate,
976
+ message: "Expiry date is not greater than or equal to the expected expiry date",
977
+ };
978
+ }
979
+ if (queryResult.expiry_date.lte &&
980
+ queryResult.expiry_date.lte.result &&
981
+ maxDate > queryResult.expiry_date.lte.expected) {
982
+ console.warn("Expiry date is not less than the expected expiry date");
983
+ isCorrect = false;
984
+ queryResultErrors.expiry_date.lte = {
985
+ expected: queryResult.expiry_date.lte.expected,
986
+ received: maxDate,
987
+ message: "Expiry date is not less than the expected expiry date",
988
+ };
989
+ }
990
+ if (queryResult.expiry_date.range) {
991
+ if (queryResult.expiry_date.range.result &&
992
+ (minDate < queryResult.expiry_date.range.expected[0] ||
993
+ maxDate > queryResult.expiry_date.range.expected[1])) {
994
+ console.warn("Expiry date is not in the expected range");
511
995
  isCorrect = false;
512
- queryResultErrors.disclose.commitment = {
513
- expected: `Commitment: ${commitmentOut?.toString() || "undefined"}`,
514
- received: `Commitment: ${commitmentIn?.toString() || "undefined"}`,
515
- message: "Failed to check the link between the validity of the ID and the data to disclose",
996
+ queryResultErrors.expiry_date.range = {
997
+ expected: queryResult.expiry_date.range.expected,
998
+ received: [minDate, maxDate],
999
+ message: "Expiry date is not in the expected range",
516
1000
  };
517
1001
  }
518
- // We can't be certain that the disclosed data is for a passport or an ID card
519
- // so we need to check both (unless the document type is revealed)
520
- const disclosedDataPassport = DisclosedData.fromBytesProof(proofData, "passport");
521
- const disclosedDataIDCard = DisclosedData.fromBytesProof(proofData, "id_card");
522
- if (queryResult.document_type) {
523
- // Document type is always at the same index in the disclosed data
524
- if (queryResult.document_type.eq &&
525
- queryResult.document_type.eq.result &&
526
- queryResult.document_type.eq.expected !== disclosedDataPassport.documentType) {
527
- console.warn("Document type does not match the expected document type");
528
- isCorrect = false;
529
- queryResultErrors.document_type.eq = {
530
- expected: `${queryResult.document_type.eq.expected}`,
531
- received: `${disclosedDataPassport.documentType ?? disclosedDataIDCard.documentType}`,
532
- message: "Document type does not match the expected document type",
533
- };
534
- }
535
- if (queryResult.document_type.disclose?.result !== disclosedDataIDCard.documentType) {
536
- console.warn("Document type does not match the disclosed document type in query result");
537
- isCorrect = false;
538
- queryResultErrors.document_type.disclose = {
539
- expected: `${queryResult.document_type.disclose?.result}`,
540
- received: `${disclosedDataIDCard.documentType ?? disclosedDataPassport.documentType}`,
541
- message: "Document type does not match the disclosed document type in query result",
542
- };
543
- }
544
- }
545
- if (queryResult.birthdate) {
546
- const birthdatePassport = disclosedDataPassport.dateOfBirth;
547
- const birthdateIDCard = disclosedDataIDCard.dateOfBirth;
548
- if (queryResult.birthdate.eq &&
549
- queryResult.birthdate.eq.result &&
550
- queryResult.birthdate.eq.expected.getTime() !== birthdatePassport.getTime() &&
551
- queryResult.birthdate.eq.expected.getTime() !== birthdateIDCard.getTime()) {
552
- console.warn("Birthdate does not match the expected birthdate");
553
- isCorrect = false;
554
- queryResultErrors.birthdate.eq = {
555
- expected: `${queryResult.birthdate.eq.expected.toISOString()}`,
556
- received: `${birthdatePassport?.toISOString() ?? birthdateIDCard?.toISOString()}`,
557
- message: "Birthdate does not match the expected birthdate",
558
- };
559
- }
560
- if (queryResult.birthdate.disclose &&
561
- queryResult.birthdate.disclose.result.getTime() !== birthdatePassport.getTime() &&
562
- queryResult.birthdate.disclose.result.getTime() !== birthdateIDCard.getTime()) {
563
- console.warn("Birthdate does not match the disclosed birthdate in query result");
564
- isCorrect = false;
565
- queryResultErrors.birthdate.disclose = {
566
- expected: `${queryResult.birthdate.disclose.result.toISOString()}`,
567
- received: `${birthdatePassport?.toISOString() ?? birthdateIDCard?.toISOString()}`,
568
- message: "Birthdate does not match the disclosed birthdate in query result",
569
- };
570
- }
571
- }
572
- if (queryResult.expiry_date) {
573
- const expiryDatePassport = disclosedDataPassport.dateOfExpiry;
574
- const expiryDateIDCard = disclosedDataIDCard.dateOfExpiry;
575
- if (queryResult.expiry_date.eq &&
576
- queryResult.expiry_date.eq.result &&
577
- queryResult.expiry_date.eq.expected.getTime() !== expiryDatePassport.getTime() &&
578
- queryResult.expiry_date.eq.expected.getTime() !== expiryDateIDCard.getTime()) {
579
- console.warn("Expiry date does not match the expected expiry date");
580
- isCorrect = false;
581
- queryResultErrors.expiry_date.eq = {
582
- expected: `${queryResult.expiry_date.eq.expected.toISOString()}`,
583
- received: `${expiryDatePassport?.toISOString() ?? expiryDateIDCard?.toISOString()}`,
584
- message: "Expiry date does not match the expected expiry date",
585
- };
586
- }
587
- if (queryResult.expiry_date.disclose &&
588
- queryResult.expiry_date.disclose.result.getTime() !== expiryDatePassport.getTime() &&
589
- queryResult.expiry_date.disclose.result.getTime() !== expiryDateIDCard.getTime()) {
590
- console.warn("Expiry date does not match the disclosed expiry date in query result");
591
- isCorrect = false;
592
- queryResultErrors.expiry_date.disclose = {
593
- expected: `${queryResult.expiry_date.disclose.result.toISOString()}`,
594
- received: `${expiryDatePassport?.toISOString() ?? expiryDateIDCard?.toISOString()}`,
595
- message: "Expiry date does not match the disclosed expiry date in query result",
596
- };
597
- }
598
- }
599
- if (queryResult.nationality) {
600
- const nationalityPassport = disclosedDataPassport.nationality;
601
- const nationalityIDCard = disclosedDataIDCard.nationality;
602
- if (queryResult.nationality.eq &&
603
- queryResult.nationality.eq.result &&
604
- queryResult.nationality.eq.expected !== nationalityPassport &&
605
- queryResult.nationality.eq.expected !== nationalityIDCard) {
606
- console.warn("Nationality does not match the expected nationality");
607
- isCorrect = false;
608
- queryResultErrors.nationality.eq = {
609
- expected: `${queryResult.nationality.eq.expected}`,
610
- received: `${nationalityPassport ?? nationalityIDCard}`,
611
- message: "Nationality does not match the expected nationality",
612
- };
613
- }
614
- if (queryResult.nationality.disclose &&
615
- queryResult.nationality.disclose.result !== nationalityPassport &&
616
- queryResult.nationality.disclose.result !== nationalityIDCard) {
617
- console.warn("Nationality does not match the disclosed nationality in query result");
618
- isCorrect = false;
619
- queryResultErrors.nationality.disclose = {
620
- expected: `${queryResult.nationality.disclose.result}`,
621
- received: `${nationalityPassport ?? nationalityIDCard}`,
622
- message: "Nationality does not match the disclosed nationality in query result",
623
- };
624
- }
1002
+ }
1003
+ if (!queryResult.expiry_date.lte &&
1004
+ !queryResult.expiry_date.range &&
1005
+ maxDate.getTime() != DEFAULT_DATE_VALUE.getTime()) {
1006
+ console.warn("Maximum expiry date should be equal to default date value");
1007
+ isCorrect = false;
1008
+ queryResultErrors.expiry_date.disclose = {
1009
+ expected: `${DEFAULT_DATE_VALUE.toISOString()}`,
1010
+ received: `${maxDate.toISOString()}`,
1011
+ message: "Maximum expiry date should be equal to default date value",
1012
+ };
1013
+ }
1014
+ if (!queryResult.expiry_date.gte &&
1015
+ !queryResult.expiry_date.range &&
1016
+ minDate.getTime() != DEFAULT_DATE_VALUE.getTime()) {
1017
+ console.warn("Minimum expiry date should be equal to default date value");
1018
+ isCorrect = false;
1019
+ queryResultErrors.expiry_date.disclose = {
1020
+ expected: `${DEFAULT_DATE_VALUE.toISOString()}`,
1021
+ received: `${minDate.toISOString()}`,
1022
+ message: "Minimum expiry date should be equal to default date value",
1023
+ };
1024
+ }
1025
+ }
1026
+ else {
1027
+ console.warn("Expiry date is not set in the query result");
1028
+ isCorrect = false;
1029
+ queryResultErrors.expiry_date.disclose = {
1030
+ message: "Expiry date is not set in the query result",
1031
+ };
1032
+ }
1033
+ if (currentDate.getTime() !== today.getTime() &&
1034
+ currentDate.getTime() !== today.getTime() - 86400000) {
1035
+ console.warn("Current date in the proof is too old");
1036
+ isCorrect = false;
1037
+ queryResultErrors.age.disclose = {
1038
+ expected: `${today.toISOString()}`,
1039
+ received: `${currentDate.toISOString()}`,
1040
+ message: "Current date in the proof is too old",
1041
+ };
1042
+ }
1043
+ return { isCorrect, queryResultErrors };
1044
+ }
1045
+ checkNationalityExclusionPublicInputs(queryResult, countryList) {
1046
+ const queryResultErrors = {
1047
+ sig_check_dsc: {},
1048
+ sig_check_id_data: {},
1049
+ data_check_integrity: {},
1050
+ disclose: {},
1051
+ age: {},
1052
+ birthdate: {},
1053
+ expiry_date: {},
1054
+ document_type: {},
1055
+ issuing_country: {},
1056
+ gender: {},
1057
+ nationality: {},
1058
+ firstname: {},
1059
+ lastname: {},
1060
+ fullname: {},
1061
+ document_number: {},
1062
+ outer: {},
1063
+ };
1064
+ let isCorrect = true;
1065
+ if (queryResult.nationality &&
1066
+ queryResult.nationality.out &&
1067
+ queryResult.nationality.out.result) {
1068
+ if (!queryResult.nationality.out.expected?.every((country) => countryList.includes(country))) {
1069
+ console.warn("Nationality exclusion list does not match the one from the query results");
1070
+ isCorrect = false;
1071
+ queryResultErrors.nationality.out = {
1072
+ expected: queryResult.nationality.out.expected,
1073
+ received: countryList,
1074
+ message: "Nationality exclusion list does not match the one from the query results",
1075
+ };
1076
+ }
1077
+ }
1078
+ else if (!queryResult.nationality || !queryResult.nationality.out) {
1079
+ console.warn("Nationality exclusion is not set in the query result");
1080
+ isCorrect = false;
1081
+ queryResultErrors.nationality.out = {
1082
+ message: "Nationality exclusion is not set in the query result",
1083
+ };
1084
+ }
1085
+ // Check the countryList is in ascending order
1086
+ // If the prover doesn't use a sorted list then the proof cannot be trusted
1087
+ // as it is requirement in the circuit for the exclusion check to work
1088
+ for (let i = 1; i < countryList.length; i++) {
1089
+ if (countryList[i] < countryList[i - 1]) {
1090
+ console.warn("The nationality exclusion list has not been sorted, and thus the proof cannot be trusted");
1091
+ isCorrect = false;
1092
+ queryResultErrors.nationality.out = {
1093
+ message: "The nationality exclusion list has not been sorted, and thus the proof cannot be trusted",
1094
+ };
1095
+ }
1096
+ }
1097
+ return { isCorrect, queryResultErrors };
1098
+ }
1099
+ checkIssuingCountryExclusionPublicInputs(queryResult, countryList) {
1100
+ const queryResultErrors = {
1101
+ sig_check_dsc: {},
1102
+ sig_check_id_data: {},
1103
+ data_check_integrity: {},
1104
+ disclose: {},
1105
+ age: {},
1106
+ birthdate: {},
1107
+ expiry_date: {},
1108
+ document_type: {},
1109
+ issuing_country: {},
1110
+ gender: {},
1111
+ nationality: {},
1112
+ firstname: {},
1113
+ lastname: {},
1114
+ fullname: {},
1115
+ document_number: {},
1116
+ outer: {},
1117
+ };
1118
+ let isCorrect = true;
1119
+ if (queryResult.issuing_country &&
1120
+ queryResult.issuing_country.out &&
1121
+ queryResult.issuing_country.out.result) {
1122
+ if (!queryResult.issuing_country.out.expected?.every((country) => countryList.includes(country))) {
1123
+ console.warn("Issuing country exclusion list does not match the one from the query results");
1124
+ isCorrect = false;
1125
+ queryResultErrors.issuing_country.out = {
1126
+ expected: queryResult.issuing_country.out.expected,
1127
+ received: countryList,
1128
+ message: "Issuing country exclusion list does not match the one from the query results",
1129
+ };
1130
+ }
1131
+ }
1132
+ else if (!queryResult.issuing_country || !queryResult.issuing_country.out) {
1133
+ console.warn("Issuing country exclusion is not set in the query result");
1134
+ isCorrect = false;
1135
+ queryResultErrors.issuing_country.out = {
1136
+ message: "Issuing country exclusion is not set in the query result",
1137
+ };
1138
+ }
1139
+ // Check the countryList is in ascending order
1140
+ // If the prover doesn't use a sorted list then the proof cannot be trusted
1141
+ // as it is requirement in the circuit for the exclusion check to work
1142
+ for (let i = 1; i < countryList.length; i++) {
1143
+ if (countryList[i] < countryList[i - 1]) {
1144
+ console.warn("The issuing country exclusion list has not been sorted, and thus the proof cannot be trusted");
1145
+ isCorrect = false;
1146
+ queryResultErrors.issuing_country.out = {
1147
+ message: "The issuing country exclusion list has not been sorted, and thus the proof cannot be trusted",
1148
+ };
1149
+ }
1150
+ }
1151
+ return { isCorrect, queryResultErrors };
1152
+ }
1153
+ checkNationalityInclusionPublicInputs(queryResult, countryList) {
1154
+ const queryResultErrors = {
1155
+ sig_check_dsc: {},
1156
+ sig_check_id_data: {},
1157
+ data_check_integrity: {},
1158
+ disclose: {},
1159
+ age: {},
1160
+ birthdate: {},
1161
+ expiry_date: {},
1162
+ document_type: {},
1163
+ issuing_country: {},
1164
+ gender: {},
1165
+ nationality: {},
1166
+ firstname: {},
1167
+ lastname: {},
1168
+ fullname: {},
1169
+ document_number: {},
1170
+ outer: {},
1171
+ };
1172
+ let isCorrect = true;
1173
+ if (queryResult.nationality &&
1174
+ queryResult.nationality.in &&
1175
+ queryResult.nationality.in.result) {
1176
+ if (!queryResult.nationality.in.expected?.every((country) => countryList.includes(country))) {
1177
+ console.warn("Nationality inclusion list does not match the one from the query results");
1178
+ isCorrect = false;
1179
+ queryResultErrors.nationality.in = {
1180
+ expected: queryResult.nationality.in.expected,
1181
+ received: countryList,
1182
+ message: "Nationality inclusion list does not match the one from the query results",
1183
+ };
1184
+ }
1185
+ }
1186
+ else if (!queryResult.nationality || !queryResult.nationality.in) {
1187
+ console.warn("Nationality inclusion is not set in the query result");
1188
+ isCorrect = false;
1189
+ queryResultErrors.nationality.in = {
1190
+ message: "Nationality inclusion is not set in the query result",
1191
+ };
1192
+ }
1193
+ return { isCorrect, queryResultErrors };
1194
+ }
1195
+ checkIssuingCountryInclusionPublicInputs(queryResult, countryList) {
1196
+ const queryResultErrors = {
1197
+ sig_check_dsc: {},
1198
+ sig_check_id_data: {},
1199
+ data_check_integrity: {},
1200
+ disclose: {},
1201
+ age: {},
1202
+ birthdate: {},
1203
+ expiry_date: {},
1204
+ document_type: {},
1205
+ issuing_country: {},
1206
+ gender: {},
1207
+ nationality: {},
1208
+ firstname: {},
1209
+ lastname: {},
1210
+ fullname: {},
1211
+ document_number: {},
1212
+ outer: {},
1213
+ };
1214
+ let isCorrect = true;
1215
+ if (queryResult.issuing_country &&
1216
+ queryResult.issuing_country.in &&
1217
+ queryResult.issuing_country.in.result) {
1218
+ if (!queryResult.issuing_country.in.expected?.every((country) => countryList.includes(country))) {
1219
+ console.warn("Issuing country inclusion list does not match the one from the query results");
1220
+ isCorrect = false;
1221
+ queryResultErrors.issuing_country.in = {
1222
+ expected: queryResult.issuing_country.in.expected,
1223
+ received: countryList,
1224
+ message: "Issuing country inclusion list does not match the one from the query results",
1225
+ };
1226
+ }
1227
+ }
1228
+ else if (!queryResult.issuing_country || !queryResult.issuing_country.in) {
1229
+ console.warn("Issuing country inclusion is not set in the query result");
1230
+ isCorrect = false;
1231
+ queryResultErrors.issuing_country.in = {
1232
+ message: "Issuing country inclusion is not set in the query result",
1233
+ };
1234
+ }
1235
+ return { isCorrect, queryResultErrors };
1236
+ }
1237
+ async checkPublicInputs(proofs, queryResult, validity) {
1238
+ let commitmentIn;
1239
+ let commitmentOut;
1240
+ let isCorrect = true;
1241
+ let uniqueIdentifier;
1242
+ const VALID_CERTIFICATE_REGISTRY_ROOT = [
1243
+ BigInt("20192042006788880778219739574377003123593792072535937278552252195461520776494"),
1244
+ BigInt("21301853597069384763054217328384418971999152625381818922211526730996340553696"),
1245
+ BigInt("10839898448097753834842514286432152806152415606387598803678317315409344029817"),
1246
+ ];
1247
+ const currentTime = new Date();
1248
+ const today = new Date(currentTime.getFullYear(), currentTime.getMonth(), currentTime.getDate(), 0, 0, 0, 0);
1249
+ let queryResultErrors = {
1250
+ sig_check_dsc: {},
1251
+ sig_check_id_data: {},
1252
+ data_check_integrity: {},
1253
+ disclose: {},
1254
+ age: {},
1255
+ birthdate: {},
1256
+ expiry_date: {},
1257
+ document_type: {},
1258
+ issuing_country: {},
1259
+ gender: {},
1260
+ nationality: {},
1261
+ firstname: {},
1262
+ lastname: {},
1263
+ fullname: {},
1264
+ document_number: {},
1265
+ outer: {},
1266
+ };
1267
+ // Since the order is important for the commitments, we need to sort the proofs
1268
+ // by their expected order: root signature check -> ID signature check -> integrity check -> disclosure
1269
+ const sortedProofs = proofs.sort((a, b) => {
1270
+ const proofOrder = [
1271
+ "sig_check_dsc",
1272
+ "sig_check_id_data",
1273
+ "data_check_integrity",
1274
+ "disclose_bytes",
1275
+ "compare_age",
1276
+ "compare_birthdate",
1277
+ "compare_expiry",
1278
+ "exclusion_check_nationality",
1279
+ "inclusion_check_nationality",
1280
+ "exclusion_check_issuing_country",
1281
+ "inclusion_check_issuing_country",
1282
+ ];
1283
+ const getIndex = (proof) => {
1284
+ const name = proof.name || "";
1285
+ return proofOrder.findIndex((p) => name.startsWith(p));
1286
+ };
1287
+ return getIndex(a) - getIndex(b);
1288
+ });
1289
+ for (const proof of sortedProofs) {
1290
+ const proofData = getProofData(proof.proof, getNumberOfPublicInputs(proof.name));
1291
+ if (proof.name?.startsWith("outer")) {
1292
+ const isForEVM = proof.name?.startsWith("outer_evm");
1293
+ const certificateRegistryRoot = getCertificateRegistryRootFromOuterProof(proofData);
1294
+ if (!VALID_CERTIFICATE_REGISTRY_ROOT.includes(certificateRegistryRoot)) {
1295
+ console.warn("The ID was signed by an unrecognized root certificate");
1296
+ isCorrect = false;
1297
+ queryResultErrors.outer.certificate = {
1298
+ expected: `Certificate registry root: ${VALID_CERTIFICATE_REGISTRY_ROOT.join(", ")}`,
1299
+ received: `Certificate registry root: ${certificateRegistryRoot.toString()}`,
1300
+ message: "The ID was signed by an unrecognized root certificate",
1301
+ };
625
1302
  }
626
- if (queryResult.document_number) {
627
- const documentNumberPassport = disclosedDataPassport.documentNumber;
628
- const documentNumberIDCard = disclosedDataIDCard.documentNumber;
629
- if (queryResult.document_number.eq &&
630
- queryResult.document_number.eq.result &&
631
- queryResult.document_number.eq.expected !== documentNumberPassport &&
632
- queryResult.document_number.eq.expected !== documentNumberIDCard) {
633
- console.warn("Document number does not match the expected document number");
634
- isCorrect = false;
635
- queryResultErrors.document_number.eq = {
636
- expected: `${queryResult.document_number.eq.expected}`,
637
- received: `${documentNumberPassport ?? documentNumberIDCard}`,
638
- message: "Document number does not match the expected document number",
639
- };
640
- }
641
- if (queryResult.document_number.disclose &&
642
- queryResult.document_number.disclose.result !== documentNumberPassport &&
643
- queryResult.document_number.disclose.result !== documentNumberIDCard) {
644
- console.warn("Document number does not match the disclosed document number in query result");
645
- isCorrect = false;
646
- queryResultErrors.document_number.disclose = {
647
- expected: `${queryResult.document_number.disclose.result}`,
648
- received: `${documentNumberPassport ?? documentNumberIDCard}`,
649
- message: "Document number does not match the disclosed document number in query result",
650
- };
651
- }
1303
+ const currentDate = getCurrentDateFromOuterProof(proofData);
1304
+ const todayToCurrentDate = today.getTime() - currentDate.getTime();
1305
+ const differenceInDays = validity ?? 180;
1306
+ const expectedDifference = differenceInDays * 86400000;
1307
+ const actualDifference = today.getTime() - (today.getTime() - expectedDifference);
1308
+ // The ID should not expire within the next 6 months (or whatever the custom value is)
1309
+ if (todayToCurrentDate >= actualDifference) {
1310
+ console.warn(`The date used to check the validity of the ID is older than ${differenceInDays} days. You can ask the user to rescan their ID or ask them to disclose their expiry date`);
1311
+ isCorrect = false;
1312
+ queryResultErrors.outer.date = {
1313
+ expected: `Difference: ${differenceInDays} days`,
1314
+ received: `Difference: ${Math.round(todayToCurrentDate / 86400000)} days`,
1315
+ message: "The date used to check the validity of the ID is older than the validity period",
1316
+ };
652
1317
  }
653
- if (queryResult.gender) {
654
- const genderPassport = disclosedDataPassport.gender;
655
- const genderIDCard = disclosedDataIDCard.gender;
656
- if (queryResult.gender.eq &&
657
- queryResult.gender.eq.result &&
658
- queryResult.gender.eq.expected !== genderPassport &&
659
- queryResult.gender.eq.expected !== genderIDCard) {
660
- console.warn("Gender does not match the expected gender");
661
- isCorrect = false;
662
- queryResultErrors.gender.eq = {
663
- expected: `${queryResult.gender.eq.expected}`,
664
- received: `${genderPassport ?? genderIDCard}`,
665
- message: "Gender does not match the expected gender",
666
- };
667
- }
668
- if (queryResult.gender.disclose &&
669
- queryResult.gender.disclose.result !== genderPassport &&
670
- queryResult.gender.disclose.result !== genderIDCard) {
671
- console.warn("Gender does not match the disclosed gender in query result");
672
- isCorrect = false;
673
- queryResultErrors.gender.disclose = {
674
- expected: `${queryResult.gender.disclose.result}`,
675
- received: `${genderPassport ?? genderIDCard}`,
676
- message: "Gender does not match the disclosed gender in query result",
677
- };
678
- }
1318
+ const paramCommitments = getParamCommitmentsFromOuterProof(proofData);
1319
+ const committedInputs = proof.committedInputs;
1320
+ const keysInCommittedInputs = Object.keys(committedInputs || {});
1321
+ if (keysInCommittedInputs.length !== paramCommitments.length) {
1322
+ console.warn("The proof does not verify all the requested conditions and information");
1323
+ isCorrect = false;
1324
+ queryResultErrors.outer.commitment = {
1325
+ expected: `Number of parameter commitments: ${paramCommitments.length}`,
1326
+ received: `Number of disclosure proofs provided: ${keysInCommittedInputs.length}`,
1327
+ message: "The proof does not verify all the requested conditions and information",
1328
+ };
679
1329
  }
680
- if (queryResult.issuing_country) {
681
- const issuingCountryPassport = disclosedDataPassport.issuingCountry;
682
- const issuingCountryIDCard = disclosedDataIDCard.issuingCountry;
683
- if (queryResult.issuing_country.eq &&
684
- queryResult.issuing_country.eq.result &&
685
- queryResult.issuing_country.eq.expected !== issuingCountryPassport &&
686
- queryResult.issuing_country.eq.expected !== issuingCountryIDCard) {
687
- console.warn("Issuing country does not match the expected issuing country");
1330
+ if (!!committedInputs?.compare_age) {
1331
+ const ageCommittedInputs = committedInputs?.compare_age;
1332
+ const ageParameterCommitment = isForEVM
1333
+ ? await getAgeEVMParameterCommitment(ageCommittedInputs.currentDate, ageCommittedInputs.minAge, ageCommittedInputs.maxAge)
1334
+ : await getAgeParameterCommitment(ageCommittedInputs.currentDate, ageCommittedInputs.minAge, ageCommittedInputs.maxAge);
1335
+ if (!paramCommitments.includes(ageParameterCommitment)) {
1336
+ console.warn("This proof does not verify the age");
688
1337
  isCorrect = false;
689
- queryResultErrors.issuing_country.eq = {
690
- expected: `${queryResult.issuing_country.eq.expected}`,
691
- received: `${issuingCountryPassport ?? issuingCountryIDCard}`,
692
- message: "Issuing country does not match the expected issuing country",
1338
+ queryResultErrors.age.commitment = {
1339
+ expected: `Age parameter commitment: ${ageParameterCommitment.toString()}`,
1340
+ received: `Parameter commitments included: ${paramCommitments.join(", ")}`,
1341
+ message: "This proof does not verify the age",
693
1342
  };
694
1343
  }
695
- if (queryResult.issuing_country.disclose &&
696
- queryResult.issuing_country.disclose.result !== issuingCountryPassport &&
697
- queryResult.issuing_country.disclose.result !== issuingCountryIDCard) {
698
- console.warn("Issuing country does not match the disclosed issuing country in query result");
1344
+ const { isCorrect: isCorrectAge, queryResultErrors: queryResultErrorsAge } = this.checkAgePublicInputs(proof, queryResult);
1345
+ isCorrect = isCorrect && isCorrectAge;
1346
+ queryResultErrors = {
1347
+ ...queryResultErrors,
1348
+ ...queryResultErrorsAge,
1349
+ };
1350
+ }
1351
+ else if (!!committedInputs?.compare_birthdate) {
1352
+ const birthdateCommittedInputs = committedInputs?.compare_birthdate;
1353
+ const birthdateParameterCommitment = isForEVM
1354
+ ? await getDateEVMParameterCommitment(ProofType.BIRTHDATE, birthdateCommittedInputs.currentDate, birthdateCommittedInputs.minDate, birthdateCommittedInputs.maxDate)
1355
+ : await getDateParameterCommitment(ProofType.BIRTHDATE, birthdateCommittedInputs.currentDate, birthdateCommittedInputs.minDate, birthdateCommittedInputs.maxDate);
1356
+ if (!paramCommitments.includes(birthdateParameterCommitment)) {
1357
+ console.warn("This proof does not verify the birthdate");
699
1358
  isCorrect = false;
700
- queryResultErrors.issuing_country.disclose = {
701
- expected: `${queryResult.issuing_country.disclose.result}`,
702
- received: `${issuingCountryPassport ?? issuingCountryIDCard}`,
703
- message: "Issuing country does not match the disclosed issuing country in query result",
1359
+ queryResultErrors.birthdate.commitment = {
1360
+ expected: `Birthdate parameter commitment: ${birthdateParameterCommitment.toString()}`,
1361
+ received: `Parameter commitments included: ${paramCommitments.join(", ")}`,
1362
+ message: "This proof does not verify the birthdate",
704
1363
  };
705
1364
  }
1365
+ const { isCorrect: isCorrectBirthdate, queryResultErrors: queryResultErrorsBirthdate } = this.checkBirthdatePublicInputs(proof, queryResult);
1366
+ isCorrect = isCorrect && isCorrectBirthdate;
1367
+ queryResultErrors = {
1368
+ ...queryResultErrors,
1369
+ ...queryResultErrorsBirthdate,
1370
+ };
706
1371
  }
707
- if (queryResult.fullname) {
708
- const fullnamePassport = disclosedDataPassport.name;
709
- const fullnameIDCard = disclosedDataIDCard.name;
710
- if (queryResult.fullname.eq &&
711
- queryResult.fullname.eq.result &&
712
- formatName(queryResult.fullname.eq.expected).toLowerCase() !==
713
- fullnamePassport.toLowerCase() &&
714
- formatName(queryResult.fullname.eq.expected).toLowerCase() !==
715
- fullnameIDCard.toLowerCase()) {
716
- console.warn("Fullname does not match the expected fullname");
1372
+ else if (!!committedInputs?.compare_expiry) {
1373
+ const expiryCommittedInputs = committedInputs?.compare_expiry;
1374
+ const expiryParameterCommitment = isForEVM
1375
+ ? await getDateEVMParameterCommitment(ProofType.EXPIRY_DATE, expiryCommittedInputs.currentDate, expiryCommittedInputs.minDate, expiryCommittedInputs.maxDate)
1376
+ : await getDateParameterCommitment(ProofType.EXPIRY_DATE, expiryCommittedInputs.currentDate, expiryCommittedInputs.minDate, expiryCommittedInputs.maxDate);
1377
+ if (!paramCommitments.includes(expiryParameterCommitment)) {
1378
+ console.warn("This proof does not verify the expiry date");
717
1379
  isCorrect = false;
718
- queryResultErrors.fullname.eq = {
719
- expected: `${queryResult.fullname.eq.expected}`,
720
- received: `${fullnamePassport ?? fullnameIDCard}`,
721
- message: "Fullname does not match the expected fullname",
1380
+ queryResultErrors.expiry_date.commitment = {
1381
+ expected: `Expiry date parameter commitment: ${expiryParameterCommitment.toString()}`,
1382
+ received: `Parameter commitments included: ${paramCommitments.join(", ")}`,
1383
+ message: "This proof does not verify the expiry date",
722
1384
  };
723
1385
  }
724
- if (queryResult.fullname.disclose &&
725
- formatName(queryResult.fullname.disclose.result).toLowerCase() !==
726
- fullnamePassport.toLowerCase() &&
727
- formatName(queryResult.fullname.disclose.result).toLowerCase() !==
728
- fullnameIDCard.toLowerCase()) {
729
- console.warn("Fullname does not match the disclosed fullname in query result");
1386
+ const { isCorrect: isCorrectExpiryDate, queryResultErrors: queryResultErrorsExpiryDate } = this.checkExpiryDatePublicInputs(proof, queryResult);
1387
+ isCorrect = isCorrect && isCorrectExpiryDate;
1388
+ queryResultErrors = {
1389
+ ...queryResultErrors,
1390
+ ...queryResultErrorsExpiryDate,
1391
+ };
1392
+ }
1393
+ else if (!!committedInputs?.disclose_bytes) {
1394
+ const discloseCommittedInputs = committedInputs?.disclose_bytes;
1395
+ const discloseParameterCommitment = isForEVM
1396
+ ? await getDiscloseEVMParameterCommitment(discloseCommittedInputs.discloseMask, discloseCommittedInputs.disclosedBytes)
1397
+ : await getDiscloseParameterCommitment(discloseCommittedInputs.discloseMask, discloseCommittedInputs.disclosedBytes);
1398
+ if (!paramCommitments.includes(discloseParameterCommitment)) {
1399
+ console.warn("This proof does not verify any of the data disclosed");
730
1400
  isCorrect = false;
731
- queryResultErrors.fullname.disclose = {
732
- expected: `${queryResult.fullname.disclose.result}`,
733
- received: `${fullnamePassport ?? fullnameIDCard}`,
734
- message: "Fullname does not match the disclosed fullname in query result",
1401
+ queryResultErrors.disclose.commitment = {
1402
+ expected: `Disclosure parameter commitment: ${discloseParameterCommitment.toString()}`,
1403
+ received: `Parameter commitments included: ${paramCommitments.join(", ")}`,
1404
+ message: "This proof does not verify any of the data disclosed",
735
1405
  };
736
1406
  }
1407
+ const { isCorrect: isCorrectDisclose, queryResultErrors: queryResultErrorsDisclose } = this.checkDiscloseBytesPublicInputs(proof, queryResult);
1408
+ isCorrect = isCorrect && isCorrectDisclose;
1409
+ queryResultErrors = {
1410
+ ...queryResultErrors,
1411
+ ...queryResultErrorsDisclose,
1412
+ };
737
1413
  }
738
- if (queryResult.firstname) {
739
- // If fullname was not revealed, then the name could be either the first name or last name
740
- const firstnamePassport = disclosedDataPassport.firstName && disclosedDataPassport.firstName.length > 0
741
- ? disclosedDataPassport.firstName
742
- : disclosedDataPassport.name;
743
- const firstnameIDCard = disclosedDataIDCard.firstName && disclosedDataIDCard.firstName.length > 0
744
- ? disclosedDataIDCard.firstName
745
- : disclosedDataIDCard.name;
746
- if (queryResult.firstname.eq &&
747
- queryResult.firstname.eq.result &&
748
- formatName(queryResult.firstname.eq.expected).toLowerCase() !==
749
- firstnamePassport.toLowerCase() &&
750
- formatName(queryResult.firstname.eq.expected).toLowerCase() !==
751
- firstnameIDCard.toLowerCase()) {
752
- console.warn("Firstname does not match the expected firstname");
1414
+ else if (!!committedInputs?.inclusion_check_nationality) {
1415
+ const inclusionCheckNationalityCommittedInputs = committedInputs?.inclusion_check_nationality;
1416
+ const inclusionCheckNationalityParameterCommitment = isForEVM
1417
+ ? await getCountryEVMParameterCommitment(ProofType.NATIONALITY_INCLUSION, inclusionCheckNationalityCommittedInputs.countries)
1418
+ : await getCountryParameterCommitment(ProofType.NATIONALITY_INCLUSION, inclusionCheckNationalityCommittedInputs.countries);
1419
+ if (!paramCommitments.includes(inclusionCheckNationalityParameterCommitment)) {
1420
+ console.warn("This proof does not verify the inclusion of the nationality");
753
1421
  isCorrect = false;
754
- queryResultErrors.firstname.eq = {
755
- expected: `${queryResult.firstname.eq.expected}`,
756
- received: `${firstnamePassport ?? firstnameIDCard}`,
757
- message: "Firstname does not match the expected firstname",
1422
+ queryResultErrors.nationality.commitment = {
1423
+ expected: `Nationality parameter commitment: ${inclusionCheckNationalityParameterCommitment.toString()}`,
1424
+ received: `Parameter commitments included: ${paramCommitments.join(", ")}`,
1425
+ message: "This proof does not verify the inclusion of the nationality",
758
1426
  };
759
1427
  }
760
- if (queryResult.firstname.disclose &&
761
- formatName(queryResult.firstname.disclose.result).toLowerCase() !==
762
- firstnamePassport.toLowerCase() &&
763
- formatName(queryResult.firstname.disclose.result).toLowerCase() !==
764
- firstnameIDCard.toLowerCase()) {
765
- console.warn("Firstname does not match the disclosed firstname in query result");
1428
+ const countryList = inclusionCheckNationalityCommittedInputs.countries;
1429
+ const { isCorrect: isCorrectNationalityInclusion, queryResultErrors: queryResultErrorsNationalityInclusion, } = this.checkNationalityInclusionPublicInputs(queryResult, countryList);
1430
+ isCorrect = isCorrect && isCorrectNationalityInclusion;
1431
+ queryResultErrors = {
1432
+ ...queryResultErrors,
1433
+ ...queryResultErrorsNationalityInclusion,
1434
+ };
1435
+ }
1436
+ else if (!!committedInputs?.inclusion_check_issuing_country) {
1437
+ const inclusionCheckIssuingCountryCommittedInputs = committedInputs?.inclusion_check_issuing_country;
1438
+ const inclusionCheckIssuingCountryParameterCommitment = isForEVM
1439
+ ? await getCountryEVMParameterCommitment(ProofType.ISSUING_COUNTRY_INCLUSION, inclusionCheckIssuingCountryCommittedInputs.countries)
1440
+ : await getCountryParameterCommitment(ProofType.ISSUING_COUNTRY_INCLUSION, inclusionCheckIssuingCountryCommittedInputs.countries);
1441
+ if (!paramCommitments.includes(inclusionCheckIssuingCountryParameterCommitment)) {
1442
+ console.warn("This proof does not verify the inclusion of the issuing country");
766
1443
  isCorrect = false;
767
- queryResultErrors.firstname.disclose = {
768
- expected: `${queryResult.firstname.disclose.result}`,
769
- received: `${firstnamePassport ?? firstnameIDCard}`,
770
- message: "Firstname does not match the disclosed firstname in query result",
1444
+ queryResultErrors.issuing_country.commitment = {
1445
+ expected: `Issuing country parameter commitment: ${inclusionCheckIssuingCountryParameterCommitment.toString()}`,
1446
+ received: `Parameter commitments included: ${paramCommitments.join(", ")}`,
1447
+ message: "This proof does not verify the inclusion of the issuing country",
771
1448
  };
772
1449
  }
1450
+ const countryList = inclusionCheckIssuingCountryCommittedInputs.countries;
1451
+ const { isCorrect: isCorrectIssuingCountryInclusion, queryResultErrors: queryResultErrorsIssuingCountryInclusion, } = this.checkIssuingCountryInclusionPublicInputs(queryResult, countryList);
1452
+ isCorrect = isCorrect && isCorrectIssuingCountryInclusion;
1453
+ queryResultErrors = {
1454
+ ...queryResultErrors,
1455
+ ...queryResultErrorsIssuingCountryInclusion,
1456
+ };
773
1457
  }
774
- if (queryResult.lastname) {
775
- // If fullname was not revealed, then the name could be either the first name or last name
776
- const lastnamePassport = disclosedDataPassport.lastName && disclosedDataPassport.lastName.length > 0
777
- ? disclosedDataPassport.lastName
778
- : disclosedDataPassport.name;
779
- const lastnameIDCard = disclosedDataIDCard.lastName && disclosedDataIDCard.lastName.length > 0
780
- ? disclosedDataIDCard.lastName
781
- : disclosedDataIDCard.name;
782
- if (queryResult.lastname.eq &&
783
- queryResult.lastname.eq.result &&
784
- formatName(queryResult.lastname.eq.expected).toLowerCase() !==
785
- lastnamePassport.toLowerCase() &&
786
- formatName(queryResult.lastname.eq.expected).toLowerCase() !==
787
- lastnameIDCard.toLowerCase()) {
788
- console.warn("Lastname does not match the expected lastname");
1458
+ else if (!!committedInputs?.exclusion_check_nationality) {
1459
+ const exclusionCheckNationalityCommittedInputs = committedInputs?.exclusion_check_nationality;
1460
+ const exclusionCheckNationalityParameterCommitment = isForEVM
1461
+ ? await getCountryEVMParameterCommitment(ProofType.NATIONALITY_EXCLUSION, exclusionCheckNationalityCommittedInputs.countries)
1462
+ : await getCountryParameterCommitment(ProofType.NATIONALITY_EXCLUSION, exclusionCheckNationalityCommittedInputs.countries);
1463
+ if (!paramCommitments.includes(exclusionCheckNationalityParameterCommitment)) {
1464
+ console.warn("This proof does not verify the exclusion of the nationality");
789
1465
  isCorrect = false;
790
- queryResultErrors.lastname.eq = {
791
- expected: `${queryResult.lastname.eq.expected}`,
792
- received: `${lastnamePassport ?? lastnameIDCard}`,
793
- message: "Lastname does not match the expected lastname",
1466
+ queryResultErrors.nationality.commitment = {
1467
+ expected: `Nationality parameter commitment: ${exclusionCheckNationalityParameterCommitment.toString()}`,
1468
+ received: `Parameter commitments included: ${paramCommitments.join(", ")}`,
1469
+ message: "This proof does not verify the exclusion of the nationality",
794
1470
  };
795
1471
  }
796
- if (queryResult.lastname.disclose &&
797
- formatName(queryResult.lastname.disclose.result).toLowerCase() !==
798
- lastnamePassport.toLowerCase() &&
799
- formatName(queryResult.lastname.disclose.result).toLowerCase() !==
800
- lastnameIDCard.toLowerCase()) {
801
- console.warn("Lastname does not match the disclosed lastname in query result");
1472
+ const countryList = exclusionCheckNationalityCommittedInputs.countries;
1473
+ const { isCorrect: isCorrectNationalityExclusion, queryResultErrors: queryResultErrorsNationalityExclusion, } = this.checkNationalityExclusionPublicInputs(queryResult, countryList);
1474
+ isCorrect = isCorrect && isCorrectNationalityExclusion;
1475
+ queryResultErrors = {
1476
+ ...queryResultErrors,
1477
+ ...queryResultErrorsNationalityExclusion,
1478
+ };
1479
+ }
1480
+ else if (!!committedInputs?.exclusion_check_issuing_country) {
1481
+ const exclusionCheckIssuingCountryCommittedInputs = committedInputs?.exclusion_check_issuing_country;
1482
+ const exclusionCheckIssuingCountryParameterCommitment = isForEVM
1483
+ ? await getCountryEVMParameterCommitment(ProofType.ISSUING_COUNTRY_EXCLUSION, exclusionCheckIssuingCountryCommittedInputs.countries)
1484
+ : await getCountryParameterCommitment(ProofType.ISSUING_COUNTRY_EXCLUSION, exclusionCheckIssuingCountryCommittedInputs.countries);
1485
+ if (!paramCommitments.includes(exclusionCheckIssuingCountryParameterCommitment)) {
1486
+ console.warn("This proof does not verify the exclusion of the issuing country");
802
1487
  isCorrect = false;
803
- queryResultErrors.lastname.disclose = {
804
- expected: `${queryResult.lastname.disclose.result}`,
805
- received: `${lastnamePassport ?? lastnameIDCard}`,
806
- message: "Lastname does not match the disclosed lastname in query result",
1488
+ queryResultErrors.issuing_country.commitment = {
1489
+ expected: `Issuing country parameter commitment: ${exclusionCheckIssuingCountryParameterCommitment.toString()}`,
1490
+ received: `Parameter commitments included: ${paramCommitments.join(", ")}`,
1491
+ message: "This proof does not verify the exclusion of the issuing country",
807
1492
  };
808
1493
  }
1494
+ const countryList = exclusionCheckIssuingCountryCommittedInputs.countries;
1495
+ const { isCorrect: isCorrectIssuingCountryExclusion, queryResultErrors: queryResultErrorsIssuingCountryExclusion, } = this.checkIssuingCountryExclusionPublicInputs(queryResult, countryList);
1496
+ isCorrect = isCorrect && isCorrectIssuingCountryExclusion;
1497
+ queryResultErrors = {
1498
+ ...queryResultErrors,
1499
+ ...queryResultErrorsIssuingCountryExclusion,
1500
+ };
1501
+ }
1502
+ uniqueIdentifier = getNullifierFromOuterProof(proofData).toString(10);
1503
+ }
1504
+ else if (proof.name?.startsWith("sig_check_dsc")) {
1505
+ commitmentOut = getCommitmentFromDSCProof(proofData);
1506
+ const merkleRoot = getMerkleRootFromDSCProof(proofData);
1507
+ if (!VALID_CERTIFICATE_REGISTRY_ROOT.includes(merkleRoot)) {
1508
+ console.warn("The ID was signed by an unrecognized root certificate");
1509
+ isCorrect = false;
1510
+ queryResultErrors.sig_check_dsc.certificate = {
1511
+ expected: `Certificate registry root: ${VALID_CERTIFICATE_REGISTRY_ROOT.join(", ")}`,
1512
+ received: `Certificate registry root: ${merkleRoot.toString()}`,
1513
+ message: "The ID was signed by an unrecognized root certificate",
1514
+ };
1515
+ }
1516
+ }
1517
+ else if (proof.name?.startsWith("sig_check_id_data")) {
1518
+ commitmentIn = getCommitmentInFromIDDataProof(proofData);
1519
+ if (commitmentIn !== commitmentOut) {
1520
+ console.warn("Failed to check the link between the certificate signature and ID signature");
1521
+ isCorrect = false;
1522
+ queryResultErrors.sig_check_id_data.commitment = {
1523
+ expected: `Commitment: ${commitmentOut?.toString() || "undefined"}`,
1524
+ received: `Commitment: ${commitmentIn?.toString() || "undefined"}`,
1525
+ message: "Failed to check the link between the certificate signature and ID signature",
1526
+ };
1527
+ }
1528
+ commitmentOut = getCommitmentOutFromIDDataProof(proofData);
1529
+ }
1530
+ else if (proof.name?.startsWith("data_check_integrity")) {
1531
+ commitmentIn = getCommitmentInFromIntegrityProof(proofData);
1532
+ if (commitmentIn !== commitmentOut) {
1533
+ console.warn("Failed to check the link between the ID signature and the data signed");
1534
+ isCorrect = false;
1535
+ queryResultErrors.data_check_integrity.commitment = {
1536
+ expected: `Commitment: ${commitmentOut?.toString() || "undefined"}`,
1537
+ received: `Commitment: ${commitmentIn?.toString() || "undefined"}`,
1538
+ message: "Failed to check the link between the ID signature and the data signed",
1539
+ };
1540
+ }
1541
+ commitmentOut = getCommitmentOutFromIntegrityProof(proofData);
1542
+ const currentDate = getCurrentDateFromIntegrityProof(proofData);
1543
+ const todayToCurrentDate = today.getTime() - currentDate.getTime();
1544
+ const differenceInDays = validity ?? 180;
1545
+ const expectedDifference = differenceInDays * 86400000;
1546
+ const actualDifference = today.getTime() - (today.getTime() - expectedDifference);
1547
+ // The ID should not expire within the next 6 months (or whatever the custom value is)
1548
+ if (todayToCurrentDate >= actualDifference) {
1549
+ console.warn(`The date used to check the validity of the ID is older than ${differenceInDays} days. You can ask the user to rescan their ID or ask them to disclose their expiry date`);
1550
+ isCorrect = false;
1551
+ queryResultErrors.data_check_integrity.date = {
1552
+ expected: `Difference: ${differenceInDays} days`,
1553
+ received: `Difference: ${Math.round(todayToCurrentDate / 86400000)} days`,
1554
+ message: "The date used to check the validity of the ID is older than the validity period",
1555
+ };
1556
+ }
1557
+ }
1558
+ else if (proof.name === "disclose_bytes") {
1559
+ commitmentIn = getCommitmentInFromDisclosureProof(proofData);
1560
+ if (commitmentIn !== commitmentOut) {
1561
+ console.warn("Failed to check the link between the validity of the ID and the data to disclose");
1562
+ isCorrect = false;
1563
+ queryResultErrors.disclose.commitment = {
1564
+ expected: `Commitment: ${commitmentOut?.toString() || "undefined"}`,
1565
+ received: `Commitment: ${commitmentIn?.toString() || "undefined"}`,
1566
+ message: "Failed to check the link between the validity of the ID and the data to disclose",
1567
+ };
1568
+ }
1569
+ const paramCommitment = getParameterCommitmentFromDisclosureProof(proofData);
1570
+ const calculatedParamCommitment = await getDiscloseParameterCommitment((proof.committedInputs?.disclose_bytes).discloseMask, (proof.committedInputs?.disclose_bytes).disclosedBytes);
1571
+ if (paramCommitment !== calculatedParamCommitment) {
1572
+ console.warn("The disclosed data does not match the data committed by the proof");
1573
+ isCorrect = false;
1574
+ queryResultErrors.disclose.commitment = {
1575
+ expected: `Commitment: ${calculatedParamCommitment}`,
1576
+ received: `Commitment: ${paramCommitment}`,
1577
+ message: "The disclosed data does not match the data committed by the proof",
1578
+ };
809
1579
  }
1580
+ const { isCorrect: isCorrectDisclose, queryResultErrors: queryResultErrorsDisclose } = this.checkDiscloseBytesPublicInputs(proof, queryResult);
1581
+ isCorrect = isCorrect && isCorrectDisclose;
1582
+ queryResultErrors = {
1583
+ ...queryResultErrors,
1584
+ ...queryResultErrorsDisclose,
1585
+ };
810
1586
  uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10);
811
1587
  }
812
1588
  else if (proof.name === "compare_age") {
@@ -820,92 +1596,24 @@ export class ZKPassport {
820
1596
  message: "Failed to check the link between the validity of the ID and the age derived from it",
821
1597
  };
822
1598
  }
823
- const minAge = getMinAgeFromProof(proofData);
824
- const maxAge = getMaxAgeFromProof(proofData);
825
- if (queryResult.age) {
826
- if (queryResult.age.gte &&
827
- queryResult.age.gte.result &&
828
- minAge < queryResult.age.gte.expected) {
829
- console.warn("Age is not greater than or equal to the expected age");
830
- isCorrect = false;
831
- queryResultErrors.age.gte = {
832
- expected: queryResult.age.gte.expected,
833
- received: minAge,
834
- message: "Age is not greater than or equal to the expected age",
835
- };
836
- }
837
- if (queryResult.age.lt &&
838
- queryResult.age.lt.result &&
839
- maxAge >= queryResult.age.lt.expected) {
840
- console.warn("Age is not less than the expected age");
841
- isCorrect = false;
842
- queryResultErrors.age.lt = {
843
- expected: queryResult.age.lt.expected,
844
- received: maxAge,
845
- message: "Age is not less than the expected age",
846
- };
847
- }
848
- if (queryResult.age.range) {
849
- if (queryResult.age.range.result &&
850
- (minAge < queryResult.age.range.expected[0] ||
851
- maxAge >= queryResult.age.range.expected[1])) {
852
- console.warn("Age is not in the expected range");
853
- isCorrect = false;
854
- queryResultErrors.age.range = {
855
- expected: queryResult.age.range.expected,
856
- received: [minAge, maxAge],
857
- message: "Age is not in the expected range",
858
- };
859
- }
860
- }
861
- if (!queryResult.age.lt && !queryResult.age.range && maxAge != 0) {
862
- console.warn("Maximum age should be equal to 0");
863
- isCorrect = false;
864
- queryResultErrors.age.disclose = {
865
- expected: 0,
866
- received: maxAge,
867
- message: "Maximum age should be equal to 0",
868
- };
869
- }
870
- if (!queryResult.age.gte && !queryResult.age.range && minAge != 0) {
871
- console.warn("Minimum age should be equal to 0");
872
- isCorrect = false;
873
- queryResultErrors.age.disclose = {
874
- expected: 0,
875
- received: minAge,
876
- message: "Minimum age should be equal to 0",
877
- };
878
- }
879
- if (queryResult.age.disclose &&
880
- (queryResult.age.disclose.result !== minAge ||
881
- queryResult.age.disclose.result !== maxAge)) {
882
- console.warn("Age does not match the disclosed age in query result");
883
- isCorrect = false;
884
- queryResultErrors.age.disclose = {
885
- expected: `${minAge}`,
886
- received: `${queryResult.age.disclose.result}`,
887
- message: "Age does not match the disclosed age in query result",
888
- };
889
- }
890
- }
891
- else {
892
- console.warn("Age is not set in the query result");
893
- isCorrect = false;
894
- queryResultErrors.age.disclose = {
895
- message: "Age is not set in the query result",
896
- };
897
- }
898
- const currentDate = getCurrentDateFromAgeProof(proofData);
899
- if (currentDate.getTime() !== today.getTime() &&
900
- currentDate.getTime() !== today.getTime() - 86400000) {
901
- console.warn("Current date in the proof is too old");
1599
+ const paramCommitment = getParameterCommitmentFromDisclosureProof(proofData);
1600
+ const committedInputs = proof.committedInputs?.compare_age;
1601
+ const calculatedParamCommitment = await getAgeParameterCommitment(committedInputs.currentDate, committedInputs.minAge, committedInputs.maxAge);
1602
+ if (paramCommitment !== calculatedParamCommitment) {
1603
+ console.warn("The conditions for the age check do not match the conditions checked by the proof");
902
1604
  isCorrect = false;
903
- queryResultErrors.age.disclose = {
904
- expected: `${today.toISOString()}`,
905
- received: `${currentDate.toISOString()}`,
906
- message: "Current date in the proof is too old",
1605
+ queryResultErrors.age.commitment = {
1606
+ expected: `Commitment: ${calculatedParamCommitment}`,
1607
+ received: `Commitment: ${paramCommitment}`,
1608
+ message: "The conditions for the age check do not match the conditions checked by the proof",
907
1609
  };
908
1610
  }
1611
+ const { isCorrect: isCorrectAge, queryResultErrors: queryResultErrorsAge } = this.checkAgePublicInputs(proof, queryResult);
1612
+ isCorrect = isCorrect && isCorrectAge;
1613
+ queryResultErrors = {
1614
+ ...queryResultErrors,
1615
+ ...queryResultErrorsAge,
1616
+ };
909
1617
  uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10);
910
1618
  }
911
1619
  else if (proof.name === "compare_birthdate") {
@@ -919,74 +1627,24 @@ export class ZKPassport {
919
1627
  message: "Failed to check the link between the validity of the ID and the birthdate derived from it",
920
1628
  };
921
1629
  }
922
- const minDate = getMinDateFromProof(proofData);
923
- const maxDate = getMaxDateFromProof(proofData);
924
- if (queryResult.birthdate) {
925
- if (queryResult.birthdate.gte &&
926
- queryResult.birthdate.gte.result &&
927
- minDate < queryResult.birthdate.gte.expected) {
928
- console.warn("Birthdate is not greater than or equal to the expected birthdate");
929
- isCorrect = false;
930
- queryResultErrors.birthdate.gte = {
931
- expected: queryResult.birthdate.gte.expected,
932
- received: minDate,
933
- message: "Birthdate is not greater than or equal to the expected birthdate",
934
- };
935
- }
936
- if (queryResult.birthdate.lte &&
937
- queryResult.birthdate.lte.result &&
938
- maxDate > queryResult.birthdate.lte.expected) {
939
- console.warn("Birthdate is not less than the expected birthdate");
940
- isCorrect = false;
941
- queryResultErrors.birthdate.lte = {
942
- expected: queryResult.birthdate.lte.expected,
943
- received: maxDate,
944
- message: "Birthdate is not less than the expected birthdate",
945
- };
946
- }
947
- if (queryResult.birthdate.range) {
948
- if (queryResult.birthdate.range.result &&
949
- (minDate < queryResult.birthdate.range.expected[0] ||
950
- maxDate > queryResult.birthdate.range.expected[1])) {
951
- console.warn("Birthdate is not in the expected range");
952
- isCorrect = false;
953
- queryResultErrors.birthdate.range = {
954
- expected: queryResult.birthdate.range.expected,
955
- received: [minDate, maxDate],
956
- message: "Birthdate is not in the expected range",
957
- };
958
- }
959
- }
960
- if (!queryResult.birthdate.lte &&
961
- !queryResult.birthdate.range &&
962
- maxDate.getTime() != defaultDateValue.getTime()) {
963
- console.warn("Maximum birthdate should be equal to default date value");
964
- isCorrect = false;
965
- queryResultErrors.birthdate.disclose = {
966
- expected: `${defaultDateValue.toISOString()}`,
967
- received: `${maxDate.toISOString()}`,
968
- message: "Maximum birthdate should be equal to default date value",
969
- };
970
- }
971
- if (!queryResult.birthdate.gte &&
972
- !queryResult.birthdate.range &&
973
- minDate.getTime() != defaultDateValue.getTime()) {
974
- console.warn("Minimum birthdate should be equal to default date value");
975
- isCorrect = false;
976
- queryResultErrors.birthdate.disclose = {
977
- expected: `${defaultDateValue.toISOString()}`,
978
- received: `${minDate.toISOString()}`,
979
- message: "Minimum birthdate should be equal to default date value",
980
- };
981
- }
982
- }
983
- else {
984
- console.warn("Birthdate is not set in the query result");
1630
+ const paramCommitment = getParameterCommitmentFromDisclosureProof(proofData);
1631
+ const committedInputs = proof.committedInputs?.compare_birthdate;
1632
+ const calculatedParamCommitment = await getDateParameterCommitment(ProofType.BIRTHDATE, committedInputs.currentDate, committedInputs.minDate, committedInputs.maxDate);
1633
+ if (paramCommitment !== calculatedParamCommitment) {
1634
+ console.warn("The conditions for the birthdate check do not match the conditions checked by the proof");
985
1635
  isCorrect = false;
986
- queryResultErrors.birthdate.disclose = {
987
- message: "Birthdate is not set in the query result",
1636
+ queryResultErrors.birthdate.commitment = {
1637
+ expected: `Commitment: ${calculatedParamCommitment}`,
1638
+ received: `Commitment: ${paramCommitment}`,
1639
+ message: "The conditions for the birthdate check do not match the conditions checked by the proof",
988
1640
  };
989
1641
  }
1642
+ const { isCorrect: isCorrectBirthdate, queryResultErrors: queryResultErrorsBirthdate } = this.checkBirthdatePublicInputs(proof, queryResult);
1643
+ isCorrect = isCorrect && isCorrectBirthdate;
1644
+ queryResultErrors = {
1645
+ ...queryResultErrors,
1646
+ ...queryResultErrorsBirthdate,
1647
+ };
990
1648
  uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10);
991
1649
  }
992
1650
  else if (proof.name === "compare_expiry") {
@@ -1000,74 +1658,24 @@ export class ZKPassport {
1000
1658
  message: "Failed to check the link between the validity of the ID and its expiry date",
1001
1659
  };
1002
1660
  }
1003
- const minDate = getMinDateFromProof(proofData);
1004
- const maxDate = getMaxDateFromProof(proofData);
1005
- if (queryResult.expiry_date) {
1006
- if (queryResult.expiry_date.gte &&
1007
- queryResult.expiry_date.gte.result &&
1008
- minDate < queryResult.expiry_date.gte.expected) {
1009
- console.warn("Expiry date is not greater than or equal to the expected expiry date");
1010
- isCorrect = false;
1011
- queryResultErrors.expiry_date.gte = {
1012
- expected: queryResult.expiry_date.gte.expected,
1013
- received: minDate,
1014
- message: "Expiry date is not greater than or equal to the expected expiry date",
1015
- };
1016
- }
1017
- if (queryResult.expiry_date.lte &&
1018
- queryResult.expiry_date.lte.result &&
1019
- maxDate > queryResult.expiry_date.lte.expected) {
1020
- console.warn("Expiry date is not less than the expected expiry date");
1021
- isCorrect = false;
1022
- queryResultErrors.expiry_date.lte = {
1023
- expected: queryResult.expiry_date.lte.expected,
1024
- received: maxDate,
1025
- message: "Expiry date is not less than the expected expiry date",
1026
- };
1027
- }
1028
- if (queryResult.expiry_date.range) {
1029
- if (queryResult.expiry_date.range.result &&
1030
- (minDate < queryResult.expiry_date.range.expected[0] ||
1031
- maxDate > queryResult.expiry_date.range.expected[1])) {
1032
- console.warn("Expiry date is not in the expected range");
1033
- isCorrect = false;
1034
- queryResultErrors.expiry_date.range = {
1035
- expected: queryResult.expiry_date.range.expected,
1036
- received: [minDate, maxDate],
1037
- message: "Expiry date is not in the expected range",
1038
- };
1039
- }
1040
- }
1041
- if (!queryResult.expiry_date.lte &&
1042
- !queryResult.expiry_date.range &&
1043
- maxDate.getTime() != defaultDateValue.getTime()) {
1044
- console.warn("Maximum expiry date should be equal to default date value");
1045
- isCorrect = false;
1046
- queryResultErrors.expiry_date.disclose = {
1047
- expected: `${defaultDateValue.toISOString()}`,
1048
- received: `${maxDate.toISOString()}`,
1049
- message: "Maximum expiry date should be equal to default date value",
1050
- };
1051
- }
1052
- if (!queryResult.expiry_date.gte &&
1053
- !queryResult.expiry_date.range &&
1054
- minDate.getTime() != defaultDateValue.getTime()) {
1055
- console.warn("Minimum expiry date should be equal to default date value");
1056
- isCorrect = false;
1057
- queryResultErrors.expiry_date.disclose = {
1058
- expected: `${defaultDateValue.toISOString()}`,
1059
- received: `${minDate.toISOString()}`,
1060
- message: "Minimum expiry date should be equal to default date value",
1061
- };
1062
- }
1063
- }
1064
- else {
1065
- console.warn("Expiry date is not set in the query result");
1661
+ const paramCommitment = getParameterCommitmentFromDisclosureProof(proofData);
1662
+ const committedInputs = proof.committedInputs?.compare_expiry;
1663
+ const calculatedParamCommitment = await getDateParameterCommitment(ProofType.EXPIRY_DATE, committedInputs.currentDate, committedInputs.minDate, committedInputs.maxDate);
1664
+ if (paramCommitment !== calculatedParamCommitment) {
1665
+ console.warn("The conditions for the expiry date check do not match the conditions checked by the proof");
1066
1666
  isCorrect = false;
1067
- queryResultErrors.expiry_date.disclose = {
1068
- message: "Expiry date is not set in the query result",
1667
+ queryResultErrors.expiry_date.commitment = {
1668
+ expected: `Commitment: ${calculatedParamCommitment}`,
1669
+ received: `Commitment: ${paramCommitment}`,
1670
+ message: "The conditions for the expiry date check do not match the conditions checked by the proof",
1069
1671
  };
1070
1672
  }
1673
+ const { isCorrect: isCorrectExpiryDate, queryResultErrors: queryResultErrorsExpiryDate } = this.checkExpiryDatePublicInputs(proof, queryResult);
1674
+ isCorrect = isCorrect && isCorrectExpiryDate;
1675
+ queryResultErrors = {
1676
+ ...queryResultErrors,
1677
+ ...queryResultErrorsExpiryDate,
1678
+ };
1071
1679
  uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10);
1072
1680
  }
1073
1681
  else if (proof.name === "exclusion_check_nationality") {
@@ -1081,39 +1689,24 @@ export class ZKPassport {
1081
1689
  message: "Failed to check the link between the validity of the ID and the nationality exclusion check",
1082
1690
  };
1083
1691
  }
1084
- const countryList = getCountryListFromExclusionProof(proofData);
1085
- if (queryResult.nationality &&
1086
- queryResult.nationality.out &&
1087
- queryResult.nationality.out.result) {
1088
- if (!queryResult.nationality.out.expected?.every((country) => countryList.includes(country))) {
1089
- console.warn("Nationality exclusion list does not match the one from the query results");
1090
- isCorrect = false;
1091
- queryResultErrors.nationality.out = {
1092
- expected: queryResult.nationality.out.expected,
1093
- received: countryList,
1094
- message: "Nationality exclusion list does not match the one from the query results",
1095
- };
1096
- }
1097
- }
1098
- else if (!queryResult.nationality || !queryResult.nationality.out) {
1099
- console.warn("Nationality exclusion is not set in the query result");
1692
+ const countryList = (proof.committedInputs?.exclusion_check_nationality).countries;
1693
+ const paramCommittment = getParameterCommitmentFromDisclosureProof(proofData);
1694
+ const calculatedParamCommitment = await getCountryParameterCommitment(ProofType.NATIONALITY_EXCLUSION, countryList, true);
1695
+ if (paramCommittment !== calculatedParamCommitment) {
1696
+ console.warn("The committed country list for the exclusion check does not match the one from the proof");
1100
1697
  isCorrect = false;
1101
- queryResultErrors.nationality.out = {
1102
- message: "Nationality exclusion is not set in the query result",
1698
+ queryResultErrors.nationality.commitment = {
1699
+ expected: `Commitment: ${calculatedParamCommitment}`,
1700
+ received: `Commitment: ${paramCommittment}`,
1701
+ message: "The committed country list for the exclusion check does not match the one from the proof",
1103
1702
  };
1104
1703
  }
1105
- // Check the countryList is in ascending order
1106
- // If the prover doesn't use a sorted list then the proof cannot be trusted
1107
- // as it is requirement in the circuit for the exclusion check to work
1108
- for (let i = 1; i < countryList.length; i++) {
1109
- if (countryList[i] < countryList[i - 1]) {
1110
- console.warn("The nationality exclusion list has not been sorted, and thus the proof cannot be trusted");
1111
- isCorrect = false;
1112
- queryResultErrors.nationality.out = {
1113
- message: "The nationality exclusion list has not been sorted, and thus the proof cannot be trusted",
1114
- };
1115
- }
1116
- }
1704
+ const { isCorrect: isCorrectNationalityExclusion, queryResultErrors: queryResultErrorsNationalityExclusion, } = this.checkNationalityExclusionPublicInputs(queryResult, countryList);
1705
+ isCorrect = isCorrect && isCorrectNationalityExclusion;
1706
+ queryResultErrors = {
1707
+ ...queryResultErrors,
1708
+ ...queryResultErrorsNationalityExclusion,
1709
+ };
1117
1710
  uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10);
1118
1711
  }
1119
1712
  else if (proof.name === "exclusion_check_issuing_country") {
@@ -1127,39 +1720,24 @@ export class ZKPassport {
1127
1720
  message: "Failed to check the link between the validity of the ID and the issuing country exclusion check",
1128
1721
  };
1129
1722
  }
1130
- const countryList = getCountryListFromExclusionProof(proofData);
1131
- if (queryResult.issuing_country &&
1132
- queryResult.issuing_country.out &&
1133
- queryResult.issuing_country.out.result) {
1134
- if (!queryResult.issuing_country.out.expected?.every((country) => countryList.includes(country))) {
1135
- console.warn("Issuing country exclusion list does not match the one from the query results");
1136
- isCorrect = false;
1137
- queryResultErrors.issuing_country.out = {
1138
- expected: queryResult.issuing_country.out.expected,
1139
- received: countryList,
1140
- message: "Issuing country exclusion list does not match the one from the query results",
1141
- };
1142
- }
1143
- }
1144
- else if (!queryResult.issuing_country || !queryResult.issuing_country.out) {
1145
- console.warn("Issuing country exclusion is not set in the query result");
1723
+ const countryList = (proof.committedInputs?.exclusion_check_issuing_country).countries;
1724
+ const paramCommittment = getParameterCommitmentFromDisclosureProof(proofData);
1725
+ const calculatedParamCommitment = await getCountryParameterCommitment(ProofType.ISSUING_COUNTRY_EXCLUSION, countryList, true);
1726
+ if (paramCommittment !== calculatedParamCommitment) {
1727
+ console.warn("The committed country list for the issuing country exclusion check does not match the one from the proof");
1146
1728
  isCorrect = false;
1147
- queryResultErrors.issuing_country.out = {
1148
- message: "Issuing country exclusion is not set in the query result",
1729
+ queryResultErrors.issuing_country.commitment = {
1730
+ expected: `Commitment: ${calculatedParamCommitment}`,
1731
+ received: `Commitment: ${paramCommittment}`,
1732
+ message: "The committed country list for the issuing country exclusion check does not match the one from the proof",
1149
1733
  };
1150
1734
  }
1151
- // Check the countryList is in ascending order
1152
- // If the prover doesn't use a sorted list then the proof cannot be trusted
1153
- // as it is requirement in the circuit for the exclusion check to work
1154
- for (let i = 1; i < countryList.length; i++) {
1155
- if (countryList[i] < countryList[i - 1]) {
1156
- console.warn("The issuing country exclusion list has not been sorted, and thus the proof cannot be trusted");
1157
- isCorrect = false;
1158
- queryResultErrors.issuing_country.out = {
1159
- message: "The issuing country exclusion list has not been sorted, and thus the proof cannot be trusted",
1160
- };
1161
- }
1162
- }
1735
+ const { isCorrect: isCorrectIssuingCountryExclusion, queryResultErrors: queryResultErrorsIssuingCountryExclusion, } = this.checkIssuingCountryExclusionPublicInputs(queryResult, countryList);
1736
+ isCorrect = isCorrect && isCorrectIssuingCountryExclusion;
1737
+ queryResultErrors = {
1738
+ ...queryResultErrors,
1739
+ ...queryResultErrorsIssuingCountryExclusion,
1740
+ };
1163
1741
  uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10);
1164
1742
  }
1165
1743
  else if (proof.name === "inclusion_check_nationality") {
@@ -1173,27 +1751,24 @@ export class ZKPassport {
1173
1751
  message: "Failed to check the link between the validity of the ID and the nationality inclusion check",
1174
1752
  };
1175
1753
  }
1176
- const countryList = getCountryListFromInclusionProof(proofData);
1177
- if (queryResult.nationality &&
1178
- queryResult.nationality.in &&
1179
- queryResult.nationality.in.result) {
1180
- if (!queryResult.nationality.in.expected?.every((country) => countryList.includes(country))) {
1181
- console.warn("Nationality inclusion list does not match the one from the query results");
1182
- isCorrect = false;
1183
- queryResultErrors.nationality.in = {
1184
- expected: queryResult.nationality.in.expected,
1185
- received: countryList,
1186
- message: "Nationality inclusion list does not match the one from the query results",
1187
- };
1188
- }
1189
- }
1190
- else if (!queryResult.nationality || !queryResult.nationality.in) {
1191
- console.warn("Nationality inclusion is not set in the query result");
1754
+ const countryList = (proof.committedInputs?.inclusion_check_nationality).countries;
1755
+ const paramCommittment = getParameterCommitmentFromDisclosureProof(proofData);
1756
+ const calculatedParamCommitment = await getCountryParameterCommitment(ProofType.NATIONALITY_INCLUSION, countryList, false);
1757
+ if (paramCommittment !== calculatedParamCommitment) {
1758
+ console.warn("The committed country list for the nationality inclusion check does not match the one from the proof");
1192
1759
  isCorrect = false;
1193
- queryResultErrors.nationality.in = {
1194
- message: "Nationality inclusion is not set in the query result",
1760
+ queryResultErrors.nationality.commitment = {
1761
+ expected: `Commitment: ${calculatedParamCommitment}`,
1762
+ received: `Commitment: ${paramCommittment}`,
1763
+ message: "The committed country list for the nationality inclusion check does not match the one from the proof",
1195
1764
  };
1196
1765
  }
1766
+ const { isCorrect: isCorrectNationalityInclusion, queryResultErrors: queryResultErrorsNationalityInclusion, } = this.checkNationalityInclusionPublicInputs(queryResult, countryList);
1767
+ isCorrect = isCorrect && isCorrectNationalityInclusion;
1768
+ queryResultErrors = {
1769
+ ...queryResultErrors,
1770
+ ...queryResultErrorsNationalityInclusion,
1771
+ };
1197
1772
  uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10);
1198
1773
  }
1199
1774
  else if (proof.name === "inclusion_check_issuing_country") {
@@ -1207,27 +1782,24 @@ export class ZKPassport {
1207
1782
  message: "Failed to check the link between the validity of the ID and the issuing country inclusion check",
1208
1783
  };
1209
1784
  }
1210
- const countryList = getCountryListFromInclusionProof(proofData);
1211
- if (queryResult.issuing_country &&
1212
- queryResult.issuing_country.in &&
1213
- queryResult.issuing_country.in.result) {
1214
- if (!queryResult.issuing_country.in.expected?.every((country) => countryList.includes(country))) {
1215
- console.warn("Issuing country inclusion list does not match the one from the query results");
1216
- isCorrect = false;
1217
- queryResultErrors.issuing_country.in = {
1218
- expected: queryResult.issuing_country.in.expected,
1219
- received: countryList,
1220
- message: "Issuing country inclusion list does not match the one from the query results",
1221
- };
1222
- }
1223
- }
1224
- else if (!queryResult.issuing_country || !queryResult.issuing_country.in) {
1225
- console.warn("Issuing country inclusion is not set in the query result");
1785
+ const countryList = (proof.committedInputs?.inclusion_check_issuing_country).countries;
1786
+ const paramCommittment = getParameterCommitmentFromDisclosureProof(proofData);
1787
+ const calculatedParamCommitment = await getCountryParameterCommitment(ProofType.ISSUING_COUNTRY_INCLUSION, countryList, false);
1788
+ if (paramCommittment !== calculatedParamCommitment) {
1789
+ console.warn("The committed country list for the issuing country inclusion check does not match the one from the proof");
1226
1790
  isCorrect = false;
1227
- queryResultErrors.issuing_country.in = {
1228
- message: "Issuing country inclusion is not set in the query result",
1791
+ queryResultErrors.issuing_country.commitment = {
1792
+ expected: `Commitment: ${calculatedParamCommitment}`,
1793
+ received: `Commitment: ${paramCommittment}`,
1794
+ message: "The committed country list for the issuing country inclusion check does not match the one from the proof",
1229
1795
  };
1230
1796
  }
1797
+ const { isCorrect: isCorrectIssuingCountryInclusion, queryResultErrors: queryResultErrorsIssuingCountryInclusion, } = this.checkIssuingCountryInclusionPublicInputs(queryResult, countryList);
1798
+ isCorrect = isCorrect && isCorrectIssuingCountryInclusion;
1799
+ queryResultErrors = {
1800
+ ...queryResultErrors,
1801
+ ...queryResultErrorsIssuingCountryInclusion,
1802
+ };
1231
1803
  uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10);
1232
1804
  }
1233
1805
  }
@@ -1262,15 +1834,51 @@ export class ZKPassport {
1262
1834
  // Only proceed with the proof verification if the public inputs are correct
1263
1835
  if (verified) {
1264
1836
  for (const proof of proofs) {
1265
- const proofData = getProofData(proof.proof, true);
1837
+ const proofData = getProofData(proof.proof, getNumberOfPublicInputs(proof.name));
1266
1838
  const hostedPackagedCircuit = await getHostedPackagedCircuitByName(proof.version, proof.name);
1267
- const vkeyBytes = Buffer.from(hostedPackagedCircuit.vkey, "base64");
1268
- try {
1269
- verified = await verifier.verifyUltraHonkProof(proofData, new Uint8Array(vkeyBytes));
1839
+ if (proof.name?.startsWith("outer_evm")) {
1840
+ try {
1841
+ const { createPublicClient, http } = await import("viem");
1842
+ const { sepolia } = await import("viem/chains");
1843
+ const verifierDetails = this.getSolidityVerifierDetails("ethereum_sepolia");
1844
+ const client = createPublicClient({
1845
+ chain: sepolia,
1846
+ transport: http("https://ethereum-sepolia-rpc.publicnode.com"),
1847
+ });
1848
+ const params = this.getSolidityVerifierParameters(proof);
1849
+ const result = await client.readContract({
1850
+ address: verifierDetails.address,
1851
+ abi: verifierDetails.abi,
1852
+ functionName: "verifyProof",
1853
+ args: [
1854
+ params.vkeyHash,
1855
+ params.proof,
1856
+ params.publicInputs,
1857
+ params.committedInputs,
1858
+ params.committedInputCounts,
1859
+ params.validityPeriodInDays,
1860
+ ],
1861
+ });
1862
+ const isVerified = Array.isArray(result) ? Boolean(result[0]) : false;
1863
+ verified = isVerified;
1864
+ }
1865
+ catch (error) {
1866
+ console.warn("Error verifying proof", error);
1867
+ verified = false;
1868
+ }
1270
1869
  }
1271
- catch (e) {
1272
- console.warn("Error verifying proof", e);
1273
- verified = false;
1870
+ else {
1871
+ const vkeyBytes = Buffer.from(hostedPackagedCircuit.vkey, "base64");
1872
+ try {
1873
+ verified = await verifier.verifyUltraHonkProof({
1874
+ proof: Buffer.from(proofData.proof.join(""), "hex"),
1875
+ publicInputs: proofData.publicInputs,
1876
+ }, new Uint8Array(vkeyBytes));
1877
+ }
1878
+ catch (e) {
1879
+ console.warn("Error verifying proof", e);
1880
+ verified = false;
1881
+ }
1274
1882
  }
1275
1883
  if (!verified) {
1276
1884
  // Break the loop if the proof is not valid
@@ -1283,6 +1891,150 @@ export class ZKPassport {
1283
1891
  uniqueIdentifier = verified ? uniqueIdentifier : undefined;
1284
1892
  return { uniqueIdentifier, verified, queryResultErrors };
1285
1893
  }
1894
+ getSolidityVerifierDetails(network) {
1895
+ if (network === "ethereum_sepolia") {
1896
+ return {
1897
+ address: "0xca644D3424c2ee577FaaF2b56C0f9D1937E8e87C",
1898
+ abi: ZKPassportVerifierAbi.abi,
1899
+ };
1900
+ }
1901
+ else if (network === "local_anvil") {
1902
+ return {
1903
+ address: "0x0",
1904
+ abi: ZKPassportVerifierAbi.abi,
1905
+ };
1906
+ }
1907
+ throw new Error(`Unsupported network: ${network}`);
1908
+ }
1909
+ getSolidityVerifierParameters(proof, validityPeriodInDays = 7) {
1910
+ if (!proof.name?.startsWith("outer_evm")) {
1911
+ throw new Error("This proof cannot be verified on an EVM chain. Please make sure to use the `compressed-evm` mode.");
1912
+ }
1913
+ const proofData = getProofData(proof.proof, getNumberOfPublicInputs(proof.name));
1914
+ // For EVM optimised proofs, the first 16 bytes of the proof are the aggregation object
1915
+ // and should be moved at the end of the public inputs
1916
+ const actualProof = proofData.proof.slice(16);
1917
+ const actualPublicInputs = proofData.publicInputs.concat(proofData.proof.slice(0, 16).map((x) => `0x${x}`));
1918
+ let committedInputCounts = [];
1919
+ let committedInputs = [];
1920
+ for (const key in proof.committedInputs) {
1921
+ const committedInputCount = getCommittedInputCount(key);
1922
+ const circuitName = key;
1923
+ committedInputCounts.push({ circuitName, count: committedInputCount });
1924
+ let compressedCommittedInputs = "";
1925
+ if (circuitName === "inclusion_check_issuing_country_evm" ||
1926
+ circuitName === "inclusion_check_nationality_evm" ||
1927
+ circuitName === "exclusion_check_issuing_country_evm" ||
1928
+ circuitName === "exclusion_check_nationality_evm") {
1929
+ const value = proof.committedInputs[circuitName];
1930
+ const formattedCountries = value.countries;
1931
+ if (circuitName === "exclusion_check_issuing_country_evm" ||
1932
+ circuitName === "exclusion_check_nationality_evm") {
1933
+ formattedCountries.sort((a, b) => a.localeCompare(b));
1934
+ }
1935
+ const proofType = (() => {
1936
+ switch (circuitName) {
1937
+ case "exclusion_check_issuing_country_evm":
1938
+ return ProofType.ISSUING_COUNTRY_EXCLUSION;
1939
+ case "exclusion_check_nationality_evm":
1940
+ return ProofType.NATIONALITY_EXCLUSION;
1941
+ case "inclusion_check_issuing_country_evm":
1942
+ return ProofType.ISSUING_COUNTRY_INCLUSION;
1943
+ case "inclusion_check_nationality_evm":
1944
+ return ProofType.NATIONALITY_INCLUSION;
1945
+ }
1946
+ })();
1947
+ compressedCommittedInputs =
1948
+ proofType.toString(16).padStart(2, "0") +
1949
+ rightPadArrayWithZeros(formattedCountries.map((c) => Array.from(new TextEncoder().encode(c))).flat(), 600)
1950
+ .map((x) => x.toString(16).padStart(2, "0"))
1951
+ .join("");
1952
+ }
1953
+ else if (circuitName === "compare_age_evm") {
1954
+ const value = proof.committedInputs[circuitName];
1955
+ const currentDateBytes = Array.from(new TextEncoder().encode(value.currentDate));
1956
+ compressedCommittedInputs =
1957
+ ProofType.AGE.toString(16).padStart(2, "0") +
1958
+ currentDateBytes.map((x) => x.toString(16).padStart(2, "0")).join("") +
1959
+ value.minAge.toString(16).padStart(2, "0") +
1960
+ value.maxAge.toString(16).padStart(2, "0");
1961
+ }
1962
+ else if (circuitName === "compare_birthdate_evm") {
1963
+ const value = proof.committedInputs[circuitName];
1964
+ const currentDateBytes = Array.from(new TextEncoder().encode(value.currentDate));
1965
+ const minDateBytes = Array.from(new TextEncoder().encode(value.minDate));
1966
+ const maxDateBytes = Array.from(new TextEncoder().encode(value.maxDate));
1967
+ compressedCommittedInputs =
1968
+ ProofType.BIRTHDATE.toString(16).padStart(2, "0") +
1969
+ currentDateBytes.map((x) => x.toString(16).padStart(2, "0")).join("") +
1970
+ minDateBytes.map((x) => x.toString(16).padStart(2, "0")).join("") +
1971
+ maxDateBytes.map((x) => x.toString(16).padStart(2, "0")).join("");
1972
+ }
1973
+ else if (circuitName === "compare_expiry_evm") {
1974
+ const value = proof.committedInputs[circuitName];
1975
+ const currentDateBytes = Array.from(new TextEncoder().encode(value.currentDate));
1976
+ const minDateBytes = Array.from(new TextEncoder().encode(value.minDate));
1977
+ const maxDateBytes = Array.from(new TextEncoder().encode(value.maxDate));
1978
+ compressedCommittedInputs =
1979
+ ProofType.EXPIRY_DATE.toString(16).padStart(2, "0") +
1980
+ currentDateBytes.map((x) => x.toString(16).padStart(2, "0")).join("") +
1981
+ minDateBytes.map((x) => x.toString(16).padStart(2, "0")).join("") +
1982
+ maxDateBytes.map((x) => x.toString(16).padStart(2, "0")).join("");
1983
+ }
1984
+ else if (circuitName === "disclose_bytes_evm") {
1985
+ const value = proof.committedInputs[circuitName];
1986
+ compressedCommittedInputs =
1987
+ ProofType.DISCLOSE.toString(16).padStart(2, "0") +
1988
+ value.discloseMask.map((x) => x.toString(16).padStart(2, "0")).join("") +
1989
+ value.disclosedBytes.map((x) => x.toString(16).padStart(2, "0")).join("");
1990
+ }
1991
+ else {
1992
+ throw new Error(`Unsupported circuit for EVM verification: ${circuitName}`);
1993
+ }
1994
+ committedInputs.push({ circuitName, inputs: compressedCommittedInputs });
1995
+ }
1996
+ const parameterCommitments = proofData.publicInputs.slice(11, proofData.publicInputs.length - 1);
1997
+ let compressedCommittedInputs = "";
1998
+ let committedInputCountsArray = [];
1999
+ for (const commitment of parameterCommitments) {
2000
+ const committedInput = committedInputs.find((x) => {
2001
+ const rawHashedInputs = sha256(hexToBytes(x.inputs));
2002
+ // Shift the hash 8 bits to the right (1 byte)
2003
+ // as one byte is dropped in the circuit to fit in the 254-bit field size
2004
+ const hashedInputs = new Uint8Array(rawHashedInputs.length);
2005
+ // Move each byte 1 position to the right (shifting 8 bits)
2006
+ for (let i = 0; i < rawHashedInputs.length - 1; i++) {
2007
+ hashedInputs[i + 1] = rawHashedInputs[i];
2008
+ }
2009
+ // First byte becomes 0 (since we're shifting right)
2010
+ hashedInputs[0] = 0;
2011
+ return bytesToHex(hashedInputs) === commitment.replace("0x", "");
2012
+ });
2013
+ if (committedInput) {
2014
+ const count = committedInputCounts.find((x) => x.circuitName === committedInput.circuitName)?.count;
2015
+ if (count) {
2016
+ committedInputCountsArray.push(count);
2017
+ compressedCommittedInputs += committedInput.inputs;
2018
+ }
2019
+ else {
2020
+ throw new Error(`Unknown circuit name: ${committedInput.circuitName}`);
2021
+ }
2022
+ }
2023
+ else {
2024
+ throw new Error(`Invalid commitment: ${commitment}`);
2025
+ }
2026
+ }
2027
+ const params = {
2028
+ // Make sure the vkeyHash is 32 bytes
2029
+ vkeyHash: `0x${proof.vkeyHash.replace("0x", "").padStart(64, "0")}`,
2030
+ proof: `0x${actualProof.join("")}`,
2031
+ publicInputs: actualPublicInputs,
2032
+ committedInputs: `0x${compressedCommittedInputs}`,
2033
+ committedInputCounts: committedInputCountsArray,
2034
+ validityPeriodInDays,
2035
+ };
2036
+ return params;
2037
+ }
1286
2038
  /**
1287
2039
  * @notice Returns the URL of the request.
1288
2040
  * @param requestId The request ID.
@@ -1292,7 +2044,7 @@ export class ZKPassport {
1292
2044
  const pubkey = bytesToHex(this.topicToKeyPair[requestId].publicKey);
1293
2045
  const base64Config = Buffer.from(JSON.stringify(this.topicToConfig[requestId])).toString("base64");
1294
2046
  const base64Service = Buffer.from(JSON.stringify(this.topicToService[requestId])).toString("base64");
1295
- return `https://zkpassport.id/r?d=${this.domain}&t=${requestId}&c=${base64Config}&s=${base64Service}&p=${pubkey}`;
2047
+ return `https://zkpassport.id/r?d=${this.domain}&t=${requestId}&c=${base64Config}&s=${base64Service}&p=${pubkey}&m=${this.topicToLocalConfig[requestId].mode}`;
1296
2048
  }
1297
2049
  /**
1298
2050
  * @notice Cancels a request by closing the WebSocket connection and deleting the associated data.