@smartledger/bsv 3.1.1 → 3.2.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.
Files changed (67) hide show
  1. package/CHANGELOG.md +123 -1
  2. package/README.md +233 -277
  3. package/bsv.bundle.js +39 -0
  4. package/bsv.min.js +8 -8
  5. package/docs/ADVANCED_COVENANT_DEVELOPMENT.md +533 -0
  6. package/docs/COVENANT_DEVELOPMENT_RESOLVED.md +169 -0
  7. package/docs/CUSTOM_SCRIPT_DEVELOPMENT.md +320 -0
  8. package/docs/README.md +201 -0
  9. package/docs/block.md +46 -0
  10. package/docs/ecies.md +102 -0
  11. package/docs/index.md +104 -0
  12. package/docs/nchain.md +958 -0
  13. package/docs/networks.md +55 -0
  14. package/docs/preimage.md +126 -0
  15. package/docs/script.md +139 -0
  16. package/docs/transaction.md +174 -0
  17. package/docs/unspentoutput.md +32 -0
  18. package/examples/README.md +200 -0
  19. package/examples/basic/transaction-creation.js +534 -0
  20. package/examples/basic/transaction_signature_api_gap.js +178 -0
  21. package/examples/covenants/advanced_covenant_demo.js +219 -0
  22. package/examples/covenants/covenant_interface_demo.js +270 -0
  23. package/examples/covenants/covenant_manual_signature_resolved.js +212 -0
  24. package/examples/covenants/covenant_signature_template.js +117 -0
  25. package/examples/covenants2/covenant_bidirectional_example.js +262 -0
  26. package/examples/covenants2/covenant_utils_demo.js +120 -0
  27. package/examples/covenants2/preimage_covenant_utils.js +287 -0
  28. package/examples/covenants2/production_integration.js +256 -0
  29. package/examples/data/covenant_utxos.json +28 -0
  30. package/examples/data/utxos.json +26 -0
  31. package/examples/preimage/README.md +178 -0
  32. package/examples/preimage/extract_preimage_bidirectional.js +421 -0
  33. package/examples/preimage/generate_sample_preimage.js +208 -0
  34. package/examples/preimage/generate_sighash_examples.js +152 -0
  35. package/examples/preimage/parse_preimage.js +117 -0
  36. package/examples/preimage/test_preimage_extractor.js +53 -0
  37. package/examples/preimage/test_varint_extraction.js +95 -0
  38. package/examples/scripts/custom_script_helper_example.js +273 -0
  39. package/examples/scripts/custom_script_signature_test.js +344 -0
  40. package/examples/scripts/script_interpreter.js +193 -0
  41. package/examples/smart_contract/complete_workflow_demo.js +343 -0
  42. package/examples/smart_contract/covenant_builder_demo.js +176 -0
  43. package/examples/smart_contract/script_testing_integration.js +198 -0
  44. package/index.js +3 -0
  45. package/lib/covenant-interface.js +713 -0
  46. package/lib/opcode.js +14 -7
  47. package/lib/smart_contract/API_REFERENCE.md +754 -0
  48. package/lib/smart_contract/DOCUMENTATION_SUMMARY.md +201 -0
  49. package/lib/smart_contract/EXAMPLES.md +751 -0
  50. package/lib/smart_contract/QUICK_START.md +549 -0
  51. package/lib/smart_contract/README.md +395 -0
  52. package/lib/smart_contract/builder.js +452 -0
  53. package/lib/smart_contract/covenant.js +336 -0
  54. package/lib/smart_contract/covenant_builder.js +512 -0
  55. package/lib/smart_contract/index.js +311 -0
  56. package/lib/smart_contract/opcode_list.js +30 -0
  57. package/lib/smart_contract/opcode_map.js +1174 -0
  58. package/lib/smart_contract/opcodes.md +1173 -0
  59. package/lib/smart_contract/preimage.js +903 -0
  60. package/lib/smart_contract/script_tester.js +487 -0
  61. package/lib/smart_contract/script_utils.js +609 -0
  62. package/lib/smart_contract/sighash.js +310 -0
  63. package/lib/smart_contract/smartledger-opcode_review.md +70 -0
  64. package/lib/smart_contract/test_integration.js +269 -0
  65. package/lib/smart_contract/utxo_generator.js +367 -0
  66. package/package.json +43 -10
  67. package/utilities/blockchain-state.json +20478 -3
