@zkpassport/sdk 0.2.4 → 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.
- package/dist/cjs/index.d.ts +4 -0
- package/dist/cjs/index.js +128 -9
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +128 -9
- package/package.json +1 -1
- package/src/index.ts +146 -19
package/dist/cjs/index.d.ts
CHANGED
|
@@ -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
|
@@ -15,6 +15,20 @@ const node_gzip_1 = require("node-gzip");
|
|
|
15
15
|
//import initACVM from '@noir-lang/acvm_js'
|
|
16
16
|
const en_json_1 = tslib_1.__importDefault(require("i18n-iso-countries/langs/en.json"));
|
|
17
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
|
+
}
|
|
18
32
|
function normalizeCountry(country) {
|
|
19
33
|
let normalizedCountry;
|
|
20
34
|
const alpha3 = (0, i18n_iso_countries_1.getAlpha3Code)(country, "en");
|
|
@@ -56,6 +70,8 @@ class ZKPassport {
|
|
|
56
70
|
this.topicToRequestReceived = {};
|
|
57
71
|
this.topicToService = {};
|
|
58
72
|
this.topicToProofs = {};
|
|
73
|
+
this.topicToExpectedProofCount = {};
|
|
74
|
+
this.topicToResults = {};
|
|
59
75
|
this.onRequestReceivedCallbacks = {};
|
|
60
76
|
this.onGeneratingProofCallbacks = {};
|
|
61
77
|
this.onBridgeConnectCallbacks = {};
|
|
@@ -74,6 +90,70 @@ class ZKPassport {
|
|
|
74
90
|
await Promise.all([initACVM(acvm), initNoirC(noirc)])
|
|
75
91
|
this.wasmVerifierInit = true
|
|
76
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
|
+
}
|
|
77
157
|
/**
|
|
78
158
|
* @notice Handle an encrypted message.
|
|
79
159
|
* @param request The request.
|
|
@@ -106,19 +186,44 @@ class ZKPassport {
|
|
|
106
186
|
};
|
|
107
187
|
this.topicToProofs[topic].push(processedProof);
|
|
108
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
|
+
}
|
|
109
195
|
}
|
|
110
196
|
else if (request.method === "done") {
|
|
111
197
|
logger_1.noLogger.debug(`User sent the query result`);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
})));
|
|
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
|
+
}
|
|
119
204
|
}
|
|
120
205
|
else if (request.method === "error") {
|
|
121
|
-
|
|
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)));
|
|
122
227
|
}
|
|
123
228
|
}
|
|
124
229
|
getZkPassportRequest(topic) {
|
|
@@ -174,6 +279,7 @@ class ZKPassport {
|
|
|
174
279
|
const base64Config = Buffer.from(JSON.stringify(this.topicToConfig[topic])).toString("base64");
|
|
175
280
|
const base64Service = Buffer.from(JSON.stringify(this.topicToService[topic])).toString("base64");
|
|
176
281
|
const pubkey = (0, utils_2.bytesToHex)(this.topicToKeyPair[topic].publicKey);
|
|
282
|
+
this.setExpectedProofCount(topic);
|
|
177
283
|
return {
|
|
178
284
|
url: `https://zkpassport.id/r?d=${this.domain}&t=${topic}&c=${base64Config}&s=${base64Service}&p=${pubkey}`,
|
|
179
285
|
requestId: topic,
|
|
@@ -204,6 +310,7 @@ class ZKPassport {
|
|
|
204
310
|
this.topicToConfig[topic] = {};
|
|
205
311
|
this.topicToService[topic] = { name, logo, purpose, scope };
|
|
206
312
|
this.topicToProofs[topic] = [];
|
|
313
|
+
this.topicToExpectedProofCount[topic] = 0;
|
|
207
314
|
this.onRequestReceivedCallbacks[topic] = [];
|
|
208
315
|
this.onGeneratingProofCallbacks[topic] = [];
|
|
209
316
|
this.onBridgeConnectCallbacks[topic] = [];
|
|
@@ -745,6 +852,16 @@ class ZKPassport {
|
|
|
745
852
|
isCorrect = false;
|
|
746
853
|
break;
|
|
747
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
|
+
}
|
|
748
865
|
uniqueIdentifier = (0, utils_1.getNullifierFromDisclosureProof)(proofData).toString(10);
|
|
749
866
|
}
|
|
750
867
|
else if (proof.name === "inclusion_check_country") {
|
|
@@ -789,7 +906,7 @@ class ZKPassport {
|
|
|
789
906
|
proofsToVerify = this.topicToProofs[requestId];
|
|
790
907
|
if (!proofsToVerify || proofsToVerify.length < 4) {
|
|
791
908
|
// It may happen that a request returns a result without proofs
|
|
792
|
-
// Meaning the ID is supported yet by ZKPassport circuits,
|
|
909
|
+
// Meaning the ID is not supported yet by ZKPassport circuits,
|
|
793
910
|
// so the results has to be trusted and cannot be independently verified
|
|
794
911
|
return { uniqueIdentifier: undefined, verified: false };
|
|
795
912
|
}
|
|
@@ -851,6 +968,8 @@ class ZKPassport {
|
|
|
851
968
|
delete this.topicToConfig[requestId];
|
|
852
969
|
delete this.topicToSharedSecret[requestId];
|
|
853
970
|
delete this.topicToProofs[requestId];
|
|
971
|
+
delete this.topicToExpectedProofCount[requestId];
|
|
972
|
+
delete this.topicToResults[requestId];
|
|
854
973
|
this.onRequestReceivedCallbacks[requestId] = [];
|
|
855
974
|
this.onGeneratingProofCallbacks[requestId] = [];
|
|
856
975
|
this.onBridgeConnectCallbacks[requestId] = [];
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -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
|
@@ -11,6 +11,20 @@ import { ungzip } from "node-gzip";
|
|
|
11
11
|
//import initACVM from '@noir-lang/acvm_js'
|
|
12
12
|
import i18en from "i18n-iso-countries/langs/en.json";
|
|
13
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
|
+
}
|
|
14
28
|
function normalizeCountry(country) {
|
|
15
29
|
let normalizedCountry;
|
|
16
30
|
const alpha3 = getAlpha3Code(country, "en");
|
|
@@ -46,6 +60,8 @@ export class ZKPassport {
|
|
|
46
60
|
this.topicToRequestReceived = {};
|
|
47
61
|
this.topicToService = {};
|
|
48
62
|
this.topicToProofs = {};
|
|
63
|
+
this.topicToExpectedProofCount = {};
|
|
64
|
+
this.topicToResults = {};
|
|
49
65
|
this.onRequestReceivedCallbacks = {};
|
|
50
66
|
this.onGeneratingProofCallbacks = {};
|
|
51
67
|
this.onBridgeConnectCallbacks = {};
|
|
@@ -64,6 +80,70 @@ export class ZKPassport {
|
|
|
64
80
|
await Promise.all([initACVM(acvm), initNoirC(noirc)])
|
|
65
81
|
this.wasmVerifierInit = true
|
|
66
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
|
+
}
|
|
67
147
|
/**
|
|
68
148
|
* @notice Handle an encrypted message.
|
|
69
149
|
* @param request The request.
|
|
@@ -96,19 +176,44 @@ export class ZKPassport {
|
|
|
96
176
|
};
|
|
97
177
|
this.topicToProofs[topic].push(processedProof);
|
|
98
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
|
+
}
|
|
99
185
|
}
|
|
100
186
|
else if (request.method === "done") {
|
|
101
187
|
logger.debug(`User sent the query result`);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
})));
|
|
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
|
+
}
|
|
109
194
|
}
|
|
110
195
|
else if (request.method === "error") {
|
|
111
|
-
|
|
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)));
|
|
112
217
|
}
|
|
113
218
|
}
|
|
114
219
|
getZkPassportRequest(topic) {
|
|
@@ -164,6 +269,7 @@ export class ZKPassport {
|
|
|
164
269
|
const base64Config = Buffer.from(JSON.stringify(this.topicToConfig[topic])).toString("base64");
|
|
165
270
|
const base64Service = Buffer.from(JSON.stringify(this.topicToService[topic])).toString("base64");
|
|
166
271
|
const pubkey = bytesToHex(this.topicToKeyPair[topic].publicKey);
|
|
272
|
+
this.setExpectedProofCount(topic);
|
|
167
273
|
return {
|
|
168
274
|
url: `https://zkpassport.id/r?d=${this.domain}&t=${topic}&c=${base64Config}&s=${base64Service}&p=${pubkey}`,
|
|
169
275
|
requestId: topic,
|
|
@@ -194,6 +300,7 @@ export class ZKPassport {
|
|
|
194
300
|
this.topicToConfig[topic] = {};
|
|
195
301
|
this.topicToService[topic] = { name, logo, purpose, scope };
|
|
196
302
|
this.topicToProofs[topic] = [];
|
|
303
|
+
this.topicToExpectedProofCount[topic] = 0;
|
|
197
304
|
this.onRequestReceivedCallbacks[topic] = [];
|
|
198
305
|
this.onGeneratingProofCallbacks[topic] = [];
|
|
199
306
|
this.onBridgeConnectCallbacks[topic] = [];
|
|
@@ -735,6 +842,16 @@ export class ZKPassport {
|
|
|
735
842
|
isCorrect = false;
|
|
736
843
|
break;
|
|
737
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
|
+
}
|
|
738
855
|
uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10);
|
|
739
856
|
}
|
|
740
857
|
else if (proof.name === "inclusion_check_country") {
|
|
@@ -779,7 +896,7 @@ export class ZKPassport {
|
|
|
779
896
|
proofsToVerify = this.topicToProofs[requestId];
|
|
780
897
|
if (!proofsToVerify || proofsToVerify.length < 4) {
|
|
781
898
|
// It may happen that a request returns a result without proofs
|
|
782
|
-
// Meaning the ID is supported yet by ZKPassport circuits,
|
|
899
|
+
// Meaning the ID is not supported yet by ZKPassport circuits,
|
|
783
900
|
// so the results has to be trusted and cannot be independently verified
|
|
784
901
|
return { uniqueIdentifier: undefined, verified: false };
|
|
785
902
|
}
|
|
@@ -841,6 +958,8 @@ export class ZKPassport {
|
|
|
841
958
|
delete this.topicToConfig[requestId];
|
|
842
959
|
delete this.topicToSharedSecret[requestId];
|
|
843
960
|
delete this.topicToProofs[requestId];
|
|
961
|
+
delete this.topicToExpectedProofCount[requestId];
|
|
962
|
+
delete this.topicToResults[requestId];
|
|
844
963
|
this.onRequestReceivedCallbacks[requestId] = [];
|
|
845
964
|
this.onGeneratingProofCallbacks[requestId] = [];
|
|
846
965
|
this.onBridgeConnectCallbacks[requestId] = [];
|
package/package.json
CHANGED
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"
|
|
@@ -43,6 +44,23 @@ import i18en from "i18n-iso-countries/langs/en.json"
|
|
|
43
44
|
|
|
44
45
|
registerLocale(i18en)
|
|
45
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
|
+
}
|
|
63
|
+
|
|
46
64
|
function normalizeCountry(country: CountryName | Alpha3Code) {
|
|
47
65
|
let normalizedCountry: Alpha3Code | undefined
|
|
48
66
|
const alpha3 = getAlpha3Code(country, "en") as Alpha3Code | undefined
|
|
@@ -242,6 +260,8 @@ export class ZKPassport {
|
|
|
242
260
|
{ name: string; logo: string; purpose: string; scope?: string }
|
|
243
261
|
> = {}
|
|
244
262
|
private topicToProofs: Record<string, Array<ProofResult>> = {}
|
|
263
|
+
private topicToExpectedProofCount: Record<string, number> = {}
|
|
264
|
+
private topicToResults: Record<string, QueryResult> = {}
|
|
245
265
|
|
|
246
266
|
private onRequestReceivedCallbacks: Record<string, Array<() => void>> = {}
|
|
247
267
|
private onGeneratingProofCallbacks: Record<string, Array<(topic: string) => void>> = {}
|
|
@@ -275,6 +295,79 @@ export class ZKPassport {
|
|
|
275
295
|
this.wasmVerifierInit = true
|
|
276
296
|
}*/
|
|
277
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
|
+
|
|
278
371
|
/**
|
|
279
372
|
* @notice Handle an encrypted message.
|
|
280
373
|
* @param request The request.
|
|
@@ -311,27 +404,45 @@ export class ZKPassport {
|
|
|
311
404
|
await Promise.all(
|
|
312
405
|
this.onProofGeneratedCallbacks[topic].map((callback) => callback(processedProof)),
|
|
313
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
|
+
}
|
|
314
415
|
} else if (request.method === "done") {
|
|
315
416
|
logger.debug(`User sent the query result`)
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
await Promise.all(
|
|
323
|
-
this.onResultCallbacks[topic].map((callback) =>
|
|
324
|
-
callback({
|
|
325
|
-
uniqueIdentifier,
|
|
326
|
-
verified,
|
|
327
|
-
result: request.params,
|
|
328
|
-
}),
|
|
329
|
-
),
|
|
330
|
-
)
|
|
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
|
+
}
|
|
331
423
|
} else if (request.method === "error") {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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)))
|
|
335
446
|
}
|
|
336
447
|
}
|
|
337
448
|
|
|
@@ -396,6 +507,7 @@ export class ZKPassport {
|
|
|
396
507
|
"base64",
|
|
397
508
|
)
|
|
398
509
|
const pubkey = bytesToHex(this.topicToKeyPair[topic].publicKey)
|
|
510
|
+
this.setExpectedProofCount(topic)
|
|
399
511
|
return {
|
|
400
512
|
url: `https://zkpassport.id/r?d=${this.domain}&t=${topic}&c=${base64Config}&s=${base64Service}&p=${pubkey}`,
|
|
401
513
|
requestId: topic,
|
|
@@ -454,6 +566,7 @@ export class ZKPassport {
|
|
|
454
566
|
this.topicToConfig[topic] = {}
|
|
455
567
|
this.topicToService[topic] = { name, logo, purpose, scope }
|
|
456
568
|
this.topicToProofs[topic] = []
|
|
569
|
+
this.topicToExpectedProofCount[topic] = 0
|
|
457
570
|
|
|
458
571
|
this.onRequestReceivedCallbacks[topic] = []
|
|
459
572
|
this.onGeneratingProofCallbacks[topic] = []
|
|
@@ -1108,6 +1221,18 @@ export class ZKPassport {
|
|
|
1108
1221
|
isCorrect = false
|
|
1109
1222
|
break
|
|
1110
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
|
+
}
|
|
1111
1236
|
uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10)
|
|
1112
1237
|
} else if (proof.name === "inclusion_check_country") {
|
|
1113
1238
|
commitmentIn = getCommitmentInFromDisclosureProof(proofData)
|
|
@@ -1161,7 +1286,7 @@ export class ZKPassport {
|
|
|
1161
1286
|
proofsToVerify = this.topicToProofs[requestId]
|
|
1162
1287
|
if (!proofsToVerify || proofsToVerify.length < 4) {
|
|
1163
1288
|
// It may happen that a request returns a result without proofs
|
|
1164
|
-
// Meaning the ID is supported yet by ZKPassport circuits,
|
|
1289
|
+
// Meaning the ID is not supported yet by ZKPassport circuits,
|
|
1165
1290
|
// so the results has to be trusted and cannot be independently verified
|
|
1166
1291
|
return { uniqueIdentifier: undefined, verified: false }
|
|
1167
1292
|
}
|
|
@@ -1232,6 +1357,8 @@ export class ZKPassport {
|
|
|
1232
1357
|
delete this.topicToConfig[requestId]
|
|
1233
1358
|
delete this.topicToSharedSecret[requestId]
|
|
1234
1359
|
delete this.topicToProofs[requestId]
|
|
1360
|
+
delete this.topicToExpectedProofCount[requestId]
|
|
1361
|
+
delete this.topicToResults[requestId]
|
|
1235
1362
|
this.onRequestReceivedCallbacks[requestId] = []
|
|
1236
1363
|
this.onGeneratingProofCallbacks[requestId] = []
|
|
1237
1364
|
this.onBridgeConnectCallbacks[requestId] = []
|