@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.
- 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 +862 -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 +350 -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_interpreter.js +236 -0
- package/lib/smart_contract/script_tester.js +487 -0
- package/lib/smart_contract/script_utils.js +621 -0
- package/lib/smart_contract/sighash.js +310 -0
- package/lib/smart_contract/smartledger-opcode_review.md +70 -0
- package/lib/smart_contract/stack_examiner.js +129 -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,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
|