@@ -0,0 +1,609 @@
1
+ /**
2
+ * script_utils.js
3
+ * ===============
4
+ *
5
+ * Comprehensive script analysis and conversion utilities for Bitcoin SV.
6
+ * Provides missing functionality for script manipulation, validation, and optimization.
7
+ *
8
+ * Features:
9
+ * - Script format conversions (Buffer ↔ ASM ↔ Hex)
10
+ * - Script validation and syntax checking
11
+ * - Script optimization and analysis
12
+ * - Performance metrics and complexity analysis
13
+ * - Human-readable explanations
14
+ */
15
+
16
+ 'use strict'
17
+
18
+ const bsv = require('../..')
19
+
20
+ /**
21
+ * Script Format Conversion Utilities
22
+ */
23
+
24
+ function scriptToASM(scriptBuffer) {
25
+ if (Buffer.isBuffer(scriptBuffer)) {
26
+ const script = bsv.Script.fromBuffer(scriptBuffer)
27
+ return script.toASM()
28
+ } else if (scriptBuffer && typeof scriptBuffer.toASM === 'function') {
29
+ return scriptBuffer.toASM()
30
+ }
31
+ throw new Error('Input must be a Buffer or Script object')
32
+ }
33
+
34
+ function asmToScript(asmString) {
35
+ try {
36
+ return bsv.Script.fromASM(asmString)
37
+ } catch (error) {
38
+ throw new Error(`Invalid ASM string: ${error.message}`)
39
+ }
40
+ }
41
+
42
+ function asmToHex(asmString) {
43
+ try {
44
+ const script = bsv.Script.fromASM(asmString)
45
+ return script.toBuffer().toString('hex')
46
+ } catch (error) {
47
+ throw new Error(`Cannot convert ASM to hex: ${error.message}`)
48
+ }
49
+ }
50
+
51
+ function hexToASM(hexString) {
52
+ try {
53
+ const buffer = Buffer.from(hexString, 'hex')
54
+ const script = bsv.Script.fromBuffer(buffer)
55
+ return script.toASM()
56
+ } catch (error) {
57
+ throw new Error(`Cannot convert hex to ASM: ${error.message}`)
58
+ }
59
+ }
60
+
61
+ function scriptToHex(script) {
62
+ if (Buffer.isBuffer(script)) {
63
+ return script.toString('hex')
64
+ } else if (script && typeof script.toBuffer === 'function') {
65
+ return script.toBuffer().toString('hex')
66
+ }
67
+ throw new Error('Input must be a Buffer or Script object')
68
+ }
69
+
70
+ /**
71
+ * Script Validation Utilities
72
+ */
73
+
74
+ function validateASM(asmString) {
75
+ try {
76
+ bsv.Script.fromASM(asmString)
77
+ return { valid: true, error: null }
78
+ } catch (error) {
79
+ return { valid: false, error: error.message }
80
+ }
81
+ }
82
+
83
+ function validateScript(script) {
84
+ try {
85
+ let scriptObj
86
+ if (typeof script === 'string') {
87
+ scriptObj = bsv.Script.fromASM(script)
88
+ } else if (Buffer.isBuffer(script)) {
89
+ scriptObj = bsv.Script.fromBuffer(script)
90
+ } else if (script && typeof script.toBuffer === 'function') {
91
+ scriptObj = script
92
+ } else {
93
+ return { valid: false, error: 'Invalid script input type' }
94
+ }
95
+
96
+ // Basic validation checks
97
+ const buffer = scriptObj.toBuffer()
98
+ const asm = scriptObj.toASM()
99
+
100
+ return {
101
+ valid: true,
102
+ error: null,
103
+ size: buffer.length,
104
+ asm: asm,
105
+ operations: asm.split(' ').length
106
+ }
107
+ } catch (error) {
108
+ return { valid: false, error: error.message }
109
+ }
110
+ }
111
+
112
+ function validateSyntax(asmString) {
113
+ const validation = validateASM(asmString)
114
+ if (!validation.valid) {
115
+ return validation
116
+ }
117
+
118
+ // Additional syntax checks
119
+ const operations = asmString.split(' ')
120
+ const issues = []
121
+
122
+ for (let i = 0; i < operations.length; i++) {
123
+ const op = operations[i]
124
+
125
+ // Check for unknown opcodes
126
+ if (op.startsWith('OP_') && !bsv.Opcode.map[op]) {
127
+ issues.push(`Unknown opcode: ${op} at position ${i}`)
128
+ }
129
+
130
+ // Check for malformed hex data
131
+ if (!op.startsWith('OP_') && op.length > 0) {
132
+ if (!/^[0-9a-fA-F]*$/.test(op)) {
133
+ issues.push(`Invalid hex data: ${op} at position ${i}`)
134
+ }
135
+ }
136
+ }
137
+
138
+ return {
139
+ valid: issues.length === 0,
140
+ error: issues.length > 0 ? issues.join('; ') : null,
141
+ issues: issues,
142
+ operations: operations.length
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Script Analysis Utilities
148
+ */
149
+
150
+ function estimateScriptSize(script) {
151
+ try {
152
+ let scriptObj
153
+ if (typeof script === 'string') {
154
+ scriptObj = bsv.Script.fromASM(script)
155
+ } else if (Array.isArray(script)) {
156
+ scriptObj = bsv.Script.fromASM(script.join(' '))
157
+ } else if (Buffer.isBuffer(script)) {
158
+ return script.length
159
+ } else if (script && typeof script.toBuffer === 'function') {
160
+ scriptObj = script
161
+ } else {
162
+ throw new Error('Invalid script input')
163
+ }
164
+
165
+ return scriptObj.toBuffer().length
166
+ } catch (error) {
167
+ return { error: error.message }
168
+ }
169
+ }
170
+
171
+ function scriptMetrics(script) {
172
+ try {
173
+ const validation = validateScript(script)
174
+ if (!validation.valid) {
175
+ return { error: validation.error }
176
+ }
177
+
178
+ const asm = validation.asm
179
+ const operations = asm.split(' ')
180
+ const size = validation.size
181
+
182
+ // Analyze operation types
183
+ const opcodeCount = operations.filter(op => op.startsWith('OP_')).length
184
+ const dataCount = operations.filter(op => !op.startsWith('OP_') && op.length > 0).length
185
+
186
+ // Estimate execution cost (simplified)
187
+ let executionCost = 0
188
+ operations.forEach(op => {
189
+ if (op.startsWith('OP_')) {
190
+ // Different opcodes have different costs
191
+ if (['OP_CHECKSIG', 'OP_CHECKSIGVERIFY'].includes(op)) {
192
+ executionCost += 100 // Signature operations are expensive
193
+ } else if (['OP_SHA256', 'OP_HASH160', 'OP_HASH256'].includes(op)) {
194
+ executionCost += 10 // Hash operations are moderately expensive
195
+ } else {
196
+ executionCost += 1 // Basic operations
197
+ }
198
+ }
199
+ })
200
+
201
+ return {
202
+ size: size,
203
+ operations: operations.length,
204
+ opcodes: opcodeCount,
205
+ dataElements: dataCount,
206
+ estimatedCost: executionCost,
207
+ complexity: executionCost > 50 ? 'high' : executionCost > 10 ? 'medium' : 'low',
208
+ asm: asm
209
+ }
210
+ } catch (error) {
211
+ return { error: error.message }
212
+ }
213
+ }
214
+
215
+ function analyzeComplexity(script) {
216
+ const metrics = scriptMetrics(script)
217
+ if (metrics.error) {
218
+ return metrics
219
+ }
220
+
221
+ const analysis = {
222
+ size: metrics.size,
223
+ operations: metrics.operations,
224
+ complexity: metrics.complexity,
225
+ recommendations: []
226
+ }
227
+
228
+ // Provide recommendations
229
+ if (metrics.size > 520) {
230
+ analysis.recommendations.push('Script exceeds standard size limit (520 bytes)')
231
+ }
232
+
233
+ if (metrics.estimatedCost > 100) {
234
+ analysis.recommendations.push('High execution cost - consider optimization')
235
+ }
236
+
237
+ if (metrics.operations > 200) {
238
+ analysis.recommendations.push('Many operations - may hit operation limits')
239
+ }
240
+
241
+ return analysis
242
+ }
243
+
244
+ /**
245
+ * Script Optimization Utilities
246
+ */
247
+
248
+ function optimizeScript(script) {
249
+ try {
250
+ let asm
251
+ if (typeof script === 'string') {
252
+ asm = script
253
+ } else if (Array.isArray(script)) {
254
+ asm = script.join(' ')
255
+ } else {
256
+ asm = scriptToASM(script)
257
+ }
258
+
259
+ const operations = asm.split(' ')
260
+ const optimized = []
261
+
262
+ for (let i = 0; i < operations.length; i++) {
263
+ const current = operations[i]
264
+ const next = operations[i + 1]
265
+
266
+ // Skip empty operations
267
+ if (!current || current.trim() === '') {
268
+ continue
269
+ }
270
+
271
+ // Optimization: Remove redundant DUP/DROP pairs
272
+ if (current === 'OP_DUP' && next === 'OP_DROP') {
273
+ i++ // Skip the next operation too
274
+ continue
275
+ }
276
+
277
+ // Optimization: Combine consecutive data pushes (simplified)
278
+ if (!current.startsWith('OP_') && next && !next.startsWith('OP_')) {
279
+ // Could combine data pushes, but keep simple for now
280
+ optimized.push(current)
281
+ } else {
282
+ optimized.push(current)
283
+ }
284
+ }
285
+
286
+ const optimizedASM = optimized.join(' ')
287
+ const originalSize = estimateScriptSize(asm)
288
+ const optimizedSize = estimateScriptSize(optimizedASM)
289
+
290
+ return {
291
+ original: asm,
292
+ optimized: optimizedASM,
293
+ originalSize: originalSize,
294
+ optimizedSize: optimizedSize,
295
+ savings: originalSize - optimizedSize,
296
+ improvement: originalSize > 0 ? ((originalSize - optimizedSize) / originalSize * 100).toFixed(1) + '%' : '0%'
297
+ }
298
+ } catch (error) {
299
+ return { error: error.message }
300
+ }
301
+ }
302
+
303
+ function findOptimizations(script) {
304
+ const optimizations = []
305
+
306
+ try {
307
+ let asm
308
+ if (typeof script === 'string') {
309
+ asm = script
310
+ } else {
311
+ asm = scriptToASM(script)
312
+ }
313
+
314
+ const operations = asm.split(' ')
315
+
316
+ // Find potential optimizations
317
+ for (let i = 0; i < operations.length - 1; i++) {
318
+ const current = operations[i]
319
+ const next = operations[i + 1]
320
+
321
+ // Redundant DUP/DROP
322
+ if (current === 'OP_DUP' && next === 'OP_DROP') {
323
+ optimizations.push({
324
+ type: 'redundant_operation',
325
+ position: i,
326
+ description: 'Remove redundant OP_DUP OP_DROP pair',
327
+ savings: 2
328
+ })
329
+ }
330
+
331
+ // Inefficient number pushes
332
+ if (current.match(/^[0-9a-fA-F]+$/) && current.length === 2) {
333
+ const num = parseInt(current, 16)
334
+ if (num >= 1 && num <= 16) {
335
+ optimizations.push({
336
+ type: 'inefficient_push',
337
+ position: i,
338
+ description: `Use OP_${num} instead of pushing ${current}`,
339
+ savings: 1
340
+ })
341
+ }
342
+ }
343
+ }
344
+
345
+ return {
346
+ optimizations: optimizations,
347
+ totalSavings: optimizations.reduce((sum, opt) => sum + opt.savings, 0)
348
+ }
349
+ } catch (error) {
350
+ return { error: error.message }
351
+ }
352
+ }
353
+
354
+ /**
355
+ * Script Comparison Utilities
356
+ */
357
+
358
+ function compareScripts(scriptA, scriptB) {
359
+ try {
360
+ let bufferA, bufferB
361
+
362
+ if (typeof scriptA === 'string') {
363
+ bufferA = bsv.Script.fromASM(scriptA).toBuffer()
364
+ } else if (Buffer.isBuffer(scriptA)) {
365
+ bufferA = scriptA
366
+ } else {
367
+ bufferA = scriptA.toBuffer()
368
+ }
369
+
370
+ if (typeof scriptB === 'string') {
371
+ bufferB = bsv.Script.fromASM(scriptB).toBuffer()
372
+ } else if (Buffer.isBuffer(scriptB)) {
373
+ bufferB = scriptB
374
+ } else {
375
+ bufferB = scriptB.toBuffer()
376
+ }
377
+
378
+ const identical = bufferA.equals(bufferB)
379
+
380
+ return {
381
+ identical: identical,
382
+ scriptA: {
383
+ size: bufferA.length,
384
+ asm: scriptToASM(bufferA)
385
+ },
386
+ scriptB: {
387
+ size: bufferB.length,
388
+ asm: scriptToASM(bufferB)
389
+ }
390
+ }
391
+ } catch (error) {
392
+ return { error: error.message }
393
+ }
394
+ }
395
+
396
+ /**
397
+ * Script Explanation Utilities
398
+ */
399
+
400
+ function explainScript(script) {
401
+ try {
402
+ let asm
403
+ if (typeof script === 'string') {
404
+ asm = script
405
+ } else {
406
+ asm = scriptToASM(script)
407
+ }
408
+
409
+ const operations = asm.split(' ')
410
+ const explanations = []
411
+
412
+ operations.forEach((op, index) => {
413
+ let explanation = ''
414
+
415
+ if (op.startsWith('OP_')) {
416
+ // Explain common opcodes
417
+ switch (op) {
418
+ case 'OP_DUP':
419
+ explanation = 'Duplicate the top stack item'
420
+ break
421
+ case 'OP_DROP':
422
+ explanation = 'Remove the top stack item'
423
+ break
424
+ case 'OP_SWAP':
425
+ explanation = 'Swap the top two stack items'
426
+ break
427
+ case 'OP_ADD':
428
+ explanation = 'Add the top two numbers'
429
+ break
430
+ case 'OP_SUB':
431
+ explanation = 'Subtract: second - first'
432
+ break
433
+ case 'OP_EQUAL':
434
+ explanation = 'Check if top two items are equal'
435
+ break
436
+ case 'OP_VERIFY':
437
+ explanation = 'Assert that top item is true (non-zero)'
438
+ break
439
+ case 'OP_CHECKSIG':
440
+ explanation = 'Verify signature against public key'
441
+ break
442
+ case 'OP_SHA256':
443
+ explanation = 'Calculate SHA256 hash of top item'
444
+ break
445
+ case 'OP_HASH160':
446
+ explanation = 'Calculate RIPEMD160(SHA256(x)) hash'
447
+ break
448
+ case 'OP_IF':
449
+ explanation = 'Begin conditional execution'
450
+ break
451
+ case 'OP_ELSE':
452
+ explanation = 'Alternative branch of conditional'
453
+ break
454
+ case 'OP_ENDIF':
455
+ explanation = 'End conditional execution'
456
+ break
457
+ default:
458
+ explanation = `Execute ${op}`
459
+ }
460
+ } else if (op.length > 0) {
461
+ explanation = `Push data: ${op} (${op.length / 2} bytes)`
462
+ }
463
+
464
+ if (explanation) {
465
+ explanations.push({
466
+ operation: op,
467
+ position: index,
468
+ explanation: explanation
469
+ })
470
+ }
471
+ })
472
+
473
+ return {
474
+ asm: asm,
475
+ operations: operations.length,
476
+ explanations: explanations,
477
+ summary: `Script with ${operations.length} operations: ${explanations.map(e => e.explanation).join(' → ')}`
478
+ }
479
+ } catch (error) {
480
+ return { error: error.message }
481
+ }
482
+ }
483
+
484
+ function covenantToEnglish(covenant) {
485
+ try {
486
+ const explanation = explainScript(covenant)
487
+ if (explanation.error) {
488
+ return explanation
489
+ }
490
+
491
+ const sentences = []
492
+
493
+ explanation.explanations.forEach(step => {
494
+ switch (step.operation) {
495
+ case 'OP_VERIFY':
496
+ sentences.push('The condition must be true for the script to succeed.')
497
+ break
498
+ case 'OP_CHECKSIG':
499
+ sentences.push('A valid signature must be provided.')
500
+ break
501
+ case 'OP_EQUAL':
502
+ sentences.push('Two values must be identical.')
503
+ break
504
+ case 'OP_ADD':
505
+ sentences.push('Two numbers are added together.')
506
+ break
507
+ default:
508
+ if (step.explanation.includes('Push data')) {
509
+ sentences.push('Some data is made available for processing.')
510
+ }
511
+ }
512
+ })
513
+
514
+ return {
515
+ english: sentences.join(' '),
516
+ technical: explanation.summary,
517
+ operations: explanation.operations
518
+ }
519
+ } catch (error) {
520
+ return { error: error.message }
521
+ }
522
+ }
523
+
524
+ /**
525
+ * Batch Testing Utilities
526
+ */
527
+
528
+ function batchTestScripts(scripts, options = {}) {
529
+ const results = []
530
+
531
+ scripts.forEach((script, index) => {
532
+ try {
533
+ const validation = validateScript(script)
534
+ const metrics = scriptMetrics(script)
535
+ const complexity = analyzeComplexity(script)
536
+
537
+ results.push({
538
+ index: index,
539
+ script: typeof script === 'string' ? script : scriptToASM(script),
540
+ valid: validation.valid,
541
+ size: validation.size,
542
+ operations: validation.operations,
543
+ complexity: complexity.complexity,
544
+ estimatedCost: metrics.estimatedCost,
545
+ recommendations: complexity.recommendations
546
+ })
547
+ } catch (error) {
548
+ results.push({
549
+ index: index,
550
+ script: typeof script === 'string' ? script : 'Invalid script',
551
+ valid: false,
552
+ error: error.message
553
+ })
554
+ }
555
+ })
556
+
557
+ const summary = {
558
+ total: scripts.length,
559
+ valid: results.filter(r => r.valid).length,
560
+ invalid: results.filter(r => !r.valid).length,
561
+ averageSize: results.filter(r => r.size).reduce((sum, r) => sum + r.size, 0) / results.filter(r => r.size).length || 0,
562
+ complexityDistribution: {
563
+ low: results.filter(r => r.complexity === 'low').length,
564
+ medium: results.filter(r => r.complexity === 'medium').length,
565
+ high: results.filter(r => r.complexity === 'high').length
566
+ }
567
+ }
568
+
569
+ return {
570
+ results: results,
571
+ summary: summary
572
+ }
573
+ }
574
+
575
+ /**
576
+ * Export all utilities
577
+ */
578
+ module.exports = {
579
+ // Format conversions
580
+ scriptToASM,
581
+ asmToScript,
582
+ asmToHex,
583
+ hexToASM,
584
+ scriptToHex,
585
+
586
+ // Validation
587
+ validateASM,
588
+ validateScript,
589
+ validateSyntax,
590
+
591
+ // Analysis
592
+ estimateScriptSize,
593
+ scriptMetrics,
594
+ analyzeComplexity,
595
+
596
+ // Optimization
597
+ optimizeScript,
598
+ findOptimizations,
599
+
600
+ // Comparison
601
+ compareScripts,
602
+
603
+ // Explanation
604
+ explainScript,
605
+ covenantToEnglish,
606
+
607
+ // Batch testing
608
+ batchTestScripts
609
+ }