@zkpassport/sdk 0.2.4 → 0.2.6
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/README.md +35 -35
- package/dist/cjs/encryption.d.ts +4 -4
- package/dist/cjs/encryption.js +17 -7
- package/dist/cjs/index.d.ts +4 -0
- package/dist/cjs/index.js +140 -16
- package/dist/esm/encryption.d.ts +4 -4
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +133 -9
- package/package.json +4 -2
- package/src/index.ts +153 -20
- package/tsconfig.json +1 -2
package/README.md
CHANGED
|
@@ -15,21 +15,21 @@ npm install @zkpassport/sdk
|
|
|
15
15
|
## How to use
|
|
16
16
|
|
|
17
17
|
```ts
|
|
18
|
-
import { ZKPassport, EU_COUNTRIES } from
|
|
18
|
+
import { ZKPassport, EU_COUNTRIES } from "@zkpassport/sdk"
|
|
19
19
|
|
|
20
20
|
// Replace with your domain
|
|
21
|
-
const zkPassport = new ZKPassport(
|
|
21
|
+
const zkPassport = new ZKPassport("demo.zkpassport.id")
|
|
22
22
|
|
|
23
23
|
// Specify your app name, logo and the purpose of the request
|
|
24
24
|
// you'll send to your visitors or users
|
|
25
25
|
const queryBuilder = await zkPassport.request({
|
|
26
|
-
name:
|
|
27
|
-
logo:
|
|
28
|
-
purpose:
|
|
26
|
+
name: "ZKPassport",
|
|
27
|
+
logo: "https://zkpassport.id/logo.png",
|
|
28
|
+
purpose: "Prove you are an adult from the EU but not from Scandinavia",
|
|
29
29
|
// The scope is optional and can be used to scope the unique identifier
|
|
30
30
|
// of the request to a specific use case
|
|
31
31
|
// By default, the request's unique identifier is scoped to your domain name only
|
|
32
|
-
scope:
|
|
32
|
+
scope: "eu-adult-not-scandinavia",
|
|
33
33
|
})
|
|
34
34
|
|
|
35
35
|
// Specify the data you want to disclose
|
|
@@ -47,10 +47,10 @@ const {
|
|
|
47
47
|
onReject,
|
|
48
48
|
onError,
|
|
49
49
|
} = queryBuilder
|
|
50
|
-
.disclose(
|
|
51
|
-
.gte(
|
|
52
|
-
.in(
|
|
53
|
-
.out(
|
|
50
|
+
.disclose("firstname")
|
|
51
|
+
.gte("age", 18)
|
|
52
|
+
.in("nationality", EU_COUNTRIES)
|
|
53
|
+
.out("nationality", ["Sweden", "Denmark"])
|
|
54
54
|
.done()
|
|
55
55
|
|
|
56
56
|
// Generate a QR Code with the url and let your user scan it
|
|
@@ -60,12 +60,12 @@ onRequestReceived(() => {
|
|
|
60
60
|
// The user scanned the QR code or clicked the link to the request
|
|
61
61
|
// Essentially, this means the request popup is now opened
|
|
62
62
|
// on the user phone
|
|
63
|
-
console.log(
|
|
63
|
+
console.log("Request received")
|
|
64
64
|
})
|
|
65
65
|
|
|
66
66
|
onGeneratingProof(() => {
|
|
67
67
|
// The user accepted the request and the proof is being generated
|
|
68
|
-
console.log(
|
|
68
|
+
console.log("Generating proof")
|
|
69
69
|
})
|
|
70
70
|
|
|
71
71
|
// You probably don't need to use this callback
|
|
@@ -75,10 +75,10 @@ onProofGenerated(({ proof, vkeyHash, version, name }: ProofResult) => {
|
|
|
75
75
|
// Here, you can retrieve the proof manually and verify it
|
|
76
76
|
// But note that the verification of the proofs is handled
|
|
77
77
|
// automatically by the SDK
|
|
78
|
-
console.log(
|
|
79
|
-
console.log(
|
|
80
|
-
console.log(
|
|
81
|
-
console.log(
|
|
78
|
+
console.log("Proof generated", proof)
|
|
79
|
+
console.log("Verification key hash", vkeyHash)
|
|
80
|
+
console.log("Version", version)
|
|
81
|
+
console.log("Name", name)
|
|
82
82
|
})
|
|
83
83
|
|
|
84
84
|
// That's the callback you're looking for
|
|
@@ -93,22 +93,22 @@ onResult(
|
|
|
93
93
|
result: QueryResult
|
|
94
94
|
}) => {
|
|
95
95
|
// All the proofs have been generated and the final result is available
|
|
96
|
-
console.log(
|
|
97
|
-
console.log(
|
|
98
|
-
console.log(
|
|
99
|
-
console.log(
|
|
96
|
+
console.log("firstname", result.firstname.disclose.result)
|
|
97
|
+
console.log("age over 18", result.age.gte.result)
|
|
98
|
+
console.log("nationality in EU", result.nationality.in.result)
|
|
99
|
+
console.log("nationality not from Scandinavia", result.nationality.out.result)
|
|
100
100
|
// You can also retrieved what were the values originally requested
|
|
101
|
-
console.log(
|
|
102
|
-
console.log(
|
|
103
|
-
console.log(
|
|
101
|
+
console.log("age over", result.age.gte.expected)
|
|
102
|
+
console.log("nationality in", result.nationality.in.expected)
|
|
103
|
+
console.log("nationality not in", result.nationality.out.expected)
|
|
104
104
|
// You can make sure the proof are valid by checking verified is set to true
|
|
105
|
-
console.log(
|
|
105
|
+
console.log("proofs are valid", verified)
|
|
106
106
|
// You can also retrieve the unique identifier associated to this request
|
|
107
107
|
// The assumption is that the unique identifier will be the same if coming
|
|
108
108
|
// from the same ID for the same domain name and scope
|
|
109
109
|
// So you can use it to identify if the user has already provided the proof
|
|
110
110
|
// for this specific use case
|
|
111
|
-
console.log(
|
|
111
|
+
console.log("unique identifier", uniqueIdentifier)
|
|
112
112
|
},
|
|
113
113
|
)
|
|
114
114
|
```
|
|
@@ -122,17 +122,17 @@ You can integrate `@zkpassport/sdk` into a Next.js application by creating a bac
|
|
|
122
122
|
**App Router:** `app/api/zkpassport/route.ts`
|
|
123
123
|
|
|
124
124
|
```typescript
|
|
125
|
-
import { NextResponse } from
|
|
126
|
-
import { ZKPassport } from
|
|
125
|
+
import { NextResponse } from "next/server"
|
|
126
|
+
import { ZKPassport } from "@zkpassport/sdk"
|
|
127
127
|
|
|
128
128
|
export async function GET() {
|
|
129
|
-
const zkPassport = new ZKPassport(
|
|
129
|
+
const zkPassport = new ZKPassport("demo.zkpassport.id") // Replace with your domain
|
|
130
130
|
const queryBuilder = await zkPassport.request({
|
|
131
|
-
name:
|
|
132
|
-
logo:
|
|
133
|
-
purpose:
|
|
131
|
+
name: "ZKPassport Demo",
|
|
132
|
+
logo: "https://via.placeholder.com/150",
|
|
133
|
+
purpose: "Verify user nationality and first name",
|
|
134
134
|
})
|
|
135
|
-
const { url } = queryBuilder.disclose(
|
|
135
|
+
const { url } = queryBuilder.disclose("nationality").disclose("firstname").done()
|
|
136
136
|
return NextResponse.json({ url })
|
|
137
137
|
}
|
|
138
138
|
```
|
|
@@ -142,14 +142,14 @@ export async function GET() {
|
|
|
142
142
|
**App Router:** `app/page.tsx`
|
|
143
143
|
|
|
144
144
|
```tsx
|
|
145
|
-
|
|
146
|
-
import { useEffect, useState } from
|
|
145
|
+
"use client"
|
|
146
|
+
import { useEffect, useState } from "react"
|
|
147
147
|
|
|
148
148
|
export default function Home() {
|
|
149
149
|
const [verificationUrl, setVerificationUrl] = useState<string | null>(null)
|
|
150
150
|
|
|
151
151
|
useEffect(() => {
|
|
152
|
-
fetch(
|
|
152
|
+
fetch("/api/zkpassport")
|
|
153
153
|
.then((res) => res.json())
|
|
154
154
|
.then((data) => setVerificationUrl(data.url))
|
|
155
155
|
.catch(console.error)
|
package/dist/cjs/encryption.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export declare function generateECDHKeyPair(): Promise<{
|
|
2
|
-
privateKey:
|
|
3
|
-
publicKey:
|
|
2
|
+
privateKey: import("@noble/secp256k1").Bytes;
|
|
3
|
+
publicKey: import("@noble/secp256k1").Bytes;
|
|
4
4
|
}>;
|
|
5
|
-
export declare function getSharedSecret(privateKey: string, publicKey: string): Promise<Uint8Array
|
|
6
|
-
export declare function encrypt(message: string, sharedSecret: Uint8Array, topic: string): Promise<Uint8Array
|
|
5
|
+
export declare function getSharedSecret(privateKey: string, publicKey: string): Promise<Uint8Array<ArrayBuffer>>;
|
|
6
|
+
export declare function encrypt(message: string, sharedSecret: Uint8Array, topic: string): Promise<Uint8Array<ArrayBufferLike>>;
|
|
7
7
|
export declare function decrypt(ciphertext: Uint8Array, sharedSecret: Uint8Array, topic: string): Promise<string>;
|
package/dist/cjs/encryption.js
CHANGED
|
@@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
15
15
|
}) : function(o, v) {
|
|
16
16
|
o["default"] = v;
|
|
17
17
|
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
25
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
36
|
exports.generateECDHKeyPair = generateECDHKeyPair;
|
|
27
37
|
exports.getSharedSecret = getSharedSecret;
|
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
|
@@ -14,7 +14,26 @@ const node_gzip_1 = require("node-gzip");
|
|
|
14
14
|
//import initNoirC from '@noir-lang/noirc_abi'
|
|
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
|
+
const buffer_1 = require("buffer/");
|
|
18
|
+
// If Buffer is not defined, then we use the Buffer from the buffer package
|
|
19
|
+
if (typeof globalThis.Buffer === "undefined") {
|
|
20
|
+
globalThis.Buffer = buffer_1.Buffer;
|
|
21
|
+
}
|
|
17
22
|
(0, i18n_iso_countries_1.registerLocale)(en_json_1.default);
|
|
23
|
+
function hasRequestedAccessToField(credentialsRequest, field) {
|
|
24
|
+
const fieldValue = credentialsRequest[field];
|
|
25
|
+
const isDefined = fieldValue !== undefined && fieldValue !== null;
|
|
26
|
+
if (!isDefined) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
for (const key in fieldValue) {
|
|
30
|
+
if (fieldValue[key] !== undefined &&
|
|
31
|
+
fieldValue[key] !== null) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
18
37
|
function normalizeCountry(country) {
|
|
19
38
|
let normalizedCountry;
|
|
20
39
|
const alpha3 = (0, i18n_iso_countries_1.getAlpha3Code)(country, "en");
|
|
@@ -56,6 +75,8 @@ class ZKPassport {
|
|
|
56
75
|
this.topicToRequestReceived = {};
|
|
57
76
|
this.topicToService = {};
|
|
58
77
|
this.topicToProofs = {};
|
|
78
|
+
this.topicToExpectedProofCount = {};
|
|
79
|
+
this.topicToResults = {};
|
|
59
80
|
this.onRequestReceivedCallbacks = {};
|
|
60
81
|
this.onGeneratingProofCallbacks = {};
|
|
61
82
|
this.onBridgeConnectCallbacks = {};
|
|
@@ -74,6 +95,70 @@ class ZKPassport {
|
|
|
74
95
|
await Promise.all([initACVM(acvm), initNoirC(noirc)])
|
|
75
96
|
this.wasmVerifierInit = true
|
|
76
97
|
}*/
|
|
98
|
+
async handleResult(topic) {
|
|
99
|
+
const result = this.topicToResults[topic];
|
|
100
|
+
// Clear the results straight away to avoid concurrency issues
|
|
101
|
+
delete this.topicToResults[topic];
|
|
102
|
+
// Verify the proofs and extract the unique identifier (aka nullifier) and the verification result
|
|
103
|
+
const { uniqueIdentifier, verified } = await this.verify(topic, this.topicToProofs[topic], result);
|
|
104
|
+
await Promise.all(this.onResultCallbacks[topic].map((callback) => callback({
|
|
105
|
+
uniqueIdentifier,
|
|
106
|
+
verified,
|
|
107
|
+
result,
|
|
108
|
+
})));
|
|
109
|
+
// Clear the expected proof count
|
|
110
|
+
delete this.topicToExpectedProofCount[topic];
|
|
111
|
+
}
|
|
112
|
+
setExpectedProofCount(topic) {
|
|
113
|
+
const fields = Object.keys(this.topicToConfig[topic]).filter((key) => hasRequestedAccessToField(this.topicToConfig[topic], key));
|
|
114
|
+
const neededCircuits = [];
|
|
115
|
+
// Determine which circuits are needed based on the requested fields
|
|
116
|
+
for (const field of fields) {
|
|
117
|
+
for (const key in this.topicToConfig[topic][field]) {
|
|
118
|
+
switch (key) {
|
|
119
|
+
case "eq":
|
|
120
|
+
case "disclose":
|
|
121
|
+
if (field !== "age" && !neededCircuits.includes("disclose_bytes")) {
|
|
122
|
+
neededCircuits.push("disclose_bytes");
|
|
123
|
+
}
|
|
124
|
+
else if (field === "age" && !neededCircuits.includes("compare_age")) {
|
|
125
|
+
neededCircuits.push("compare_age");
|
|
126
|
+
}
|
|
127
|
+
break;
|
|
128
|
+
case "gte":
|
|
129
|
+
case "gt":
|
|
130
|
+
case "lte":
|
|
131
|
+
case "lt":
|
|
132
|
+
case "range":
|
|
133
|
+
if (field === "age" && !neededCircuits.includes("compare_age")) {
|
|
134
|
+
neededCircuits.push("compare_age");
|
|
135
|
+
}
|
|
136
|
+
else if (field === "expiry_date" && !neededCircuits.includes("compare_expiry")) {
|
|
137
|
+
neededCircuits.push("compare_expiry");
|
|
138
|
+
}
|
|
139
|
+
else if (field === "birthdate" && !neededCircuits.includes("compare_birthdate")) {
|
|
140
|
+
neededCircuits.push("compare_birthdate");
|
|
141
|
+
}
|
|
142
|
+
break;
|
|
143
|
+
case "in":
|
|
144
|
+
if (field === "nationality" && !neededCircuits.includes("inclusion_check_country")) {
|
|
145
|
+
neededCircuits.push("inclusion_check_country");
|
|
146
|
+
}
|
|
147
|
+
break;
|
|
148
|
+
case "out":
|
|
149
|
+
if (field === "nationality" && !neededCircuits.includes("exclusion_check_country")) {
|
|
150
|
+
neededCircuits.push("exclusion_check_country");
|
|
151
|
+
}
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// From the circuits needed, determine the expected proof count
|
|
157
|
+
// There are at least 4 proofs, 3 base proofs and 1 disclosure proof minimum
|
|
158
|
+
// Each separate needed circuit adds 1 disclosure proof
|
|
159
|
+
this.topicToExpectedProofCount[topic] =
|
|
160
|
+
neededCircuits.length === 0 ? 4 : 3 + neededCircuits.length;
|
|
161
|
+
}
|
|
77
162
|
/**
|
|
78
163
|
* @notice Handle an encrypted message.
|
|
79
164
|
* @param request The request.
|
|
@@ -92,7 +177,7 @@ class ZKPassport {
|
|
|
92
177
|
else if (request.method === "proof") {
|
|
93
178
|
logger_1.noLogger.debug(`User generated proof`);
|
|
94
179
|
// Uncompress the proof and convert it to a hex string
|
|
95
|
-
const bytesProof = Buffer.from(request.params.proof, "base64");
|
|
180
|
+
const bytesProof = buffer_1.Buffer.from(request.params.proof, "base64");
|
|
96
181
|
const uncompressedProof = await (0, node_gzip_1.ungzip)(bytesProof);
|
|
97
182
|
// The gzip lib in the app compress the proof as ASCII
|
|
98
183
|
// and since the app passes the proof as a hex string, we can
|
|
@@ -106,19 +191,44 @@ class ZKPassport {
|
|
|
106
191
|
};
|
|
107
192
|
this.topicToProofs[topic].push(processedProof);
|
|
108
193
|
await Promise.all(this.onProofGeneratedCallbacks[topic].map((callback) => callback(processedProof)));
|
|
194
|
+
// If the results were received before all the proofs were generated,
|
|
195
|
+
// we can handle the result now
|
|
196
|
+
if (this.topicToResults[topic] &&
|
|
197
|
+
this.topicToExpectedProofCount[topic] === this.topicToProofs[topic].length) {
|
|
198
|
+
await this.handleResult(topic);
|
|
199
|
+
}
|
|
109
200
|
}
|
|
110
201
|
else if (request.method === "done") {
|
|
111
202
|
logger_1.noLogger.debug(`User sent the query result`);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
})));
|
|
203
|
+
this.topicToResults[topic] = request.params;
|
|
204
|
+
// Make sure all the proofs have been received, otherwise we'll handle the result later
|
|
205
|
+
// once the proofs have all been received
|
|
206
|
+
if (this.topicToExpectedProofCount[topic] === this.topicToProofs[topic].length) {
|
|
207
|
+
await this.handleResult(topic);
|
|
208
|
+
}
|
|
119
209
|
}
|
|
120
210
|
else if (request.method === "error") {
|
|
121
|
-
|
|
211
|
+
const error = request.params.error;
|
|
212
|
+
if (error && error === "This ID is not supported yet") {
|
|
213
|
+
// This means the user has an ID that is not supported yet
|
|
214
|
+
// So we won't receive any proofs and we can handle the result now
|
|
215
|
+
this.topicToExpectedProofCount[topic] = 0;
|
|
216
|
+
if (this.topicToResults[topic]) {
|
|
217
|
+
await this.handleResult(topic);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
else if (error && error.startsWith("Cannot generate proof")) {
|
|
221
|
+
// This means one of the disclosure proofs failed to be generated
|
|
222
|
+
// So we need to remove one from the expected proof count
|
|
223
|
+
this.topicToExpectedProofCount[topic] -= 1;
|
|
224
|
+
// If the expected proof count is now equal to the number of proofs received
|
|
225
|
+
// and the results were received, we can handle the result now
|
|
226
|
+
if (this.topicToResults[topic] &&
|
|
227
|
+
this.topicToExpectedProofCount[topic] === this.topicToProofs[topic].length) {
|
|
228
|
+
await this.handleResult(topic);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
await Promise.all(this.onErrorCallbacks[topic].map((callback) => callback(error)));
|
|
122
232
|
}
|
|
123
233
|
}
|
|
124
234
|
getZkPassportRequest(topic) {
|
|
@@ -171,9 +281,10 @@ class ZKPassport {
|
|
|
171
281
|
return this.getZkPassportRequest(topic)
|
|
172
282
|
},*/
|
|
173
283
|
done: () => {
|
|
174
|
-
const base64Config = Buffer.from(JSON.stringify(this.topicToConfig[topic])).toString("base64");
|
|
175
|
-
const base64Service = Buffer.from(JSON.stringify(this.topicToService[topic])).toString("base64");
|
|
284
|
+
const base64Config = buffer_1.Buffer.from(JSON.stringify(this.topicToConfig[topic])).toString("base64");
|
|
285
|
+
const base64Service = buffer_1.Buffer.from(JSON.stringify(this.topicToService[topic])).toString("base64");
|
|
176
286
|
const pubkey = (0, utils_2.bytesToHex)(this.topicToKeyPair[topic].publicKey);
|
|
287
|
+
this.setExpectedProofCount(topic);
|
|
177
288
|
return {
|
|
178
289
|
url: `https://zkpassport.id/r?d=${this.domain}&t=${topic}&c=${base64Config}&s=${base64Service}&p=${pubkey}`,
|
|
179
290
|
requestId: topic,
|
|
@@ -204,6 +315,7 @@ class ZKPassport {
|
|
|
204
315
|
this.topicToConfig[topic] = {};
|
|
205
316
|
this.topicToService[topic] = { name, logo, purpose, scope };
|
|
206
317
|
this.topicToProofs[topic] = [];
|
|
318
|
+
this.topicToExpectedProofCount[topic] = 0;
|
|
207
319
|
this.onRequestReceivedCallbacks[topic] = [];
|
|
208
320
|
this.onGeneratingProofCallbacks[topic] = [];
|
|
209
321
|
this.onBridgeConnectCallbacks[topic] = [];
|
|
@@ -226,7 +338,7 @@ class ZKPassport {
|
|
|
226
338
|
logger_1.noLogger.debug("[frontend] Received handshake:", event.data);
|
|
227
339
|
this.topicToRequestReceived[topic] = true;
|
|
228
340
|
this.topicToSharedSecret[topic] = await (0, encryption_1.getSharedSecret)((0, utils_2.bytesToHex)(keyPair.privateKey), data.params.pubkey);
|
|
229
|
-
logger_1.noLogger.debug("[frontend] Shared secret:", Buffer.from(this.topicToSharedSecret[topic]).toString("hex"));
|
|
341
|
+
logger_1.noLogger.debug("[frontend] Shared secret:", buffer_1.Buffer.from(this.topicToSharedSecret[topic]).toString("hex"));
|
|
230
342
|
const encryptedMessage = await (0, json_rpc_1.createEncryptedJsonRpcRequest)("hello", null, this.topicToSharedSecret[topic], topic);
|
|
231
343
|
logger_1.noLogger.debug("[frontend] Sending encrypted message:", encryptedMessage);
|
|
232
344
|
wsClient.send(JSON.stringify(encryptedMessage));
|
|
@@ -745,6 +857,16 @@ class ZKPassport {
|
|
|
745
857
|
isCorrect = false;
|
|
746
858
|
break;
|
|
747
859
|
}
|
|
860
|
+
// Check the countryList is in ascending order
|
|
861
|
+
// If the prover doesn't use a sorted list then the proof cannot be trusted
|
|
862
|
+
// as it is requirement in the circuit for the exclusion check to work
|
|
863
|
+
for (let i = 1; i < countryList.length; i++) {
|
|
864
|
+
if (countryList[i] < countryList[i - 1]) {
|
|
865
|
+
console.warn("The nationality exclusion list has not been sorted, and thus the proof cannot be trusted");
|
|
866
|
+
isCorrect = false;
|
|
867
|
+
break;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
748
870
|
uniqueIdentifier = (0, utils_1.getNullifierFromDisclosureProof)(proofData).toString(10);
|
|
749
871
|
}
|
|
750
872
|
else if (proof.name === "inclusion_check_country") {
|
|
@@ -789,7 +911,7 @@ class ZKPassport {
|
|
|
789
911
|
proofsToVerify = this.topicToProofs[requestId];
|
|
790
912
|
if (!proofsToVerify || proofsToVerify.length < 4) {
|
|
791
913
|
// It may happen that a request returns a result without proofs
|
|
792
|
-
// Meaning the ID is supported yet by ZKPassport circuits,
|
|
914
|
+
// Meaning the ID is not supported yet by ZKPassport circuits,
|
|
793
915
|
// so the results has to be trusted and cannot be independently verified
|
|
794
916
|
return { uniqueIdentifier: undefined, verified: false };
|
|
795
917
|
}
|
|
@@ -811,7 +933,7 @@ class ZKPassport {
|
|
|
811
933
|
for (const proof of proofsToVerify) {
|
|
812
934
|
const proofData = (0, utils_1.getProofData)(proof.proof, true);
|
|
813
935
|
const hostedPackagedCircuit = await (0, utils_1.getHostedPackagedCircuitByName)(proof.version, proof.name);
|
|
814
|
-
const vkeyBytes = Buffer.from(hostedPackagedCircuit.vkey, "base64");
|
|
936
|
+
const vkeyBytes = buffer_1.Buffer.from(hostedPackagedCircuit.vkey, "base64");
|
|
815
937
|
try {
|
|
816
938
|
verified = await verifier.verifyUltraHonkProof(proofData, new Uint8Array(vkeyBytes));
|
|
817
939
|
}
|
|
@@ -836,8 +958,8 @@ class ZKPassport {
|
|
|
836
958
|
*/
|
|
837
959
|
getUrl(requestId) {
|
|
838
960
|
const pubkey = (0, utils_2.bytesToHex)(this.topicToKeyPair[requestId].publicKey);
|
|
839
|
-
const base64Config = Buffer.from(JSON.stringify(this.topicToConfig[requestId])).toString("base64");
|
|
840
|
-
const base64Service = Buffer.from(JSON.stringify(this.topicToService[requestId])).toString("base64");
|
|
961
|
+
const base64Config = buffer_1.Buffer.from(JSON.stringify(this.topicToConfig[requestId])).toString("base64");
|
|
962
|
+
const base64Service = buffer_1.Buffer.from(JSON.stringify(this.topicToService[requestId])).toString("base64");
|
|
841
963
|
return `https://zkpassport.id/r?d=${this.domain}&t=${requestId}&c=${base64Config}&s=${base64Service}&p=${pubkey}`;
|
|
842
964
|
}
|
|
843
965
|
/**
|
|
@@ -851,6 +973,8 @@ class ZKPassport {
|
|
|
851
973
|
delete this.topicToConfig[requestId];
|
|
852
974
|
delete this.topicToSharedSecret[requestId];
|
|
853
975
|
delete this.topicToProofs[requestId];
|
|
976
|
+
delete this.topicToExpectedProofCount[requestId];
|
|
977
|
+
delete this.topicToResults[requestId];
|
|
854
978
|
this.onRequestReceivedCallbacks[requestId] = [];
|
|
855
979
|
this.onGeneratingProofCallbacks[requestId] = [];
|
|
856
980
|
this.onBridgeConnectCallbacks[requestId] = [];
|
package/dist/esm/encryption.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export declare function generateECDHKeyPair(): Promise<{
|
|
2
|
-
privateKey:
|
|
3
|
-
publicKey:
|
|
2
|
+
privateKey: import("@noble/secp256k1").Bytes;
|
|
3
|
+
publicKey: import("@noble/secp256k1").Bytes;
|
|
4
4
|
}>;
|
|
5
|
-
export declare function getSharedSecret(privateKey: string, publicKey: string): Promise<Uint8Array
|
|
6
|
-
export declare function encrypt(message: string, sharedSecret: Uint8Array, topic: string): Promise<Uint8Array
|
|
5
|
+
export declare function getSharedSecret(privateKey: string, publicKey: string): Promise<Uint8Array<ArrayBuffer>>;
|
|
6
|
+
export declare function encrypt(message: string, sharedSecret: Uint8Array, topic: string): Promise<Uint8Array<ArrayBufferLike>>;
|
|
7
7
|
export declare function decrypt(ciphertext: Uint8Array, sharedSecret: Uint8Array, topic: string): Promise<string>;
|
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
|
@@ -10,7 +10,26 @@ import { ungzip } from "node-gzip";
|
|
|
10
10
|
//import initNoirC from '@noir-lang/noirc_abi'
|
|
11
11
|
//import initACVM from '@noir-lang/acvm_js'
|
|
12
12
|
import i18en from "i18n-iso-countries/langs/en.json";
|
|
13
|
+
import { Buffer } from "buffer/";
|
|
14
|
+
// If Buffer is not defined, then we use the Buffer from the buffer package
|
|
15
|
+
if (typeof globalThis.Buffer === "undefined") {
|
|
16
|
+
globalThis.Buffer = Buffer;
|
|
17
|
+
}
|
|
13
18
|
registerLocale(i18en);
|
|
19
|
+
function hasRequestedAccessToField(credentialsRequest, field) {
|
|
20
|
+
const fieldValue = credentialsRequest[field];
|
|
21
|
+
const isDefined = fieldValue !== undefined && fieldValue !== null;
|
|
22
|
+
if (!isDefined) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
for (const key in fieldValue) {
|
|
26
|
+
if (fieldValue[key] !== undefined &&
|
|
27
|
+
fieldValue[key] !== null) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
14
33
|
function normalizeCountry(country) {
|
|
15
34
|
let normalizedCountry;
|
|
16
35
|
const alpha3 = getAlpha3Code(country, "en");
|
|
@@ -46,6 +65,8 @@ export class ZKPassport {
|
|
|
46
65
|
this.topicToRequestReceived = {};
|
|
47
66
|
this.topicToService = {};
|
|
48
67
|
this.topicToProofs = {};
|
|
68
|
+
this.topicToExpectedProofCount = {};
|
|
69
|
+
this.topicToResults = {};
|
|
49
70
|
this.onRequestReceivedCallbacks = {};
|
|
50
71
|
this.onGeneratingProofCallbacks = {};
|
|
51
72
|
this.onBridgeConnectCallbacks = {};
|
|
@@ -64,6 +85,70 @@ export class ZKPassport {
|
|
|
64
85
|
await Promise.all([initACVM(acvm), initNoirC(noirc)])
|
|
65
86
|
this.wasmVerifierInit = true
|
|
66
87
|
}*/
|
|
88
|
+
async handleResult(topic) {
|
|
89
|
+
const result = this.topicToResults[topic];
|
|
90
|
+
// Clear the results straight away to avoid concurrency issues
|
|
91
|
+
delete this.topicToResults[topic];
|
|
92
|
+
// Verify the proofs and extract the unique identifier (aka nullifier) and the verification result
|
|
93
|
+
const { uniqueIdentifier, verified } = await this.verify(topic, this.topicToProofs[topic], result);
|
|
94
|
+
await Promise.all(this.onResultCallbacks[topic].map((callback) => callback({
|
|
95
|
+
uniqueIdentifier,
|
|
96
|
+
verified,
|
|
97
|
+
result,
|
|
98
|
+
})));
|
|
99
|
+
// Clear the expected proof count
|
|
100
|
+
delete this.topicToExpectedProofCount[topic];
|
|
101
|
+
}
|
|
102
|
+
setExpectedProofCount(topic) {
|
|
103
|
+
const fields = Object.keys(this.topicToConfig[topic]).filter((key) => hasRequestedAccessToField(this.topicToConfig[topic], key));
|
|
104
|
+
const neededCircuits = [];
|
|
105
|
+
// Determine which circuits are needed based on the requested fields
|
|
106
|
+
for (const field of fields) {
|
|
107
|
+
for (const key in this.topicToConfig[topic][field]) {
|
|
108
|
+
switch (key) {
|
|
109
|
+
case "eq":
|
|
110
|
+
case "disclose":
|
|
111
|
+
if (field !== "age" && !neededCircuits.includes("disclose_bytes")) {
|
|
112
|
+
neededCircuits.push("disclose_bytes");
|
|
113
|
+
}
|
|
114
|
+
else if (field === "age" && !neededCircuits.includes("compare_age")) {
|
|
115
|
+
neededCircuits.push("compare_age");
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
case "gte":
|
|
119
|
+
case "gt":
|
|
120
|
+
case "lte":
|
|
121
|
+
case "lt":
|
|
122
|
+
case "range":
|
|
123
|
+
if (field === "age" && !neededCircuits.includes("compare_age")) {
|
|
124
|
+
neededCircuits.push("compare_age");
|
|
125
|
+
}
|
|
126
|
+
else if (field === "expiry_date" && !neededCircuits.includes("compare_expiry")) {
|
|
127
|
+
neededCircuits.push("compare_expiry");
|
|
128
|
+
}
|
|
129
|
+
else if (field === "birthdate" && !neededCircuits.includes("compare_birthdate")) {
|
|
130
|
+
neededCircuits.push("compare_birthdate");
|
|
131
|
+
}
|
|
132
|
+
break;
|
|
133
|
+
case "in":
|
|
134
|
+
if (field === "nationality" && !neededCircuits.includes("inclusion_check_country")) {
|
|
135
|
+
neededCircuits.push("inclusion_check_country");
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
case "out":
|
|
139
|
+
if (field === "nationality" && !neededCircuits.includes("exclusion_check_country")) {
|
|
140
|
+
neededCircuits.push("exclusion_check_country");
|
|
141
|
+
}
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// From the circuits needed, determine the expected proof count
|
|
147
|
+
// There are at least 4 proofs, 3 base proofs and 1 disclosure proof minimum
|
|
148
|
+
// Each separate needed circuit adds 1 disclosure proof
|
|
149
|
+
this.topicToExpectedProofCount[topic] =
|
|
150
|
+
neededCircuits.length === 0 ? 4 : 3 + neededCircuits.length;
|
|
151
|
+
}
|
|
67
152
|
/**
|
|
68
153
|
* @notice Handle an encrypted message.
|
|
69
154
|
* @param request The request.
|
|
@@ -96,19 +181,44 @@ export class ZKPassport {
|
|
|
96
181
|
};
|
|
97
182
|
this.topicToProofs[topic].push(processedProof);
|
|
98
183
|
await Promise.all(this.onProofGeneratedCallbacks[topic].map((callback) => callback(processedProof)));
|
|
184
|
+
// If the results were received before all the proofs were generated,
|
|
185
|
+
// we can handle the result now
|
|
186
|
+
if (this.topicToResults[topic] &&
|
|
187
|
+
this.topicToExpectedProofCount[topic] === this.topicToProofs[topic].length) {
|
|
188
|
+
await this.handleResult(topic);
|
|
189
|
+
}
|
|
99
190
|
}
|
|
100
191
|
else if (request.method === "done") {
|
|
101
192
|
logger.debug(`User sent the query result`);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
})));
|
|
193
|
+
this.topicToResults[topic] = request.params;
|
|
194
|
+
// Make sure all the proofs have been received, otherwise we'll handle the result later
|
|
195
|
+
// once the proofs have all been received
|
|
196
|
+
if (this.topicToExpectedProofCount[topic] === this.topicToProofs[topic].length) {
|
|
197
|
+
await this.handleResult(topic);
|
|
198
|
+
}
|
|
109
199
|
}
|
|
110
200
|
else if (request.method === "error") {
|
|
111
|
-
|
|
201
|
+
const error = request.params.error;
|
|
202
|
+
if (error && error === "This ID is not supported yet") {
|
|
203
|
+
// This means the user has an ID that is not supported yet
|
|
204
|
+
// So we won't receive any proofs and we can handle the result now
|
|
205
|
+
this.topicToExpectedProofCount[topic] = 0;
|
|
206
|
+
if (this.topicToResults[topic]) {
|
|
207
|
+
await this.handleResult(topic);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else if (error && error.startsWith("Cannot generate proof")) {
|
|
211
|
+
// This means one of the disclosure proofs failed to be generated
|
|
212
|
+
// So we need to remove one from the expected proof count
|
|
213
|
+
this.topicToExpectedProofCount[topic] -= 1;
|
|
214
|
+
// If the expected proof count is now equal to the number of proofs received
|
|
215
|
+
// and the results were received, we can handle the result now
|
|
216
|
+
if (this.topicToResults[topic] &&
|
|
217
|
+
this.topicToExpectedProofCount[topic] === this.topicToProofs[topic].length) {
|
|
218
|
+
await this.handleResult(topic);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
await Promise.all(this.onErrorCallbacks[topic].map((callback) => callback(error)));
|
|
112
222
|
}
|
|
113
223
|
}
|
|
114
224
|
getZkPassportRequest(topic) {
|
|
@@ -164,6 +274,7 @@ export class ZKPassport {
|
|
|
164
274
|
const base64Config = Buffer.from(JSON.stringify(this.topicToConfig[topic])).toString("base64");
|
|
165
275
|
const base64Service = Buffer.from(JSON.stringify(this.topicToService[topic])).toString("base64");
|
|
166
276
|
const pubkey = bytesToHex(this.topicToKeyPair[topic].publicKey);
|
|
277
|
+
this.setExpectedProofCount(topic);
|
|
167
278
|
return {
|
|
168
279
|
url: `https://zkpassport.id/r?d=${this.domain}&t=${topic}&c=${base64Config}&s=${base64Service}&p=${pubkey}`,
|
|
169
280
|
requestId: topic,
|
|
@@ -194,6 +305,7 @@ export class ZKPassport {
|
|
|
194
305
|
this.topicToConfig[topic] = {};
|
|
195
306
|
this.topicToService[topic] = { name, logo, purpose, scope };
|
|
196
307
|
this.topicToProofs[topic] = [];
|
|
308
|
+
this.topicToExpectedProofCount[topic] = 0;
|
|
197
309
|
this.onRequestReceivedCallbacks[topic] = [];
|
|
198
310
|
this.onGeneratingProofCallbacks[topic] = [];
|
|
199
311
|
this.onBridgeConnectCallbacks[topic] = [];
|
|
@@ -735,6 +847,16 @@ export class ZKPassport {
|
|
|
735
847
|
isCorrect = false;
|
|
736
848
|
break;
|
|
737
849
|
}
|
|
850
|
+
// Check the countryList is in ascending order
|
|
851
|
+
// If the prover doesn't use a sorted list then the proof cannot be trusted
|
|
852
|
+
// as it is requirement in the circuit for the exclusion check to work
|
|
853
|
+
for (let i = 1; i < countryList.length; i++) {
|
|
854
|
+
if (countryList[i] < countryList[i - 1]) {
|
|
855
|
+
console.warn("The nationality exclusion list has not been sorted, and thus the proof cannot be trusted");
|
|
856
|
+
isCorrect = false;
|
|
857
|
+
break;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
738
860
|
uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10);
|
|
739
861
|
}
|
|
740
862
|
else if (proof.name === "inclusion_check_country") {
|
|
@@ -779,7 +901,7 @@ export class ZKPassport {
|
|
|
779
901
|
proofsToVerify = this.topicToProofs[requestId];
|
|
780
902
|
if (!proofsToVerify || proofsToVerify.length < 4) {
|
|
781
903
|
// It may happen that a request returns a result without proofs
|
|
782
|
-
// Meaning the ID is supported yet by ZKPassport circuits,
|
|
904
|
+
// Meaning the ID is not supported yet by ZKPassport circuits,
|
|
783
905
|
// so the results has to be trusted and cannot be independently verified
|
|
784
906
|
return { uniqueIdentifier: undefined, verified: false };
|
|
785
907
|
}
|
|
@@ -841,6 +963,8 @@ export class ZKPassport {
|
|
|
841
963
|
delete this.topicToConfig[requestId];
|
|
842
964
|
delete this.topicToSharedSecret[requestId];
|
|
843
965
|
delete this.topicToProofs[requestId];
|
|
966
|
+
delete this.topicToExpectedProofCount[requestId];
|
|
967
|
+
delete this.topicToResults[requestId];
|
|
844
968
|
this.onRequestReceivedCallbacks[requestId] = [];
|
|
845
969
|
this.onGeneratingProofCallbacks[requestId] = [];
|
|
846
970
|
this.onBridgeConnectCallbacks[requestId] = [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zkpassport/sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
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",
|
|
@@ -34,13 +34,15 @@
|
|
|
34
34
|
"@types/node-gzip": "^1.1.3",
|
|
35
35
|
"@types/ws": "^8.5.12",
|
|
36
36
|
"jest": "^29.7.0",
|
|
37
|
+
"ts-node": "^10.9.2",
|
|
37
38
|
"typescript": "^5.6.2"
|
|
38
39
|
},
|
|
39
40
|
"dependencies": {
|
|
40
41
|
"@aztec/bb.js": "^0.67.0",
|
|
41
42
|
"@noble/ciphers": "^1.2.1",
|
|
42
43
|
"@noble/secp256k1": "^2.2.3",
|
|
43
|
-
"@zkpassport/utils": "^0.2.
|
|
44
|
+
"@zkpassport/utils": "^0.2.16",
|
|
45
|
+
"buffer": "^6.0.3",
|
|
44
46
|
"i18n-iso-countries": "^7.12.0",
|
|
45
47
|
"node-gzip": "^1.1.2",
|
|
46
48
|
"ws": "^8.18.0"
|
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"
|
|
@@ -40,9 +41,32 @@ import { ungzip } from "node-gzip"
|
|
|
40
41
|
//import initNoirC from '@noir-lang/noirc_abi'
|
|
41
42
|
//import initACVM from '@noir-lang/acvm_js'
|
|
42
43
|
import i18en from "i18n-iso-countries/langs/en.json"
|
|
44
|
+
import { Buffer } from "buffer/"
|
|
45
|
+
|
|
46
|
+
// If Buffer is not defined, then we use the Buffer from the buffer package
|
|
47
|
+
if (typeof globalThis.Buffer === "undefined") {
|
|
48
|
+
globalThis.Buffer = Buffer as any
|
|
49
|
+
}
|
|
43
50
|
|
|
44
51
|
registerLocale(i18en)
|
|
45
52
|
|
|
53
|
+
function hasRequestedAccessToField(credentialsRequest: Query, field: IDCredential): boolean {
|
|
54
|
+
const fieldValue = credentialsRequest[field as keyof Query]
|
|
55
|
+
const isDefined = fieldValue !== undefined && fieldValue !== null
|
|
56
|
+
if (!isDefined) {
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
59
|
+
for (const key in fieldValue) {
|
|
60
|
+
if (
|
|
61
|
+
fieldValue[key as keyof typeof fieldValue] !== undefined &&
|
|
62
|
+
fieldValue[key as keyof typeof fieldValue] !== null
|
|
63
|
+
) {
|
|
64
|
+
return true
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return false
|
|
68
|
+
}
|
|
69
|
+
|
|
46
70
|
function normalizeCountry(country: CountryName | Alpha3Code) {
|
|
47
71
|
let normalizedCountry: Alpha3Code | undefined
|
|
48
72
|
const alpha3 = getAlpha3Code(country, "en") as Alpha3Code | undefined
|
|
@@ -242,6 +266,8 @@ export class ZKPassport {
|
|
|
242
266
|
{ name: string; logo: string; purpose: string; scope?: string }
|
|
243
267
|
> = {}
|
|
244
268
|
private topicToProofs: Record<string, Array<ProofResult>> = {}
|
|
269
|
+
private topicToExpectedProofCount: Record<string, number> = {}
|
|
270
|
+
private topicToResults: Record<string, QueryResult> = {}
|
|
245
271
|
|
|
246
272
|
private onRequestReceivedCallbacks: Record<string, Array<() => void>> = {}
|
|
247
273
|
private onGeneratingProofCallbacks: Record<string, Array<(topic: string) => void>> = {}
|
|
@@ -275,6 +301,79 @@ export class ZKPassport {
|
|
|
275
301
|
this.wasmVerifierInit = true
|
|
276
302
|
}*/
|
|
277
303
|
|
|
304
|
+
private async handleResult(topic: string) {
|
|
305
|
+
const result = this.topicToResults[topic]
|
|
306
|
+
// Clear the results straight away to avoid concurrency issues
|
|
307
|
+
delete this.topicToResults[topic]
|
|
308
|
+
// Verify the proofs and extract the unique identifier (aka nullifier) and the verification result
|
|
309
|
+
const { uniqueIdentifier, verified } = await this.verify(
|
|
310
|
+
topic,
|
|
311
|
+
this.topicToProofs[topic],
|
|
312
|
+
result,
|
|
313
|
+
)
|
|
314
|
+
await Promise.all(
|
|
315
|
+
this.onResultCallbacks[topic].map((callback) =>
|
|
316
|
+
callback({
|
|
317
|
+
uniqueIdentifier,
|
|
318
|
+
verified,
|
|
319
|
+
result,
|
|
320
|
+
}),
|
|
321
|
+
),
|
|
322
|
+
)
|
|
323
|
+
// Clear the expected proof count
|
|
324
|
+
delete this.topicToExpectedProofCount[topic]
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private setExpectedProofCount(topic: string) {
|
|
328
|
+
const fields = Object.keys(this.topicToConfig[topic] as Query).filter((key) =>
|
|
329
|
+
hasRequestedAccessToField(this.topicToConfig[topic] as Query, key as IDCredential),
|
|
330
|
+
)
|
|
331
|
+
const neededCircuits: string[] = []
|
|
332
|
+
// Determine which circuits are needed based on the requested fields
|
|
333
|
+
for (const field of fields) {
|
|
334
|
+
for (const key in this.topicToConfig[topic][field as IDCredential]) {
|
|
335
|
+
switch (key) {
|
|
336
|
+
case "eq":
|
|
337
|
+
case "disclose":
|
|
338
|
+
if (field !== "age" && !neededCircuits.includes("disclose_bytes")) {
|
|
339
|
+
neededCircuits.push("disclose_bytes")
|
|
340
|
+
} else if (field === "age" && !neededCircuits.includes("compare_age")) {
|
|
341
|
+
neededCircuits.push("compare_age")
|
|
342
|
+
}
|
|
343
|
+
break
|
|
344
|
+
case "gte":
|
|
345
|
+
case "gt":
|
|
346
|
+
case "lte":
|
|
347
|
+
case "lt":
|
|
348
|
+
case "range":
|
|
349
|
+
if (field === "age" && !neededCircuits.includes("compare_age")) {
|
|
350
|
+
neededCircuits.push("compare_age")
|
|
351
|
+
} else if (field === "expiry_date" && !neededCircuits.includes("compare_expiry")) {
|
|
352
|
+
neededCircuits.push("compare_expiry")
|
|
353
|
+
} else if (field === "birthdate" && !neededCircuits.includes("compare_birthdate")) {
|
|
354
|
+
neededCircuits.push("compare_birthdate")
|
|
355
|
+
}
|
|
356
|
+
break
|
|
357
|
+
case "in":
|
|
358
|
+
if (field === "nationality" && !neededCircuits.includes("inclusion_check_country")) {
|
|
359
|
+
neededCircuits.push("inclusion_check_country")
|
|
360
|
+
}
|
|
361
|
+
break
|
|
362
|
+
case "out":
|
|
363
|
+
if (field === "nationality" && !neededCircuits.includes("exclusion_check_country")) {
|
|
364
|
+
neededCircuits.push("exclusion_check_country")
|
|
365
|
+
}
|
|
366
|
+
break
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
// From the circuits needed, determine the expected proof count
|
|
371
|
+
// There are at least 4 proofs, 3 base proofs and 1 disclosure proof minimum
|
|
372
|
+
// Each separate needed circuit adds 1 disclosure proof
|
|
373
|
+
this.topicToExpectedProofCount[topic] =
|
|
374
|
+
neededCircuits.length === 0 ? 4 : 3 + neededCircuits.length
|
|
375
|
+
}
|
|
376
|
+
|
|
278
377
|
/**
|
|
279
378
|
* @notice Handle an encrypted message.
|
|
280
379
|
* @param request The request.
|
|
@@ -311,27 +410,45 @@ export class ZKPassport {
|
|
|
311
410
|
await Promise.all(
|
|
312
411
|
this.onProofGeneratedCallbacks[topic].map((callback) => callback(processedProof)),
|
|
313
412
|
)
|
|
413
|
+
// If the results were received before all the proofs were generated,
|
|
414
|
+
// we can handle the result now
|
|
415
|
+
if (
|
|
416
|
+
this.topicToResults[topic] &&
|
|
417
|
+
this.topicToExpectedProofCount[topic] === this.topicToProofs[topic].length
|
|
418
|
+
) {
|
|
419
|
+
await this.handleResult(topic)
|
|
420
|
+
}
|
|
314
421
|
} else if (request.method === "done") {
|
|
315
422
|
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
|
-
)
|
|
423
|
+
this.topicToResults[topic] = request.params
|
|
424
|
+
// Make sure all the proofs have been received, otherwise we'll handle the result later
|
|
425
|
+
// once the proofs have all been received
|
|
426
|
+
if (this.topicToExpectedProofCount[topic] === this.topicToProofs[topic].length) {
|
|
427
|
+
await this.handleResult(topic)
|
|
428
|
+
}
|
|
331
429
|
} else if (request.method === "error") {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
430
|
+
const error = request.params.error
|
|
431
|
+
if (error && error === "This ID is not supported yet") {
|
|
432
|
+
// This means the user has an ID that is not supported yet
|
|
433
|
+
// So we won't receive any proofs and we can handle the result now
|
|
434
|
+
this.topicToExpectedProofCount[topic] = 0
|
|
435
|
+
if (this.topicToResults[topic]) {
|
|
436
|
+
await this.handleResult(topic)
|
|
437
|
+
}
|
|
438
|
+
} else if (error && error.startsWith("Cannot generate proof")) {
|
|
439
|
+
// This means one of the disclosure proofs failed to be generated
|
|
440
|
+
// So we need to remove one from the expected proof count
|
|
441
|
+
this.topicToExpectedProofCount[topic] -= 1
|
|
442
|
+
// If the expected proof count is now equal to the number of proofs received
|
|
443
|
+
// and the results were received, we can handle the result now
|
|
444
|
+
if (
|
|
445
|
+
this.topicToResults[topic] &&
|
|
446
|
+
this.topicToExpectedProofCount[topic] === this.topicToProofs[topic].length
|
|
447
|
+
) {
|
|
448
|
+
await this.handleResult(topic)
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
await Promise.all(this.onErrorCallbacks[topic].map((callback) => callback(error)))
|
|
335
452
|
}
|
|
336
453
|
}
|
|
337
454
|
|
|
@@ -396,6 +513,7 @@ export class ZKPassport {
|
|
|
396
513
|
"base64",
|
|
397
514
|
)
|
|
398
515
|
const pubkey = bytesToHex(this.topicToKeyPair[topic].publicKey)
|
|
516
|
+
this.setExpectedProofCount(topic)
|
|
399
517
|
return {
|
|
400
518
|
url: `https://zkpassport.id/r?d=${this.domain}&t=${topic}&c=${base64Config}&s=${base64Service}&p=${pubkey}`,
|
|
401
519
|
requestId: topic,
|
|
@@ -442,7 +560,7 @@ export class ZKPassport {
|
|
|
442
560
|
scope?: string
|
|
443
561
|
topicOverride?: string
|
|
444
562
|
keyPairOverride?: { privateKey: Uint8Array; publicKey: Uint8Array }
|
|
445
|
-
}) {
|
|
563
|
+
}): Promise<QueryBuilder> {
|
|
446
564
|
const topic = topicOverride || randomBytes(16).toString("hex")
|
|
447
565
|
|
|
448
566
|
const keyPair = keyPairOverride || (await generateECDHKeyPair())
|
|
@@ -454,6 +572,7 @@ export class ZKPassport {
|
|
|
454
572
|
this.topicToConfig[topic] = {}
|
|
455
573
|
this.topicToService[topic] = { name, logo, purpose, scope }
|
|
456
574
|
this.topicToProofs[topic] = []
|
|
575
|
+
this.topicToExpectedProofCount[topic] = 0
|
|
457
576
|
|
|
458
577
|
this.onRequestReceivedCallbacks[topic] = []
|
|
459
578
|
this.onGeneratingProofCallbacks[topic] = []
|
|
@@ -1108,6 +1227,18 @@ export class ZKPassport {
|
|
|
1108
1227
|
isCorrect = false
|
|
1109
1228
|
break
|
|
1110
1229
|
}
|
|
1230
|
+
// Check the countryList is in ascending order
|
|
1231
|
+
// If the prover doesn't use a sorted list then the proof cannot be trusted
|
|
1232
|
+
// as it is requirement in the circuit for the exclusion check to work
|
|
1233
|
+
for (let i = 1; i < countryList.length; i++) {
|
|
1234
|
+
if (countryList[i] < countryList[i - 1]) {
|
|
1235
|
+
console.warn(
|
|
1236
|
+
"The nationality exclusion list has not been sorted, and thus the proof cannot be trusted",
|
|
1237
|
+
)
|
|
1238
|
+
isCorrect = false
|
|
1239
|
+
break
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1111
1242
|
uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10)
|
|
1112
1243
|
} else if (proof.name === "inclusion_check_country") {
|
|
1113
1244
|
commitmentIn = getCommitmentInFromDisclosureProof(proofData)
|
|
@@ -1161,7 +1292,7 @@ export class ZKPassport {
|
|
|
1161
1292
|
proofsToVerify = this.topicToProofs[requestId]
|
|
1162
1293
|
if (!proofsToVerify || proofsToVerify.length < 4) {
|
|
1163
1294
|
// It may happen that a request returns a result without proofs
|
|
1164
|
-
// Meaning the ID is supported yet by ZKPassport circuits,
|
|
1295
|
+
// Meaning the ID is not supported yet by ZKPassport circuits,
|
|
1165
1296
|
// so the results has to be trusted and cannot be independently verified
|
|
1166
1297
|
return { uniqueIdentifier: undefined, verified: false }
|
|
1167
1298
|
}
|
|
@@ -1232,6 +1363,8 @@ export class ZKPassport {
|
|
|
1232
1363
|
delete this.topicToConfig[requestId]
|
|
1233
1364
|
delete this.topicToSharedSecret[requestId]
|
|
1234
1365
|
delete this.topicToProofs[requestId]
|
|
1366
|
+
delete this.topicToExpectedProofCount[requestId]
|
|
1367
|
+
delete this.topicToResults[requestId]
|
|
1235
1368
|
this.onRequestReceivedCallbacks[requestId] = []
|
|
1236
1369
|
this.onGeneratingProofCallbacks[requestId] = []
|
|
1237
1370
|
this.onBridgeConnectCallbacks[requestId] = []
|