@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
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verify Credential Example
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates on-chain verification of credential proofs.
|
|
5
|
+
*
|
|
6
|
+
* Run: node examples/verify-credential.js
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
KYCClient,
|
|
11
|
+
Predicate,
|
|
12
|
+
PredicateOp,
|
|
13
|
+
} = require('../client/kyc-client');
|
|
14
|
+
const { VerifiableCredential, CredentialWallet } = require('../lib');
|
|
15
|
+
|
|
16
|
+
async function main() {
|
|
17
|
+
console.log('='.repeat(70));
|
|
18
|
+
console.log(' ZK KYC/DID - Verify Credential Example');
|
|
19
|
+
console.log('='.repeat(70));
|
|
20
|
+
console.log();
|
|
21
|
+
|
|
22
|
+
// ========================================
|
|
23
|
+
// Setup: Create credential
|
|
24
|
+
// ========================================
|
|
25
|
+
console.log('SETUP: Create Sample Credential');
|
|
26
|
+
console.log('-'.repeat(70));
|
|
27
|
+
|
|
28
|
+
const wallet = new CredentialWallet();
|
|
29
|
+
await wallet.init();
|
|
30
|
+
|
|
31
|
+
const credential = new VerifiableCredential({
|
|
32
|
+
issuer: '0x1234567890abcdef1234567890abcdef12345678',
|
|
33
|
+
subject: wallet.subjectDID,
|
|
34
|
+
claims: {
|
|
35
|
+
age: 25,
|
|
36
|
+
country: 'US',
|
|
37
|
+
verified: true,
|
|
38
|
+
},
|
|
39
|
+
type: 'KYC',
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
await credential.init();
|
|
43
|
+
credential.sign(0xabcdefn);
|
|
44
|
+
wallet.addCredential(credential);
|
|
45
|
+
|
|
46
|
+
console.log(` Credential created for: ${wallet.subjectDID}`);
|
|
47
|
+
console.log();
|
|
48
|
+
|
|
49
|
+
// ========================================
|
|
50
|
+
// Step 1: Initialize client
|
|
51
|
+
// ========================================
|
|
52
|
+
console.log('STEP 1: Initialize KYC Client');
|
|
53
|
+
console.log('-'.repeat(70));
|
|
54
|
+
|
|
55
|
+
// In a real scenario, you'd connect to a provider
|
|
56
|
+
const client = new KYCClient({
|
|
57
|
+
provider: null, // Would be ethers provider
|
|
58
|
+
signer: null, // Would be ethers signer
|
|
59
|
+
verifierAddress: '0x...', // Contract address
|
|
60
|
+
demoMode: true, // Skip actual proof generation
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await client.init(wallet);
|
|
64
|
+
|
|
65
|
+
console.log(` Client initialized`);
|
|
66
|
+
console.log(` Demo mode: ${client.demoMode}`);
|
|
67
|
+
console.log();
|
|
68
|
+
|
|
69
|
+
// ========================================
|
|
70
|
+
// Step 2: Define predicates
|
|
71
|
+
// ========================================
|
|
72
|
+
console.log('STEP 2: Define Predicates');
|
|
73
|
+
console.log('-'.repeat(70));
|
|
74
|
+
|
|
75
|
+
const predicates = [
|
|
76
|
+
new Predicate({
|
|
77
|
+
claimKey: 'age',
|
|
78
|
+
op: PredicateOp.GTE,
|
|
79
|
+
value: 18,
|
|
80
|
+
}),
|
|
81
|
+
new Predicate({
|
|
82
|
+
claimKey: 'country',
|
|
83
|
+
op: PredicateOp.IN,
|
|
84
|
+
value: ['US', 'UK', 'CA'],
|
|
85
|
+
}),
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
console.log(' Predicates:');
|
|
89
|
+
for (const pred of predicates) {
|
|
90
|
+
console.log(` ${pred.claimKey} ${pred.op} ${JSON.stringify(pred.value)}`);
|
|
91
|
+
}
|
|
92
|
+
console.log();
|
|
93
|
+
|
|
94
|
+
// ========================================
|
|
95
|
+
// Step 3: Generate proof
|
|
96
|
+
// ========================================
|
|
97
|
+
console.log('STEP 3: Generate ZK Proof');
|
|
98
|
+
console.log('-'.repeat(70));
|
|
99
|
+
|
|
100
|
+
const credentialId = credential.id;
|
|
101
|
+
const proofData = await client.generateProof(credentialId, predicates);
|
|
102
|
+
|
|
103
|
+
console.log(' Proof generated:');
|
|
104
|
+
console.log(` Credential root: 0x${proofData.credentialRoot.toString(16).slice(0, 30)}...`);
|
|
105
|
+
console.log(` Issuer: ${proofData.publicInputs.issuerPublicKey.slice(0, 30)}...`);
|
|
106
|
+
console.log(` Predicates: ${proofData.publicInputs.predicates.length}`);
|
|
107
|
+
console.log();
|
|
108
|
+
|
|
109
|
+
// ========================================
|
|
110
|
+
// Step 4: Verify on-chain (simulated)
|
|
111
|
+
// ========================================
|
|
112
|
+
console.log('STEP 4: Verify On-Chain (Simulated)');
|
|
113
|
+
console.log('-'.repeat(70));
|
|
114
|
+
|
|
115
|
+
console.log(' In production, this would:');
|
|
116
|
+
console.log(' 1. Send proof to CredentialVerifier contract');
|
|
117
|
+
console.log(' 2. Contract verifies ZK proof via verifier contract');
|
|
118
|
+
console.log(' 3. Contract checks issuer is trusted');
|
|
119
|
+
console.log(' 4. Contract emits CredentialVerified event');
|
|
120
|
+
console.log();
|
|
121
|
+
|
|
122
|
+
console.log(' Verification result: ✓ Proof valid (simulated)');
|
|
123
|
+
console.log();
|
|
124
|
+
|
|
125
|
+
// ========================================
|
|
126
|
+
// Privacy Summary
|
|
127
|
+
// ========================================
|
|
128
|
+
console.log('PRIVACY SUMMARY');
|
|
129
|
+
console.log('-'.repeat(70));
|
|
130
|
+
console.log();
|
|
131
|
+
console.log(' ┌───────────────────────┬───────────────────────────────────┐');
|
|
132
|
+
console.log(' │ PUBLIC │ HIDDEN │');
|
|
133
|
+
console.log(' ├───────────────────────┼───────────────────────────────────┤');
|
|
134
|
+
console.log(' │ Issuer public key │ User\'s DID │');
|
|
135
|
+
console.log(' │ Predicates (>=18, IN) │ Actual age │');
|
|
136
|
+
console.log(' │ Credential root │ Actual country │');
|
|
137
|
+
console.log(' │ ZK proof │ Other claims │');
|
|
138
|
+
console.log(' │ │ Blinding factors │');
|
|
139
|
+
console.log(' └───────────────────────┴───────────────────────────────────┘');
|
|
140
|
+
console.log();
|
|
141
|
+
|
|
142
|
+
console.log('='.repeat(70));
|
|
143
|
+
console.log(' SUCCESS! Credential verification flow completed.');
|
|
144
|
+
console.log('='.repeat(70));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
main().catch(err => {
|
|
148
|
+
console.error('Error:', err);
|
|
149
|
+
process.exit(1);
|
|
150
|
+
});
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verify Setup Example
|
|
3
|
+
*
|
|
4
|
+
* Quick verification that all components are working correctly.
|
|
5
|
+
* Run this first to ensure the package is properly installed.
|
|
6
|
+
*
|
|
7
|
+
* Run: node examples/verify-setup.js
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
VerifiableCredential,
|
|
12
|
+
CredentialWallet,
|
|
13
|
+
Predicate,
|
|
14
|
+
PredicateOp,
|
|
15
|
+
init,
|
|
16
|
+
poseidonHash,
|
|
17
|
+
} = require('../lib');
|
|
18
|
+
|
|
19
|
+
async function main() {
|
|
20
|
+
console.log('='.repeat(60));
|
|
21
|
+
console.log(' ZK-KYC-DID - Setup Verification');
|
|
22
|
+
console.log('='.repeat(60));
|
|
23
|
+
console.log();
|
|
24
|
+
|
|
25
|
+
let passed = 0;
|
|
26
|
+
let failed = 0;
|
|
27
|
+
|
|
28
|
+
// Test 1: Library Initialization
|
|
29
|
+
console.log('TEST 1: Library Initialization');
|
|
30
|
+
try {
|
|
31
|
+
await init();
|
|
32
|
+
console.log(' ✓ Library initialized');
|
|
33
|
+
passed++;
|
|
34
|
+
} catch (e) {
|
|
35
|
+
console.log(` ✗ Failed: ${e.message}`);
|
|
36
|
+
failed++;
|
|
37
|
+
}
|
|
38
|
+
console.log();
|
|
39
|
+
|
|
40
|
+
// Test 2: Poseidon Hash
|
|
41
|
+
console.log('TEST 2: Poseidon Hash');
|
|
42
|
+
try {
|
|
43
|
+
const hash = await poseidonHash([1n, 2n, 3n]);
|
|
44
|
+
const hex = '0x' + BigInt(hash).toString(16).padStart(64, '0');
|
|
45
|
+
console.log(` ✓ Poseidon(1,2,3) = ${hex.slice(0, 30)}...`);
|
|
46
|
+
console.log(` ✓ Hash length: ${hex.length} chars`);
|
|
47
|
+
passed++;
|
|
48
|
+
} catch (e) {
|
|
49
|
+
console.log(` ✗ Failed: ${e.message}`);
|
|
50
|
+
failed++;
|
|
51
|
+
}
|
|
52
|
+
console.log();
|
|
53
|
+
|
|
54
|
+
// Test 3: Credential Creation
|
|
55
|
+
console.log('TEST 3: Credential Creation');
|
|
56
|
+
try {
|
|
57
|
+
const credential = new VerifiableCredential({
|
|
58
|
+
issuer: '0x1234',
|
|
59
|
+
subject: 'did:zk:test',
|
|
60
|
+
claims: { age: 25, country: 'US' },
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await credential.init();
|
|
64
|
+
const commitments = credential.getAllCommitments();
|
|
65
|
+
|
|
66
|
+
console.log(` ✓ Credential created`);
|
|
67
|
+
console.log(` ✓ Commitments: ${Object.keys(commitments).length}`);
|
|
68
|
+
console.log(` ✓ ID: ${credential.id.slice(0, 30)}...`);
|
|
69
|
+
passed++;
|
|
70
|
+
} catch (e) {
|
|
71
|
+
console.log(` ✗ Failed: ${e.message}`);
|
|
72
|
+
failed++;
|
|
73
|
+
}
|
|
74
|
+
console.log();
|
|
75
|
+
|
|
76
|
+
// Test 4: Credential Wallet
|
|
77
|
+
console.log('TEST 4: Credential Wallet');
|
|
78
|
+
try {
|
|
79
|
+
const wallet = new CredentialWallet();
|
|
80
|
+
await wallet.init();
|
|
81
|
+
|
|
82
|
+
const credential = new VerifiableCredential({
|
|
83
|
+
issuer: '0x1234',
|
|
84
|
+
subject: wallet.subjectDID,
|
|
85
|
+
claims: { age: 25 },
|
|
86
|
+
});
|
|
87
|
+
await credential.init();
|
|
88
|
+
|
|
89
|
+
wallet.addCredential(credential);
|
|
90
|
+
|
|
91
|
+
console.log(` ✓ Wallet created`);
|
|
92
|
+
console.log(` ✓ DID: ${wallet.subjectDID.slice(0, 30)}...`);
|
|
93
|
+
console.log(` ✓ Credentials: ${wallet.getAllCredentials().length}`);
|
|
94
|
+
passed++;
|
|
95
|
+
} catch (e) {
|
|
96
|
+
console.log(` ✗ Failed: ${e.message}`);
|
|
97
|
+
failed++;
|
|
98
|
+
}
|
|
99
|
+
console.log();
|
|
100
|
+
|
|
101
|
+
// Test 5: Predicates
|
|
102
|
+
console.log('TEST 5: Predicates');
|
|
103
|
+
try {
|
|
104
|
+
const pred1 = new Predicate({
|
|
105
|
+
claimKey: 'age',
|
|
106
|
+
op: PredicateOp.GTE,
|
|
107
|
+
value: 18,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const pred2 = new Predicate({
|
|
111
|
+
claimKey: 'country',
|
|
112
|
+
op: PredicateOp.IN,
|
|
113
|
+
value: ['US', 'UK'],
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
console.log(` ✓ Predicate 1: age >= 18`);
|
|
117
|
+
console.log(` ✓ Predicate 2: country IN [US, UK]`);
|
|
118
|
+
console.log(` ✓ Evaluation: ${pred1.evaluate(25)} (should be true)`);
|
|
119
|
+
console.log(` ✓ Evaluation: ${pred2.evaluate('US')} (should be true)`);
|
|
120
|
+
passed++;
|
|
121
|
+
} catch (e) {
|
|
122
|
+
console.log(` ✗ Failed: ${e.message}`);
|
|
123
|
+
failed++;
|
|
124
|
+
}
|
|
125
|
+
console.log();
|
|
126
|
+
|
|
127
|
+
// Test 6: Serialization
|
|
128
|
+
console.log('TEST 6: Serialization');
|
|
129
|
+
try {
|
|
130
|
+
const wallet = new CredentialWallet();
|
|
131
|
+
await wallet.init();
|
|
132
|
+
|
|
133
|
+
const credential = new VerifiableCredential({
|
|
134
|
+
issuer: '0x1234',
|
|
135
|
+
subject: wallet.subjectDID,
|
|
136
|
+
claims: { age: 25 },
|
|
137
|
+
});
|
|
138
|
+
await credential.init();
|
|
139
|
+
wallet.addCredential(credential);
|
|
140
|
+
|
|
141
|
+
const exported = wallet.toJSON();
|
|
142
|
+
const restored = await CredentialWallet.fromJSON(exported);
|
|
143
|
+
|
|
144
|
+
console.log(` ✓ Wallet serialized: ${JSON.stringify(exported).length} bytes`);
|
|
145
|
+
console.log(` ✓ Wallet restored`);
|
|
146
|
+
console.log(` ✓ DID matches: ${restored.subjectDID === wallet.subjectDID}`);
|
|
147
|
+
passed++;
|
|
148
|
+
} catch (e) {
|
|
149
|
+
console.log(` ✗ Failed: ${e.message}`);
|
|
150
|
+
failed++;
|
|
151
|
+
}
|
|
152
|
+
console.log();
|
|
153
|
+
|
|
154
|
+
// Results
|
|
155
|
+
console.log('='.repeat(60));
|
|
156
|
+
if (failed === 0) {
|
|
157
|
+
console.log(` ✓ ALL ${passed} TESTS PASSED`);
|
|
158
|
+
console.log();
|
|
159
|
+
console.log(' Your setup is complete! Try running:');
|
|
160
|
+
console.log(' node examples/create-credential.js');
|
|
161
|
+
console.log(' node examples/generate-proof.js');
|
|
162
|
+
console.log(' node examples/verify-credential.js');
|
|
163
|
+
} else {
|
|
164
|
+
console.log(` ✗ ${failed} TESTS FAILED, ${passed} PASSED`);
|
|
165
|
+
console.log();
|
|
166
|
+
console.log(' Please check your installation:');
|
|
167
|
+
console.log(' npm install');
|
|
168
|
+
}
|
|
169
|
+
console.log('='.repeat(60));
|
|
170
|
+
|
|
171
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
main().catch(err => {
|
|
175
|
+
console.error('Error:', err);
|
|
176
|
+
process.exit(1);
|
|
177
|
+
});
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifiable Credential Management
|
|
3
|
+
*
|
|
4
|
+
* Implements W3C Verifiable Credentials with Zero-Knowledge selective disclosure.
|
|
5
|
+
* Credentials can prove predicates (e.g., "age >= 18") without revealing the actual value.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { buildPoseidon } = require('circomlibjs');
|
|
9
|
+
const crypto = require('crypto');
|
|
10
|
+
|
|
11
|
+
let poseidon = null;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Initialize Poseidon hash
|
|
15
|
+
*/
|
|
16
|
+
async function init() {
|
|
17
|
+
if (!poseidon) {
|
|
18
|
+
poseidon = await buildPoseidon();
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Poseidon hash helper
|
|
25
|
+
*/
|
|
26
|
+
function poseidonHash(inputs) {
|
|
27
|
+
if (!poseidon) throw new Error('Call init() first');
|
|
28
|
+
const hash = poseidon(inputs.map(x => BigInt(x)));
|
|
29
|
+
return poseidon.F.toString(hash);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Generate a random field element
|
|
34
|
+
*/
|
|
35
|
+
function randomFieldElement() {
|
|
36
|
+
const bytes = crypto.randomBytes(32);
|
|
37
|
+
const value = BigInt('0x' + bytes.toString('hex'));
|
|
38
|
+
const FIELD_MOD = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
|
|
39
|
+
return value % FIELD_MOD;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Verifiable Credential
|
|
44
|
+
*
|
|
45
|
+
* Based on W3C Verifiable Credentials standard with ZK extensions.
|
|
46
|
+
*/
|
|
47
|
+
class VerifiableCredential {
|
|
48
|
+
/**
|
|
49
|
+
* Create a new credential
|
|
50
|
+
* @param {Object} options
|
|
51
|
+
* @param {string} options.issuer - Issuer DID or public key
|
|
52
|
+
* @param {string} options.subject - Subject DID (who the credential is about)
|
|
53
|
+
* @param {Object} options.claims - Key-value pairs of claims (e.g., { age: 25, country: "US" })
|
|
54
|
+
* @param {string} [options.type] - Credential type (e.g., "KYC", "Identity")
|
|
55
|
+
* @param {number} [options.expirationDate] - Unix timestamp
|
|
56
|
+
*/
|
|
57
|
+
constructor(options) {
|
|
58
|
+
this.issuer = options.issuer;
|
|
59
|
+
this.subject = options.subject;
|
|
60
|
+
this.claims = { ...options.claims };
|
|
61
|
+
this.type = options.type || 'VerifiableCredential';
|
|
62
|
+
this.issuedAt = Date.now();
|
|
63
|
+
this.expirationDate = options.expirationDate || null;
|
|
64
|
+
this.id = options.id || `credential:${Date.now()}:${randomFieldElement().toString(16)}`;
|
|
65
|
+
|
|
66
|
+
// ZK-specific fields
|
|
67
|
+
this.claimCommitments = {};
|
|
68
|
+
this.blindingFactors = {};
|
|
69
|
+
this.signature = null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Initialize credential (compute commitments)
|
|
74
|
+
*/
|
|
75
|
+
async init() {
|
|
76
|
+
await init();
|
|
77
|
+
|
|
78
|
+
// Create commitments for each claim
|
|
79
|
+
for (const [key, value] of Object.entries(this.claims)) {
|
|
80
|
+
const blinding = randomFieldElement();
|
|
81
|
+
this.blindingFactors[key] = blinding;
|
|
82
|
+
|
|
83
|
+
// Commitment = Poseidon(key, value, blinding)
|
|
84
|
+
this.claimCommitments[key] = poseidonHash([
|
|
85
|
+
BigInt(key.length), // Domain separation
|
|
86
|
+
BigInt(value.toString().length),
|
|
87
|
+
BigInt(blinding),
|
|
88
|
+
BigInt(this.hashClaimValue(key, value)),
|
|
89
|
+
]);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Hash a claim value (for commitment)
|
|
97
|
+
* @private
|
|
98
|
+
*/
|
|
99
|
+
hashClaimValue(key, value) {
|
|
100
|
+
// Convert value to consistent format
|
|
101
|
+
if (typeof value === 'number') {
|
|
102
|
+
return BigInt(value);
|
|
103
|
+
}
|
|
104
|
+
if (typeof value === 'string') {
|
|
105
|
+
// Hash string values
|
|
106
|
+
return BigInt('0x' + crypto.createHash('sha256').update(value).digest('hex').slice(0, 16));
|
|
107
|
+
}
|
|
108
|
+
if (typeof value === 'boolean') {
|
|
109
|
+
return value ? 1n : 0n;
|
|
110
|
+
}
|
|
111
|
+
return BigInt(value.toString());
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Sign the credential (issuer does this)
|
|
116
|
+
* @param {bigint} issuerPrivateKey - Issuer's private key
|
|
117
|
+
*/
|
|
118
|
+
sign(issuerPrivateKey) {
|
|
119
|
+
// Create signature over all commitments
|
|
120
|
+
const commitments = Object.values(this.claimCommitments).sort();
|
|
121
|
+
const message = poseidonHash([...commitments, BigInt(this.issuer)]);
|
|
122
|
+
|
|
123
|
+
// Simple signature scheme (in production, use proper ECDSA/EdDSA)
|
|
124
|
+
// For now, we'll use a commitment to the signature
|
|
125
|
+
this.signature = poseidonHash([message, BigInt(issuerPrivateKey)]);
|
|
126
|
+
|
|
127
|
+
return this.signature;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Verify credential signature
|
|
132
|
+
* @param {bigint} issuerPublicKey - Issuer's public key
|
|
133
|
+
* @returns {boolean}
|
|
134
|
+
*/
|
|
135
|
+
verifySignature(issuerPublicKey) {
|
|
136
|
+
if (!this.signature) return false;
|
|
137
|
+
|
|
138
|
+
const commitments = Object.values(this.claimCommitments).sort();
|
|
139
|
+
const message = poseidonHash([...commitments, BigInt(this.issuer)]);
|
|
140
|
+
const expectedSignature = poseidonHash([message, BigInt(issuerPublicKey)]);
|
|
141
|
+
|
|
142
|
+
// In production, use proper signature verification
|
|
143
|
+
// This is a simplified version
|
|
144
|
+
return this.signature === expectedSignature;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get commitment for a specific claim
|
|
149
|
+
* @param {string} claimKey
|
|
150
|
+
* @returns {string} Hex commitment
|
|
151
|
+
*/
|
|
152
|
+
getCommitment(claimKey) {
|
|
153
|
+
if (!this.claimCommitments[claimKey]) {
|
|
154
|
+
throw new Error(`Claim "${claimKey}" not found`);
|
|
155
|
+
}
|
|
156
|
+
return '0x' + BigInt(this.claimCommitments[claimKey]).toString(16).padStart(64, '0');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Get all commitments
|
|
161
|
+
* @returns {Object} claimKey -> commitment hex
|
|
162
|
+
*/
|
|
163
|
+
getAllCommitments() {
|
|
164
|
+
const result = {};
|
|
165
|
+
for (const [key, commitment] of Object.entries(this.claimCommitments)) {
|
|
166
|
+
result[key] = '0x' + BigInt(commitment).toString(16).padStart(64, '0');
|
|
167
|
+
}
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get blinding factor for a claim (needed for ZK proofs)
|
|
173
|
+
* @param {string} claimKey
|
|
174
|
+
* @returns {bigint}
|
|
175
|
+
*/
|
|
176
|
+
getBlindingFactor(claimKey) {
|
|
177
|
+
if (!this.blindingFactors[claimKey]) {
|
|
178
|
+
throw new Error(`Blinding factor for "${claimKey}" not found`);
|
|
179
|
+
}
|
|
180
|
+
return BigInt(this.blindingFactors[claimKey]);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Check if credential is expired
|
|
185
|
+
* @returns {boolean}
|
|
186
|
+
*/
|
|
187
|
+
isExpired() {
|
|
188
|
+
if (!this.expirationDate) return false;
|
|
189
|
+
return Date.now() > this.expirationDate;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Serialize credential for storage
|
|
194
|
+
* @returns {Object}
|
|
195
|
+
*/
|
|
196
|
+
toJSON() {
|
|
197
|
+
return {
|
|
198
|
+
id: this.id,
|
|
199
|
+
issuer: this.issuer,
|
|
200
|
+
subject: this.subject,
|
|
201
|
+
type: this.type,
|
|
202
|
+
issuedAt: this.issuedAt,
|
|
203
|
+
expirationDate: this.expirationDate,
|
|
204
|
+
claimCommitments: Object.fromEntries(
|
|
205
|
+
Object.entries(this.claimCommitments).map(([k, v]) => [
|
|
206
|
+
k,
|
|
207
|
+
v.toString()
|
|
208
|
+
])
|
|
209
|
+
),
|
|
210
|
+
blindingFactors: Object.fromEntries(
|
|
211
|
+
Object.entries(this.blindingFactors).map(([k, v]) => [
|
|
212
|
+
k,
|
|
213
|
+
v.toString()
|
|
214
|
+
])
|
|
215
|
+
),
|
|
216
|
+
signature: this.signature?.toString() || null,
|
|
217
|
+
// Note: claims are NOT included in JSON for privacy
|
|
218
|
+
// User must store them separately if needed
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Deserialize credential from storage
|
|
224
|
+
* @param {Object} json
|
|
225
|
+
* @param {Object} [claims] - Optional claims (if user stored them)
|
|
226
|
+
* @returns {Promise<VerifiableCredential>}
|
|
227
|
+
*/
|
|
228
|
+
static async fromJSON(json, claims = {}) {
|
|
229
|
+
const cred = new VerifiableCredential({
|
|
230
|
+
issuer: json.issuer,
|
|
231
|
+
subject: json.subject,
|
|
232
|
+
claims,
|
|
233
|
+
type: json.type,
|
|
234
|
+
expirationDate: json.expirationDate,
|
|
235
|
+
id: json.id,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
cred.issuedAt = json.issuedAt;
|
|
239
|
+
cred.claimCommitments = Object.fromEntries(
|
|
240
|
+
Object.entries(json.claimCommitments || {}).map(([k, v]) => [k, BigInt(v)])
|
|
241
|
+
);
|
|
242
|
+
cred.blindingFactors = Object.fromEntries(
|
|
243
|
+
Object.entries(json.blindingFactors || {}).map(([k, v]) => [k, BigInt(v)])
|
|
244
|
+
);
|
|
245
|
+
cred.signature = json.signature ? BigInt(json.signature) : null;
|
|
246
|
+
|
|
247
|
+
await cred.init();
|
|
248
|
+
return cred;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Credential Wallet
|
|
254
|
+
*
|
|
255
|
+
* Manages multiple credentials for a user
|
|
256
|
+
*/
|
|
257
|
+
class CredentialWallet {
|
|
258
|
+
/**
|
|
259
|
+
* Create a credential wallet
|
|
260
|
+
* @param {string} [subjectDID] - User's DID
|
|
261
|
+
*/
|
|
262
|
+
constructor(subjectDID = null) {
|
|
263
|
+
this.subjectDID = subjectDID || this.generateDID();
|
|
264
|
+
this.credentials = new Map(); // credentialId -> VerifiableCredential
|
|
265
|
+
this.initialized = false;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Initialize wallet
|
|
270
|
+
*/
|
|
271
|
+
async init() {
|
|
272
|
+
await init();
|
|
273
|
+
this.initialized = true;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Generate a DID for the user
|
|
278
|
+
* @returns {string}
|
|
279
|
+
*/
|
|
280
|
+
generateDID() {
|
|
281
|
+
const bytes = crypto.randomBytes(32);
|
|
282
|
+
const did = 'did:zk:' + bytes.toString('hex');
|
|
283
|
+
return did;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Add a credential to the wallet
|
|
288
|
+
* @param {VerifiableCredential} credential
|
|
289
|
+
*/
|
|
290
|
+
addCredential(credential) {
|
|
291
|
+
if (credential.subject !== this.subjectDID) {
|
|
292
|
+
throw new Error('Credential subject does not match wallet DID');
|
|
293
|
+
}
|
|
294
|
+
this.credentials.set(credential.id, credential);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Get a credential by ID
|
|
299
|
+
* @param {string} credentialId
|
|
300
|
+
* @returns {VerifiableCredential|null}
|
|
301
|
+
*/
|
|
302
|
+
getCredential(credentialId) {
|
|
303
|
+
return this.credentials.get(credentialId) || null;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Get all credentials
|
|
308
|
+
* @returns {VerifiableCredential[]}
|
|
309
|
+
*/
|
|
310
|
+
getAllCredentials() {
|
|
311
|
+
return Array.from(this.credentials.values());
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Find credentials by type
|
|
316
|
+
* @param {string} type
|
|
317
|
+
* @returns {VerifiableCredential[]}
|
|
318
|
+
*/
|
|
319
|
+
getCredentialsByType(type) {
|
|
320
|
+
return Array.from(this.credentials.values()).filter(c => c.type === type);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Remove a credential
|
|
325
|
+
* @param {string} credentialId
|
|
326
|
+
*/
|
|
327
|
+
removeCredential(credentialId) {
|
|
328
|
+
this.credentials.delete(credentialId);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Export wallet (for backup)
|
|
333
|
+
* @returns {Object}
|
|
334
|
+
*/
|
|
335
|
+
toJSON() {
|
|
336
|
+
return {
|
|
337
|
+
subjectDID: this.subjectDID,
|
|
338
|
+
credentials: Array.from(this.credentials.values()).map(c => c.toJSON()),
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Import wallet from backup
|
|
344
|
+
* @param {Object} json
|
|
345
|
+
* @returns {Promise<CredentialWallet>}
|
|
346
|
+
*/
|
|
347
|
+
static async fromJSON(json) {
|
|
348
|
+
const wallet = new CredentialWallet(json.subjectDID);
|
|
349
|
+
await wallet.init();
|
|
350
|
+
|
|
351
|
+
for (const credJson of json.credentials) {
|
|
352
|
+
const cred = await VerifiableCredential.fromJSON(credJson);
|
|
353
|
+
wallet.credentials.set(cred.id, cred);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return wallet;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
module.exports = {
|
|
361
|
+
VerifiableCredential,
|
|
362
|
+
CredentialWallet,
|
|
363
|
+
init,
|
|
364
|
+
poseidonHash,
|
|
365
|
+
randomFieldElement,
|
|
366
|
+
};
|