@zkpassport/sdk 0.2.3 → 0.2.5

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.
@@ -134,6 +134,8 @@ export declare class ZKPassport {
134
134
  private topicToRequestReceived;
135
135
  private topicToService;
136
136
  private topicToProofs;
137
+ private topicToExpectedProofCount;
138
+ private topicToResults;
137
139
  private onRequestReceivedCallbacks;
138
140
  private onGeneratingProofCallbacks;
139
141
  private onBridgeConnectCallbacks;
@@ -142,6 +144,8 @@ export declare class ZKPassport {
142
144
  private onRejectCallbacks;
143
145
  private onErrorCallbacks;
144
146
  constructor(_domain?: string);
147
+ private handleResult;
148
+ private setExpectedProofCount;
145
149
  /**
146
150
  * @notice Handle an encrypted message.
147
151
  * @param request The request.
package/dist/cjs/index.js CHANGED
@@ -1,29 +1,7 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
2
  Object.defineProperty(exports, "__esModule", { value: true });
26
3
  exports.ZKPassport = exports.MERCOSUR_COUNTRIES = exports.ASEAN_COUNTRIES = exports.SCHENGEN_COUNTRIES = exports.EEA_COUNTRIES = exports.EU_COUNTRIES = exports.SANCTIONED_COUNTRIES = void 0;
4
+ const tslib_1 = require("tslib");
27
5
  const crypto_1 = require("crypto");
28
6
  const i18n_iso_countries_1 = require("i18n-iso-countries");
29
7
  const utils_1 = require("@zkpassport/utils");
@@ -35,7 +13,22 @@ const logger_1 = require("./logger");
35
13
  const node_gzip_1 = require("node-gzip");
36
14
  //import initNoirC from '@noir-lang/noirc_abi'
37
15
  //import initACVM from '@noir-lang/acvm_js'
38
- (0, i18n_iso_countries_1.registerLocale)(require("i18n-iso-countries/langs/en.json"));
16
+ const en_json_1 = tslib_1.__importDefault(require("i18n-iso-countries/langs/en.json"));
17
+ (0, i18n_iso_countries_1.registerLocale)(en_json_1.default);
18
+ function hasRequestedAccessToField(credentialsRequest, field) {
19
+ const fieldValue = credentialsRequest[field];
20
+ const isDefined = fieldValue !== undefined && fieldValue !== null;
21
+ if (!isDefined) {
22
+ return false;
23
+ }
24
+ for (const key in fieldValue) {
25
+ if (fieldValue[key] !== undefined &&
26
+ fieldValue[key] !== null) {
27
+ return true;
28
+ }
29
+ }
30
+ return false;
31
+ }
39
32
  function normalizeCountry(country) {
40
33
  let normalizedCountry;
41
34
  const alpha3 = (0, i18n_iso_countries_1.getAlpha3Code)(country, "en");
@@ -77,6 +70,8 @@ class ZKPassport {
77
70
  this.topicToRequestReceived = {};
78
71
  this.topicToService = {};
79
72
  this.topicToProofs = {};
73
+ this.topicToExpectedProofCount = {};
74
+ this.topicToResults = {};
80
75
  this.onRequestReceivedCallbacks = {};
81
76
  this.onGeneratingProofCallbacks = {};
82
77
  this.onBridgeConnectCallbacks = {};
@@ -95,6 +90,70 @@ class ZKPassport {
95
90
  await Promise.all([initACVM(acvm), initNoirC(noirc)])
96
91
  this.wasmVerifierInit = true
97
92
  }*/
93
+ async handleResult(topic) {
94
+ const result = this.topicToResults[topic];
95
+ // Clear the results straight away to avoid concurrency issues
96
+ delete this.topicToResults[topic];
97
+ // Verify the proofs and extract the unique identifier (aka nullifier) and the verification result
98
+ const { uniqueIdentifier, verified } = await this.verify(topic, this.topicToProofs[topic], result);
99
+ await Promise.all(this.onResultCallbacks[topic].map((callback) => callback({
100
+ uniqueIdentifier,
101
+ verified,
102
+ result,
103
+ })));
104
+ // Clear the expected proof count
105
+ delete this.topicToExpectedProofCount[topic];
106
+ }
107
+ setExpectedProofCount(topic) {
108
+ const fields = Object.keys(this.topicToConfig[topic]).filter((key) => hasRequestedAccessToField(this.topicToConfig[topic], key));
109
+ const neededCircuits = [];
110
+ // Determine which circuits are needed based on the requested fields
111
+ for (const field of fields) {
112
+ for (const key in this.topicToConfig[topic][field]) {
113
+ switch (key) {
114
+ case "eq":
115
+ case "disclose":
116
+ if (field !== "age" && !neededCircuits.includes("disclose_bytes")) {
117
+ neededCircuits.push("disclose_bytes");
118
+ }
119
+ else if (field === "age" && !neededCircuits.includes("compare_age")) {
120
+ neededCircuits.push("compare_age");
121
+ }
122
+ break;
123
+ case "gte":
124
+ case "gt":
125
+ case "lte":
126
+ case "lt":
127
+ case "range":
128
+ if (field === "age" && !neededCircuits.includes("compare_age")) {
129
+ neededCircuits.push("compare_age");
130
+ }
131
+ else if (field === "expiry_date" && !neededCircuits.includes("compare_expiry")) {
132
+ neededCircuits.push("compare_expiry");
133
+ }
134
+ else if (field === "birthdate" && !neededCircuits.includes("compare_birthdate")) {
135
+ neededCircuits.push("compare_birthdate");
136
+ }
137
+ break;
138
+ case "in":
139
+ if (field === "nationality" && !neededCircuits.includes("inclusion_check_country")) {
140
+ neededCircuits.push("inclusion_check_country");
141
+ }
142
+ break;
143
+ case "out":
144
+ if (field === "nationality" && !neededCircuits.includes("exclusion_check_country")) {
145
+ neededCircuits.push("exclusion_check_country");
146
+ }
147
+ break;
148
+ }
149
+ }
150
+ }
151
+ // From the circuits needed, determine the expected proof count
152
+ // There are at least 4 proofs, 3 base proofs and 1 disclosure proof minimum
153
+ // Each separate needed circuit adds 1 disclosure proof
154
+ this.topicToExpectedProofCount[topic] =
155
+ neededCircuits.length === 0 ? 4 : 3 + neededCircuits.length;
156
+ }
98
157
  /**
99
158
  * @notice Handle an encrypted message.
100
159
  * @param request The request.
@@ -127,19 +186,44 @@ class ZKPassport {
127
186
  };
128
187
  this.topicToProofs[topic].push(processedProof);
129
188
  await Promise.all(this.onProofGeneratedCallbacks[topic].map((callback) => callback(processedProof)));
189
+ // If the results were received before all the proofs were generated,
190
+ // we can handle the result now
191
+ if (this.topicToResults[topic] &&
192
+ this.topicToExpectedProofCount[topic] === this.topicToProofs[topic].length) {
193
+ await this.handleResult(topic);
194
+ }
130
195
  }
131
196
  else if (request.method === "done") {
132
197
  logger_1.noLogger.debug(`User sent the query result`);
133
- // Verify the proofs and extract the unique identifier (aka nullifier) and the verification result
134
- const { uniqueIdentifier, verified } = await this.verify(topic, this.topicToProofs[topic], request.params);
135
- await Promise.all(this.onResultCallbacks[topic].map((callback) => callback({
136
- uniqueIdentifier,
137
- verified,
138
- result: request.params,
139
- })));
198
+ this.topicToResults[topic] = request.params;
199
+ // Make sure all the proofs have been received, otherwise we'll handle the result later
200
+ // once the proofs have all been received
201
+ if (this.topicToExpectedProofCount[topic] === this.topicToProofs[topic].length) {
202
+ await this.handleResult(topic);
203
+ }
140
204
  }
141
205
  else if (request.method === "error") {
142
- await Promise.all(this.onErrorCallbacks[topic].map((callback) => callback(request.params.error)));
206
+ const error = request.params.error;
207
+ if (error && error === "This ID is not supported yet") {
208
+ // This means the user has an ID that is not supported yet
209
+ // So we won't receive any proofs and we can handle the result now
210
+ this.topicToExpectedProofCount[topic] = 0;
211
+ if (this.topicToResults[topic]) {
212
+ await this.handleResult(topic);
213
+ }
214
+ }
215
+ else if (error && error.startsWith("Cannot generate proof")) {
216
+ // This means one of the disclosure proofs failed to be generated
217
+ // So we need to remove one from the expected proof count
218
+ this.topicToExpectedProofCount[topic] -= 1;
219
+ // If the expected proof count is now equal to the number of proofs received
220
+ // and the results were received, we can handle the result now
221
+ if (this.topicToResults[topic] &&
222
+ this.topicToExpectedProofCount[topic] === this.topicToProofs[topic].length) {
223
+ await this.handleResult(topic);
224
+ }
225
+ }
226
+ await Promise.all(this.onErrorCallbacks[topic].map((callback) => callback(error)));
143
227
  }
144
228
  }
145
229
  getZkPassportRequest(topic) {
@@ -195,6 +279,7 @@ class ZKPassport {
195
279
  const base64Config = Buffer.from(JSON.stringify(this.topicToConfig[topic])).toString("base64");
196
280
  const base64Service = Buffer.from(JSON.stringify(this.topicToService[topic])).toString("base64");
197
281
  const pubkey = (0, utils_2.bytesToHex)(this.topicToKeyPair[topic].publicKey);
282
+ this.setExpectedProofCount(topic);
198
283
  return {
199
284
  url: `https://zkpassport.id/r?d=${this.domain}&t=${topic}&c=${base64Config}&s=${base64Service}&p=${pubkey}`,
200
285
  requestId: topic,
@@ -225,6 +310,7 @@ class ZKPassport {
225
310
  this.topicToConfig[topic] = {};
226
311
  this.topicToService[topic] = { name, logo, purpose, scope };
227
312
  this.topicToProofs[topic] = [];
313
+ this.topicToExpectedProofCount[topic] = 0;
228
314
  this.onRequestReceivedCallbacks[topic] = [];
229
315
  this.onGeneratingProofCallbacks[topic] = [];
230
316
  this.onBridgeConnectCallbacks[topic] = [];
@@ -766,6 +852,16 @@ class ZKPassport {
766
852
  isCorrect = false;
767
853
  break;
768
854
  }
855
+ // Check the countryList is in ascending order
856
+ // If the prover doesn't use a sorted list then the proof cannot be trusted
857
+ // as it is requirement in the circuit for the exclusion check to work
858
+ for (let i = 1; i < countryList.length; i++) {
859
+ if (countryList[i] < countryList[i - 1]) {
860
+ console.warn("The nationality exclusion list has not been sorted, and thus the proof cannot be trusted");
861
+ isCorrect = false;
862
+ break;
863
+ }
864
+ }
769
865
  uniqueIdentifier = (0, utils_1.getNullifierFromDisclosureProof)(proofData).toString(10);
770
866
  }
771
867
  else if (proof.name === "inclusion_check_country") {
@@ -810,12 +906,12 @@ class ZKPassport {
810
906
  proofsToVerify = this.topicToProofs[requestId];
811
907
  if (!proofsToVerify || proofsToVerify.length < 4) {
812
908
  // It may happen that a request returns a result without proofs
813
- // Meaning the ID is supported yet by ZKPassport circuits,
909
+ // Meaning the ID is not supported yet by ZKPassport circuits,
814
910
  // so the results has to be trusted and cannot be independently verified
815
911
  return { uniqueIdentifier: undefined, verified: false };
816
912
  }
817
913
  }
818
- const { BarretenbergVerifier } = await Promise.resolve().then(() => __importStar(require("@aztec/bb.js")));
914
+ const { BarretenbergVerifier } = await Promise.resolve().then(() => tslib_1.__importStar(require("@aztec/bb.js")));
819
915
  const verifier = new BarretenbergVerifier();
820
916
  /*if (!this.wasmVerifierInit) {
821
917
  await this.initWasmVerifier()
@@ -872,6 +968,8 @@ class ZKPassport {
872
968
  delete this.topicToConfig[requestId];
873
969
  delete this.topicToSharedSecret[requestId];
874
970
  delete this.topicToProofs[requestId];
971
+ delete this.topicToExpectedProofCount[requestId];
972
+ delete this.topicToResults[requestId];
875
973
  this.onRequestReceivedCallbacks[requestId] = [];
876
974
  this.onGeneratingProofCallbacks[requestId] = [];
877
975
  this.onBridgeConnectCallbacks[requestId] = [];
@@ -134,6 +134,8 @@ export declare class ZKPassport {
134
134
  private topicToRequestReceived;
135
135
  private topicToService;
136
136
  private topicToProofs;
137
+ private topicToExpectedProofCount;
138
+ private topicToResults;
137
139
  private onRequestReceivedCallbacks;
138
140
  private onGeneratingProofCallbacks;
139
141
  private onBridgeConnectCallbacks;
@@ -142,6 +144,8 @@ export declare class ZKPassport {
142
144
  private onRejectCallbacks;
143
145
  private onErrorCallbacks;
144
146
  constructor(_domain?: string);
147
+ private handleResult;
148
+ private setExpectedProofCount;
145
149
  /**
146
150
  * @notice Handle an encrypted message.
147
151
  * @param request The request.
package/dist/esm/index.js CHANGED
@@ -9,7 +9,22 @@ import { noLogger as logger } from "./logger";
9
9
  import { ungzip } from "node-gzip";
10
10
  //import initNoirC from '@noir-lang/noirc_abi'
11
11
  //import initACVM from '@noir-lang/acvm_js'
12
- registerLocale(require("i18n-iso-countries/langs/en.json"));
12
+ import i18en from "i18n-iso-countries/langs/en.json";
13
+ registerLocale(i18en);
14
+ function hasRequestedAccessToField(credentialsRequest, field) {
15
+ const fieldValue = credentialsRequest[field];
16
+ const isDefined = fieldValue !== undefined && fieldValue !== null;
17
+ if (!isDefined) {
18
+ return false;
19
+ }
20
+ for (const key in fieldValue) {
21
+ if (fieldValue[key] !== undefined &&
22
+ fieldValue[key] !== null) {
23
+ return true;
24
+ }
25
+ }
26
+ return false;
27
+ }
13
28
  function normalizeCountry(country) {
14
29
  let normalizedCountry;
15
30
  const alpha3 = getAlpha3Code(country, "en");
@@ -45,6 +60,8 @@ export class ZKPassport {
45
60
  this.topicToRequestReceived = {};
46
61
  this.topicToService = {};
47
62
  this.topicToProofs = {};
63
+ this.topicToExpectedProofCount = {};
64
+ this.topicToResults = {};
48
65
  this.onRequestReceivedCallbacks = {};
49
66
  this.onGeneratingProofCallbacks = {};
50
67
  this.onBridgeConnectCallbacks = {};
@@ -63,6 +80,70 @@ export class ZKPassport {
63
80
  await Promise.all([initACVM(acvm), initNoirC(noirc)])
64
81
  this.wasmVerifierInit = true
65
82
  }*/
83
+ async handleResult(topic) {
84
+ const result = this.topicToResults[topic];
85
+ // Clear the results straight away to avoid concurrency issues
86
+ delete this.topicToResults[topic];
87
+ // Verify the proofs and extract the unique identifier (aka nullifier) and the verification result
88
+ const { uniqueIdentifier, verified } = await this.verify(topic, this.topicToProofs[topic], result);
89
+ await Promise.all(this.onResultCallbacks[topic].map((callback) => callback({
90
+ uniqueIdentifier,
91
+ verified,
92
+ result,
93
+ })));
94
+ // Clear the expected proof count
95
+ delete this.topicToExpectedProofCount[topic];
96
+ }
97
+ setExpectedProofCount(topic) {
98
+ const fields = Object.keys(this.topicToConfig[topic]).filter((key) => hasRequestedAccessToField(this.topicToConfig[topic], key));
99
+ const neededCircuits = [];
100
+ // Determine which circuits are needed based on the requested fields
101
+ for (const field of fields) {
102
+ for (const key in this.topicToConfig[topic][field]) {
103
+ switch (key) {
104
+ case "eq":
105
+ case "disclose":
106
+ if (field !== "age" && !neededCircuits.includes("disclose_bytes")) {
107
+ neededCircuits.push("disclose_bytes");
108
+ }
109
+ else if (field === "age" && !neededCircuits.includes("compare_age")) {
110
+ neededCircuits.push("compare_age");
111
+ }
112
+ break;
113
+ case "gte":
114
+ case "gt":
115
+ case "lte":
116
+ case "lt":
117
+ case "range":
118
+ if (field === "age" && !neededCircuits.includes("compare_age")) {
119
+ neededCircuits.push("compare_age");
120
+ }
121
+ else if (field === "expiry_date" && !neededCircuits.includes("compare_expiry")) {
122
+ neededCircuits.push("compare_expiry");
123
+ }
124
+ else if (field === "birthdate" && !neededCircuits.includes("compare_birthdate")) {
125
+ neededCircuits.push("compare_birthdate");
126
+ }
127
+ break;
128
+ case "in":
129
+ if (field === "nationality" && !neededCircuits.includes("inclusion_check_country")) {
130
+ neededCircuits.push("inclusion_check_country");
131
+ }
132
+ break;
133
+ case "out":
134
+ if (field === "nationality" && !neededCircuits.includes("exclusion_check_country")) {
135
+ neededCircuits.push("exclusion_check_country");
136
+ }
137
+ break;
138
+ }
139
+ }
140
+ }
141
+ // From the circuits needed, determine the expected proof count
142
+ // There are at least 4 proofs, 3 base proofs and 1 disclosure proof minimum
143
+ // Each separate needed circuit adds 1 disclosure proof
144
+ this.topicToExpectedProofCount[topic] =
145
+ neededCircuits.length === 0 ? 4 : 3 + neededCircuits.length;
146
+ }
66
147
  /**
67
148
  * @notice Handle an encrypted message.
68
149
  * @param request The request.
@@ -95,19 +176,44 @@ export class ZKPassport {
95
176
  };
96
177
  this.topicToProofs[topic].push(processedProof);
97
178
  await Promise.all(this.onProofGeneratedCallbacks[topic].map((callback) => callback(processedProof)));
179
+ // If the results were received before all the proofs were generated,
180
+ // we can handle the result now
181
+ if (this.topicToResults[topic] &&
182
+ this.topicToExpectedProofCount[topic] === this.topicToProofs[topic].length) {
183
+ await this.handleResult(topic);
184
+ }
98
185
  }
99
186
  else if (request.method === "done") {
100
187
  logger.debug(`User sent the query result`);
101
- // Verify the proofs and extract the unique identifier (aka nullifier) and the verification result
102
- const { uniqueIdentifier, verified } = await this.verify(topic, this.topicToProofs[topic], request.params);
103
- await Promise.all(this.onResultCallbacks[topic].map((callback) => callback({
104
- uniqueIdentifier,
105
- verified,
106
- result: request.params,
107
- })));
188
+ this.topicToResults[topic] = request.params;
189
+ // Make sure all the proofs have been received, otherwise we'll handle the result later
190
+ // once the proofs have all been received
191
+ if (this.topicToExpectedProofCount[topic] === this.topicToProofs[topic].length) {
192
+ await this.handleResult(topic);
193
+ }
108
194
  }
109
195
  else if (request.method === "error") {
110
- await Promise.all(this.onErrorCallbacks[topic].map((callback) => callback(request.params.error)));
196
+ const error = request.params.error;
197
+ if (error && error === "This ID is not supported yet") {
198
+ // This means the user has an ID that is not supported yet
199
+ // So we won't receive any proofs and we can handle the result now
200
+ this.topicToExpectedProofCount[topic] = 0;
201
+ if (this.topicToResults[topic]) {
202
+ await this.handleResult(topic);
203
+ }
204
+ }
205
+ else if (error && error.startsWith("Cannot generate proof")) {
206
+ // This means one of the disclosure proofs failed to be generated
207
+ // So we need to remove one from the expected proof count
208
+ this.topicToExpectedProofCount[topic] -= 1;
209
+ // If the expected proof count is now equal to the number of proofs received
210
+ // and the results were received, we can handle the result now
211
+ if (this.topicToResults[topic] &&
212
+ this.topicToExpectedProofCount[topic] === this.topicToProofs[topic].length) {
213
+ await this.handleResult(topic);
214
+ }
215
+ }
216
+ await Promise.all(this.onErrorCallbacks[topic].map((callback) => callback(error)));
111
217
  }
112
218
  }
113
219
  getZkPassportRequest(topic) {
@@ -163,6 +269,7 @@ export class ZKPassport {
163
269
  const base64Config = Buffer.from(JSON.stringify(this.topicToConfig[topic])).toString("base64");
164
270
  const base64Service = Buffer.from(JSON.stringify(this.topicToService[topic])).toString("base64");
165
271
  const pubkey = bytesToHex(this.topicToKeyPair[topic].publicKey);
272
+ this.setExpectedProofCount(topic);
166
273
  return {
167
274
  url: `https://zkpassport.id/r?d=${this.domain}&t=${topic}&c=${base64Config}&s=${base64Service}&p=${pubkey}`,
168
275
  requestId: topic,
@@ -193,6 +300,7 @@ export class ZKPassport {
193
300
  this.topicToConfig[topic] = {};
194
301
  this.topicToService[topic] = { name, logo, purpose, scope };
195
302
  this.topicToProofs[topic] = [];
303
+ this.topicToExpectedProofCount[topic] = 0;
196
304
  this.onRequestReceivedCallbacks[topic] = [];
197
305
  this.onGeneratingProofCallbacks[topic] = [];
198
306
  this.onBridgeConnectCallbacks[topic] = [];
@@ -734,6 +842,16 @@ export class ZKPassport {
734
842
  isCorrect = false;
735
843
  break;
736
844
  }
845
+ // Check the countryList is in ascending order
846
+ // If the prover doesn't use a sorted list then the proof cannot be trusted
847
+ // as it is requirement in the circuit for the exclusion check to work
848
+ for (let i = 1; i < countryList.length; i++) {
849
+ if (countryList[i] < countryList[i - 1]) {
850
+ console.warn("The nationality exclusion list has not been sorted, and thus the proof cannot be trusted");
851
+ isCorrect = false;
852
+ break;
853
+ }
854
+ }
737
855
  uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10);
738
856
  }
739
857
  else if (proof.name === "inclusion_check_country") {
@@ -778,7 +896,7 @@ export class ZKPassport {
778
896
  proofsToVerify = this.topicToProofs[requestId];
779
897
  if (!proofsToVerify || proofsToVerify.length < 4) {
780
898
  // It may happen that a request returns a result without proofs
781
- // Meaning the ID is supported yet by ZKPassport circuits,
899
+ // Meaning the ID is not supported yet by ZKPassport circuits,
782
900
  // so the results has to be trusted and cannot be independently verified
783
901
  return { uniqueIdentifier: undefined, verified: false };
784
902
  }
@@ -840,6 +958,8 @@ export class ZKPassport {
840
958
  delete this.topicToConfig[requestId];
841
959
  delete this.topicToSharedSecret[requestId];
842
960
  delete this.topicToProofs[requestId];
961
+ delete this.topicToExpectedProofCount[requestId];
962
+ delete this.topicToResults[requestId];
843
963
  this.onRequestReceivedCallbacks[requestId] = [];
844
964
  this.onGeneratingProofCallbacks[requestId] = [];
845
965
  this.onBridgeConnectCallbacks[requestId] = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zkpassport/sdk",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Privacy-preserving identity verification using passports and ID cards",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/esm/index.js",
package/src/index.ts CHANGED
@@ -30,6 +30,7 @@ import {
30
30
  DisclosedData,
31
31
  formatName,
32
32
  getHostedPackagedCircuitByName,
33
+ Query,
33
34
  } from "@zkpassport/utils"
34
35
  import { bytesToHex } from "@noble/ciphers/utils"
35
36
  import { getWebSocketClient, WebSocketClient } from "./websocket"
@@ -39,8 +40,26 @@ import { noLogger as logger } from "./logger"
39
40
  import { ungzip } from "node-gzip"
40
41
  //import initNoirC from '@noir-lang/noirc_abi'
41
42
  //import initACVM from '@noir-lang/acvm_js'
43
+ import i18en from "i18n-iso-countries/langs/en.json"
42
44
 
43
- registerLocale(require("i18n-iso-countries/langs/en.json"))
45
+ registerLocale(i18en)
46
+
47
+ function hasRequestedAccessToField(credentialsRequest: Query, field: IDCredential): boolean {
48
+ const fieldValue = credentialsRequest[field as keyof Query]
49
+ const isDefined = fieldValue !== undefined && fieldValue !== null
50
+ if (!isDefined) {
51
+ return false
52
+ }
53
+ for (const key in fieldValue) {
54
+ if (
55
+ fieldValue[key as keyof typeof fieldValue] !== undefined &&
56
+ fieldValue[key as keyof typeof fieldValue] !== null
57
+ ) {
58
+ return true
59
+ }
60
+ }
61
+ return false
62
+ }
44
63
 
45
64
  function normalizeCountry(country: CountryName | Alpha3Code) {
46
65
  let normalizedCountry: Alpha3Code | undefined
@@ -241,6 +260,8 @@ export class ZKPassport {
241
260
  { name: string; logo: string; purpose: string; scope?: string }
242
261
  > = {}
243
262
  private topicToProofs: Record<string, Array<ProofResult>> = {}
263
+ private topicToExpectedProofCount: Record<string, number> = {}
264
+ private topicToResults: Record<string, QueryResult> = {}
244
265
 
245
266
  private onRequestReceivedCallbacks: Record<string, Array<() => void>> = {}
246
267
  private onGeneratingProofCallbacks: Record<string, Array<(topic: string) => void>> = {}
@@ -274,6 +295,79 @@ export class ZKPassport {
274
295
  this.wasmVerifierInit = true
275
296
  }*/
276
297
 
298
+ private async handleResult(topic: string) {
299
+ const result = this.topicToResults[topic]
300
+ // Clear the results straight away to avoid concurrency issues
301
+ delete this.topicToResults[topic]
302
+ // Verify the proofs and extract the unique identifier (aka nullifier) and the verification result
303
+ const { uniqueIdentifier, verified } = await this.verify(
304
+ topic,
305
+ this.topicToProofs[topic],
306
+ result,
307
+ )
308
+ await Promise.all(
309
+ this.onResultCallbacks[topic].map((callback) =>
310
+ callback({
311
+ uniqueIdentifier,
312
+ verified,
313
+ result,
314
+ }),
315
+ ),
316
+ )
317
+ // Clear the expected proof count
318
+ delete this.topicToExpectedProofCount[topic]
319
+ }
320
+
321
+ private setExpectedProofCount(topic: string) {
322
+ const fields = Object.keys(this.topicToConfig[topic] as Query).filter((key) =>
323
+ hasRequestedAccessToField(this.topicToConfig[topic] as Query, key as IDCredential),
324
+ )
325
+ const neededCircuits: string[] = []
326
+ // Determine which circuits are needed based on the requested fields
327
+ for (const field of fields) {
328
+ for (const key in this.topicToConfig[topic][field as IDCredential]) {
329
+ switch (key) {
330
+ case "eq":
331
+ case "disclose":
332
+ if (field !== "age" && !neededCircuits.includes("disclose_bytes")) {
333
+ neededCircuits.push("disclose_bytes")
334
+ } else if (field === "age" && !neededCircuits.includes("compare_age")) {
335
+ neededCircuits.push("compare_age")
336
+ }
337
+ break
338
+ case "gte":
339
+ case "gt":
340
+ case "lte":
341
+ case "lt":
342
+ case "range":
343
+ if (field === "age" && !neededCircuits.includes("compare_age")) {
344
+ neededCircuits.push("compare_age")
345
+ } else if (field === "expiry_date" && !neededCircuits.includes("compare_expiry")) {
346
+ neededCircuits.push("compare_expiry")
347
+ } else if (field === "birthdate" && !neededCircuits.includes("compare_birthdate")) {
348
+ neededCircuits.push("compare_birthdate")
349
+ }
350
+ break
351
+ case "in":
352
+ if (field === "nationality" && !neededCircuits.includes("inclusion_check_country")) {
353
+ neededCircuits.push("inclusion_check_country")
354
+ }
355
+ break
356
+ case "out":
357
+ if (field === "nationality" && !neededCircuits.includes("exclusion_check_country")) {
358
+ neededCircuits.push("exclusion_check_country")
359
+ }
360
+ break
361
+ }
362
+ }
363
+ }
364
+ // From the circuits needed, determine the expected proof count
365
+ // There are at least 4 proofs, 3 base proofs and 1 disclosure proof minimum
366
+ // Each separate needed circuit adds 1 disclosure proof
367
+ this.topicToExpectedProofCount[topic] =
368
+ neededCircuits.length === 0 ? 4 : 3 + neededCircuits.length
369
+ }
370
+
277
371
  /**
278
372
  * @notice Handle an encrypted message.
279
373
  * @param request The request.
@@ -310,27 +404,45 @@ export class ZKPassport {
310
404
  await Promise.all(
311
405
  this.onProofGeneratedCallbacks[topic].map((callback) => callback(processedProof)),
312
406
  )
407
+ // If the results were received before all the proofs were generated,
408
+ // we can handle the result now
409
+ if (
410
+ this.topicToResults[topic] &&
411
+ this.topicToExpectedProofCount[topic] === this.topicToProofs[topic].length
412
+ ) {
413
+ await this.handleResult(topic)
414
+ }
313
415
  } else if (request.method === "done") {
314
416
  logger.debug(`User sent the query result`)
315
- // Verify the proofs and extract the unique identifier (aka nullifier) and the verification result
316
- const { uniqueIdentifier, verified } = await this.verify(
317
- topic,
318
- this.topicToProofs[topic],
319
- request.params,
320
- )
321
- await Promise.all(
322
- this.onResultCallbacks[topic].map((callback) =>
323
- callback({
324
- uniqueIdentifier,
325
- verified,
326
- result: request.params,
327
- }),
328
- ),
329
- )
417
+ this.topicToResults[topic] = request.params
418
+ // Make sure all the proofs have been received, otherwise we'll handle the result later
419
+ // once the proofs have all been received
420
+ if (this.topicToExpectedProofCount[topic] === this.topicToProofs[topic].length) {
421
+ await this.handleResult(topic)
422
+ }
330
423
  } else if (request.method === "error") {
331
- await Promise.all(
332
- this.onErrorCallbacks[topic].map((callback) => callback(request.params.error)),
333
- )
424
+ const error = request.params.error
425
+ if (error && error === "This ID is not supported yet") {
426
+ // This means the user has an ID that is not supported yet
427
+ // So we won't receive any proofs and we can handle the result now
428
+ this.topicToExpectedProofCount[topic] = 0
429
+ if (this.topicToResults[topic]) {
430
+ await this.handleResult(topic)
431
+ }
432
+ } else if (error && error.startsWith("Cannot generate proof")) {
433
+ // This means one of the disclosure proofs failed to be generated
434
+ // So we need to remove one from the expected proof count
435
+ this.topicToExpectedProofCount[topic] -= 1
436
+ // If the expected proof count is now equal to the number of proofs received
437
+ // and the results were received, we can handle the result now
438
+ if (
439
+ this.topicToResults[topic] &&
440
+ this.topicToExpectedProofCount[topic] === this.topicToProofs[topic].length
441
+ ) {
442
+ await this.handleResult(topic)
443
+ }
444
+ }
445
+ await Promise.all(this.onErrorCallbacks[topic].map((callback) => callback(error)))
334
446
  }
335
447
  }
336
448
 
@@ -395,6 +507,7 @@ export class ZKPassport {
395
507
  "base64",
396
508
  )
397
509
  const pubkey = bytesToHex(this.topicToKeyPair[topic].publicKey)
510
+ this.setExpectedProofCount(topic)
398
511
  return {
399
512
  url: `https://zkpassport.id/r?d=${this.domain}&t=${topic}&c=${base64Config}&s=${base64Service}&p=${pubkey}`,
400
513
  requestId: topic,
@@ -453,6 +566,7 @@ export class ZKPassport {
453
566
  this.topicToConfig[topic] = {}
454
567
  this.topicToService[topic] = { name, logo, purpose, scope }
455
568
  this.topicToProofs[topic] = []
569
+ this.topicToExpectedProofCount[topic] = 0
456
570
 
457
571
  this.onRequestReceivedCallbacks[topic] = []
458
572
  this.onGeneratingProofCallbacks[topic] = []
@@ -1107,6 +1221,18 @@ export class ZKPassport {
1107
1221
  isCorrect = false
1108
1222
  break
1109
1223
  }
1224
+ // Check the countryList is in ascending order
1225
+ // If the prover doesn't use a sorted list then the proof cannot be trusted
1226
+ // as it is requirement in the circuit for the exclusion check to work
1227
+ for (let i = 1; i < countryList.length; i++) {
1228
+ if (countryList[i] < countryList[i - 1]) {
1229
+ console.warn(
1230
+ "The nationality exclusion list has not been sorted, and thus the proof cannot be trusted",
1231
+ )
1232
+ isCorrect = false
1233
+ break
1234
+ }
1235
+ }
1110
1236
  uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10)
1111
1237
  } else if (proof.name === "inclusion_check_country") {
1112
1238
  commitmentIn = getCommitmentInFromDisclosureProof(proofData)
@@ -1160,7 +1286,7 @@ export class ZKPassport {
1160
1286
  proofsToVerify = this.topicToProofs[requestId]
1161
1287
  if (!proofsToVerify || proofsToVerify.length < 4) {
1162
1288
  // It may happen that a request returns a result without proofs
1163
- // Meaning the ID is supported yet by ZKPassport circuits,
1289
+ // Meaning the ID is not supported yet by ZKPassport circuits,
1164
1290
  // so the results has to be trusted and cannot be independently verified
1165
1291
  return { uniqueIdentifier: undefined, verified: false }
1166
1292
  }
@@ -1231,6 +1357,8 @@ export class ZKPassport {
1231
1357
  delete this.topicToConfig[requestId]
1232
1358
  delete this.topicToSharedSecret[requestId]
1233
1359
  delete this.topicToProofs[requestId]
1360
+ delete this.topicToExpectedProofCount[requestId]
1361
+ delete this.topicToResults[requestId]
1234
1362
  this.onRequestReceivedCallbacks[requestId] = []
1235
1363
  this.onGeneratingProofCallbacks[requestId] = []
1236
1364
  this.onBridgeConnectCallbacks[requestId] = []