@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.
- package/CHANGELOG.md +123 -1
- package/README.md +233 -277
- package/bsv.bundle.js +39 -0
- package/bsv.min.js +8 -8
- package/docs/ADVANCED_COVENANT_DEVELOPMENT.md +533 -0
- package/docs/COVENANT_DEVELOPMENT_RESOLVED.md +169 -0
- package/docs/CUSTOM_SCRIPT_DEVELOPMENT.md +320 -0
- package/docs/README.md +201 -0
- package/docs/block.md +46 -0
- package/docs/ecies.md +102 -0
- package/docs/index.md +104 -0
- package/docs/nchain.md +958 -0
- package/docs/networks.md +55 -0
- package/docs/preimage.md +126 -0
- package/docs/script.md +139 -0
- package/docs/transaction.md +174 -0
- package/docs/unspentoutput.md +32 -0
- package/examples/README.md +200 -0
- package/examples/basic/transaction-creation.js +534 -0
- package/examples/basic/transaction_signature_api_gap.js +178 -0
- package/examples/covenants/advanced_covenant_demo.js +219 -0
- package/examples/covenants/covenant_interface_demo.js +270 -0
- package/examples/covenants/covenant_manual_signature_resolved.js +212 -0
- package/examples/covenants/covenant_signature_template.js +117 -0
- package/examples/covenants2/covenant_bidirectional_example.js +262 -0
- package/examples/covenants2/covenant_utils_demo.js +120 -0
- package/examples/covenants2/preimage_covenant_utils.js +287 -0
- package/examples/covenants2/production_integration.js +256 -0
- package/examples/data/covenant_utxos.json +28 -0
- package/examples/data/utxos.json +26 -0
- package/examples/preimage/README.md +178 -0
- package/examples/preimage/extract_preimage_bidirectional.js +421 -0
- package/examples/preimage/generate_sample_preimage.js +208 -0
- package/examples/preimage/generate_sighash_examples.js +152 -0
- package/examples/preimage/parse_preimage.js +117 -0
- package/examples/preimage/test_preimage_extractor.js +53 -0
- package/examples/preimage/test_varint_extraction.js +95 -0
- package/examples/scripts/custom_script_helper_example.js +273 -0
- package/examples/scripts/custom_script_signature_test.js +344 -0
- package/examples/scripts/script_interpreter.js +193 -0
- package/examples/smart_contract/complete_workflow_demo.js +343 -0
- package/examples/smart_contract/covenant_builder_demo.js +176 -0
- package/examples/smart_contract/script_testing_integration.js +198 -0
- package/index.js +3 -0
- package/lib/covenant-interface.js +713 -0
- package/lib/opcode.js +14 -7
- package/lib/smart_contract/API_REFERENCE.md +754 -0
- package/lib/smart_contract/DOCUMENTATION_SUMMARY.md +201 -0
- package/lib/smart_contract/EXAMPLES.md +751 -0
- package/lib/smart_contract/QUICK_START.md +549 -0
- package/lib/smart_contract/README.md +395 -0
- package/lib/smart_contract/builder.js +452 -0
- package/lib/smart_contract/covenant.js +336 -0
- package/lib/smart_contract/covenant_builder.js +512 -0
- package/lib/smart_contract/index.js +311 -0
- package/lib/smart_contract/opcode_list.js +30 -0
- package/lib/smart_contract/opcode_map.js +1174 -0
- package/lib/smart_contract/opcodes.md +1173 -0
- package/lib/smart_contract/preimage.js +903 -0
- package/lib/smart_contract/script_tester.js +487 -0
- package/lib/smart_contract/script_utils.js +609 -0
- package/lib/smart_contract/sighash.js +310 -0
- package/lib/smart_contract/smartledger-opcode_review.md +70 -0
- package/lib/smart_contract/test_integration.js +269 -0
- package/lib/smart_contract/utxo_generator.js +367 -0
- package/package.json +43 -10
- 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
|
+
}
|