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,903 @@
1
+ /**
2
+ * SmartContract.Preimage Class
3
+ * ============================
4
+ *
5
+ * Enhanced BIP-143 preimage utilities with:
6
+ * - Complete CompactSize varint support (1-3 bytes)
7
+ * - SIGHASH flag detection and zero hash warnings
8
+ * - Bidirectional field extraction (LEFT/RIGHT/DYNAMIC)
9
+ * - Multi-input transaction handling
10
+ *
11
+ * Based on examples/preimage/extract_preimage_bidirectional.js
12
+ */
13
+
14
+ 'use strict'
15
+
16
+ var bsv = require('../..')
17
+ var crypto = require('crypto')
18
+
19
+ /**
20
+ * Preimage Class - Enhanced preimage field extraction
21
+ * @param {Buffer|string} preimage - Raw preimage data
22
+ * @param {Object} options - Configuration options
23
+ */
24
+ function Preimage(preimage, options) {
25
+ if (!(this instanceof Preimage)) {
26
+ return new Preimage(preimage, options)
27
+ }
28
+
29
+ this.preimage = Buffer.isBuffer(preimage) ? preimage : Buffer.from(preimage, 'hex')
30
+ this.options = options || {}
31
+ this.strategy = this.options.strategy || 'DYNAMIC' // LEFT, RIGHT, DYNAMIC
32
+
33
+ // Initialize extracted fields
34
+ this.fields = null
35
+ this.sighashInfo = null
36
+
37
+ // Auto-extract if not deferred
38
+ if (!this.options.deferExtraction) {
39
+ this._extractFields()
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Extract all preimage fields using bidirectional strategy
45
+ * @param {string} extractionStrategy - 'LEFT', 'RIGHT', or 'DYNAMIC'
46
+ * @returns {Object} Extracted preimage fields
47
+ */
48
+ Preimage.prototype.extract = function(extractionStrategy) {
49
+ this.strategy = extractionStrategy || this.strategy
50
+ this._extractFields()
51
+ return this.fields
52
+ }
53
+
54
+ /**
55
+ * Get SIGHASH flag information and warnings
56
+ * @returns {Object} SIGHASH analysis
57
+ */
58
+ Preimage.prototype.getSighashInfo = function() {
59
+ if (!this.fields) {
60
+ this._extractFields()
61
+ }
62
+ return this.sighashInfo
63
+ }
64
+
65
+ /**
66
+ * Get specific field by name
67
+ * @param {string} fieldName - Field name (version, hashPrevouts, etc.)
68
+ * @returns {Buffer} Field value
69
+ */
70
+ Preimage.prototype.getField = function(fieldName) {
71
+ if (!this.fields) {
72
+ this._extractFields()
73
+ }
74
+ return this.fields[fieldName]
75
+ }
76
+
77
+ /**
78
+ * Extract any preimage field with bidirectional strategy and generate ASM
79
+ * @param {string} fieldName - Field to extract
80
+ * @param {Object} options - Extraction options (includeComments: boolean)
81
+ * @returns {Object} Field data with ASM generation
82
+ */
83
+ Preimage.prototype.extractField = function(fieldName, options) {
84
+ options = options || {}
85
+
86
+ if (!this.fields) {
87
+ this._extractFields()
88
+ }
89
+
90
+ // Parse preimage structure for dynamic extraction
91
+ var parsed = this._parsePreimageStructure()
92
+
93
+ // Generate bidirectional ASM for field extraction
94
+ var asm = this._generateBidirectionalASM(fieldName, parsed, options.includeComments)
95
+
96
+ // Extract field value
97
+ var fieldValue = this._extractSpecificField(fieldName, parsed)
98
+
99
+ // Generate interpretation
100
+ var interpretation = this._interpretField(fieldName, fieldValue, parsed)
101
+
102
+ return {
103
+ field: fieldName,
104
+ value: fieldValue ? fieldValue.toString('hex') : null,
105
+ buffer: fieldValue,
106
+ asm: asm,
107
+ interpretation: interpretation,
108
+ strategy: this._getExtractionStrategy(fieldName),
109
+ structure: parsed._structure
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Generate Bitcoin Script ASM for extracting any preimage field
115
+ * @param {string} fieldName - Field to extract
116
+ * @param {boolean} includeComments - Whether to include comments in ASM (default: false)
117
+ * @returns {string} ASM code for field extraction
118
+ */
119
+ Preimage.prototype.generateASM = function(fieldName, includeComments) {
120
+ var parsed = this._parsePreimageStructure()
121
+ return this._generateBidirectionalASM(fieldName, parsed, includeComments)
122
+ }
123
+
124
+ /**
125
+ * Extract multiple fields with their ASM generation
126
+ * @param {Array} fieldNames - Array of field names to extract
127
+ * @returns {Object} Multiple field extraction results
128
+ */
129
+ Preimage.prototype.extractFields = function(fieldNames) {
130
+ var results = {}
131
+
132
+ fieldNames.forEach(function(fieldName) {
133
+ results[fieldName] = this.extractField(fieldName)
134
+ }.bind(this))
135
+
136
+ return results
137
+ }
138
+
139
+ /**
140
+ * Validate preimage structure
141
+ * @returns {Object} Validation result
142
+ */
143
+ Preimage.prototype.validate = function() {
144
+ if (!this.fields) {
145
+ this._extractFields()
146
+ }
147
+
148
+ var errors = []
149
+ var warnings = []
150
+
151
+ // Check required fields
152
+ var requiredFields = [
153
+ 'version', 'hashPrevouts', 'hashSequence', 'outpoint',
154
+ 'scriptCode', 'amount', 'sequence', 'hashOutputs', 'locktime', 'sighash'
155
+ ]
156
+
157
+ requiredFields.forEach(function(field) {
158
+ if (!this.fields[field]) {
159
+ errors.push('Missing required field: ' + field)
160
+ }
161
+ }.bind(this))
162
+
163
+ // Check for zero hashes that might indicate SIGHASH flags
164
+ if (this.sighashInfo.hasZeroHashes) {
165
+ warnings.push('Zero hashes detected - check SIGHASH flags: ' + this.sighashInfo.zeroFields.join(', '))
166
+ }
167
+
168
+ // Validate preimage length
169
+ if (this.preimage.length < 104) { // Minimum BIP-143 preimage size
170
+ errors.push('Preimage too short: ' + this.preimage.length + ' bytes (minimum 104)')
171
+ }
172
+
173
+ return {
174
+ valid: errors.length === 0,
175
+ errors: errors,
176
+ warnings: warnings,
177
+ sighashInfo: this.sighashInfo
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Convert preimage to detailed object representation
183
+ * @returns {Object} Complete preimage breakdown
184
+ */
185
+ Preimage.prototype.toObject = function() {
186
+ if (!this.fields) {
187
+ this._extractFields()
188
+ }
189
+
190
+ return {
191
+ preimage: this.preimage.toString('hex'),
192
+ length: this.preimage.length,
193
+ strategy: this.strategy,
194
+ fields: {
195
+ version: this.fields.version ? this.fields.version.toString('hex') : null,
196
+ hashPrevouts: this.fields.hashPrevouts ? this.fields.hashPrevouts.toString('hex') : null,
197
+ hashSequence: this.fields.hashSequence ? this.fields.hashSequence.toString('hex') : null,
198
+ outpoint: this.fields.outpoint ? this.fields.outpoint.toString('hex') : null,
199
+ scriptCode: this.fields.scriptCode ? this.fields.scriptCode.toString('hex') : null,
200
+ scriptCodeLength: this.fields.scriptCodeLength,
201
+ amount: this.fields.amount ? this.fields.amount.toString('hex') : null,
202
+ sequence: this.fields.sequence ? this.fields.sequence.toString('hex') : null,
203
+ hashOutputs: this.fields.hashOutputs ? this.fields.hashOutputs.toString('hex') : null,
204
+ locktime: this.fields.locktime ? this.fields.locktime.toString('hex') : null,
205
+ sighash: this.fields.sighash ? this.fields.sighash.toString('hex') : null
206
+ },
207
+ sighashInfo: this.sighashInfo,
208
+ validation: this.validate()
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Internal field extraction implementation
214
+ * @private
215
+ */
216
+ Preimage.prototype._extractFields = function() {
217
+ this.fields = {}
218
+ this.sighashInfo = {
219
+ flag: null,
220
+ hasZeroHashes: false,
221
+ zeroFields: [],
222
+ warnings: []
223
+ }
224
+
225
+ try {
226
+ if (this.strategy === 'LEFT' || this.strategy === 'DYNAMIC') {
227
+ this._extractFromLeft()
228
+ } else if (this.strategy === 'RIGHT') {
229
+ this._extractFromRight()
230
+ }
231
+
232
+ // Analyze SIGHASH flags and zero hashes
233
+ this._analyzeSighash()
234
+
235
+ } catch (error) {
236
+ if (this.strategy === 'DYNAMIC') {
237
+ // Try alternative strategy on failure
238
+ try {
239
+ this.strategy = 'RIGHT'
240
+ this._extractFromRight()
241
+ this._analyzeSighash()
242
+ } catch (fallbackError) {
243
+ throw new Error('Failed to extract preimage fields with any strategy: ' + error.message)
244
+ }
245
+ } else {
246
+ throw error
247
+ }
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Extract fields from left to right (standard approach)
253
+ * @private
254
+ */
255
+ Preimage.prototype._extractFromLeft = function() {
256
+ var offset = 0
257
+
258
+ // version (4 bytes)
259
+ this.fields.version = this.preimage.slice(offset, offset + 4)
260
+ offset += 4
261
+
262
+ // hashPrevouts (32 bytes)
263
+ this.fields.hashPrevouts = this.preimage.slice(offset, offset + 32)
264
+ offset += 32
265
+
266
+ // hashSequence (32 bytes)
267
+ this.fields.hashSequence = this.preimage.slice(offset, offset + 32)
268
+ offset += 32
269
+
270
+ // outpoint (36 bytes: 32 byte txid + 4 byte vout)
271
+ this.fields.outpoint = this.preimage.slice(offset, offset + 36)
272
+ offset += 36
273
+
274
+ // scriptCode - starts with CompactSize varint
275
+ var scriptResult = Preimage.decodeCompactSize(this.preimage, offset)
276
+ this.fields.scriptCodeLength = scriptResult.value
277
+ offset = scriptResult.nextOffset
278
+
279
+ this.fields.scriptCode = this.preimage.slice(offset, offset + this.fields.scriptCodeLength)
280
+ offset += this.fields.scriptCodeLength
281
+
282
+ // amount (8 bytes)
283
+ this.fields.amount = this.preimage.slice(offset, offset + 8)
284
+ offset += 8
285
+
286
+ // sequence (4 bytes)
287
+ this.fields.sequence = this.preimage.slice(offset, offset + 4)
288
+ offset += 4
289
+
290
+ // hashOutputs (32 bytes)
291
+ this.fields.hashOutputs = this.preimage.slice(offset, offset + 32)
292
+ offset += 32
293
+
294
+ // locktime (4 bytes)
295
+ this.fields.locktime = this.preimage.slice(offset, offset + 4)
296
+ offset += 4
297
+
298
+ // sighash (4 bytes)
299
+ this.fields.sighash = this.preimage.slice(offset, offset + 4)
300
+ }
301
+
302
+ /**
303
+ * Extract fields from right to left (fallback strategy)
304
+ * @private
305
+ */
306
+ Preimage.prototype._extractFromRight = function() {
307
+ var length = this.preimage.length
308
+ var offset = length
309
+
310
+ // sighash (4 bytes from end)
311
+ offset -= 4
312
+ this.fields.sighash = this.preimage.slice(offset, offset + 4)
313
+
314
+ // locktime (4 bytes)
315
+ offset -= 4
316
+ this.fields.locktime = this.preimage.slice(offset, offset + 4)
317
+
318
+ // hashOutputs (32 bytes)
319
+ offset -= 32
320
+ this.fields.hashOutputs = this.preimage.slice(offset, offset + 32)
321
+
322
+ // sequence (4 bytes)
323
+ offset -= 4
324
+ this.fields.sequence = this.preimage.slice(offset, offset + 4)
325
+
326
+ // amount (8 bytes)
327
+ offset -= 8
328
+ this.fields.amount = this.preimage.slice(offset, offset + 8)
329
+
330
+ // Now work from the left for scriptCode with varint
331
+ var leftOffset = 4 + 32 + 32 + 36 // Skip version, hashPrevouts, hashSequence, outpoint
332
+
333
+ var scriptResult = Preimage.decodeCompactSize(this.preimage, leftOffset)
334
+ this.fields.scriptCodeLength = scriptResult.value
335
+ this.fields.scriptCode = this.preimage.slice(scriptResult.nextOffset, scriptResult.nextOffset + this.fields.scriptCodeLength)
336
+
337
+ // Extract remaining left-side fields
338
+ this.fields.version = this.preimage.slice(0, 4)
339
+ this.fields.hashPrevouts = this.preimage.slice(4, 36)
340
+ this.fields.hashSequence = this.preimage.slice(36, 68)
341
+ this.fields.outpoint = this.preimage.slice(68, 104)
342
+ }
343
+
344
+ /**
345
+ * Analyze SIGHASH flags and detect zero hashes
346
+ * @private
347
+ */
348
+ Preimage.prototype._analyzeSighash = function() {
349
+ if (!this.fields.sighash) return
350
+
351
+ var sighashFlag = this.fields.sighash.readUInt32LE(0)
352
+ this.sighashInfo.flag = sighashFlag
353
+
354
+ // Check for SIGHASH flag components
355
+ var baseType = sighashFlag & 0x1f
356
+ var anyoneCanPay = (sighashFlag & 0x80) !== 0
357
+ var forkId = (sighashFlag & 0x40) !== 0
358
+
359
+ // Detect zero hashes based on SIGHASH flags
360
+ var zeroHash = Buffer.alloc(32)
361
+
362
+ if (anyoneCanPay && this.fields.hashPrevouts.equals(zeroHash)) {
363
+ this.sighashInfo.hasZeroHashes = true
364
+ this.sighashInfo.zeroFields.push('hashPrevouts')
365
+ this.sighashInfo.warnings.push('ANYONECANPAY flag detected - hashPrevouts is zero')
366
+ }
367
+
368
+ if (baseType === 2 && this.fields.hashSequence.equals(zeroHash)) { // SIGHASH_NONE
369
+ this.sighashInfo.hasZeroHashes = true
370
+ this.sighashInfo.zeroFields.push('hashSequence')
371
+ this.sighashInfo.warnings.push('SIGHASH_NONE flag detected - hashSequence is zero')
372
+ }
373
+
374
+ if ((baseType === 2 || baseType === 3) && this.fields.hashOutputs.equals(zeroHash)) { // NONE or SINGLE
375
+ this.sighashInfo.hasZeroHashes = true
376
+ this.sighashInfo.zeroFields.push('hashOutputs')
377
+ this.sighashInfo.warnings.push('SIGHASH_NONE/SINGLE flag detected - hashOutputs is zero')
378
+ }
379
+
380
+ // Set human-readable flag description
381
+ var flagNames = []
382
+ if (baseType === 1) flagNames.push('ALL')
383
+ else if (baseType === 2) flagNames.push('NONE')
384
+ else if (baseType === 3) flagNames.push('SINGLE')
385
+
386
+ if (anyoneCanPay) flagNames.push('ANYONECANPAY')
387
+ if (forkId) flagNames.push('FORKID')
388
+
389
+ this.sighashInfo.flagName = flagNames.join(' | ')
390
+ }
391
+
392
+ /**
393
+ * Parse preimage structure for bidirectional extraction
394
+ * @private
395
+ * @returns {Object} Parsed preimage structure with all fields
396
+ */
397
+ Preimage.prototype._parsePreimageStructure = function() {
398
+ var buf = this.preimage
399
+ var offset = 0
400
+ var parsed = {}
401
+
402
+ // Define field structures
403
+ var leftFixedFields = [
404
+ { name: 'nVersion', len: 4 },
405
+ { name: 'hashPrevouts', len: 32 },
406
+ { name: 'hashSequence', len: 32 },
407
+ { name: 'outpoint_txid', len: 32 },
408
+ { name: 'outpoint_vout', len: 4 }
409
+ ]
410
+
411
+ var rightFixedFields = [
412
+ { name: 'value', len: 8 },
413
+ { name: 'nSequence', len: 4 },
414
+ { name: 'hashOutputs', len: 32 },
415
+ { name: 'nLocktime', len: 4 },
416
+ { name: 'sighashType', len: 4 }
417
+ ]
418
+
419
+ // Parse LEFT fixed fields
420
+ for (var i = 0; i < leftFixedFields.length; i++) {
421
+ var field = leftFixedFields[i]
422
+ parsed[field.name] = buf.slice(offset, offset + field.len).toString('hex')
423
+ offset += field.len
424
+ }
425
+
426
+ // Parse CompactSize varint for scriptLen
427
+ var scriptLenInfo = Preimage.decodeCompactSize(buf, offset)
428
+ parsed.scriptLen = scriptLenInfo.value
429
+ parsed.scriptLenSize = scriptLenInfo.bytes
430
+ parsed.scriptLenRaw = buf.slice(offset, offset + scriptLenInfo.bytes).toString('hex')
431
+ offset += scriptLenInfo.bytes
432
+
433
+ // Parse variable scriptCode
434
+ parsed.scriptCode = buf.slice(offset, offset + parsed.scriptLen).toString('hex')
435
+ offset += parsed.scriptLen
436
+
437
+ // Parse RIGHT fields
438
+ for (var j = 0; j < rightFixedFields.length; j++) {
439
+ var rightField = rightFixedFields[j]
440
+ parsed[rightField.name] = buf.slice(offset, offset + rightField.len).toString('hex')
441
+ offset += rightField.len
442
+ }
443
+
444
+ // Add structure info
445
+ var leftFixedTotal = leftFixedFields.reduce(function(sum, f) { return sum + f.len }, 0)
446
+ var rightTotal = rightFixedFields.reduce(function(sum, f) { return sum + f.len }, 0)
447
+
448
+ parsed._structure = {
449
+ leftFixed: leftFixedTotal,
450
+ scriptLenVarint: scriptLenInfo.bytes,
451
+ scriptCode: parsed.scriptLen,
452
+ rightFixed: rightTotal,
453
+ totalCalculated: leftFixedTotal + scriptLenInfo.bytes + parsed.scriptLen + rightTotal,
454
+ totalActual: buf.length
455
+ }
456
+
457
+ return parsed
458
+ }
459
+
460
+ /**
461
+ * Generate bidirectional ASM for field extraction
462
+ * @private
463
+ * @param {string} fieldName - Field to extract
464
+ * @param {Object} parsed - Parsed preimage structure
465
+ * @param {boolean} includeComments - Whether to include comments in ASM
466
+ * @returns {string} ASM code
467
+ */
468
+ Preimage.prototype._generateBidirectionalASM = function(fieldName, parsed, includeComments) {
469
+ // Field mappings
470
+ var rightFields = ['value', 'nSequence', 'hashOutputs', 'nLocktime', 'sighashType']
471
+ var leftFields = ['nVersion', 'hashPrevouts', 'hashSequence', 'outpoint_txid', 'outpoint_vout']
472
+
473
+ if (rightFields.includes(fieldName)) {
474
+ return this._generateRightExtractionASM(fieldName, includeComments)
475
+ } else if (leftFields.includes(fieldName)) {
476
+ return this._generateLeftExtractionASM(fieldName, includeComments)
477
+ } else if (fieldName === 'scriptCode') {
478
+ return this._generateDynamicExtractionASM(parsed, includeComments)
479
+ } else if (fieldName === 'scriptLen') {
480
+ return this._generateScriptLenExtractionASM(parsed, includeComments)
481
+ } else {
482
+ throw new Error('Unknown field: ' + fieldName)
483
+ }
484
+ }
485
+
486
+ /**
487
+ * Generate ASM for RIGHT-side field extraction
488
+ * @private
489
+ */
490
+ Preimage.prototype._generateRightExtractionASM = function(fieldName, includeComments) {
491
+ var rightFields = [
492
+ { name: 'value', len: 8 },
493
+ { name: 'nSequence', len: 4 },
494
+ { name: 'hashOutputs', len: 32 },
495
+ { name: 'nLocktime', len: 4 },
496
+ { name: 'sighashType', len: 4 }
497
+ ]
498
+
499
+ // Calculate offset from end (accumulate lengths of fields that come AFTER target field)
500
+ var offsetFromEnd = 0
501
+ var targetLen = 0
502
+ var found = false
503
+
504
+ for (var i = 0; i < rightFields.length; i++) {
505
+ var f = rightFields[i]
506
+ if (f.name === fieldName) {
507
+ targetLen = f.len
508
+ found = true
509
+ break
510
+ }
511
+ offsetFromEnd += f.len
512
+ }
513
+
514
+ if (!found) throw new Error('Field not found in RIGHT fields: ' + fieldName)
515
+
516
+ var rightTotal = 52 // Total bytes in right zone
517
+ var splitPoint = rightTotal - offsetFromEnd // Split point from end
518
+
519
+ var instructions = []
520
+
521
+ if (includeComments) {
522
+ instructions.push('# 🔄 Extract ' + fieldName + ' from RIGHT side (bidirectional strategy)')
523
+ instructions.push('OP_SIZE # Push preimage size: [preimage, size]')
524
+ } else {
525
+ instructions.push('OP_SIZE')
526
+ }
527
+
528
+ if (includeComments) {
529
+ instructions = instructions.concat([
530
+ splitPoint + ' OP_SUB # Calculate split point: [preimage, split_point]',
531
+ 'OP_SPLIT # Split: [left_part, right_part]',
532
+ 'OP_DROP # Drop left: [right_part]',
533
+ targetLen + ' OP_SPLIT # Extract field: [remaining, ' + fieldName + ']',
534
+ 'OP_DROP # Clean up: [' + fieldName + ']',
535
+ '# ✅ Result: ' + fieldName + ' is now on top of stack'
536
+ ])
537
+ } else {
538
+ instructions = instructions.concat([
539
+ splitPoint + ' OP_SUB',
540
+ 'OP_SPLIT',
541
+ 'OP_DROP',
542
+ targetLen + ' OP_SPLIT',
543
+ 'OP_DROP'
544
+ ])
545
+ }
546
+
547
+ return instructions.join('\n')
548
+ }
549
+
550
+ /**
551
+ * Generate ASM for LEFT-side field extraction
552
+ * @private
553
+ */
554
+ Preimage.prototype._generateLeftExtractionASM = function(fieldName, includeComments) {
555
+ var leftFields = [
556
+ { name: 'nVersion', len: 4, offset: 0 },
557
+ { name: 'hashPrevouts', len: 32, offset: 4 },
558
+ { name: 'hashSequence', len: 32, offset: 36 },
559
+ { name: 'outpoint_txid', len: 32, offset: 68 },
560
+ { name: 'outpoint_vout', len: 4, offset: 100 }
561
+ ]
562
+
563
+ var fieldInfo = leftFields.find(function(f) { return f.name === fieldName })
564
+ if (!fieldInfo) throw new Error('Field not found in LEFT fields: ' + fieldName)
565
+
566
+ var instructions = []
567
+
568
+ if (includeComments) {
569
+ instructions = [
570
+ '# 🔄 Extract ' + fieldName + ' from LEFT side (bidirectional strategy)',
571
+ fieldInfo.offset + ' OP_SPLIT # Skip to field: [prefix, remainder]',
572
+ 'OP_DROP # Drop prefix: [remainder]',
573
+ fieldInfo.len + ' OP_SPLIT # Extract field: [' + fieldName + ', suffix]',
574
+ 'OP_DROP # Clean up: [' + fieldName + ']',
575
+ '# ✅ Result: ' + fieldName + ' is now on top of stack'
576
+ ]
577
+ } else {
578
+ instructions = [
579
+ fieldInfo.offset + ' OP_SPLIT',
580
+ 'OP_DROP',
581
+ fieldInfo.len + ' OP_SPLIT',
582
+ 'OP_DROP'
583
+ ]
584
+ }
585
+
586
+ return instructions.join('\n')
587
+ }
588
+
589
+ /**
590
+ * Generate ASM for dynamic scriptCode extraction
591
+ * @private
592
+ */
593
+ Preimage.prototype._generateDynamicExtractionASM = function(parsed, includeComments) {
594
+ var leftZone = 104 + parsed.scriptLenSize // 104 bytes fixed + varint size
595
+
596
+ return [
597
+ '# 🎯 Extract scriptCode DYNAMICALLY with CompactSize varint support',
598
+ leftZone + ' OP_SPLIT # Skip left zone + scriptLen varint: [left_zone, remainder]',
599
+ 'OP_DROP # Drop left: [remainder]',
600
+ parsed.scriptLen + ' OP_SPLIT # Extract scriptCode: [scriptCode, right_zone]',
601
+ 'OP_DROP # Clean up: [scriptCode]',
602
+ '# ✅ Result: scriptCode extracted with ' + parsed.scriptLenSize + '-byte varint awareness'
603
+ ].join('\n')
604
+ }
605
+
606
+ /**
607
+ * Generate ASM for scriptLen extraction
608
+ * @private
609
+ */
610
+ Preimage.prototype._generateScriptLenExtractionASM = function(parsed, includeComments) {
611
+ return [
612
+ '# 🎯 Extract scriptLen CompactSize varint (' + parsed.scriptLenSize + ' bytes)',
613
+ '104 OP_SPLIT # Skip left fixed fields: [left_zone, remainder]',
614
+ 'OP_DROP # Drop left: [remainder]',
615
+ parsed.scriptLenSize + ' OP_SPLIT # Extract varint: [scriptLen_varint, suffix]',
616
+ 'OP_DROP # Clean up: [scriptLen_varint]',
617
+ '# ✅ Result: CompactSize varint (decode off-chain to get ' + parsed.scriptLen + ')'
618
+ ].join('\n')
619
+ }
620
+
621
+ /**
622
+ * Extract specific field from parsed structure
623
+ * @private
624
+ */
625
+ Preimage.prototype._extractSpecificField = function(fieldName, parsed) {
626
+ var hexValue = parsed[fieldName]
627
+ return hexValue ? Buffer.from(hexValue, 'hex') : null
628
+ }
629
+
630
+ /**
631
+ * Interpret field value with context
632
+ * @private
633
+ */
634
+ Preimage.prototype._interpretField = function(fieldName, fieldValue, parsed) {
635
+ if (!fieldValue) return null
636
+
637
+ var interpretation = {
638
+ raw: fieldValue.toString('hex'),
639
+ bytes: fieldValue.length
640
+ }
641
+
642
+ try {
643
+ switch (fieldName) {
644
+ case 'nVersion':
645
+ interpretation.value = fieldValue.readUInt32LE(0)
646
+ interpretation.description = 'Transaction version ' + interpretation.value
647
+ break
648
+
649
+ case 'value':
650
+ if (fieldValue.length === 8) {
651
+ interpretation.satoshis = fieldValue.readBigUInt64LE(0).toString()
652
+ interpretation.description = interpretation.satoshis + ' satoshis'
653
+ }
654
+ break
655
+
656
+ case 'sighashType':
657
+ var sighashInt = fieldValue.readUInt32LE(0)
658
+ var types = {
659
+ 1: 'SIGHASH_ALL',
660
+ 65: 'SIGHASH_ALL | FORKID',
661
+ 2: 'SIGHASH_NONE',
662
+ 66: 'SIGHASH_NONE | FORKID',
663
+ 3: 'SIGHASH_SINGLE',
664
+ 67: 'SIGHASH_SINGLE | FORKID'
665
+ }
666
+ interpretation.value = sighashInt
667
+ interpretation.description = types[sighashInt] || 'Custom (' + sighashInt + ')'
668
+ break
669
+
670
+ case 'outpoint_vout':
671
+ interpretation.value = fieldValue.readUInt32LE(0)
672
+ interpretation.description = 'Output index ' + interpretation.value
673
+ break
674
+
675
+ case 'scriptLen':
676
+ interpretation.varintSize = parsed.scriptLenSize
677
+ interpretation.scriptLength = parsed.scriptLen
678
+ interpretation.description = parsed.scriptLen + ' bytes encoded as ' + parsed.scriptLenSize + '-byte varint'
679
+ break
680
+
681
+ case 'scriptCode':
682
+ if (fieldValue.length === 25 && fieldValue[0] === 0x76 && fieldValue[1] === 0xa9) {
683
+ interpretation.description = 'Standard P2PKH script (25 bytes)'
684
+ interpretation.type = 'P2PKH'
685
+ } else if (fieldValue.length > 0 && fieldValue[0] === 0x6a) {
686
+ interpretation.description = 'OP_RETURN data script (' + fieldValue.length + ' bytes)'
687
+ interpretation.type = 'OP_RETURN'
688
+ } else {
689
+ interpretation.description = 'Custom script (' + fieldValue.length + ' bytes)'
690
+ interpretation.type = 'CUSTOM'
691
+ }
692
+ break
693
+
694
+ default:
695
+ if (['hashPrevouts', 'hashSequence', 'hashOutputs'].includes(fieldName)) {
696
+ var zeroHash = Buffer.alloc(32)
697
+ if (fieldValue.equals(zeroHash)) {
698
+ interpretation.isZero = true
699
+ interpretation.description = 'Zero hash (check SIGHASH flags)'
700
+ } else {
701
+ interpretation.description = '32-byte hash'
702
+ }
703
+ }
704
+ }
705
+ } catch (error) {
706
+ interpretation.error = error.message
707
+ }
708
+
709
+ return interpretation
710
+ }
711
+
712
+ /**
713
+ * Get extraction strategy for field
714
+ * @private
715
+ */
716
+ Preimage.prototype._getExtractionStrategy = function(fieldName) {
717
+ var rightFields = ['value', 'nSequence', 'hashOutputs', 'nLocktime', 'sighashType']
718
+ var leftFields = ['nVersion', 'hashPrevouts', 'hashSequence', 'outpoint_txid', 'outpoint_vout']
719
+
720
+ if (rightFields.includes(fieldName)) return 'RIGHT'
721
+ if (leftFields.includes(fieldName)) return 'LEFT'
722
+ if (fieldName === 'scriptCode') return 'DYNAMIC'
723
+ if (fieldName === 'scriptLen') return 'VARINT'
724
+ return 'UNKNOWN'
725
+ }
726
+
727
+ /**
728
+ * Static utility methods
729
+ */
730
+
731
+ /**
732
+ * Decode CompactSize varint (1-3 bytes for practical Bitcoin amounts)
733
+ * @param {Buffer} buffer - Buffer to read from
734
+ * @param {number} offset - Starting offset
735
+ * @returns {Object} Decoded value and next offset
736
+ */
737
+ Preimage.decodeCompactSize = function(buffer, offset) {
738
+ var firstByte = buffer[offset]
739
+
740
+ if (firstByte < 0xfd) {
741
+ // 1 byte encoding (0-252)
742
+ return { value: firstByte, nextOffset: offset + 1, bytes: 1 }
743
+ } else if (firstByte === 0xfd) {
744
+ // 2 byte encoding (253-65535)
745
+ var value = buffer.readUInt16LE(offset + 1)
746
+ return { value: value, nextOffset: offset + 3, bytes: 3 }
747
+ } else if (firstByte === 0xfe) {
748
+ // 4 byte encoding (65536-4294967295) - rare for script lengths
749
+ var value = buffer.readUInt32LE(offset + 1)
750
+ return { value: value, nextOffset: offset + 5, bytes: 5 }
751
+ } else {
752
+ // 8 byte encoding - extremely rare, try to handle gracefully
753
+ console.warn('Encountered 8-byte CompactSize - this is very unusual for script lengths')
754
+ // For our purposes, assume it's a reasonable script length and try to parse it
755
+ // This is a fallback for malformed or test data
756
+ try {
757
+ // Read as 4-byte for now since 8-byte values would be enormous for scripts
758
+ var value = buffer.readUInt32LE(offset + 1)
759
+ return { value: value, nextOffset: offset + 9, bytes: 9 } // Skip full 8 bytes + marker
760
+ } catch (e) {
761
+ throw new Error('Invalid 8-byte CompactSize encoding in preimage')
762
+ }
763
+ }
764
+ }
765
+
766
+ /**
767
+ * Create preimage from transaction and input details
768
+ * @param {Transaction} transaction - Transaction to create preimage for
769
+ * @param {number} inputIndex - Input index to sign
770
+ * @param {Script} subscript - Subscript for signing
771
+ * @param {number} satoshis - Input amount in satoshis
772
+ * @param {number} sighashType - SIGHASH type
773
+ * @returns {Preimage} Preimage instance
774
+ */
775
+ Preimage.fromTransaction = function(transaction, inputIndex, subscript, satoshis, sighashType) {
776
+ var preimageBuffer = bsv.Transaction.sighash.sighashPreimage(
777
+ transaction,
778
+ sighashType,
779
+ inputIndex,
780
+ subscript,
781
+ new bsv.crypto.BN(satoshis)
782
+ )
783
+
784
+ return new Preimage(preimageBuffer, {
785
+ transaction: transaction,
786
+ inputIndex: inputIndex,
787
+ subscript: subscript,
788
+ satoshis: satoshis,
789
+ sighashType: sighashType
790
+ })
791
+ }
792
+
793
+ /**
794
+ * Generate example preimage with specific SIGHASH flags
795
+ * @param {number} sighashType - SIGHASH type to demonstrate
796
+ * @returns {Preimage} Example preimage
797
+ */
798
+ Preimage.createExample = function(sighashType) {
799
+ // Create a simple example transaction
800
+ var privateKey = bsv.PrivateKey.fromRandom()
801
+ var address = privateKey.toAddress()
802
+
803
+ var utxo = {
804
+ txId: '0'.repeat(64),
805
+ outputIndex: 0,
806
+ script: bsv.Script.buildPublicKeyHashOut(address).toHex(),
807
+ satoshis: 100000
808
+ }
809
+
810
+ var transaction = new bsv.Transaction()
811
+ .from(utxo)
812
+ .to(address, 99000)
813
+
814
+ var subscript = bsv.Script.fromHex(utxo.script)
815
+
816
+ return Preimage.fromTransaction(transaction, 0, subscript, utxo.satoshis, sighashType)
817
+ }
818
+
819
+ /**
820
+ * Extract any field from raw preimage hex (static utility)
821
+ * @param {string} preimageHex - Raw preimage as hex string
822
+ * @param {string} fieldName - Field to extract
823
+ * @param {Object} options - Extraction options
824
+ * @returns {Object} Field extraction result with ASM
825
+ */
826
+ Preimage.extractFromHex = function(preimageHex, fieldName, options) {
827
+ var preimage = new Preimage(preimageHex, { deferExtraction: true })
828
+ return preimage.extractField(fieldName, options)
829
+ }
830
+
831
+ /**
832
+ * Generate ASM for field extraction from raw hex (static utility)
833
+ * @param {string} preimageHex - Raw preimage as hex string
834
+ * @param {string} fieldName - Field to extract ASM for
835
+ * @returns {string} ASM code for stack manipulation
836
+ */
837
+ Preimage.generateASMFromHex = function(preimageHex, fieldName, includeComments) {
838
+ var preimage = new Preimage(preimageHex, { deferExtraction: true })
839
+ return preimage.generateASM(fieldName, includeComments)
840
+ }
841
+
842
+ /**
843
+ * Batch extract multiple fields from raw hex (static utility)
844
+ * @param {string} preimageHex - Raw preimage as hex string
845
+ * @param {Array} fieldNames - Array of field names
846
+ * @returns {Object} Multiple field extraction results
847
+ */
848
+ Preimage.extractMultipleFromHex = function(preimageHex, fieldNames) {
849
+ var preimage = new Preimage(preimageHex, { deferExtraction: true })
850
+ return preimage.extractFields(fieldNames)
851
+ }
852
+
853
+ /**
854
+ * Validate preimage structure from raw hex (static utility)
855
+ * @param {string} preimageHex - Raw preimage as hex string
856
+ * @returns {Object} Validation result
857
+ */
858
+ Preimage.validateFromHex = function(preimageHex) {
859
+ var preimage = new Preimage(preimageHex)
860
+ return preimage.validate()
861
+ }
862
+
863
+ /**
864
+ * Parse preimage structure and return detailed analysis (static utility)
865
+ * @param {string} preimageHex - Raw preimage as hex string
866
+ * @returns {Object} Complete preimage analysis
867
+ */
868
+ Preimage.analyzeFromHex = function(preimageHex) {
869
+ var preimage = new Preimage(preimageHex)
870
+ var parsed = preimage._parsePreimageStructure()
871
+ var validation = preimage.validate()
872
+
873
+ return {
874
+ hex: preimageHex,
875
+ length: preimageHex.length / 2, // Convert from hex chars to bytes
876
+ structure: parsed._structure,
877
+ fields: parsed,
878
+ validation: validation,
879
+ sighashInfo: preimage.getSighashInfo(),
880
+
881
+ // Helper methods for common operations
882
+ extractField: function(fieldName) {
883
+ return preimage.extractField(fieldName)
884
+ },
885
+
886
+ generateASM: function(fieldName) {
887
+ return preimage.generateASM(fieldName)
888
+ },
889
+
890
+ getSummary: function() {
891
+ return {
892
+ totalBytes: parsed._structure.totalActual,
893
+ scriptLength: parsed.scriptLen,
894
+ scriptVarintSize: parsed.scriptLenSize,
895
+ sighashType: parsed.sighashType,
896
+ valid: validation.valid,
897
+ warnings: validation.warnings
898
+ }
899
+ }
900
+ }
901
+ }
902
+
903
+ module.exports = Preimage