@smartledger/bsv 3.1.1 → 3.2.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.
Files changed (69) 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 +862 -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 +350 -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_interpreter.js +236 -0
  61. package/lib/smart_contract/script_tester.js +487 -0
  62. package/lib/smart_contract/script_utils.js +621 -0
  63. package/lib/smart_contract/sighash.js +310 -0
  64. package/lib/smart_contract/smartledger-opcode_review.md +70 -0
  65. package/lib/smart_contract/stack_examiner.js +129 -0
  66. package/lib/smart_contract/test_integration.js +269 -0
  67. package/lib/smart_contract/utxo_generator.js +367 -0
  68. package/package.json +43 -10
  69. package/utilities/blockchain-state.json +20478 -3
@@ -0,0 +1,621 @@
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(' ').filter(op => op.length > 0) // Filter out empty strings
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
+ length: size, // Changed from 'size' to 'length' for consistency
203
+ size: size, // Keep both for compatibility
204
+ operations: operations.length,
205
+ opcodeCount: opcodeCount, // Changed from 'opcodes' for clarity
206
+ opcodes: opcodeCount, // Keep both for compatibility
207
+ dataElements: dataCount,
208
+ estimatedCost: executionCost,
209
+ complexity: executionCost > 50 ? 'high' : executionCost > 10 ? 'medium' : 'low',
210
+ asm: asm
211
+ }
212
+ } catch (error) {
213
+ return { error: error.message }
214
+ }
215
+ }
216
+
217
+ function analyzeComplexity(script) {
218
+ const metrics = scriptMetrics(script)
219
+ if (metrics.error) {
220
+ return metrics
221
+ }
222
+
223
+ const analysis = {
224
+ size: metrics.size,
225
+ operations: metrics.operations,
226
+ complexity: metrics.complexity,
227
+ recommendations: []
228
+ }
229
+
230
+ // Provide recommendations
231
+ if (metrics.size > 520) {
232
+ analysis.recommendations.push('Script exceeds standard size limit (520 bytes)')
233
+ }
234
+
235
+ if (metrics.estimatedCost > 100) {
236
+ analysis.recommendations.push('High execution cost - consider optimization')
237
+ }
238
+
239
+ if (metrics.operations > 200) {
240
+ analysis.recommendations.push('Many operations - may hit operation limits')
241
+ }
242
+
243
+ return analysis
244
+ }
245
+
246
+ /**
247
+ * Script Optimization Utilities
248
+ */
249
+
250
+ function optimizeScript(script) {
251
+ try {
252
+ let asm
253
+ if (typeof script === 'string') {
254
+ asm = script
255
+ } else if (Array.isArray(script)) {
256
+ asm = script.join(' ')
257
+ } else {
258
+ asm = scriptToASM(script)
259
+ }
260
+
261
+ const operations = asm.split(' ')
262
+ const optimized = []
263
+
264
+ for (let i = 0; i < operations.length; i++) {
265
+ const current = operations[i]
266
+ const next = operations[i + 1]
267
+
268
+ // Skip empty operations
269
+ if (!current || current.trim() === '') {
270
+ continue
271
+ }
272
+
273
+ // Optimization: Remove redundant DUP/DROP pairs
274
+ if (current === 'OP_DUP' && next === 'OP_DROP') {
275
+ i++ // Skip the next operation too
276
+ continue
277
+ }
278
+
279
+ // Optimization: Combine consecutive data pushes (simplified)
280
+ if (!current.startsWith('OP_') && next && !next.startsWith('OP_')) {
281
+ // Could combine data pushes, but keep simple for now
282
+ optimized.push(current)
283
+ } else {
284
+ optimized.push(current)
285
+ }
286
+ }
287
+
288
+ const optimizedASM = optimized.join(' ')
289
+ const originalSize = estimateScriptSize(asm)
290
+ const optimizedSize = estimateScriptSize(optimizedASM)
291
+
292
+ // Create script object from optimized ASM
293
+ let optimizedScript = null
294
+ try {
295
+ optimizedScript = bsv.Script.fromASM(optimizedASM)
296
+ } catch (err) {
297
+ // If we can't create a script object, that's okay
298
+ optimizedScript = null
299
+ }
300
+
301
+ return {
302
+ original: asm,
303
+ optimized: optimizedASM,
304
+ originalSize: originalSize,
305
+ optimizedSize: optimizedSize,
306
+ savings: originalSize - optimizedSize,
307
+ improvement: originalSize > 0 ? ((originalSize - optimizedSize) / originalSize * 100).toFixed(1) + '%' : '0%',
308
+ script: optimizedScript // Add the script object
309
+ }
310
+ } catch (error) {
311
+ return { error: error.message }
312
+ }
313
+ }
314
+
315
+ function findOptimizations(script) {
316
+ const optimizations = []
317
+
318
+ try {
319
+ let asm
320
+ if (typeof script === 'string') {
321
+ asm = script
322
+ } else {
323
+ asm = scriptToASM(script)
324
+ }
325
+
326
+ const operations = asm.split(' ')
327
+
328
+ // Find potential optimizations
329
+ for (let i = 0; i < operations.length - 1; i++) {
330
+ const current = operations[i]
331
+ const next = operations[i + 1]
332
+
333
+ // Redundant DUP/DROP
334
+ if (current === 'OP_DUP' && next === 'OP_DROP') {
335
+ optimizations.push({
336
+ type: 'redundant_operation',
337
+ position: i,
338
+ description: 'Remove redundant OP_DUP OP_DROP pair',
339
+ savings: 2
340
+ })
341
+ }
342
+
343
+ // Inefficient number pushes
344
+ if (current.match(/^[0-9a-fA-F]+$/) && current.length === 2) {
345
+ const num = parseInt(current, 16)
346
+ if (num >= 1 && num <= 16) {
347
+ optimizations.push({
348
+ type: 'inefficient_push',
349
+ position: i,
350
+ description: `Use OP_${num} instead of pushing ${current}`,
351
+ savings: 1
352
+ })
353
+ }
354
+ }
355
+ }
356
+
357
+ return {
358
+ optimizations: optimizations,
359
+ totalSavings: optimizations.reduce((sum, opt) => sum + opt.savings, 0)
360
+ }
361
+ } catch (error) {
362
+ return { error: error.message }
363
+ }
364
+ }
365
+
366
+ /**
367
+ * Script Comparison Utilities
368
+ */
369
+
370
+ function compareScripts(scriptA, scriptB) {
371
+ try {
372
+ let bufferA, bufferB
373
+
374
+ if (typeof scriptA === 'string') {
375
+ bufferA = bsv.Script.fromASM(scriptA).toBuffer()
376
+ } else if (Buffer.isBuffer(scriptA)) {
377
+ bufferA = scriptA
378
+ } else {
379
+ bufferA = scriptA.toBuffer()
380
+ }
381
+
382
+ if (typeof scriptB === 'string') {
383
+ bufferB = bsv.Script.fromASM(scriptB).toBuffer()
384
+ } else if (Buffer.isBuffer(scriptB)) {
385
+ bufferB = scriptB
386
+ } else {
387
+ bufferB = scriptB.toBuffer()
388
+ }
389
+
390
+ const identical = bufferA.equals(bufferB)
391
+
392
+ return {
393
+ identical: identical,
394
+ scriptA: {
395
+ size: bufferA.length,
396
+ asm: scriptToASM(bufferA)
397
+ },
398
+ scriptB: {
399
+ size: bufferB.length,
400
+ asm: scriptToASM(bufferB)
401
+ }
402
+ }
403
+ } catch (error) {
404
+ return { error: error.message }
405
+ }
406
+ }
407
+
408
+ /**
409
+ * Script Explanation Utilities
410
+ */
411
+
412
+ function explainScript(script) {
413
+ try {
414
+ let asm
415
+ if (typeof script === 'string') {
416
+ asm = script
417
+ } else {
418
+ asm = scriptToASM(script)
419
+ }
420
+
421
+ const operations = asm.split(' ')
422
+ const explanations = []
423
+
424
+ operations.forEach((op, index) => {
425
+ let explanation = ''
426
+
427
+ if (op.startsWith('OP_')) {
428
+ // Explain common opcodes
429
+ switch (op) {
430
+ case 'OP_DUP':
431
+ explanation = 'Duplicate the top stack item'
432
+ break
433
+ case 'OP_DROP':
434
+ explanation = 'Remove the top stack item'
435
+ break
436
+ case 'OP_SWAP':
437
+ explanation = 'Swap the top two stack items'
438
+ break
439
+ case 'OP_ADD':
440
+ explanation = 'Add the top two numbers'
441
+ break
442
+ case 'OP_SUB':
443
+ explanation = 'Subtract: second - first'
444
+ break
445
+ case 'OP_EQUAL':
446
+ explanation = 'Check if top two items are equal'
447
+ break
448
+ case 'OP_VERIFY':
449
+ explanation = 'Assert that top item is true (non-zero)'
450
+ break
451
+ case 'OP_CHECKSIG':
452
+ explanation = 'Verify signature against public key'
453
+ break
454
+ case 'OP_SHA256':
455
+ explanation = 'Calculate SHA256 hash of top item'
456
+ break
457
+ case 'OP_HASH160':
458
+ explanation = 'Calculate RIPEMD160(SHA256(x)) hash'
459
+ break
460
+ case 'OP_IF':
461
+ explanation = 'Begin conditional execution'
462
+ break
463
+ case 'OP_ELSE':
464
+ explanation = 'Alternative branch of conditional'
465
+ break
466
+ case 'OP_ENDIF':
467
+ explanation = 'End conditional execution'
468
+ break
469
+ default:
470
+ explanation = `Execute ${op}`
471
+ }
472
+ } else if (op.length > 0) {
473
+ explanation = `Push data: ${op} (${op.length / 2} bytes)`
474
+ }
475
+
476
+ if (explanation) {
477
+ explanations.push({
478
+ operation: op,
479
+ position: index,
480
+ explanation: explanation
481
+ })
482
+ }
483
+ })
484
+
485
+ return {
486
+ asm: asm,
487
+ operations: operations.length,
488
+ explanations: explanations,
489
+ summary: `Script with ${operations.length} operations: ${explanations.map(e => e.explanation).join(' → ')}`
490
+ }
491
+ } catch (error) {
492
+ return { error: error.message }
493
+ }
494
+ }
495
+
496
+ function covenantToEnglish(covenant) {
497
+ try {
498
+ const explanation = explainScript(covenant)
499
+ if (explanation.error) {
500
+ return explanation
501
+ }
502
+
503
+ const sentences = []
504
+
505
+ explanation.explanations.forEach(step => {
506
+ switch (step.operation) {
507
+ case 'OP_VERIFY':
508
+ sentences.push('The condition must be true for the script to succeed.')
509
+ break
510
+ case 'OP_CHECKSIG':
511
+ sentences.push('A valid signature must be provided.')
512
+ break
513
+ case 'OP_EQUAL':
514
+ sentences.push('Two values must be identical.')
515
+ break
516
+ case 'OP_ADD':
517
+ sentences.push('Two numbers are added together.')
518
+ break
519
+ default:
520
+ if (step.explanation.includes('Push data')) {
521
+ sentences.push('Some data is made available for processing.')
522
+ }
523
+ }
524
+ })
525
+
526
+ return {
527
+ english: sentences.join(' '),
528
+ technical: explanation.summary,
529
+ operations: explanation.operations
530
+ }
531
+ } catch (error) {
532
+ return { error: error.message }
533
+ }
534
+ }
535
+
536
+ /**
537
+ * Batch Testing Utilities
538
+ */
539
+
540
+ function batchTestScripts(scripts, options = {}) {
541
+ const results = []
542
+
543
+ scripts.forEach((script, index) => {
544
+ try {
545
+ const validation = validateScript(script)
546
+ const metrics = scriptMetrics(script)
547
+ const complexity = analyzeComplexity(script)
548
+
549
+ results.push({
550
+ index: index,
551
+ script: typeof script === 'string' ? script : scriptToASM(script),
552
+ valid: validation.valid,
553
+ size: validation.size,
554
+ operations: validation.operations,
555
+ complexity: complexity.complexity,
556
+ estimatedCost: metrics.estimatedCost,
557
+ recommendations: complexity.recommendations
558
+ })
559
+ } catch (error) {
560
+ results.push({
561
+ index: index,
562
+ script: typeof script === 'string' ? script : 'Invalid script',
563
+ valid: false,
564
+ error: error.message
565
+ })
566
+ }
567
+ })
568
+
569
+ const summary = {
570
+ total: scripts.length,
571
+ valid: results.filter(r => r.valid).length,
572
+ invalid: results.filter(r => !r.valid).length,
573
+ averageSize: results.filter(r => r.size).reduce((sum, r) => sum + r.size, 0) / results.filter(r => r.size).length || 0,
574
+ complexityDistribution: {
575
+ low: results.filter(r => r.complexity === 'low').length,
576
+ medium: results.filter(r => r.complexity === 'medium').length,
577
+ high: results.filter(r => r.complexity === 'high').length
578
+ }
579
+ }
580
+
581
+ return {
582
+ results: results,
583
+ summary: summary
584
+ }
585
+ }
586
+
587
+ /**
588
+ * Export all utilities
589
+ */
590
+ module.exports = {
591
+ // Format conversions
592
+ scriptToASM,
593
+ asmToScript,
594
+ asmToHex,
595
+ hexToASM,
596
+ scriptToHex,
597
+
598
+ // Validation
599
+ validateASM,
600
+ validateScript,
601
+ validateSyntax,
602
+
603
+ // Analysis
604
+ estimateScriptSize,
605
+ scriptMetrics,
606
+ analyzeComplexity,
607
+
608
+ // Optimization
609
+ optimizeScript,
610
+ findOptimizations,
611
+
612
+ // Comparison
613
+ compareScripts,
614
+
615
+ // Explanation
616
+ explainScript,
617
+ covenantToEnglish,
618
+
619
+ // Batch testing
620
+ batchTestScripts
621
+ }