@cheny56/zk-kyc-did 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +520 -0
- package/circuits/credential-proof.circom +108 -0
- package/client/index.d.ts +5 -0
- package/client/index.js +13 -0
- package/client/kyc-client.js +243 -0
- package/contracts/CredentialVerifier.sol +119 -0
- package/contracts/interfaces/IVerifier.sol +23 -0
- package/examples/create-credential.js +141 -0
- package/examples/generate-proof.js +176 -0
- package/examples/verify-credential.js +150 -0
- package/examples/verify-setup.js +177 -0
- package/lib/credential.js +366 -0
- package/lib/index.d.ts +212 -0
- package/lib/index.js +39 -0
- package/lib/predicate.js +209 -0
- package/package.json +80 -0
package/README.md
ADDED
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
# @cheny56/zk-kyc-did
|
|
2
|
+
|
|
3
|
+
**Zero-Knowledge KYC and Decentralized Identity**
|
|
4
|
+
|
|
5
|
+
Prove you meet requirements (age, nationality, verification status) without revealing personal data. Based on W3C Verifiable Credentials with ZK selective disclosure.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@cheny56/zk-kyc-did)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- π **Selective Disclosure** - Prove predicates (age >= 18) without revealing exact values
|
|
13
|
+
- π **W3C Verifiable Credentials** - Standards-compliant credential format
|
|
14
|
+
- π **Any EVM Chain** - Works on Ethereum, Quorum, Polygon, etc.
|
|
15
|
+
- π¦ **Self-Contained** - No external dependencies on special nodes
|
|
16
|
+
- π― **Privacy-First** - Only commitments and proofs are public
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @cheny56/zk-kyc-did
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```javascript
|
|
27
|
+
const { VerifiableCredential, CredentialWallet, Predicate, PredicateOp } = require('@cheny56/zk-kyc-did');
|
|
28
|
+
|
|
29
|
+
// 1. User creates wallet
|
|
30
|
+
const wallet = new CredentialWallet();
|
|
31
|
+
await wallet.init();
|
|
32
|
+
|
|
33
|
+
// 2. KYC provider issues credential
|
|
34
|
+
const credential = new VerifiableCredential({
|
|
35
|
+
issuer: '0x...', // KYC provider
|
|
36
|
+
subject: wallet.subjectDID,
|
|
37
|
+
claims: { age: 25, country: 'US', verified: true },
|
|
38
|
+
});
|
|
39
|
+
await credential.init();
|
|
40
|
+
wallet.addCredential(credential);
|
|
41
|
+
|
|
42
|
+
// 3. User proves predicates without revealing values
|
|
43
|
+
const predicates = [
|
|
44
|
+
new Predicate({ claimKey: 'age', op: PredicateOp.GTE, value: 18 }),
|
|
45
|
+
new Predicate({ claimKey: 'country', op: PredicateOp.IN, value: ['US', 'UK'] }),
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
// 4. Generate ZK proof
|
|
49
|
+
const proof = await generateProof(credential, predicates);
|
|
50
|
+
|
|
51
|
+
// 5. Verify on-chain
|
|
52
|
+
await contract.verifyCredential(proof, predicates);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Table of Contents
|
|
56
|
+
|
|
57
|
+
- [How It Works](#how-it-works)
|
|
58
|
+
- [Core Concepts](#core-concepts)
|
|
59
|
+
- [API Reference](#api-reference)
|
|
60
|
+
- [Step-by-Step Guide](#step-by-step-guide)
|
|
61
|
+
- [Examples](#examples)
|
|
62
|
+
- [Circuit Compilation](#circuit-compilation)
|
|
63
|
+
- [Contract Deployment](#contract-deployment)
|
|
64
|
+
- [Use Cases](#use-cases)
|
|
65
|
+
- [Security Considerations](#security-considerations)
|
|
66
|
+
|
|
67
|
+
## How It Works
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
71
|
+
β ZK KYC/DID FLOW β
|
|
72
|
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
|
|
73
|
+
β β
|
|
74
|
+
β 1. CREDENTIAL ISSUANCE (Off-Chain) β
|
|
75
|
+
β βββββββββββββββ βββββββββββββββ ββββββββββββββββββββββββββ β
|
|
76
|
+
β β KYC Providerβ --> β User's β --> β Credential with β β
|
|
77
|
+
β β β β Claims β β Commitments β β
|
|
78
|
+
β β {age: 25, β β β β (values hidden) β β
|
|
79
|
+
β β country:US}β β β β β β
|
|
80
|
+
β βββββββββββββββ βββββββββββββββ ββββββββββββββββββββββββββ β
|
|
81
|
+
β β
|
|
82
|
+
β 2. PROOF GENERATION (User Side) β
|
|
83
|
+
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
|
|
84
|
+
β β User wants to prove: β β
|
|
85
|
+
β β - age >= 18 (without revealing 25) β β
|
|
86
|
+
β β - country IN [US, UK] (without revealing US) β β
|
|
87
|
+
β β β β
|
|
88
|
+
β β ZK Circuit proves: β β
|
|
89
|
+
β β 1. Credential is valid (signature check) β β
|
|
90
|
+
β β 2. age >= 18 (range proof) β β
|
|
91
|
+
β β 3. country IN set (membership proof) β β
|
|
92
|
+
β β β β
|
|
93
|
+
β β WITHOUT revealing: β β
|
|
94
|
+
β β - Exact age β β
|
|
95
|
+
β β - Which country β β
|
|
96
|
+
β β - Other claims β β
|
|
97
|
+
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
|
|
98
|
+
β β
|
|
99
|
+
β 3. ON-CHAIN VERIFICATION β
|
|
100
|
+
β ββββββββββββββ ββββββββββββββ ββββββββββββββββββββββββββ β
|
|
101
|
+
β β ZK Proof β --> β Verifier β --> β Contract verifies β β
|
|
102
|
+
β β β β Contract β β and grants access β β
|
|
103
|
+
β ββββββββββββββ ββββββββββββββ ββββββββββββββββββββββββββ β
|
|
104
|
+
β β
|
|
105
|
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Core Concepts
|
|
109
|
+
|
|
110
|
+
### Verifiable Credential
|
|
111
|
+
|
|
112
|
+
A **Credential** contains claims (key-value pairs) that are committed using Poseidon hash:
|
|
113
|
+
|
|
114
|
+
```javascript
|
|
115
|
+
Credential = {
|
|
116
|
+
issuer: "0x...", // KYC provider
|
|
117
|
+
subject: "did:zk:...", // User's DID
|
|
118
|
+
claims: {
|
|
119
|
+
age: 25, // Hidden in commitment
|
|
120
|
+
country: "US", // Hidden in commitment
|
|
121
|
+
verified: true // Hidden in commitment
|
|
122
|
+
},
|
|
123
|
+
claimCommitments: { // Public (on-chain)
|
|
124
|
+
age: "0xabc...",
|
|
125
|
+
country: "0xdef...",
|
|
126
|
+
verified: "0x123..."
|
|
127
|
+
},
|
|
128
|
+
signature: "0x..." // Issuer's signature
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Selective Disclosure
|
|
133
|
+
|
|
134
|
+
**Predicates** allow proving properties without revealing values:
|
|
135
|
+
|
|
136
|
+
```javascript
|
|
137
|
+
// Prove age >= 18 without revealing age is 25
|
|
138
|
+
new Predicate({ claimKey: 'age', op: PredicateOp.GTE, value: 18 })
|
|
139
|
+
|
|
140
|
+
// Prove country is in allowed list without revealing which
|
|
141
|
+
new Predicate({
|
|
142
|
+
claimKey: 'country',
|
|
143
|
+
op: PredicateOp.IN,
|
|
144
|
+
value: ['US', 'UK', 'CA']
|
|
145
|
+
})
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### DID (Decentralized Identifier)
|
|
149
|
+
|
|
150
|
+
A **DID** is a self-sovereign identifier:
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
did:zk:0x1234567890abcdef...
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Users control their DID and can generate multiple credentials linked to it.
|
|
157
|
+
|
|
158
|
+
## API Reference
|
|
159
|
+
|
|
160
|
+
### VerifiableCredential
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
const { VerifiableCredential } = require('@cheny56/zk-kyc-did');
|
|
164
|
+
|
|
165
|
+
// Create credential
|
|
166
|
+
const credential = new VerifiableCredential({
|
|
167
|
+
issuer: '0x...',
|
|
168
|
+
subject: 'did:zk:...',
|
|
169
|
+
claims: { age: 25, country: 'US' },
|
|
170
|
+
type: 'KYC',
|
|
171
|
+
expirationDate: Date.now() + 365*24*60*60*1000, // 1 year
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
await credential.init();
|
|
175
|
+
|
|
176
|
+
// Sign credential (issuer does this)
|
|
177
|
+
credential.sign(issuerPrivateKey);
|
|
178
|
+
|
|
179
|
+
// Get commitments
|
|
180
|
+
const commitments = credential.getAllCommitments();
|
|
181
|
+
|
|
182
|
+
// Verify signature
|
|
183
|
+
const valid = credential.verifySignature(issuerPublicKey);
|
|
184
|
+
|
|
185
|
+
// Check expiration
|
|
186
|
+
const expired = credential.isExpired();
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### CredentialWallet
|
|
190
|
+
|
|
191
|
+
```javascript
|
|
192
|
+
const { CredentialWallet } = require('@cheny56/zk-kyc-did');
|
|
193
|
+
|
|
194
|
+
const wallet = new CredentialWallet();
|
|
195
|
+
await wallet.init();
|
|
196
|
+
|
|
197
|
+
// Add credential
|
|
198
|
+
wallet.addCredential(credential);
|
|
199
|
+
|
|
200
|
+
// Get credentials
|
|
201
|
+
const all = wallet.getAllCredentials();
|
|
202
|
+
const kycCreds = wallet.getCredentialsByType('KYC');
|
|
203
|
+
|
|
204
|
+
// Export for backup
|
|
205
|
+
const backup = wallet.toJSON();
|
|
206
|
+
|
|
207
|
+
// Restore from backup
|
|
208
|
+
const restored = await CredentialWallet.fromJSON(backup);
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Predicate
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
const { Predicate, PredicateOp } = require('@cheny56/zk-kyc-did');
|
|
215
|
+
|
|
216
|
+
// Create predicates
|
|
217
|
+
const predicates = [
|
|
218
|
+
new Predicate({ claimKey: 'age', op: PredicateOp.GTE, value: 18 }),
|
|
219
|
+
new Predicate({ claimKey: 'country', op: PredicateOp.IN, value: ['US', 'UK'] }),
|
|
220
|
+
new Predicate({ claimKey: 'verified', op: PredicateOp.EQ, value: true }),
|
|
221
|
+
];
|
|
222
|
+
|
|
223
|
+
// Evaluate predicate
|
|
224
|
+
const satisfied = predicate.evaluate(claimValue);
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### KYCClient
|
|
228
|
+
|
|
229
|
+
```javascript
|
|
230
|
+
const { KYCClient } = require('@cheny56/zk-kyc-did/client');
|
|
231
|
+
|
|
232
|
+
const client = new KYCClient({
|
|
233
|
+
provider,
|
|
234
|
+
signer,
|
|
235
|
+
verifierAddress: '0x...',
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
await client.init();
|
|
239
|
+
|
|
240
|
+
// Generate proof
|
|
241
|
+
const proofData = await client.generateProof(credentialId, predicates);
|
|
242
|
+
|
|
243
|
+
// Verify on-chain
|
|
244
|
+
const { verified } = await client.verifyOnChain(proofData);
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Step-by-Step Guide
|
|
248
|
+
|
|
249
|
+
### Step 1: Install and Initialize
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
npm install @cheny56/zk-kyc-did
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Step 2: Create User Wallet
|
|
256
|
+
|
|
257
|
+
```javascript
|
|
258
|
+
const { CredentialWallet } = require('@cheny56/zk-kyc-did');
|
|
259
|
+
|
|
260
|
+
const wallet = new CredentialWallet();
|
|
261
|
+
await wallet.init();
|
|
262
|
+
|
|
263
|
+
console.log('Your DID:', wallet.subjectDID);
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Step 3: Issue Credential (KYC Provider)
|
|
267
|
+
|
|
268
|
+
```javascript
|
|
269
|
+
const { VerifiableCredential } = require('@cheny56/zk-kyc-did');
|
|
270
|
+
|
|
271
|
+
// KYC provider creates credential
|
|
272
|
+
const credential = new VerifiableCredential({
|
|
273
|
+
issuer: kycProviderPublicKey,
|
|
274
|
+
subject: userWallet.subjectDID,
|
|
275
|
+
claims: {
|
|
276
|
+
age: 25,
|
|
277
|
+
country: 'US',
|
|
278
|
+
documentType: 'passport',
|
|
279
|
+
verified: true,
|
|
280
|
+
},
|
|
281
|
+
type: 'KYC',
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
await credential.init();
|
|
285
|
+
credential.sign(kycProviderPrivateKey);
|
|
286
|
+
|
|
287
|
+
// Send to user (user stores in wallet)
|
|
288
|
+
userWallet.addCredential(credential);
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Step 4: Generate Proof
|
|
292
|
+
|
|
293
|
+
```javascript
|
|
294
|
+
const { Predicate, PredicateOp, generateProofInputs } = require('@cheny56/zk-kyc-did');
|
|
295
|
+
|
|
296
|
+
// Define what to prove
|
|
297
|
+
const predicates = [
|
|
298
|
+
new Predicate({ claimKey: 'age', op: PredicateOp.GTE, value: 18 }),
|
|
299
|
+
new Predicate({ claimKey: 'country', op: PredicateOp.IN, value: ['US', 'UK', 'CA'] }),
|
|
300
|
+
];
|
|
301
|
+
|
|
302
|
+
// Generate proof inputs
|
|
303
|
+
const privateInputs = await generateProofInputs(credential, predicates);
|
|
304
|
+
|
|
305
|
+
// Use snarkjs to generate actual proof
|
|
306
|
+
const { proof, publicSignals } = await snarkjs.groth16.fullProve(
|
|
307
|
+
privateInputs,
|
|
308
|
+
'circuits/credential-proof.wasm',
|
|
309
|
+
'keys/credential-proof_final.zkey'
|
|
310
|
+
);
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Step 5: Verify On-Chain
|
|
314
|
+
|
|
315
|
+
```javascript
|
|
316
|
+
const { KYCClient } = require('@cheny56/zk-kyc-did/client');
|
|
317
|
+
|
|
318
|
+
const client = new KYCClient({
|
|
319
|
+
provider,
|
|
320
|
+
signer,
|
|
321
|
+
verifierAddress: '0x...',
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
await client.init();
|
|
325
|
+
|
|
326
|
+
const proofData = await client.generateProof(credentialId, predicates);
|
|
327
|
+
const { verified } = await client.verifyOnChain(proofData);
|
|
328
|
+
|
|
329
|
+
if (verified) {
|
|
330
|
+
console.log('Access granted!');
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
## Examples
|
|
335
|
+
|
|
336
|
+
### Basic Credential Creation
|
|
337
|
+
|
|
338
|
+
```javascript
|
|
339
|
+
// examples/create-credential.js
|
|
340
|
+
const { VerifiableCredential, CredentialWallet } = require('@cheny56/zk-kyc-did');
|
|
341
|
+
|
|
342
|
+
async function main() {
|
|
343
|
+
const wallet = new CredentialWallet();
|
|
344
|
+
await wallet.init();
|
|
345
|
+
|
|
346
|
+
const credential = new VerifiableCredential({
|
|
347
|
+
issuer: '0x...',
|
|
348
|
+
subject: wallet.subjectDID,
|
|
349
|
+
claims: { age: 25, country: 'US' },
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
await credential.init();
|
|
353
|
+
wallet.addCredential(credential);
|
|
354
|
+
|
|
355
|
+
console.log('Credential created:', credential.id);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
main();
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Generate Proof
|
|
362
|
+
|
|
363
|
+
```javascript
|
|
364
|
+
// examples/generate-proof.js
|
|
365
|
+
const { Predicate, PredicateOp, generateProofInputs } = require('@cheny56/zk-kyc-did');
|
|
366
|
+
|
|
367
|
+
const predicates = [
|
|
368
|
+
new Predicate({ claimKey: 'age', op: PredicateOp.GTE, value: 18 }),
|
|
369
|
+
];
|
|
370
|
+
|
|
371
|
+
const privateInputs = await generateProofInputs(credential, predicates);
|
|
372
|
+
// ... generate proof with snarkjs
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## Circuit Compilation
|
|
376
|
+
|
|
377
|
+
```bash
|
|
378
|
+
# Install circom
|
|
379
|
+
npm install -g circom snarkjs
|
|
380
|
+
|
|
381
|
+
# Compile circuit
|
|
382
|
+
circom circuits/credential-proof.circom --r1cs --wasm --sym -o build/
|
|
383
|
+
|
|
384
|
+
# Download powers of tau
|
|
385
|
+
wget https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_12.ptau -O pot12_final.ptau
|
|
386
|
+
|
|
387
|
+
# Generate proving keys
|
|
388
|
+
snarkjs groth16 setup build/credential-proof.r1cs pot12_final.ptau keys/credential-proof_0000.zkey
|
|
389
|
+
|
|
390
|
+
# Contribute randomness
|
|
391
|
+
snarkjs zkey contribute keys/credential-proof_0000.zkey keys/credential-proof_final.zkey
|
|
392
|
+
|
|
393
|
+
# Export Solidity verifier
|
|
394
|
+
snarkjs zkey export solidityverifier keys/credential-proof_final.zkey contracts/CredentialProofVerifier.sol
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
## Contract Deployment
|
|
398
|
+
|
|
399
|
+
```javascript
|
|
400
|
+
const { ethers } = require('ethers');
|
|
401
|
+
|
|
402
|
+
async function deploy() {
|
|
403
|
+
const provider = new ethers.JsonRpcProvider('http://localhost:8545');
|
|
404
|
+
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
|
|
405
|
+
|
|
406
|
+
// Deploy verifier contract (generated by snarkjs)
|
|
407
|
+
const Verifier = await ethers.getContractFactory('CredentialProofVerifier');
|
|
408
|
+
const verifier = await Verifier.deploy();
|
|
409
|
+
|
|
410
|
+
// Deploy CredentialVerifier
|
|
411
|
+
const CredentialVerifier = await ethers.getContractFactory('CredentialVerifier');
|
|
412
|
+
const credentialVerifier = await CredentialVerifier.deploy(await verifier.getAddress());
|
|
413
|
+
|
|
414
|
+
// Add trusted issuers
|
|
415
|
+
await credentialVerifier.addTrustedIssuer(KYC_PROVIDER_ADDRESS);
|
|
416
|
+
|
|
417
|
+
console.log('Deployed:', await credentialVerifier.getAddress());
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
## Use Cases
|
|
422
|
+
|
|
423
|
+
### 1. Age Verification
|
|
424
|
+
|
|
425
|
+
```javascript
|
|
426
|
+
// Prove user is 18+ without revealing exact age
|
|
427
|
+
const predicate = new Predicate({
|
|
428
|
+
claimKey: 'age',
|
|
429
|
+
op: PredicateOp.GTE,
|
|
430
|
+
value: 18,
|
|
431
|
+
});
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### 2. Geographic Restrictions
|
|
435
|
+
|
|
436
|
+
```javascript
|
|
437
|
+
// Prove user is from allowed country without revealing which
|
|
438
|
+
const predicate = new Predicate({
|
|
439
|
+
claimKey: 'country',
|
|
440
|
+
op: PredicateOp.IN,
|
|
441
|
+
value: ['US', 'UK', 'CA', 'EU'],
|
|
442
|
+
});
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### 3. KYC Tier Verification
|
|
446
|
+
|
|
447
|
+
```javascript
|
|
448
|
+
// Prove user has completed KYC level 2
|
|
449
|
+
const predicate = new Predicate({
|
|
450
|
+
claimKey: 'kycTier',
|
|
451
|
+
op: PredicateOp.GTE,
|
|
452
|
+
value: 2,
|
|
453
|
+
});
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### 4. Document Verification
|
|
457
|
+
|
|
458
|
+
```javascript
|
|
459
|
+
// Prove user has verified passport
|
|
460
|
+
const predicate = new Predicate({
|
|
461
|
+
claimKey: 'documentVerified',
|
|
462
|
+
op: PredicateOp.EQ,
|
|
463
|
+
value: true,
|
|
464
|
+
});
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
## Security Considerations
|
|
468
|
+
|
|
469
|
+
1. **Trusted Issuers**: Only credentials from trusted issuers should be accepted
|
|
470
|
+
2. **Credential Expiration**: Always check `credential.isExpired()` before use
|
|
471
|
+
3. **Signature Verification**: Verify issuer signatures before accepting credentials
|
|
472
|
+
4. **Predicate Validation**: Ensure predicates are satisfied before generating proofs
|
|
473
|
+
5. **Key Management**: Protect credential wallets and private keys
|
|
474
|
+
|
|
475
|
+
## Privacy Properties
|
|
476
|
+
|
|
477
|
+
| Information | Public | Hidden |
|
|
478
|
+
|-------------|--------|--------|
|
|
479
|
+
| Issuer public key | β
| |
|
|
480
|
+
| Predicates (>=18, IN[...]) | β
| |
|
|
481
|
+
| Credential root | β
| |
|
|
482
|
+
| ZK proof | β
| |
|
|
483
|
+
| User's DID | | β
|
|
|
484
|
+
| Actual claim values | | β
|
|
|
485
|
+
| Blinding factors | | β
|
|
|
486
|
+
| Credential signature | | β
|
|
|
487
|
+
| Other claims | | β
|
|
|
488
|
+
|
|
489
|
+
## Package Contents
|
|
490
|
+
|
|
491
|
+
```
|
|
492
|
+
zk-kyc-did/
|
|
493
|
+
βββ lib/
|
|
494
|
+
β βββ index.js # Main exports
|
|
495
|
+
β βββ credential.js # Credential management
|
|
496
|
+
β βββ predicate.js # Predicate proofs
|
|
497
|
+
βββ client/
|
|
498
|
+
β βββ kyc-client.js # Contract interaction
|
|
499
|
+
βββ contracts/
|
|
500
|
+
β βββ CredentialVerifier.sol
|
|
501
|
+
β βββ interfaces/
|
|
502
|
+
βββ circuits/
|
|
503
|
+
β βββ credential-proof.circom
|
|
504
|
+
βββ examples/
|
|
505
|
+
β βββ create-credential.js
|
|
506
|
+
β βββ generate-proof.js
|
|
507
|
+
β βββ verify-credential.js
|
|
508
|
+
β βββ verify-setup.js
|
|
509
|
+
βββ package.json
|
|
510
|
+
βββ README.md
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
## License
|
|
514
|
+
|
|
515
|
+
MIT
|
|
516
|
+
|
|
517
|
+
## Related
|
|
518
|
+
|
|
519
|
+
- [@cheny56/zk-voting](../zk-voting) - ZK voting system
|
|
520
|
+
- [@cheny56/zk-confidential-offchain](../zk-confidential-offchain) - Confidential tokens
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
pragma circom 2.1.0;
|
|
2
|
+
|
|
3
|
+
include "node_modules/circomlib/circuits/poseidon.circom";
|
|
4
|
+
include "node_modules/circomlib/circuits/comparators.circom";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Credential Predicate Proof Circuit
|
|
8
|
+
*
|
|
9
|
+
* Proves that a credential satisfies one or more predicates without revealing:
|
|
10
|
+
* - The actual claim values
|
|
11
|
+
* - The blinding factors
|
|
12
|
+
* - The credential signature
|
|
13
|
+
*
|
|
14
|
+
* Public Inputs:
|
|
15
|
+
* - issuerPublicKey: Issuer's public key
|
|
16
|
+
* - predicateCommitments: Hash of all predicates
|
|
17
|
+
* - credentialRoot: Root commitment of all claims
|
|
18
|
+
*
|
|
19
|
+
* Private Inputs:
|
|
20
|
+
* - claimValues: Actual claim values
|
|
21
|
+
* - blindingFactors: Blinding factors for commitments
|
|
22
|
+
* - signature: Credential signature
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
template CredentialProof(maxClaims, maxPredicates) {
|
|
26
|
+
// Public inputs
|
|
27
|
+
signal input issuerPublicKey;
|
|
28
|
+
signal input predicateCommitments;
|
|
29
|
+
signal input credentialRoot;
|
|
30
|
+
|
|
31
|
+
// Private inputs - claims
|
|
32
|
+
signal input claimValues[maxClaims];
|
|
33
|
+
signal input blindingFactors[maxClaims];
|
|
34
|
+
signal input claimKeys[maxClaims]; // Hash of claim key names
|
|
35
|
+
|
|
36
|
+
// Private inputs - predicates
|
|
37
|
+
signal input predicateClaimIndices[maxPredicates]; // Which claim each predicate checks
|
|
38
|
+
signal input predicateOps[maxPredicates]; // Operator (encoded as number)
|
|
39
|
+
signal input predicateValues[maxPredicates]; // Comparison values
|
|
40
|
+
|
|
41
|
+
// Private inputs - signature
|
|
42
|
+
signal input signature;
|
|
43
|
+
|
|
44
|
+
// ========================================
|
|
45
|
+
// 1. Verify claim commitments
|
|
46
|
+
// ========================================
|
|
47
|
+
component claimHashers[maxClaims];
|
|
48
|
+
signal claimCommitments[maxClaims];
|
|
49
|
+
|
|
50
|
+
for (var i = 0; i < maxClaims; i++) {
|
|
51
|
+
claimHashers[i] = Poseidon(4);
|
|
52
|
+
claimHashers[i].inputs[0] <== claimKeys[i];
|
|
53
|
+
claimHashers[i].inputs[1] <== claimValues[i];
|
|
54
|
+
claimHashers[i].inputs[2] <== blindingFactors[i];
|
|
55
|
+
claimHashers[i].inputs[3] <== 0; // Padding
|
|
56
|
+
claimCommitments[i] <== claimHashers[i].out;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ========================================
|
|
60
|
+
// 2. Compute credential root
|
|
61
|
+
// ========================================
|
|
62
|
+
component rootHasher = Poseidon(maxClaims + 1);
|
|
63
|
+
rootHasher.inputs[0] <== issuerPublicKey;
|
|
64
|
+
for (var i = 0; i < maxClaims; i++) {
|
|
65
|
+
rootHasher.inputs[i + 1] <== claimCommitments[i];
|
|
66
|
+
}
|
|
67
|
+
credentialRoot === rootHasher.out;
|
|
68
|
+
|
|
69
|
+
// ========================================
|
|
70
|
+
// 3. Verify predicates
|
|
71
|
+
// ========================================
|
|
72
|
+
component comparators[maxPredicates];
|
|
73
|
+
|
|
74
|
+
for (var i = 0; i < maxPredicates; i++) {
|
|
75
|
+
// Get the claim value for this predicate
|
|
76
|
+
signal claimValue;
|
|
77
|
+
|
|
78
|
+
// Select claim value based on predicateClaimIndices[i]
|
|
79
|
+
// Simplified: assume predicateClaimIndices[i] points to correct claim
|
|
80
|
+
claimValue <== claimValues[predicateClaimIndices[i]];
|
|
81
|
+
|
|
82
|
+
// Operator encoding:
|
|
83
|
+
// 0 = EQ, 1 = NE, 2 = GT, 3 = GTE, 4 = LT, 5 = LTE
|
|
84
|
+
|
|
85
|
+
// For now, implement GTE (most common for KYC: age >= 18)
|
|
86
|
+
component gte = GreaterEqThan(64);
|
|
87
|
+
gte.in[0] <== claimValue;
|
|
88
|
+
gte.in[1] <== predicateValues[i];
|
|
89
|
+
gte.out === 1; // Must be true
|
|
90
|
+
|
|
91
|
+
// Note: Full implementation would check predicateOps[i] and use appropriate comparator
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ========================================
|
|
95
|
+
// 4. Verify signature (simplified)
|
|
96
|
+
// ========================================
|
|
97
|
+
// In production, use proper signature verification
|
|
98
|
+
// For now, we verify signature commitment matches
|
|
99
|
+
component sigHasher = Poseidon(3);
|
|
100
|
+
sigHasher.inputs[0] <== credentialRoot;
|
|
101
|
+
sigHasher.inputs[1] <== issuerPublicKey;
|
|
102
|
+
sigHasher.inputs[2] <== signature;
|
|
103
|
+
// Signature verification would go here
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Instantiate with reasonable limits
|
|
107
|
+
// Adjust based on your use case
|
|
108
|
+
component main {public [issuerPublicKey, predicateCommitments, credentialRoot]} = CredentialProof(10, 5);
|
package/client/index.js
ADDED