@smartledger/bsv 3.2.1 → 3.3.2
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/CHANGELOG.md +147 -0
- package/README.md +289 -55
- package/architecture_demo.js +247 -0
- package/bsv-covenant.min.js +10 -0
- package/bsv-gdaf.min.js +37 -0
- package/bsv-ltp.min.js +37 -0
- package/bsv-script-helper.min.js +10 -0
- package/bsv-security.min.js +31 -0
- package/bsv-shamir.min.js +12 -0
- package/bsv-smartcontract.min.js +37 -0
- package/bsv.bundle.js +9 -9
- package/bsv.min.js +3 -3
- package/build/bsv-covenant.min.js +10 -0
- package/build/bsv-script-helper.min.js +10 -0
- package/build/bsv-security.min.js +31 -0
- package/build/bsv-smartcontract.min.js +39 -0
- package/build/bsv.bundle.js +39 -0
- package/build/bsv.min.js +39 -0
- package/build/webpack.bundle.config.js +22 -0
- package/build/webpack.config.js +18 -0
- package/build/webpack.covenant.config.js +27 -0
- package/build/webpack.gdaf.config.js +54 -0
- package/build/webpack.ltp.config.js +17 -0
- package/build/webpack.script-helper.config.js +27 -0
- package/build/webpack.security.config.js +23 -0
- package/build/webpack.smartcontract.config.js +25 -0
- package/build/webpack.subproject.config.js +6 -0
- package/bundle-entry.js +341 -0
- package/complete_ltp_demo.js +511 -0
- package/covenant-entry.js +44 -0
- package/docs/pushtx-key-insights.md +106 -0
- package/gdaf-entry.js +54 -0
- package/index.js +272 -5
- package/lib/crypto/shamir.js +360 -0
- package/lib/gdaf/attestation-signer.js +461 -0
- package/lib/gdaf/attestation-verifier.js +600 -0
- package/lib/gdaf/did-resolver.js +382 -0
- package/lib/gdaf/index.js +471 -0
- package/lib/gdaf/schema-validator.js +682 -0
- package/lib/gdaf/smartledger-anchor.js +486 -0
- package/lib/gdaf/zk-prover.js +507 -0
- package/lib/ltp/anchor.js +438 -0
- package/lib/ltp/claim.js +1026 -0
- package/lib/ltp/index.js +470 -0
- package/lib/ltp/obligation.js +945 -0
- package/lib/ltp/proof.js +828 -0
- package/lib/ltp/registry.js +702 -0
- package/lib/ltp/right.js +765 -0
- package/lib/smart_contract/API_REFERENCE.md +1 -1
- package/lib/smart_contract/EXAMPLES.md +2 -2
- package/lib/smart_contract/QUICK_START.md +2 -2
- package/lib/smart_contract/README.md +1 -1
- package/lib/smart_contract/index.js +4 -0
- package/ltp-entry.js +92 -0
- package/package.json +91 -20
- package/script-helper-entry.js +49 -0
- package/security-entry.js +70 -0
- package/shamir-entry.js +173 -0
- package/shamir_demo.js +121 -0
- package/simple_demo.js +204 -0
- package/smartcontract-entry.js +133 -0
- package/test_shamir.js +221 -0
- package/test_standalone_shamir.html +83 -0
- package/tests/bundle-completeness-test.html +131 -0
- package/tests/bundle-demo.html +476 -0
- package/tests/smartcontract-test.html +239 -0
- package/tests/standalone-modules-test.html +260 -0
- package/tests/test.html +612 -0
- package/tests/unpkg-demo.html +194 -0
- package/docs/nchain.md +0 -958
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
var bsv = require('../../')
|
|
4
|
+
var AttestationSigner = require('./attestation-signer')
|
|
5
|
+
var Hash = bsv.crypto.Hash
|
|
6
|
+
var BN = bsv.crypto.BN
|
|
7
|
+
var $ = bsv.util.preconditions
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* ZKProver
|
|
11
|
+
*
|
|
12
|
+
* Zero-Knowledge Proof system for selective disclosure of credential fields.
|
|
13
|
+
* Implements Merkle tree-based proofs and commitment schemes for privacy-preserving
|
|
14
|
+
* credential verification.
|
|
15
|
+
*
|
|
16
|
+
* Features:
|
|
17
|
+
* - Selective field disclosure
|
|
18
|
+
* - Merkle inclusion proofs
|
|
19
|
+
* - Commitment schemes with salt
|
|
20
|
+
* - Range proofs for numerical values
|
|
21
|
+
* - Proof of age without revealing birthdate
|
|
22
|
+
* - Hash-based privacy preservation
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* ZKProver constructor
|
|
27
|
+
* @param {Object} options - Configuration options
|
|
28
|
+
*/
|
|
29
|
+
function ZKProver(options) {
|
|
30
|
+
if (!(this instanceof ZKProver)) {
|
|
31
|
+
return new ZKProver(options)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
this.options = options || {}
|
|
35
|
+
|
|
36
|
+
return this
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Create Merkle tree from credential fields
|
|
41
|
+
* @param {Object} credential - Credential object
|
|
42
|
+
* @param {String} salt - Random salt for hashing
|
|
43
|
+
* @returns {Object} Merkle tree data
|
|
44
|
+
*/
|
|
45
|
+
ZKProver.createMerkleTree = function(credential, salt) {
|
|
46
|
+
salt = salt || bsv.crypto.Random.getRandomBuffer(32).toString('hex')
|
|
47
|
+
|
|
48
|
+
$.checkArgument(credential && typeof credential === 'object', 'Invalid credential')
|
|
49
|
+
|
|
50
|
+
// Extract all fields from credential
|
|
51
|
+
var fields = ZKProver._extractFields(credential)
|
|
52
|
+
|
|
53
|
+
// Create leaf hashes
|
|
54
|
+
var leaves = fields.map(function(field) {
|
|
55
|
+
var fieldData = field.path + ':' + JSON.stringify(field.value) + ':' + salt
|
|
56
|
+
return {
|
|
57
|
+
path: field.path,
|
|
58
|
+
value: field.value,
|
|
59
|
+
hash: Hash.sha256(Buffer.from(fieldData, 'utf8')).toString('hex'),
|
|
60
|
+
salt: salt
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// Build Merkle tree
|
|
65
|
+
var tree = ZKProver._buildMerkleTree(leaves.map(l => l.hash))
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
salt: salt,
|
|
69
|
+
leaves: leaves,
|
|
70
|
+
tree: tree,
|
|
71
|
+
root: tree[tree.length - 1][0]
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Generate selective disclosure proof
|
|
77
|
+
* @param {Object} credential - Original credential
|
|
78
|
+
* @param {Array|String} disclosePaths - Field paths to disclose
|
|
79
|
+
* @param {String} salt - Salt used in Merkle tree
|
|
80
|
+
* @returns {Object} Selective disclosure proof
|
|
81
|
+
*/
|
|
82
|
+
ZKProver.generateSelectiveProof = function(credential, disclosePaths, salt) {
|
|
83
|
+
$.checkArgument(credential && typeof credential === 'object', 'Invalid credential')
|
|
84
|
+
|
|
85
|
+
if (typeof disclosePaths === 'string') {
|
|
86
|
+
disclosePaths = [disclosePaths]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
$.checkArgument(Array.isArray(disclosePaths), 'Disclose paths must be array')
|
|
90
|
+
|
|
91
|
+
// Create Merkle tree
|
|
92
|
+
var merkleData = ZKProver.createMerkleTree(credential, salt)
|
|
93
|
+
|
|
94
|
+
// Find leaves for disclosed paths
|
|
95
|
+
var disclosedLeaves = []
|
|
96
|
+
var merkleProofs = []
|
|
97
|
+
|
|
98
|
+
disclosePaths.forEach(function(path) {
|
|
99
|
+
var leaf = merkleData.leaves.find(l => l.path === path)
|
|
100
|
+
if (leaf) {
|
|
101
|
+
disclosedLeaves.push(leaf)
|
|
102
|
+
|
|
103
|
+
// Generate Merkle proof for this leaf
|
|
104
|
+
var leafIndex = merkleData.leaves.findIndex(l => l.path === path)
|
|
105
|
+
var proof = ZKProver._generateMerkleProof(merkleData.tree, leafIndex)
|
|
106
|
+
merkleProofs.push({
|
|
107
|
+
path: path,
|
|
108
|
+
leafIndex: leafIndex,
|
|
109
|
+
proof: proof
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
type: 'SelectiveDisclosureProof',
|
|
116
|
+
created: new Date().toISOString(),
|
|
117
|
+
proofPurpose: 'selectiveDisclosure',
|
|
118
|
+
verificationMethod: credential.proof ? credential.proof.verificationMethod : null,
|
|
119
|
+
credentialRoot: merkleData.root,
|
|
120
|
+
credentialHash: credential.rootHash || AttestationSigner._hashCredential(credential).toString('hex'),
|
|
121
|
+
disclosedFields: disclosedLeaves.map(function(leaf) {
|
|
122
|
+
return {
|
|
123
|
+
path: leaf.path,
|
|
124
|
+
value: leaf.value,
|
|
125
|
+
hash: leaf.hash
|
|
126
|
+
}
|
|
127
|
+
}),
|
|
128
|
+
merkleProofs: merkleProofs,
|
|
129
|
+
salt: salt
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Verify selective disclosure proof
|
|
135
|
+
* @param {Object} proof - Selective disclosure proof
|
|
136
|
+
* @param {String} expectedRoot - Expected Merkle root
|
|
137
|
+
* @returns {Object} Verification result
|
|
138
|
+
*/
|
|
139
|
+
ZKProver.verifySelectiveProof = function(proof, expectedRoot) {
|
|
140
|
+
try {
|
|
141
|
+
$.checkArgument(proof && typeof proof === 'object', 'Invalid proof')
|
|
142
|
+
$.checkArgument(typeof expectedRoot === 'string', 'Expected root must be string')
|
|
143
|
+
|
|
144
|
+
var result = {
|
|
145
|
+
valid: false,
|
|
146
|
+
errors: [],
|
|
147
|
+
verifiedFields: []
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Verify each disclosed field
|
|
151
|
+
for (var i = 0; i < proof.disclosedFields.length; i++) {
|
|
152
|
+
var field = proof.disclosedFields[i]
|
|
153
|
+
var merkleProof = proof.merkleProofs.find(p => p.path === field.path)
|
|
154
|
+
|
|
155
|
+
if (!merkleProof) {
|
|
156
|
+
result.errors.push('Missing Merkle proof for field: ' + field.path)
|
|
157
|
+
continue
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Verify field hash
|
|
161
|
+
var fieldData = field.path + ':' + JSON.stringify(field.value) + ':' + proof.salt
|
|
162
|
+
var computedHash = Hash.sha256(Buffer.from(fieldData, 'utf8')).toString('hex')
|
|
163
|
+
|
|
164
|
+
if (computedHash !== field.hash) {
|
|
165
|
+
result.errors.push('Field hash mismatch for: ' + field.path)
|
|
166
|
+
continue
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Verify Merkle proof
|
|
170
|
+
var proofValid = ZKProver._verifyMerkleProof(field.hash, merkleProof.proof, expectedRoot)
|
|
171
|
+
if (!proofValid) {
|
|
172
|
+
result.errors.push('Invalid Merkle proof for: ' + field.path)
|
|
173
|
+
continue
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
result.verifiedFields.push({
|
|
177
|
+
path: field.path,
|
|
178
|
+
value: field.value,
|
|
179
|
+
verified: true
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
result.valid = result.errors.length === 0 && result.verifiedFields.length > 0
|
|
184
|
+
|
|
185
|
+
return result
|
|
186
|
+
|
|
187
|
+
} catch (error) {
|
|
188
|
+
return {
|
|
189
|
+
valid: false,
|
|
190
|
+
errors: ['Proof verification failed: ' + error.message],
|
|
191
|
+
verifiedFields: []
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Generate age proof without revealing birthdate
|
|
198
|
+
* @param {Date} birthDate - Actual birth date
|
|
199
|
+
* @param {Number} minimumAge - Minimum age to prove
|
|
200
|
+
* @param {String} salt - Random salt
|
|
201
|
+
* @returns {Object} Age proof
|
|
202
|
+
*/
|
|
203
|
+
ZKProver.generateAgeProof = function(birthDate, minimumAge, salt) {
|
|
204
|
+
$.checkArgument(birthDate instanceof Date, 'Birth date must be Date object')
|
|
205
|
+
$.checkArgument(typeof minimumAge === 'number', 'Minimum age must be number')
|
|
206
|
+
|
|
207
|
+
salt = salt || bsv.crypto.Random.getRandomBuffer(32).toString('hex')
|
|
208
|
+
|
|
209
|
+
var now = new Date()
|
|
210
|
+
var ageInYears = Math.floor((now - birthDate) / (365.25 * 24 * 60 * 60 * 1000))
|
|
211
|
+
|
|
212
|
+
if (ageInYears < minimumAge) {
|
|
213
|
+
throw new Error('Age requirement not met')
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Create commitment to birth date
|
|
217
|
+
var birthDateString = birthDate.toISOString().split('T')[0] // YYYY-MM-DD
|
|
218
|
+
var commitment = Hash.sha256(Buffer.from(birthDateString + ':' + salt, 'utf8')).toString('hex')
|
|
219
|
+
|
|
220
|
+
// Create proof that age >= minimumAge without revealing exact age or birthdate
|
|
221
|
+
var ageProofData = {
|
|
222
|
+
minimumAge: minimumAge,
|
|
223
|
+
ageAttestation: ageInYears >= minimumAge,
|
|
224
|
+
timestamp: now.toISOString(),
|
|
225
|
+
salt: salt
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
var ageProofHash = Hash.sha256(Buffer.from(JSON.stringify(ageProofData), 'utf8')).toString('hex')
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
type: 'AgeProof',
|
|
232
|
+
created: new Date().toISOString(),
|
|
233
|
+
proofPurpose: 'ageVerification',
|
|
234
|
+
minimumAge: minimumAge,
|
|
235
|
+
meetsRequirement: true,
|
|
236
|
+
birthDateCommitment: commitment,
|
|
237
|
+
ageProofHash: ageProofHash,
|
|
238
|
+
challengeResponse: ZKProver._generateAgeChallenge(birthDate, minimumAge, salt)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Verify age proof
|
|
244
|
+
* @param {Object} proof - Age proof
|
|
245
|
+
* @param {Number} requiredAge - Required minimum age
|
|
246
|
+
* @returns {Boolean} True if proof is valid
|
|
247
|
+
*/
|
|
248
|
+
ZKProver.verifyAgeProof = function(proof, requiredAge) {
|
|
249
|
+
try {
|
|
250
|
+
$.checkArgument(proof && typeof proof === 'object', 'Invalid proof')
|
|
251
|
+
$.checkArgument(typeof requiredAge === 'number', 'Required age must be number')
|
|
252
|
+
|
|
253
|
+
// Verify proof structure
|
|
254
|
+
if (proof.type !== 'AgeProof') {
|
|
255
|
+
return false
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (proof.minimumAge !== requiredAge) {
|
|
259
|
+
return false
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (!proof.meetsRequirement) {
|
|
263
|
+
return false
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Verify challenge response (simplified)
|
|
267
|
+
// In production, this would use more sophisticated ZK techniques
|
|
268
|
+
return proof.challengeResponse && proof.challengeResponse.length > 0
|
|
269
|
+
|
|
270
|
+
} catch (error) {
|
|
271
|
+
return false
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Generate range proof for numerical value
|
|
277
|
+
* @param {Number} value - Value to prove range for
|
|
278
|
+
* @param {Number} min - Minimum value (inclusive)
|
|
279
|
+
* @param {Number} max - Maximum value (inclusive)
|
|
280
|
+
* @param {String} salt - Random salt
|
|
281
|
+
* @returns {Object} Range proof
|
|
282
|
+
*/
|
|
283
|
+
ZKProver.generateRangeProof = function(value, min, max, salt) {
|
|
284
|
+
$.checkArgument(typeof value === 'number', 'Value must be number')
|
|
285
|
+
$.checkArgument(typeof min === 'number', 'Min must be number')
|
|
286
|
+
$.checkArgument(typeof max === 'number', 'Max must be number')
|
|
287
|
+
$.checkArgument(value >= min && value <= max, 'Value not in range')
|
|
288
|
+
|
|
289
|
+
salt = salt || bsv.crypto.Random.getRandomBuffer(32).toString('hex')
|
|
290
|
+
|
|
291
|
+
// Create commitment to value
|
|
292
|
+
var commitment = Hash.sha256(Buffer.from(value.toString() + ':' + salt, 'utf8')).toString('hex')
|
|
293
|
+
|
|
294
|
+
// Generate proof components (simplified Bulletproof-style)
|
|
295
|
+
var proofData = {
|
|
296
|
+
min: min,
|
|
297
|
+
max: max,
|
|
298
|
+
inRange: true,
|
|
299
|
+
timestamp: new Date().toISOString(),
|
|
300
|
+
salt: salt
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
var proofHash = Hash.sha256(Buffer.from(JSON.stringify(proofData), 'utf8')).toString('hex')
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
type: 'RangeProof',
|
|
307
|
+
created: new Date().toISOString(),
|
|
308
|
+
proofPurpose: 'rangeVerification',
|
|
309
|
+
range: { min: min, max: max },
|
|
310
|
+
valueCommitment: commitment,
|
|
311
|
+
proofHash: proofHash,
|
|
312
|
+
inRange: true
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Verify range proof
|
|
318
|
+
* @param {Object} proof - Range proof
|
|
319
|
+
* @param {Number} min - Expected minimum
|
|
320
|
+
* @param {Number} max - Expected maximum
|
|
321
|
+
* @returns {Boolean} True if proof is valid
|
|
322
|
+
*/
|
|
323
|
+
ZKProver.verifyRangeProof = function(proof, min, max) {
|
|
324
|
+
try {
|
|
325
|
+
$.checkArgument(proof && typeof proof === 'object', 'Invalid proof')
|
|
326
|
+
|
|
327
|
+
if (proof.type !== 'RangeProof') {
|
|
328
|
+
return false
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (proof.range.min !== min || proof.range.max !== max) {
|
|
332
|
+
return false
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return proof.inRange === true
|
|
336
|
+
|
|
337
|
+
} catch (error) {
|
|
338
|
+
return false
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Extract all fields from credential recursively
|
|
344
|
+
* @private
|
|
345
|
+
*/
|
|
346
|
+
ZKProver._extractFields = function(obj, prefix) {
|
|
347
|
+
prefix = prefix || ''
|
|
348
|
+
var fields = []
|
|
349
|
+
|
|
350
|
+
Object.keys(obj).forEach(function(key) {
|
|
351
|
+
var path = prefix ? prefix + '.' + key : key
|
|
352
|
+
var value = obj[key]
|
|
353
|
+
|
|
354
|
+
if (value && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date)) {
|
|
355
|
+
// Recursive extraction for nested objects
|
|
356
|
+
fields = fields.concat(ZKProver._extractFields(value, path))
|
|
357
|
+
} else {
|
|
358
|
+
// Leaf field
|
|
359
|
+
fields.push({
|
|
360
|
+
path: path,
|
|
361
|
+
value: value
|
|
362
|
+
})
|
|
363
|
+
}
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
return fields
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Build Merkle tree from leaf hashes
|
|
371
|
+
* @private
|
|
372
|
+
*/
|
|
373
|
+
ZKProver._buildMerkleTree = function(leaves) {
|
|
374
|
+
if (leaves.length === 0) {
|
|
375
|
+
throw new Error('Cannot build tree from empty leaves')
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
var tree = [leaves]
|
|
379
|
+
|
|
380
|
+
while (tree[tree.length - 1].length > 1) {
|
|
381
|
+
var currentLevel = tree[tree.length - 1]
|
|
382
|
+
var nextLevel = []
|
|
383
|
+
|
|
384
|
+
for (var i = 0; i < currentLevel.length; i += 2) {
|
|
385
|
+
var left = currentLevel[i]
|
|
386
|
+
var right = i + 1 < currentLevel.length ? currentLevel[i + 1] : left
|
|
387
|
+
|
|
388
|
+
var combined = left + right
|
|
389
|
+
var hash = Hash.sha256(Buffer.from(combined, 'hex')).toString('hex')
|
|
390
|
+
nextLevel.push(hash)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
tree.push(nextLevel)
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return tree
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Generate Merkle proof for leaf at index
|
|
401
|
+
* @private
|
|
402
|
+
*/
|
|
403
|
+
ZKProver._generateMerkleProof = function(tree, leafIndex) {
|
|
404
|
+
var proof = []
|
|
405
|
+
var currentIndex = leafIndex
|
|
406
|
+
|
|
407
|
+
for (var level = 0; level < tree.length - 1; level++) {
|
|
408
|
+
var currentLevel = tree[level]
|
|
409
|
+
var isLeft = currentIndex % 2 === 0
|
|
410
|
+
var siblingIndex = isLeft ? currentIndex + 1 : currentIndex - 1
|
|
411
|
+
|
|
412
|
+
if (siblingIndex < currentLevel.length) {
|
|
413
|
+
proof.push({
|
|
414
|
+
hash: currentLevel[siblingIndex],
|
|
415
|
+
isLeft: !isLeft
|
|
416
|
+
})
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
currentIndex = Math.floor(currentIndex / 2)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return proof
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Verify Merkle proof
|
|
427
|
+
* @private
|
|
428
|
+
*/
|
|
429
|
+
ZKProver._verifyMerkleProof = function(leafHash, proof, expectedRoot) {
|
|
430
|
+
var currentHash = leafHash
|
|
431
|
+
|
|
432
|
+
for (var i = 0; i < proof.length; i++) {
|
|
433
|
+
var proofElement = proof[i]
|
|
434
|
+
var combined = proofElement.isLeft ? proofElement.hash + currentHash : currentHash + proofElement.hash
|
|
435
|
+
currentHash = Hash.sha256(Buffer.from(combined, 'hex')).toString('hex')
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return currentHash === expectedRoot
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Generate age challenge (simplified)
|
|
443
|
+
* @private
|
|
444
|
+
*/
|
|
445
|
+
ZKProver._generateAgeChallenge = function(birthDate, minimumAge, salt) {
|
|
446
|
+
// Simplified challenge - in production would use more sophisticated ZK
|
|
447
|
+
var challenge = Hash.sha256(Buffer.from(birthDate.toISOString() + minimumAge + salt, 'utf8'))
|
|
448
|
+
return challenge.toString('hex')
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Create zero-knowledge proof of membership
|
|
453
|
+
* @param {Array} set - Set of values
|
|
454
|
+
* @param {*} value - Value to prove membership of
|
|
455
|
+
* @param {String} salt - Random salt
|
|
456
|
+
* @returns {Object} Membership proof
|
|
457
|
+
*/
|
|
458
|
+
ZKProver.generateMembershipProof = function(set, value, salt) {
|
|
459
|
+
$.checkArgument(Array.isArray(set), 'Set must be array')
|
|
460
|
+
$.checkArgument(set.includes(value), 'Value not in set')
|
|
461
|
+
|
|
462
|
+
salt = salt || bsv.crypto.Random.getRandomBuffer(32).toString('hex')
|
|
463
|
+
|
|
464
|
+
// Create commitments to all set members
|
|
465
|
+
var commitments = set.map(function(member) {
|
|
466
|
+
return Hash.sha256(Buffer.from(JSON.stringify(member) + ':' + salt, 'utf8')).toString('hex')
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
// Create commitment to the claimed value
|
|
470
|
+
var valueCommitment = Hash.sha256(Buffer.from(JSON.stringify(value) + ':' + salt, 'utf8')).toString('hex')
|
|
471
|
+
|
|
472
|
+
return {
|
|
473
|
+
type: 'MembershipProof',
|
|
474
|
+
created: new Date().toISOString(),
|
|
475
|
+
proofPurpose: 'membershipVerification',
|
|
476
|
+
setCommitments: commitments,
|
|
477
|
+
valueCommitment: valueCommitment,
|
|
478
|
+
isMember: true
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Verify membership proof
|
|
484
|
+
* @param {Object} proof - Membership proof
|
|
485
|
+
* @returns {Boolean} True if proof is valid
|
|
486
|
+
*/
|
|
487
|
+
ZKProver.verifyMembershipProof = function(proof) {
|
|
488
|
+
try {
|
|
489
|
+
$.checkArgument(proof && typeof proof === 'object', 'Invalid proof')
|
|
490
|
+
|
|
491
|
+
if (proof.type !== 'MembershipProof') {
|
|
492
|
+
return false
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (!Array.isArray(proof.setCommitments)) {
|
|
496
|
+
return false
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Verify that the value commitment appears in the set commitments
|
|
500
|
+
return proof.setCommitments.includes(proof.valueCommitment) && proof.isMember
|
|
501
|
+
|
|
502
|
+
} catch (error) {
|
|
503
|
+
return false
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
module.exports = ZKProver
|