smartledger-bsv 3.2.1 → 3.3.1
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
package/lib/ltp/proof.js
ADDED
|
@@ -0,0 +1,828 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
var bsv = require('../../')
|
|
4
|
+
var Hash = bsv.crypto.Hash
|
|
5
|
+
var ECDSA = bsv.crypto.ECDSA
|
|
6
|
+
var Signature = bsv.crypto.Signature
|
|
7
|
+
var PrivateKey = bsv.PrivateKey
|
|
8
|
+
var $ = bsv.util.preconditions
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Legal Token Protocol - Proof Generation Primitives
|
|
12
|
+
*
|
|
13
|
+
* Provides primitives for cryptographic proof generation and verification
|
|
14
|
+
* without direct blockchain publishing. External systems handle proof storage
|
|
15
|
+
* and verification anchoring operations.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
var LTPProof = {
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Prepare signature proof for external verification
|
|
22
|
+
* @param {Object} token - Token to sign
|
|
23
|
+
* @param {PrivateKey} privateKey - Signing private key
|
|
24
|
+
* @param {Object} options - Signing options
|
|
25
|
+
* @returns {Object} Prepared signature proof data
|
|
26
|
+
*/
|
|
27
|
+
prepareSignatureProof: function(token, privateKey, options) {
|
|
28
|
+
options = options || {}
|
|
29
|
+
|
|
30
|
+
$.checkArgument(token && typeof token === 'object', 'Invalid token')
|
|
31
|
+
$.checkArgument(privateKey instanceof PrivateKey, 'Invalid private key')
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
// Canonicalize token for signing
|
|
35
|
+
var canonical = this._canonicalizeForSigning(token)
|
|
36
|
+
var tokenHash = Hash.sha256(Buffer.from(canonical))
|
|
37
|
+
|
|
38
|
+
// Create ECDSA signature
|
|
39
|
+
var ecdsa = new ECDSA()
|
|
40
|
+
ecdsa.hashbuf = tokenHash
|
|
41
|
+
ecdsa.privkey = privateKey
|
|
42
|
+
ecdsa.pubkey = privateKey.toPublicKey()
|
|
43
|
+
|
|
44
|
+
ecdsa.sign()
|
|
45
|
+
var signature = ecdsa.sig
|
|
46
|
+
|
|
47
|
+
// Create proof object
|
|
48
|
+
var proof = {
|
|
49
|
+
type: 'EcdsaSecp256k1Signature2019',
|
|
50
|
+
created: new Date().toISOString(),
|
|
51
|
+
verificationMethod: 'did:smartledger:' + privateKey.toPublicKey().toString() + '#keys-1',
|
|
52
|
+
proofPurpose: options.purpose || 'assertionMethod',
|
|
53
|
+
jws: this._createJWS(tokenHash, signature),
|
|
54
|
+
proofValue: signature.toDER().toString('hex')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
success: true,
|
|
59
|
+
proof: proof,
|
|
60
|
+
tokenHash: tokenHash.toString('hex'),
|
|
61
|
+
canonical: canonical,
|
|
62
|
+
signingKey: {
|
|
63
|
+
publicKey: privateKey.toPublicKey().toString(),
|
|
64
|
+
keyId: 'did:smartledger:' + privateKey.toPublicKey().toString() + '#keys-1'
|
|
65
|
+
},
|
|
66
|
+
externalOperations: {
|
|
67
|
+
storeProof: {
|
|
68
|
+
endpoint: 'POST /proofs/signature',
|
|
69
|
+
data: {
|
|
70
|
+
tokenHash: tokenHash.toString('hex'),
|
|
71
|
+
proof: proof,
|
|
72
|
+
signingKey: privateKey.toPublicKey().toString()
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
verifyProof: {
|
|
76
|
+
endpoint: 'POST /proofs/verify-signature',
|
|
77
|
+
data: {
|
|
78
|
+
tokenHash: tokenHash.toString('hex'),
|
|
79
|
+
signature: signature.toDER().toString('hex'),
|
|
80
|
+
publicKey: privateKey.toPublicKey().toString()
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
} catch (error) {
|
|
87
|
+
return {
|
|
88
|
+
success: false,
|
|
89
|
+
error: error.message
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Prepare signature verification for external processing
|
|
96
|
+
* @param {Object} token - Token with proof
|
|
97
|
+
* @param {String} publicKey - Signer's public key (hex)
|
|
98
|
+
* @returns {Object} Prepared verification data
|
|
99
|
+
*/
|
|
100
|
+
prepareSignatureVerification: function(token, publicKey) {
|
|
101
|
+
try {
|
|
102
|
+
$.checkArgument(token && typeof token === 'object', 'Invalid token')
|
|
103
|
+
$.checkArgument(typeof publicKey === 'string', 'Public key must be string')
|
|
104
|
+
|
|
105
|
+
if (!token.proof) {
|
|
106
|
+
return {
|
|
107
|
+
success: false,
|
|
108
|
+
error: 'No proof found in token'
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Recreate canonical representation
|
|
113
|
+
var canonical = this._canonicalizeForSigning(token)
|
|
114
|
+
var tokenHash = Hash.sha256(Buffer.from(canonical))
|
|
115
|
+
|
|
116
|
+
// Parse signature from proof
|
|
117
|
+
var signature = Signature.fromDER(Buffer.from(token.proof.proofValue, 'hex'))
|
|
118
|
+
|
|
119
|
+
// Prepare verification data
|
|
120
|
+
var verificationData = {
|
|
121
|
+
tokenHash: tokenHash.toString('hex'),
|
|
122
|
+
signature: signature.toDER().toString('hex'),
|
|
123
|
+
publicKey: publicKey,
|
|
124
|
+
proof: token.proof,
|
|
125
|
+
canonical: canonical
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Perform local verification for immediate feedback
|
|
129
|
+
var ecdsa = new ECDSA()
|
|
130
|
+
ecdsa.hashbuf = tokenHash
|
|
131
|
+
ecdsa.sig = signature
|
|
132
|
+
ecdsa.pubkey = bsv.PublicKey.fromString(publicKey)
|
|
133
|
+
|
|
134
|
+
var isValid = ecdsa.verify()
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
success: true,
|
|
138
|
+
verification: {
|
|
139
|
+
valid: isValid,
|
|
140
|
+
tokenHash: tokenHash.toString('hex'),
|
|
141
|
+
signatureValid: isValid,
|
|
142
|
+
publicKey: publicKey,
|
|
143
|
+
verifiedAt: new Date().toISOString()
|
|
144
|
+
},
|
|
145
|
+
verificationData: verificationData,
|
|
146
|
+
externalOperations: {
|
|
147
|
+
recordVerification: {
|
|
148
|
+
endpoint: 'POST /proofs/verification-record',
|
|
149
|
+
data: {
|
|
150
|
+
tokenHash: tokenHash.toString('hex'),
|
|
151
|
+
result: isValid,
|
|
152
|
+
publicKey: publicKey,
|
|
153
|
+
verifiedAt: new Date().toISOString()
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
auditVerification: {
|
|
157
|
+
endpoint: 'POST /audit/proof-verification',
|
|
158
|
+
data: {
|
|
159
|
+
tokenId: token.id,
|
|
160
|
+
proofType: token.proof.type,
|
|
161
|
+
result: isValid,
|
|
162
|
+
verifier: publicKey
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
} catch (error) {
|
|
169
|
+
return {
|
|
170
|
+
success: false,
|
|
171
|
+
error: error.message
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Prepare selective disclosure proof for external processing
|
|
178
|
+
* @param {Object} token - Original token
|
|
179
|
+
* @param {Array} revealedFields - Fields to reveal
|
|
180
|
+
* @param {String} nonce - Proof nonce
|
|
181
|
+
* @returns {Object} Prepared selective disclosure data
|
|
182
|
+
*/
|
|
183
|
+
prepareSelectiveDisclosure: function(token, revealedFields, nonce) {
|
|
184
|
+
$.checkArgument(token && typeof token === 'object', 'Invalid token')
|
|
185
|
+
$.checkArgument(Array.isArray(revealedFields), 'Revealed fields must be array')
|
|
186
|
+
$.checkArgument(typeof nonce === 'string', 'Nonce must be string')
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
// Create field commitments
|
|
190
|
+
var fieldCommitments = this._createFieldCommitments(token, nonce)
|
|
191
|
+
|
|
192
|
+
// Create Merkle tree of field commitments
|
|
193
|
+
var merkleTree = this._createMerkleTree(fieldCommitments)
|
|
194
|
+
|
|
195
|
+
// Create disclosure for revealed fields
|
|
196
|
+
var disclosures = []
|
|
197
|
+
var merkleProofs = []
|
|
198
|
+
|
|
199
|
+
revealedFields.forEach(function(fieldPath) {
|
|
200
|
+
var value = this._getNestedValue(token, fieldPath)
|
|
201
|
+
var commitment = this._findFieldCommitment(fieldCommitments, fieldPath)
|
|
202
|
+
|
|
203
|
+
if (commitment) {
|
|
204
|
+
disclosures.push({
|
|
205
|
+
path: fieldPath,
|
|
206
|
+
value: value,
|
|
207
|
+
commitment: commitment.hash,
|
|
208
|
+
salt: commitment.salt
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
// Generate Merkle proof for this field
|
|
212
|
+
var merkleProof = this._generateMerkleProof(merkleTree, commitment.index)
|
|
213
|
+
merkleProofs.push({
|
|
214
|
+
path: fieldPath,
|
|
215
|
+
leafIndex: commitment.index,
|
|
216
|
+
proof: merkleProof
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
}.bind(this))
|
|
220
|
+
|
|
221
|
+
var proof = {
|
|
222
|
+
type: 'LegalTokenSelectiveDisclosure',
|
|
223
|
+
created: new Date().toISOString(),
|
|
224
|
+
proofPurpose: 'selectiveDisclosure',
|
|
225
|
+
verificationMethod: token.proof ? token.proof.verificationMethod : null,
|
|
226
|
+
nonce: nonce,
|
|
227
|
+
merkleRoot: merkleTree.root,
|
|
228
|
+
disclosures: disclosures,
|
|
229
|
+
merkleProofs: merkleProofs,
|
|
230
|
+
totalFields: fieldCommitments.length
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
success: true,
|
|
235
|
+
proof: proof,
|
|
236
|
+
hiddenFieldCount: fieldCommitments.length - disclosures.length,
|
|
237
|
+
revealedFieldCount: disclosures.length,
|
|
238
|
+
externalOperations: {
|
|
239
|
+
storeDisclosure: {
|
|
240
|
+
endpoint: 'POST /proofs/selective-disclosure',
|
|
241
|
+
data: {
|
|
242
|
+
tokenId: token.id,
|
|
243
|
+
proof: proof,
|
|
244
|
+
merkleRoot: merkleTree.root
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
verifyDisclosure: {
|
|
248
|
+
endpoint: 'POST /proofs/verify-disclosure',
|
|
249
|
+
data: {
|
|
250
|
+
proof: proof,
|
|
251
|
+
expectedNonce: nonce
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
auditDisclosure: {
|
|
255
|
+
endpoint: 'POST /audit/selective-disclosure',
|
|
256
|
+
data: {
|
|
257
|
+
tokenId: token.id,
|
|
258
|
+
revealedFields: revealedFields,
|
|
259
|
+
totalFields: fieldCommitments.length,
|
|
260
|
+
disclosedAt: new Date().toISOString()
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
} catch (error) {
|
|
267
|
+
return {
|
|
268
|
+
success: false,
|
|
269
|
+
error: error.message
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Prepare selective disclosure verification for external processing
|
|
276
|
+
* @param {Object} proof - Selective disclosure proof
|
|
277
|
+
* @param {String} expectedNonce - Expected nonce
|
|
278
|
+
* @returns {Object} Prepared verification data
|
|
279
|
+
*/
|
|
280
|
+
prepareSelectiveDisclosureVerification: function(proof, expectedNonce) {
|
|
281
|
+
try {
|
|
282
|
+
$.checkArgument(proof && typeof proof === 'object', 'Invalid proof')
|
|
283
|
+
$.checkArgument(typeof expectedNonce === 'string', 'Expected nonce must be string')
|
|
284
|
+
|
|
285
|
+
if (proof.nonce !== expectedNonce) {
|
|
286
|
+
return {
|
|
287
|
+
success: false,
|
|
288
|
+
error: 'Nonce mismatch'
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
var validDisclosures = []
|
|
293
|
+
var errors = []
|
|
294
|
+
|
|
295
|
+
// Verify each disclosure
|
|
296
|
+
proof.disclosures.forEach(function(disclosure, index) {
|
|
297
|
+
var merkleProof = proof.merkleProofs[index]
|
|
298
|
+
|
|
299
|
+
// Verify field commitment
|
|
300
|
+
var expectedCommitment = this._hashField(disclosure.path, disclosure.value, disclosure.salt)
|
|
301
|
+
if (expectedCommitment !== disclosure.commitment) {
|
|
302
|
+
errors.push('Invalid commitment for field: ' + disclosure.path)
|
|
303
|
+
return
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Verify Merkle proof
|
|
307
|
+
var isValidProof = this._verifyMerkleProof(
|
|
308
|
+
disclosure.commitment,
|
|
309
|
+
merkleProof.leafIndex,
|
|
310
|
+
merkleProof.proof,
|
|
311
|
+
proof.merkleRoot
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
if (isValidProof) {
|
|
315
|
+
validDisclosures.push({
|
|
316
|
+
path: disclosure.path,
|
|
317
|
+
value: disclosure.value,
|
|
318
|
+
verified: true
|
|
319
|
+
})
|
|
320
|
+
} else {
|
|
321
|
+
errors.push('Invalid Merkle proof for field: ' + disclosure.path)
|
|
322
|
+
}
|
|
323
|
+
}.bind(this))
|
|
324
|
+
|
|
325
|
+
var verification = {
|
|
326
|
+
valid: errors.length === 0,
|
|
327
|
+
errors: errors,
|
|
328
|
+
disclosures: validDisclosures,
|
|
329
|
+
merkleRoot: proof.merkleRoot,
|
|
330
|
+
totalFields: proof.totalFields,
|
|
331
|
+
verifiedAt: new Date().toISOString()
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
success: true,
|
|
336
|
+
verification: verification,
|
|
337
|
+
externalOperations: {
|
|
338
|
+
recordVerification: {
|
|
339
|
+
endpoint: 'POST /proofs/disclosure-verification',
|
|
340
|
+
data: {
|
|
341
|
+
proofId: proof.id || 'unknown',
|
|
342
|
+
result: verification.valid,
|
|
343
|
+
verifiedFields: validDisclosures.length,
|
|
344
|
+
totalFields: proof.totalFields,
|
|
345
|
+
verifiedAt: verification.verifiedAt
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
auditVerification: {
|
|
349
|
+
endpoint: 'POST /audit/disclosure-verification',
|
|
350
|
+
data: {
|
|
351
|
+
merkleRoot: proof.merkleRoot,
|
|
352
|
+
disclosuresVerified: validDisclosures.length,
|
|
353
|
+
errors: errors.length,
|
|
354
|
+
result: verification.valid
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
} catch (error) {
|
|
361
|
+
return {
|
|
362
|
+
success: false,
|
|
363
|
+
error: error.message
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Prepare legal validity proof for external compliance checking
|
|
370
|
+
* @param {Object} token - Legal token
|
|
371
|
+
* @param {Object} jurisdiction - Jurisdiction rules
|
|
372
|
+
* @param {String} nonce - Proof nonce
|
|
373
|
+
* @returns {Object} Prepared legal validity data
|
|
374
|
+
*/
|
|
375
|
+
prepareLegalValidityProof: function(token, jurisdiction, nonce) {
|
|
376
|
+
$.checkArgument(token && typeof token === 'object', 'Invalid token')
|
|
377
|
+
$.checkArgument(jurisdiction && typeof jurisdiction === 'object', 'Invalid jurisdiction')
|
|
378
|
+
$.checkArgument(typeof nonce === 'string', 'Nonce must be string')
|
|
379
|
+
|
|
380
|
+
try {
|
|
381
|
+
// Check legal requirements
|
|
382
|
+
var requirements = jurisdiction.requirements || []
|
|
383
|
+
var validityChecks = []
|
|
384
|
+
var isValid = true
|
|
385
|
+
|
|
386
|
+
requirements.forEach(function(requirement) {
|
|
387
|
+
var check = this._checkLegalRequirement(token, requirement)
|
|
388
|
+
validityChecks.push(check)
|
|
389
|
+
if (!check.satisfied) {
|
|
390
|
+
isValid = false
|
|
391
|
+
}
|
|
392
|
+
}.bind(this))
|
|
393
|
+
|
|
394
|
+
// Create proof
|
|
395
|
+
var proof = {
|
|
396
|
+
type: 'LegalValidityProof',
|
|
397
|
+
created: new Date().toISOString(),
|
|
398
|
+
proofPurpose: 'legalValidity',
|
|
399
|
+
jurisdiction: jurisdiction.code,
|
|
400
|
+
nonce: nonce,
|
|
401
|
+
valid: isValid,
|
|
402
|
+
checks: validityChecks,
|
|
403
|
+
complianceHash: this._hashCompliance(token, jurisdiction, nonce)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
success: true,
|
|
408
|
+
proof: proof,
|
|
409
|
+
valid: isValid,
|
|
410
|
+
complianceReport: {
|
|
411
|
+
jurisdiction: jurisdiction.code,
|
|
412
|
+
totalRequirements: requirements.length,
|
|
413
|
+
satisfiedRequirements: validityChecks.filter(c => c.satisfied).length,
|
|
414
|
+
failedRequirements: validityChecks.filter(c => !c.satisfied).length,
|
|
415
|
+
overallCompliance: isValid
|
|
416
|
+
},
|
|
417
|
+
externalOperations: {
|
|
418
|
+
submitCompliance: {
|
|
419
|
+
endpoint: 'POST /compliance/legal-validity',
|
|
420
|
+
data: {
|
|
421
|
+
tokenId: token.id,
|
|
422
|
+
jurisdiction: jurisdiction.code,
|
|
423
|
+
proof: proof,
|
|
424
|
+
complianceReport: {
|
|
425
|
+
jurisdiction: jurisdiction.code,
|
|
426
|
+
totalRequirements: requirements.length,
|
|
427
|
+
satisfiedRequirements: validityChecks.filter(c => c.satisfied).length,
|
|
428
|
+
failedRequirements: validityChecks.filter(c => !c.satisfied).length,
|
|
429
|
+
overallCompliance: isValid
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
auditCompliance: {
|
|
434
|
+
endpoint: 'POST /audit/compliance-check',
|
|
435
|
+
data: {
|
|
436
|
+
tokenId: token.id,
|
|
437
|
+
jurisdiction: jurisdiction.code,
|
|
438
|
+
result: isValid,
|
|
439
|
+
checkedAt: new Date().toISOString(),
|
|
440
|
+
requirements: validityChecks
|
|
441
|
+
}
|
|
442
|
+
},
|
|
443
|
+
notifyRegulator: isValid ? null : {
|
|
444
|
+
endpoint: 'POST /notifications/compliance-failure',
|
|
445
|
+
data: {
|
|
446
|
+
tokenId: token.id,
|
|
447
|
+
jurisdiction: jurisdiction.code,
|
|
448
|
+
failedRequirements: validityChecks.filter(c => !c.satisfied)
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
} catch (error) {
|
|
455
|
+
return {
|
|
456
|
+
success: false,
|
|
457
|
+
error: error.message
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
},
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Prepare zero-knowledge proof for external processing
|
|
464
|
+
* @param {Object} token - Token data
|
|
465
|
+
* @param {Object} statement - Statement to prove
|
|
466
|
+
* @param {String} nonce - Proof nonce
|
|
467
|
+
* @returns {Object} Prepared ZK proof data
|
|
468
|
+
*/
|
|
469
|
+
prepareZeroKnowledgeProof: function(token, statement, nonce) {
|
|
470
|
+
$.checkArgument(token && typeof token === 'object', 'Invalid token')
|
|
471
|
+
$.checkArgument(statement && typeof statement === 'object', 'Invalid statement')
|
|
472
|
+
$.checkArgument(typeof nonce === 'string', 'Nonce must be string')
|
|
473
|
+
|
|
474
|
+
try {
|
|
475
|
+
// Create commitment to token data
|
|
476
|
+
var tokenCommitment = Hash.sha256(Buffer.from(this._canonicalizeForSigning(token) + nonce)).toString('hex')
|
|
477
|
+
|
|
478
|
+
// Create statement proof (simplified - real ZK would use more sophisticated cryptography)
|
|
479
|
+
var statementProof = {
|
|
480
|
+
type: 'LegalTokenZKProof',
|
|
481
|
+
created: new Date().toISOString(),
|
|
482
|
+
proofPurpose: 'zeroKnowledge',
|
|
483
|
+
statement: {
|
|
484
|
+
claim: statement.claim || 'UNSPECIFIED',
|
|
485
|
+
predicate: statement.predicate || 'SATISFIES',
|
|
486
|
+
threshold: statement.threshold || null
|
|
487
|
+
},
|
|
488
|
+
commitment: tokenCommitment,
|
|
489
|
+
nonce: nonce,
|
|
490
|
+
proofData: this._generateZKProofData(token, statement, nonce)
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return {
|
|
494
|
+
success: true,
|
|
495
|
+
proof: statementProof,
|
|
496
|
+
commitment: tokenCommitment,
|
|
497
|
+
externalOperations: {
|
|
498
|
+
storeZKProof: {
|
|
499
|
+
endpoint: 'POST /proofs/zero-knowledge',
|
|
500
|
+
data: {
|
|
501
|
+
tokenId: token.id,
|
|
502
|
+
proof: statementProof,
|
|
503
|
+
statement: statement
|
|
504
|
+
}
|
|
505
|
+
},
|
|
506
|
+
verifyZKProof: {
|
|
507
|
+
endpoint: 'POST /proofs/verify-zk',
|
|
508
|
+
data: {
|
|
509
|
+
proof: statementProof,
|
|
510
|
+
expectedNonce: nonce
|
|
511
|
+
}
|
|
512
|
+
},
|
|
513
|
+
auditZKProof: {
|
|
514
|
+
endpoint: 'POST /audit/zk-proof',
|
|
515
|
+
data: {
|
|
516
|
+
tokenId: token.id,
|
|
517
|
+
statement: statement.claim,
|
|
518
|
+
proofGenerated: new Date().toISOString()
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
} catch (error) {
|
|
525
|
+
return {
|
|
526
|
+
success: false,
|
|
527
|
+
error: error.message
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
},
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Generate ZK proof data (simplified implementation)
|
|
534
|
+
* @private
|
|
535
|
+
*/
|
|
536
|
+
_generateZKProofData: function(token, statement, nonce) {
|
|
537
|
+
// Simplified ZK proof generation - real implementation would use more sophisticated cryptography
|
|
538
|
+
var proofData = {
|
|
539
|
+
challenge: Hash.sha256(Buffer.from(statement.claim + nonce)).toString('hex'),
|
|
540
|
+
response: Hash.sha256(Buffer.from(this._canonicalizeForSigning(token) + statement.claim)).toString('hex'),
|
|
541
|
+
witness: Hash.sha256(Buffer.from(nonce + token.id + statement.predicate)).toString('hex')
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
return proofData
|
|
545
|
+
},
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Create JWS signature
|
|
549
|
+
* @private
|
|
550
|
+
*/
|
|
551
|
+
_createJWS: function(hash, signature) {
|
|
552
|
+
var header = {
|
|
553
|
+
alg: 'ES256K',
|
|
554
|
+
typ: 'JWT'
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
var headerB64 = Buffer.from(JSON.stringify(header)).toString('base64url')
|
|
558
|
+
var payloadB64 = hash.toString('base64url')
|
|
559
|
+
var signatureB64 = signature.toDER().toString('base64url')
|
|
560
|
+
|
|
561
|
+
return headerB64 + '..' + signatureB64
|
|
562
|
+
},
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Canonicalize token for signing
|
|
566
|
+
* @private
|
|
567
|
+
*/
|
|
568
|
+
_canonicalizeForSigning: function(token) {
|
|
569
|
+
// Remove proof and hash fields
|
|
570
|
+
var signingToken = JSON.parse(JSON.stringify(token))
|
|
571
|
+
delete signingToken.proof
|
|
572
|
+
delete signingToken.tokenHash
|
|
573
|
+
|
|
574
|
+
// Sort keys recursively
|
|
575
|
+
var canonical = this._sortObjectKeys(signingToken)
|
|
576
|
+
|
|
577
|
+
return JSON.stringify(canonical)
|
|
578
|
+
},
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Create field commitments
|
|
582
|
+
* @private
|
|
583
|
+
*/
|
|
584
|
+
_createFieldCommitments: function(token, nonce) {
|
|
585
|
+
var commitments = []
|
|
586
|
+
var index = 0
|
|
587
|
+
|
|
588
|
+
this._traverseObject(token, '', function(path, value) {
|
|
589
|
+
if (path !== 'proof' && path !== 'tokenHash') {
|
|
590
|
+
var salt = Hash.sha256(Buffer.from(nonce + path + index)).toString('hex')
|
|
591
|
+
var hash = this._hashField(path, value, salt)
|
|
592
|
+
|
|
593
|
+
commitments.push({
|
|
594
|
+
path: path,
|
|
595
|
+
value: value,
|
|
596
|
+
salt: salt,
|
|
597
|
+
hash: hash,
|
|
598
|
+
index: index
|
|
599
|
+
})
|
|
600
|
+
|
|
601
|
+
index++
|
|
602
|
+
}
|
|
603
|
+
}.bind(this))
|
|
604
|
+
|
|
605
|
+
return commitments
|
|
606
|
+
},
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Hash field with salt
|
|
610
|
+
* @private
|
|
611
|
+
*/
|
|
612
|
+
_hashField: function(path, value, salt) {
|
|
613
|
+
var data = path + ':' + JSON.stringify(value) + ':' + salt
|
|
614
|
+
return Hash.sha256(Buffer.from(data)).toString('hex')
|
|
615
|
+
},
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Create Merkle tree from commitments
|
|
619
|
+
* @private
|
|
620
|
+
*/
|
|
621
|
+
_createMerkleTree: function(commitments) {
|
|
622
|
+
var leaves = commitments.map(function(c) { return c.hash })
|
|
623
|
+
|
|
624
|
+
if (leaves.length === 0) {
|
|
625
|
+
return { root: null, levels: [] }
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
var levels = [leaves]
|
|
629
|
+
var currentLevel = leaves
|
|
630
|
+
|
|
631
|
+
while (currentLevel.length > 1) {
|
|
632
|
+
var nextLevel = []
|
|
633
|
+
|
|
634
|
+
for (var i = 0; i < currentLevel.length; i += 2) {
|
|
635
|
+
var left = currentLevel[i]
|
|
636
|
+
var right = currentLevel[i + 1] || left
|
|
637
|
+
|
|
638
|
+
var combined = Buffer.concat([
|
|
639
|
+
Buffer.from(left, 'hex'),
|
|
640
|
+
Buffer.from(right, 'hex')
|
|
641
|
+
])
|
|
642
|
+
|
|
643
|
+
var hash = Hash.sha256(combined).toString('hex')
|
|
644
|
+
nextLevel.push(hash)
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
levels.push(nextLevel)
|
|
648
|
+
currentLevel = nextLevel
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
return {
|
|
652
|
+
root: currentLevel[0],
|
|
653
|
+
levels: levels
|
|
654
|
+
}
|
|
655
|
+
},
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Generate Merkle proof for leaf
|
|
659
|
+
* @private
|
|
660
|
+
*/
|
|
661
|
+
_generateMerkleProof: function(tree, leafIndex) {
|
|
662
|
+
var proof = []
|
|
663
|
+
var index = leafIndex
|
|
664
|
+
|
|
665
|
+
for (var level = 0; level < tree.levels.length - 1; level++) {
|
|
666
|
+
var currentLevel = tree.levels[level]
|
|
667
|
+
var isLeft = index % 2 === 0
|
|
668
|
+
var siblingIndex = isLeft ? index + 1 : index - 1
|
|
669
|
+
|
|
670
|
+
if (siblingIndex < currentLevel.length) {
|
|
671
|
+
proof.push({
|
|
672
|
+
hash: currentLevel[siblingIndex],
|
|
673
|
+
isLeft: !isLeft
|
|
674
|
+
})
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
index = Math.floor(index / 2)
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
return proof
|
|
681
|
+
},
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Verify Merkle proof
|
|
685
|
+
* @private
|
|
686
|
+
*/
|
|
687
|
+
_verifyMerkleProof: function(leaf, leafIndex, proof, expectedRoot) {
|
|
688
|
+
var currentHash = leaf
|
|
689
|
+
var currentIndex = leafIndex
|
|
690
|
+
|
|
691
|
+
for (var i = 0; i < proof.length; i++) {
|
|
692
|
+
var step = proof[i]
|
|
693
|
+
var isLeft = currentIndex % 2 === 0
|
|
694
|
+
|
|
695
|
+
var combined
|
|
696
|
+
if (step.isLeft) {
|
|
697
|
+
combined = Buffer.concat([
|
|
698
|
+
Buffer.from(step.hash, 'hex'),
|
|
699
|
+
Buffer.from(currentHash, 'hex')
|
|
700
|
+
])
|
|
701
|
+
} else {
|
|
702
|
+
combined = Buffer.concat([
|
|
703
|
+
Buffer.from(currentHash, 'hex'),
|
|
704
|
+
Buffer.from(step.hash, 'hex')
|
|
705
|
+
])
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
currentHash = Hash.sha256(combined).toString('hex')
|
|
709
|
+
currentIndex = Math.floor(currentIndex / 2)
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
return currentHash === expectedRoot
|
|
713
|
+
},
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Traverse object and call callback for each field
|
|
717
|
+
* @private
|
|
718
|
+
*/
|
|
719
|
+
_traverseObject: function(obj, basePath, callback) {
|
|
720
|
+
if (Array.isArray(obj)) {
|
|
721
|
+
obj.forEach(function(item, index) {
|
|
722
|
+
var path = basePath + '[' + index + ']'
|
|
723
|
+
this._traverseObject(item, path, callback)
|
|
724
|
+
}.bind(this))
|
|
725
|
+
} else if (obj !== null && typeof obj === 'object') {
|
|
726
|
+
Object.keys(obj).forEach(function(key) {
|
|
727
|
+
var path = basePath ? basePath + '.' + key : key
|
|
728
|
+
this._traverseObject(obj[key], path, callback)
|
|
729
|
+
}.bind(this))
|
|
730
|
+
} else {
|
|
731
|
+
callback(basePath, obj)
|
|
732
|
+
}
|
|
733
|
+
},
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Get nested value from object
|
|
737
|
+
* @private
|
|
738
|
+
*/
|
|
739
|
+
_getNestedValue: function(obj, path) {
|
|
740
|
+
return path.split('.').reduce(function(current, prop) {
|
|
741
|
+
return current && current[prop]
|
|
742
|
+
}, obj)
|
|
743
|
+
},
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Find field commitment by path
|
|
747
|
+
* @private
|
|
748
|
+
*/
|
|
749
|
+
_findFieldCommitment: function(commitments, path) {
|
|
750
|
+
return commitments.find(function(c) { return c.path === path })
|
|
751
|
+
},
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Check legal requirement
|
|
755
|
+
* @private
|
|
756
|
+
*/
|
|
757
|
+
_checkLegalRequirement: function(token, requirement) {
|
|
758
|
+
// Simplified legal requirement checking
|
|
759
|
+
// In practice, this would be much more sophisticated
|
|
760
|
+
|
|
761
|
+
switch (requirement.type) {
|
|
762
|
+
case 'field_present':
|
|
763
|
+
var value = this._getNestedValue(token, requirement.field)
|
|
764
|
+
return {
|
|
765
|
+
requirement: requirement.type,
|
|
766
|
+
field: requirement.field,
|
|
767
|
+
satisfied: value !== undefined && value !== null
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
case 'field_value':
|
|
771
|
+
var value = this._getNestedValue(token, requirement.field)
|
|
772
|
+
return {
|
|
773
|
+
requirement: requirement.type,
|
|
774
|
+
field: requirement.field,
|
|
775
|
+
satisfied: value === requirement.expectedValue
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
case 'temporal_validity':
|
|
779
|
+
var now = new Date()
|
|
780
|
+
var issuanceDate = new Date(token.issuanceDate)
|
|
781
|
+
return {
|
|
782
|
+
requirement: requirement.type,
|
|
783
|
+
satisfied: issuanceDate <= now
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
default:
|
|
787
|
+
return {
|
|
788
|
+
requirement: requirement.type,
|
|
789
|
+
satisfied: false,
|
|
790
|
+
error: 'Unknown requirement type'
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
},
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Hash compliance information
|
|
797
|
+
* @private
|
|
798
|
+
*/
|
|
799
|
+
_hashCompliance: function(token, jurisdiction, nonce) {
|
|
800
|
+
var complianceData = {
|
|
801
|
+
tokenId: token.id,
|
|
802
|
+
jurisdiction: jurisdiction.code,
|
|
803
|
+
nonce: nonce,
|
|
804
|
+
timestamp: new Date().toISOString()
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
return Hash.sha256(Buffer.from(JSON.stringify(complianceData))).toString('hex')
|
|
808
|
+
},
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* Sort object keys recursively
|
|
812
|
+
* @private
|
|
813
|
+
*/
|
|
814
|
+
_sortObjectKeys: function(obj) {
|
|
815
|
+
if (Array.isArray(obj)) {
|
|
816
|
+
return obj.map(this._sortObjectKeys.bind(this))
|
|
817
|
+
} else if (obj !== null && typeof obj === 'object') {
|
|
818
|
+
var sorted = {}
|
|
819
|
+
Object.keys(obj).sort().forEach(function(key) {
|
|
820
|
+
sorted[key] = this._sortObjectKeys(obj[key])
|
|
821
|
+
}.bind(this))
|
|
822
|
+
return sorted
|
|
823
|
+
}
|
|
824
|
+
return obj
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
module.exports = LTPProof
|