smartledger-bsv 3.2.2 → 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/architecture_demo.js +247 -0
- package/bsv-gdaf.min.js +37 -0
- package/bsv-ltp.min.js +37 -0
- package/bsv-shamir.min.js +12 -0
- package/bsv.bundle.js +9 -9
- package/build/bsv-smartcontract.min.js +10 -8
- package/build/bsv.bundle.js +9 -9
- package/build/bsv.min.js +10 -8
- package/build/webpack.gdaf.config.js +54 -0
- package/build/webpack.ltp.config.js +17 -0
- package/bundle-entry.js +77 -1
- package/complete_ltp_demo.js +511 -0
- package/gdaf-entry.js +54 -0
- package/index.js +259 -0
- 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/ltp-entry.js +92 -0
- package/package.json +44 -4
- package/shamir-entry.js +173 -0
- package/shamir_demo.js +121 -0
- package/simple_demo.js +204 -0
- package/test_shamir.js +221 -0
- package/test_standalone_shamir.html +83 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
var bsv = require('../../')
|
|
4
|
+
var Hash = bsv.crypto.Hash
|
|
5
|
+
var $ = bsv.util.preconditions
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Legal Token Protocol - Blockchain Anchoring Primitives
|
|
9
|
+
*
|
|
10
|
+
* Provides primitives for preparing legal tokens for blockchain anchoring
|
|
11
|
+
* without directly publishing to blockchain. External services handle
|
|
12
|
+
* the actual transaction broadcasting and confirmation.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
var LTPAnchor = {
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Prepare token commitment for blockchain anchoring
|
|
19
|
+
* @param {Object} token - Token to prepare for anchoring
|
|
20
|
+
* @param {Object} options - Anchoring options
|
|
21
|
+
* @returns {Object} Prepared anchor data
|
|
22
|
+
*/
|
|
23
|
+
prepareTokenCommitment: function(token, options) {
|
|
24
|
+
options = options || {}
|
|
25
|
+
|
|
26
|
+
$.checkArgument(token && typeof token === 'object', 'Invalid token')
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
// Create commitment hash
|
|
30
|
+
var commitment = this._createCommitment(token, options)
|
|
31
|
+
|
|
32
|
+
// Prepare OP_RETURN data
|
|
33
|
+
var opReturnData = this._formatOpReturn(commitment, options)
|
|
34
|
+
|
|
35
|
+
// Create transaction template
|
|
36
|
+
var txTemplate = this._createTxTemplate(opReturnData, options)
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
success: true,
|
|
40
|
+
commitment: commitment,
|
|
41
|
+
opReturnData: opReturnData,
|
|
42
|
+
txTemplate: txTemplate,
|
|
43
|
+
preparedAt: new Date().toISOString(),
|
|
44
|
+
metadata: {
|
|
45
|
+
tokenId: token.id,
|
|
46
|
+
purpose: options.purpose || 'legal_token_anchor',
|
|
47
|
+
version: 'LTP.v1'
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
} catch (error) {
|
|
52
|
+
return {
|
|
53
|
+
success: false,
|
|
54
|
+
error: error.message
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Prepare batch of tokens for anchoring
|
|
61
|
+
* @param {Array} tokens - Tokens to prepare for anchoring
|
|
62
|
+
* @param {Object} options - Anchoring options
|
|
63
|
+
* @returns {Object} Prepared batch anchor data
|
|
64
|
+
*/
|
|
65
|
+
prepareBatchCommitment: function(tokens, options) {
|
|
66
|
+
options = options || {}
|
|
67
|
+
|
|
68
|
+
$.checkArgument(Array.isArray(tokens), 'Tokens must be array')
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
// Create batch commitment
|
|
72
|
+
var batchCommitment = this._createBatchCommitment(tokens, options)
|
|
73
|
+
|
|
74
|
+
// Prepare OP_RETURN data for batch
|
|
75
|
+
var opReturnData = this._formatOpReturn(batchCommitment, options)
|
|
76
|
+
|
|
77
|
+
// Create transaction template
|
|
78
|
+
var txTemplate = this._createTxTemplate(opReturnData, options)
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
success: true,
|
|
82
|
+
batchCommitment: batchCommitment,
|
|
83
|
+
opReturnData: opReturnData,
|
|
84
|
+
txTemplate: txTemplate,
|
|
85
|
+
tokenCount: tokens.length,
|
|
86
|
+
preparedAt: new Date().toISOString(),
|
|
87
|
+
metadata: {
|
|
88
|
+
purpose: options.purpose || 'legal_token_batch_anchor',
|
|
89
|
+
version: 'LTP.v1'
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
} catch (error) {
|
|
94
|
+
return {
|
|
95
|
+
success: false,
|
|
96
|
+
error: error.message
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Verify token anchor using external transaction data
|
|
103
|
+
* @param {Object} token - Token to verify
|
|
104
|
+
* @param {String} txid - Transaction ID from external anchor
|
|
105
|
+
* @param {Object} txData - Transaction data from blockchain
|
|
106
|
+
* @returns {Object} Verification result
|
|
107
|
+
*/
|
|
108
|
+
verifyTokenAnchor: function(token, txid, txData) {
|
|
109
|
+
$.checkArgument(token && typeof token === 'object', 'Invalid token')
|
|
110
|
+
$.checkArgument(typeof txid === 'string', 'Transaction ID must be string')
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
// Recreate expected commitment
|
|
114
|
+
var expectedCommitment = this._createCommitment(token, {})
|
|
115
|
+
|
|
116
|
+
// Extract OP_RETURN data from transaction
|
|
117
|
+
var opReturnData = this._extractOpReturn(txData)
|
|
118
|
+
|
|
119
|
+
if (!opReturnData) {
|
|
120
|
+
return {
|
|
121
|
+
valid: false,
|
|
122
|
+
error: 'No OP_RETURN data found in transaction'
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Verify commitment matches
|
|
127
|
+
var commitmentValid = this._verifyCommitment(expectedCommitment, opReturnData)
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
valid: commitmentValid,
|
|
131
|
+
txid: txid,
|
|
132
|
+
commitment: expectedCommitment,
|
|
133
|
+
anchorData: opReturnData,
|
|
134
|
+
verifiedAt: new Date().toISOString()
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
} catch (error) {
|
|
138
|
+
return {
|
|
139
|
+
valid: false,
|
|
140
|
+
error: error.message
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Format revocation data for external registry
|
|
147
|
+
* @param {String} tokenId - Token ID to revoke
|
|
148
|
+
* @param {Object} revocationData - Revocation details
|
|
149
|
+
* @returns {Object} Formatted revocation data
|
|
150
|
+
*/
|
|
151
|
+
formatRevocation: function(tokenId, revocationData) {
|
|
152
|
+
$.checkArgument(typeof tokenId === 'string', 'Token ID must be string')
|
|
153
|
+
$.checkArgument(revocationData && typeof revocationData === 'object', 'Invalid revocation data')
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
var revocation = {
|
|
157
|
+
type: 'LTP_REVOCATION',
|
|
158
|
+
tokenId: tokenId,
|
|
159
|
+
reason: revocationData.reason || 'UNSPECIFIED',
|
|
160
|
+
revokedBy: revocationData.revokedBy || 'UNKNOWN',
|
|
161
|
+
revokedAt: new Date().toISOString(),
|
|
162
|
+
effectiveDate: revocationData.effectiveDate || new Date().toISOString(),
|
|
163
|
+
legalBasis: revocationData.legalBasis || null,
|
|
164
|
+
evidence: revocationData.evidence || null
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Create revocation hash
|
|
168
|
+
var revocationHash = Hash.sha256(Buffer.from(JSON.stringify(revocation))).toString('hex')
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
success: true,
|
|
172
|
+
revocation: revocation,
|
|
173
|
+
revocationHash: revocationHash,
|
|
174
|
+
opReturnData: this._formatOpReturn({ hash: revocationHash, type: 'REVOCATION' }, {})
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
} catch (error) {
|
|
178
|
+
return {
|
|
179
|
+
success: false,
|
|
180
|
+
error: error.message
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Create commitment hash from token
|
|
187
|
+
* @private
|
|
188
|
+
*/
|
|
189
|
+
_createCommitment: function(token, options) {
|
|
190
|
+
// Create canonical representation
|
|
191
|
+
var canonical = this._canonicalizeToken(token)
|
|
192
|
+
|
|
193
|
+
// Create commitment object
|
|
194
|
+
var commitment = {
|
|
195
|
+
type: 'LTP_TOKEN',
|
|
196
|
+
tokenHash: Hash.sha256(Buffer.from(canonical)).toString('hex'),
|
|
197
|
+
tokenId: token.id,
|
|
198
|
+
timestamp: new Date().toISOString(),
|
|
199
|
+
purpose: options.purpose || 'legal_token'
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Hash the commitment
|
|
203
|
+
var commitmentHash = Hash.sha256(Buffer.from(JSON.stringify(commitment))).toString('hex')
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
hash: commitmentHash,
|
|
207
|
+
data: commitment,
|
|
208
|
+
canonical: canonical
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Create batch commitment from multiple tokens
|
|
214
|
+
* @private
|
|
215
|
+
*/
|
|
216
|
+
_createBatchCommitment: function(tokens, options) {
|
|
217
|
+
var tokenHashes = []
|
|
218
|
+
var tokenIds = []
|
|
219
|
+
|
|
220
|
+
tokens.forEach(function(token) {
|
|
221
|
+
var commitment = this._createCommitment(token, options)
|
|
222
|
+
tokenHashes.push(commitment.hash)
|
|
223
|
+
tokenIds.push(token.id)
|
|
224
|
+
}.bind(this))
|
|
225
|
+
|
|
226
|
+
// Create Merkle tree
|
|
227
|
+
var merkleRoot = this._createMerkleRoot(tokenHashes)
|
|
228
|
+
|
|
229
|
+
var batchCommitment = {
|
|
230
|
+
type: 'LTP_BATCH',
|
|
231
|
+
merkleRoot: merkleRoot,
|
|
232
|
+
tokenCount: tokens.length,
|
|
233
|
+
tokenIds: tokenIds,
|
|
234
|
+
timestamp: new Date().toISOString(),
|
|
235
|
+
purpose: options.purpose || 'batch_anchor'
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
var batchHash = Hash.sha256(Buffer.from(JSON.stringify(batchCommitment))).toString('hex')
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
hash: batchHash,
|
|
242
|
+
data: batchCommitment,
|
|
243
|
+
merkleRoot: merkleRoot,
|
|
244
|
+
tokenHashes: tokenHashes
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Format OP_RETURN data
|
|
250
|
+
* @private
|
|
251
|
+
*/
|
|
252
|
+
_formatOpReturn: function(commitment, options) {
|
|
253
|
+
var prefix = options.prefix || 'LTP.v1'
|
|
254
|
+
var data = commitment.hash
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
prefix: prefix,
|
|
258
|
+
data: data,
|
|
259
|
+
full: prefix + '.' + data,
|
|
260
|
+
bytes: Buffer.from(prefix + '.' + data, 'utf8'),
|
|
261
|
+
size: Buffer.from(prefix + '.' + data, 'utf8').length
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Create transaction template
|
|
267
|
+
* @private
|
|
268
|
+
*/
|
|
269
|
+
_createTxTemplate: function(opReturnData, options) {
|
|
270
|
+
try {
|
|
271
|
+
// Create basic transaction structure
|
|
272
|
+
var tx = {
|
|
273
|
+
version: 1,
|
|
274
|
+
inputs: [], // To be filled by implementing application
|
|
275
|
+
outputs: [
|
|
276
|
+
{
|
|
277
|
+
satoshis: 0,
|
|
278
|
+
script: 'OP_RETURN ' + opReturnData.data
|
|
279
|
+
}
|
|
280
|
+
],
|
|
281
|
+
locktime: 0
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Add fee output if specified
|
|
285
|
+
if (options.feeOutput) {
|
|
286
|
+
tx.outputs.push(options.feeOutput)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
template: tx,
|
|
291
|
+
opReturn: opReturnData,
|
|
292
|
+
estimatedSize: this._estimateSize(tx),
|
|
293
|
+
instructions: {
|
|
294
|
+
step1: 'Add input UTXOs to cover fees',
|
|
295
|
+
step2: 'Add change output if needed',
|
|
296
|
+
step3: 'Sign transaction with appropriate keys',
|
|
297
|
+
step4: 'Broadcast to BSV network'
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
} catch (error) {
|
|
302
|
+
throw new Error('Failed to create transaction template: ' + error.message)
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Extract OP_RETURN data from transaction
|
|
308
|
+
* @private
|
|
309
|
+
*/
|
|
310
|
+
_extractOpReturn: function(txData) {
|
|
311
|
+
try {
|
|
312
|
+
if (!txData || !txData.outputs) {
|
|
313
|
+
return null
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
for (var i = 0; i < txData.outputs.length; i++) {
|
|
317
|
+
var output = txData.outputs[i]
|
|
318
|
+
if (output.script && output.script.startsWith('OP_RETURN')) {
|
|
319
|
+
var data = output.script.replace('OP_RETURN ', '')
|
|
320
|
+
return {
|
|
321
|
+
raw: data,
|
|
322
|
+
decoded: Buffer.from(data, 'hex').toString('utf8')
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return null
|
|
328
|
+
} catch (error) {
|
|
329
|
+
return null
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Verify commitment matches OP_RETURN data
|
|
335
|
+
* @private
|
|
336
|
+
*/
|
|
337
|
+
_verifyCommitment: function(commitment, opReturnData) {
|
|
338
|
+
try {
|
|
339
|
+
if (!opReturnData || !opReturnData.decoded) {
|
|
340
|
+
return false
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Check if our commitment hash appears in the OP_RETURN
|
|
344
|
+
return opReturnData.decoded.includes(commitment.hash)
|
|
345
|
+
} catch (error) {
|
|
346
|
+
return false
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Canonicalize token for hashing
|
|
352
|
+
* @private
|
|
353
|
+
*/
|
|
354
|
+
_canonicalizeToken: function(token) {
|
|
355
|
+
// Remove dynamic fields
|
|
356
|
+
var canonical = JSON.parse(JSON.stringify(token))
|
|
357
|
+
delete canonical.proof
|
|
358
|
+
delete canonical.tokenHash
|
|
359
|
+
delete canonical.anchorTx
|
|
360
|
+
delete canonical.anchorBlock
|
|
361
|
+
|
|
362
|
+
// Sort keys recursively
|
|
363
|
+
return JSON.stringify(this._sortObjectKeys(canonical))
|
|
364
|
+
},
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Create Merkle root from array of hashes
|
|
368
|
+
* @private
|
|
369
|
+
*/
|
|
370
|
+
_createMerkleRoot: function(hashes) {
|
|
371
|
+
if (hashes.length === 0) {
|
|
372
|
+
return null
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (hashes.length === 1) {
|
|
376
|
+
return hashes[0]
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
var level = hashes.slice()
|
|
380
|
+
|
|
381
|
+
while (level.length > 1) {
|
|
382
|
+
var nextLevel = []
|
|
383
|
+
|
|
384
|
+
for (var i = 0; i < level.length; i += 2) {
|
|
385
|
+
var left = level[i]
|
|
386
|
+
var right = level[i + 1] || left
|
|
387
|
+
|
|
388
|
+
var combined = Buffer.concat([
|
|
389
|
+
Buffer.from(left, 'hex'),
|
|
390
|
+
Buffer.from(right, 'hex')
|
|
391
|
+
])
|
|
392
|
+
|
|
393
|
+
var hash = Hash.sha256(combined).toString('hex')
|
|
394
|
+
nextLevel.push(hash)
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
level = nextLevel
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return level[0]
|
|
401
|
+
},
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Estimate transaction size
|
|
405
|
+
* @private
|
|
406
|
+
*/
|
|
407
|
+
_estimateSize: function(tx) {
|
|
408
|
+
// Basic estimation - actual size depends on inputs
|
|
409
|
+
var baseSize = 10 // version + locktime + input/output counts
|
|
410
|
+
var outputSize = tx.outputs.length * 34 // approximate output size
|
|
411
|
+
var estimatedInputSize = 148 // approximate input size with signature
|
|
412
|
+
|
|
413
|
+
return {
|
|
414
|
+
base: baseSize + outputSize,
|
|
415
|
+
withOneInput: baseSize + outputSize + estimatedInputSize,
|
|
416
|
+
bytesPerInput: estimatedInputSize
|
|
417
|
+
}
|
|
418
|
+
},
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Sort object keys recursively
|
|
422
|
+
* @private
|
|
423
|
+
*/
|
|
424
|
+
_sortObjectKeys: function(obj) {
|
|
425
|
+
if (Array.isArray(obj)) {
|
|
426
|
+
return obj.map(this._sortObjectKeys.bind(this))
|
|
427
|
+
} else if (obj !== null && typeof obj === 'object') {
|
|
428
|
+
var sorted = {}
|
|
429
|
+
Object.keys(obj).sort().forEach(function(key) {
|
|
430
|
+
sorted[key] = this._sortObjectKeys(obj[key])
|
|
431
|
+
}.bind(this))
|
|
432
|
+
return sorted
|
|
433
|
+
}
|
|
434
|
+
return obj
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
module.exports = LTPAnchor
|