@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,1174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* opcode_map.js
|
|
3
|
+
* --------------------------------------------------------------------
|
|
4
|
+
* Comprehensive mapping of Bitcoin Script opcodes to JavaScript stack
|
|
5
|
+
* manipulation functions. This enables writing covenant logic in JavaScript
|
|
6
|
+
* and automatically generating the corresponding Bitcoin Script ASM and hex.
|
|
7
|
+
*
|
|
8
|
+
* Each entry provides:
|
|
9
|
+
* - code: hexadecimal opcode value
|
|
10
|
+
* - action: JavaScript function that simulates the opcode's stack behavior
|
|
11
|
+
* - description: Human-readable explanation of the operation
|
|
12
|
+
* - category: Functional grouping for organization
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// Helper functions for Bitcoin Script number encoding/decoding
|
|
16
|
+
const scriptNum = {
|
|
17
|
+
encode: (num) => {
|
|
18
|
+
if (num === 0) return Buffer.alloc(0);
|
|
19
|
+
const negative = num < 0;
|
|
20
|
+
const abs = Math.abs(num);
|
|
21
|
+
const bytes = [];
|
|
22
|
+
let temp = abs;
|
|
23
|
+
while (temp > 0) {
|
|
24
|
+
bytes.push(temp & 0xff);
|
|
25
|
+
temp >>= 8;
|
|
26
|
+
}
|
|
27
|
+
if (bytes[bytes.length - 1] & 0x80) {
|
|
28
|
+
bytes.push(negative ? 0x80 : 0);
|
|
29
|
+
} else if (negative) {
|
|
30
|
+
bytes[bytes.length - 1] |= 0x80;
|
|
31
|
+
}
|
|
32
|
+
return Buffer.from(bytes);
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
decode: (buf) => {
|
|
36
|
+
if (buf.length === 0) return 0;
|
|
37
|
+
const bytes = Array.from(buf);
|
|
38
|
+
const negative = bytes[bytes.length - 1] & 0x80;
|
|
39
|
+
if (negative) bytes[bytes.length - 1] &= 0x7f;
|
|
40
|
+
let result = 0;
|
|
41
|
+
for (let i = bytes.length - 1; i >= 0; i--) {
|
|
42
|
+
result = (result << 8) + bytes[i];
|
|
43
|
+
}
|
|
44
|
+
return negative ? -result : result;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const opcodeMap = {
|
|
49
|
+
/* ==================== CONSTANTS AND PUSH DATA ==================== */
|
|
50
|
+
OP_FALSE: {
|
|
51
|
+
code: 0x00,
|
|
52
|
+
category: 'constants',
|
|
53
|
+
description: 'Push empty array (false value)',
|
|
54
|
+
action: s => s.push(Buffer.alloc(0))
|
|
55
|
+
},
|
|
56
|
+
OP_0: {
|
|
57
|
+
code: 0x00,
|
|
58
|
+
category: 'constants',
|
|
59
|
+
description: 'Push empty array (alias for OP_FALSE)',
|
|
60
|
+
action: s => s.push(Buffer.alloc(0))
|
|
61
|
+
},
|
|
62
|
+
OP_PUSHDATA1: {
|
|
63
|
+
code: 0x4c,
|
|
64
|
+
category: 'pushdata',
|
|
65
|
+
description: 'Push next [1 byte] bytes of data',
|
|
66
|
+
action: "Read next byte as length; push that many bytes"
|
|
67
|
+
},
|
|
68
|
+
OP_PUSHDATA2: {
|
|
69
|
+
code: 0x4d,
|
|
70
|
+
category: 'pushdata',
|
|
71
|
+
description: 'Push next [2 bytes LE] bytes of data',
|
|
72
|
+
action: "Read next 2 bytes as length; push that many bytes"
|
|
73
|
+
},
|
|
74
|
+
OP_PUSHDATA4: {
|
|
75
|
+
code: 0x4e,
|
|
76
|
+
category: 'pushdata',
|
|
77
|
+
description: 'Push next [4 bytes LE] bytes of data',
|
|
78
|
+
action: "Read next 4 bytes as length; push that many bytes"
|
|
79
|
+
},
|
|
80
|
+
OP_1NEGATE: {
|
|
81
|
+
code: 0x4f,
|
|
82
|
+
category: 'constants',
|
|
83
|
+
description: 'Push number -1',
|
|
84
|
+
action: s => s.push(scriptNum.encode(-1))
|
|
85
|
+
},
|
|
86
|
+
OP_RESERVED: {
|
|
87
|
+
code: 0x50,
|
|
88
|
+
category: 'reserved',
|
|
89
|
+
description: 'Reserved opcode (makes transaction invalid)',
|
|
90
|
+
action: () => { throw new Error('OP_RESERVED encountered'); }
|
|
91
|
+
},
|
|
92
|
+
OP_TRUE: {
|
|
93
|
+
code: 0x51,
|
|
94
|
+
category: 'constants',
|
|
95
|
+
description: 'Push number 1 (true value)',
|
|
96
|
+
action: s => s.push(scriptNum.encode(1))
|
|
97
|
+
},
|
|
98
|
+
OP_1: {
|
|
99
|
+
code: 0x51,
|
|
100
|
+
category: 'constants',
|
|
101
|
+
description: 'Push number 1 (alias for OP_TRUE)',
|
|
102
|
+
action: s => s.push(scriptNum.encode(1))
|
|
103
|
+
},
|
|
104
|
+
OP_2: {
|
|
105
|
+
code: 0x52,
|
|
106
|
+
category: 'constants',
|
|
107
|
+
description: 'Push number 2',
|
|
108
|
+
action: s => s.push(scriptNum.encode(2))
|
|
109
|
+
},
|
|
110
|
+
OP_3: {
|
|
111
|
+
code: 0x53,
|
|
112
|
+
category: 'constants',
|
|
113
|
+
description: 'Push number 3',
|
|
114
|
+
action: s => s.push(scriptNum.encode(3))
|
|
115
|
+
},
|
|
116
|
+
OP_4: {
|
|
117
|
+
code: 0x54,
|
|
118
|
+
category: 'constants',
|
|
119
|
+
description: 'Push number 4',
|
|
120
|
+
action: s => s.push(scriptNum.encode(4))
|
|
121
|
+
},
|
|
122
|
+
OP_5: {
|
|
123
|
+
code: 0x55,
|
|
124
|
+
category: 'constants',
|
|
125
|
+
description: 'Push number 5',
|
|
126
|
+
action: s => s.push(scriptNum.encode(5))
|
|
127
|
+
},
|
|
128
|
+
OP_6: {
|
|
129
|
+
code: 0x56,
|
|
130
|
+
category: 'constants',
|
|
131
|
+
description: 'Push number 6',
|
|
132
|
+
action: s => s.push(scriptNum.encode(6))
|
|
133
|
+
},
|
|
134
|
+
OP_7: {
|
|
135
|
+
code: 0x57,
|
|
136
|
+
category: 'constants',
|
|
137
|
+
description: 'Push number 7',
|
|
138
|
+
action: s => s.push(scriptNum.encode(7))
|
|
139
|
+
},
|
|
140
|
+
OP_8: {
|
|
141
|
+
code: 0x58,
|
|
142
|
+
category: 'constants',
|
|
143
|
+
description: 'Push number 8',
|
|
144
|
+
action: s => s.push(scriptNum.encode(8))
|
|
145
|
+
},
|
|
146
|
+
OP_9: {
|
|
147
|
+
code: 0x59,
|
|
148
|
+
category: 'constants',
|
|
149
|
+
description: 'Push number 9',
|
|
150
|
+
action: s => s.push(scriptNum.encode(9))
|
|
151
|
+
},
|
|
152
|
+
OP_10: {
|
|
153
|
+
code: 0x5a,
|
|
154
|
+
category: 'constants',
|
|
155
|
+
description: 'Push number 10',
|
|
156
|
+
action: s => s.push(scriptNum.encode(10))
|
|
157
|
+
},
|
|
158
|
+
OP_11: {
|
|
159
|
+
code: 0x5b,
|
|
160
|
+
category: 'constants',
|
|
161
|
+
description: 'Push number 11',
|
|
162
|
+
action: s => s.push(scriptNum.encode(11))
|
|
163
|
+
},
|
|
164
|
+
OP_12: {
|
|
165
|
+
code: 0x5c,
|
|
166
|
+
category: 'constants',
|
|
167
|
+
description: 'Push number 12',
|
|
168
|
+
action: s => s.push(scriptNum.encode(12))
|
|
169
|
+
},
|
|
170
|
+
OP_13: {
|
|
171
|
+
code: 0x5d,
|
|
172
|
+
category: 'constants',
|
|
173
|
+
description: 'Push number 13',
|
|
174
|
+
action: s => s.push(scriptNum.encode(13))
|
|
175
|
+
},
|
|
176
|
+
OP_14: {
|
|
177
|
+
code: 0x5e,
|
|
178
|
+
category: 'constants',
|
|
179
|
+
description: 'Push number 14',
|
|
180
|
+
action: s => s.push(scriptNum.encode(14))
|
|
181
|
+
},
|
|
182
|
+
OP_15: {
|
|
183
|
+
code: 0x5f,
|
|
184
|
+
category: 'constants',
|
|
185
|
+
description: 'Push number 15',
|
|
186
|
+
action: s => s.push(scriptNum.encode(15))
|
|
187
|
+
},
|
|
188
|
+
OP_16: {
|
|
189
|
+
code: 0x60,
|
|
190
|
+
category: 'constants',
|
|
191
|
+
description: 'Push number 16',
|
|
192
|
+
action: s => s.push(scriptNum.encode(16))
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
/* ==================== FLOW CONTROL ==================== */
|
|
196
|
+
OP_NOP: {
|
|
197
|
+
code: 0x61,
|
|
198
|
+
category: 'flow_control',
|
|
199
|
+
description: 'No operation (do nothing)',
|
|
200
|
+
action: s => {}
|
|
201
|
+
},
|
|
202
|
+
OP_VER: {
|
|
203
|
+
code: 0x62,
|
|
204
|
+
category: 'flow_control',
|
|
205
|
+
description: 'Push transaction version (disabled)',
|
|
206
|
+
action: () => { throw new Error('OP_VER is disabled'); }
|
|
207
|
+
},
|
|
208
|
+
OP_IF: {
|
|
209
|
+
code: 0x63,
|
|
210
|
+
category: 'flow_control',
|
|
211
|
+
description: 'Execute if top stack value is true',
|
|
212
|
+
action: "// Conditional execution (requires parser state)"
|
|
213
|
+
},
|
|
214
|
+
OP_NOTIF: {
|
|
215
|
+
code: 0x64,
|
|
216
|
+
category: 'flow_control',
|
|
217
|
+
description: 'Execute if top stack value is false',
|
|
218
|
+
action: "// Conditional execution (requires parser state)"
|
|
219
|
+
},
|
|
220
|
+
OP_VERIF: {
|
|
221
|
+
code: 0x65,
|
|
222
|
+
category: 'flow_control',
|
|
223
|
+
description: 'Conditional execution based on transaction version (disabled)',
|
|
224
|
+
action: () => { throw new Error('OP_VERIF is disabled'); }
|
|
225
|
+
},
|
|
226
|
+
OP_VERNOTIF: {
|
|
227
|
+
code: 0x66,
|
|
228
|
+
category: 'flow_control',
|
|
229
|
+
description: 'Conditional execution based on transaction version (disabled)',
|
|
230
|
+
action: () => { throw new Error('OP_VERNOTIF is disabled'); }
|
|
231
|
+
},
|
|
232
|
+
OP_ELSE: {
|
|
233
|
+
code: 0x67,
|
|
234
|
+
category: 'flow_control',
|
|
235
|
+
description: 'Else branch of conditional',
|
|
236
|
+
action: "// Conditional execution (requires parser state)"
|
|
237
|
+
},
|
|
238
|
+
OP_ENDIF: {
|
|
239
|
+
code: 0x68,
|
|
240
|
+
category: 'flow_control',
|
|
241
|
+
description: 'End conditional block',
|
|
242
|
+
action: "// Conditional execution (requires parser state)"
|
|
243
|
+
},
|
|
244
|
+
OP_VERIFY: {
|
|
245
|
+
code: 0x69,
|
|
246
|
+
category: 'flow_control',
|
|
247
|
+
description: 'Fail if top stack value is not true',
|
|
248
|
+
action: s => {
|
|
249
|
+
const v = s.pop();
|
|
250
|
+
if (!v || v.length === 0 || scriptNum.decode(v) === 0) {
|
|
251
|
+
throw new Error('OP_VERIFY failed');
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
OP_RETURN: {
|
|
256
|
+
code: 0x6a,
|
|
257
|
+
category: 'flow_control',
|
|
258
|
+
description: 'Terminate script execution immediately',
|
|
259
|
+
action: () => { throw new Error('Script terminated by OP_RETURN'); }
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
/* ==================== STACK MANIPULATION ==================== */
|
|
263
|
+
OP_TOALTSTACK: {
|
|
264
|
+
code: 0x6b,
|
|
265
|
+
category: 'stack',
|
|
266
|
+
description: 'Move top item from main stack to alt stack',
|
|
267
|
+
action: (s, a) => a.push(s.pop())
|
|
268
|
+
},
|
|
269
|
+
OP_FROMALTSTACK: {
|
|
270
|
+
code: 0x6c,
|
|
271
|
+
category: 'stack',
|
|
272
|
+
description: 'Move top item from alt stack to main stack',
|
|
273
|
+
action: (s, a) => s.push(a.pop())
|
|
274
|
+
},
|
|
275
|
+
OP_2DROP: {
|
|
276
|
+
code: 0x6d,
|
|
277
|
+
category: 'stack',
|
|
278
|
+
description: 'Remove top two stack items',
|
|
279
|
+
action: s => { s.pop(); s.pop(); }
|
|
280
|
+
},
|
|
281
|
+
OP_2DUP: {
|
|
282
|
+
code: 0x6e,
|
|
283
|
+
category: 'stack',
|
|
284
|
+
description: 'Duplicate top two stack items',
|
|
285
|
+
action: s => {
|
|
286
|
+
const a = s[s.length - 1], b = s[s.length - 2];
|
|
287
|
+
s.push(Buffer.from(b), Buffer.from(a));
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
OP_3DUP: {
|
|
291
|
+
code: 0x6f,
|
|
292
|
+
category: 'stack',
|
|
293
|
+
description: 'Duplicate top three stack items',
|
|
294
|
+
action: s => {
|
|
295
|
+
const a = s[s.length - 1], b = s[s.length - 2], c = s[s.length - 3];
|
|
296
|
+
s.push(Buffer.from(c), Buffer.from(b), Buffer.from(a));
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
OP_2OVER: {
|
|
300
|
+
code: 0x70,
|
|
301
|
+
category: 'stack',
|
|
302
|
+
description: 'Copy 3rd and 4th items to top',
|
|
303
|
+
action: s => {
|
|
304
|
+
const c = s[s.length - 3], d = s[s.length - 4];
|
|
305
|
+
s.push(Buffer.from(d), Buffer.from(c));
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
OP_2ROT: {
|
|
309
|
+
code: 0x71,
|
|
310
|
+
category: 'stack',
|
|
311
|
+
description: 'Move 5th and 6th items to top',
|
|
312
|
+
action: s => {
|
|
313
|
+
const e = s.splice(-5, 1)[0], f = s.splice(-5, 1)[0];
|
|
314
|
+
s.push(e, f);
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
OP_2SWAP: {
|
|
318
|
+
code: 0x72,
|
|
319
|
+
category: 'stack',
|
|
320
|
+
description: 'Swap top two pairs of items',
|
|
321
|
+
action: s => {
|
|
322
|
+
const a = s.pop(), b = s.pop(), c = s.pop(), d = s.pop();
|
|
323
|
+
s.push(b, a, d, c);
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
OP_IFDUP: {
|
|
327
|
+
code: 0x73,
|
|
328
|
+
category: 'stack',
|
|
329
|
+
description: 'Duplicate top item if it is not zero',
|
|
330
|
+
action: s => {
|
|
331
|
+
const top = s[s.length - 1];
|
|
332
|
+
if (top.length > 0 && scriptNum.decode(top) !== 0) s.push(Buffer.from(top));
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
OP_DEPTH: {
|
|
336
|
+
code: 0x74,
|
|
337
|
+
category: 'stack',
|
|
338
|
+
description: 'Push stack size as number',
|
|
339
|
+
action: s => s.push(scriptNum.encode(s.length))
|
|
340
|
+
},
|
|
341
|
+
OP_DROP: {
|
|
342
|
+
code: 0x75,
|
|
343
|
+
category: 'stack',
|
|
344
|
+
description: 'Remove top stack item',
|
|
345
|
+
action: s => s.pop()
|
|
346
|
+
},
|
|
347
|
+
OP_DUP: {
|
|
348
|
+
code: 0x76,
|
|
349
|
+
category: 'stack',
|
|
350
|
+
description: 'Duplicate top stack item',
|
|
351
|
+
action: s => s.push(Buffer.from(s[s.length - 1]))
|
|
352
|
+
},
|
|
353
|
+
OP_NIP: {
|
|
354
|
+
code: 0x77,
|
|
355
|
+
category: 'stack',
|
|
356
|
+
description: 'Remove second-to-top item',
|
|
357
|
+
action: s => { const top = s.pop(); s.pop(); s.push(top); }
|
|
358
|
+
},
|
|
359
|
+
OP_OVER: {
|
|
360
|
+
code: 0x78,
|
|
361
|
+
category: 'stack',
|
|
362
|
+
description: 'Copy second-to-top item to top',
|
|
363
|
+
action: s => s.push(Buffer.from(s[s.length - 2]))
|
|
364
|
+
},
|
|
365
|
+
OP_PICK: {
|
|
366
|
+
code: 0x79,
|
|
367
|
+
category: 'stack',
|
|
368
|
+
description: 'Copy nth item to top (0-indexed from top)',
|
|
369
|
+
action: s => {
|
|
370
|
+
const n = scriptNum.decode(s.pop());
|
|
371
|
+
s.push(Buffer.from(s[s.length - 1 - n]));
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
OP_ROLL: {
|
|
375
|
+
code: 0x7a,
|
|
376
|
+
category: 'stack',
|
|
377
|
+
description: 'Move nth item to top (0-indexed from top)',
|
|
378
|
+
action: s => {
|
|
379
|
+
const n = scriptNum.decode(s.pop());
|
|
380
|
+
const item = s.splice(s.length - 1 - n, 1)[0];
|
|
381
|
+
s.push(item);
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
OP_ROT: {
|
|
385
|
+
code: 0x7b,
|
|
386
|
+
category: 'stack',
|
|
387
|
+
description: 'Rotate top three items left',
|
|
388
|
+
action: s => {
|
|
389
|
+
const a = s.pop(), b = s.pop(), c = s.pop();
|
|
390
|
+
s.push(b, a, c);
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
OP_SWAP: {
|
|
394
|
+
code: 0x7c,
|
|
395
|
+
category: 'stack',
|
|
396
|
+
description: 'Swap top two items',
|
|
397
|
+
action: s => {
|
|
398
|
+
const a = s.pop(), b = s.pop();
|
|
399
|
+
s.push(a, b);
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
OP_TUCK: {
|
|
403
|
+
code: 0x7d,
|
|
404
|
+
category: 'stack',
|
|
405
|
+
description: 'Copy top item below second item',
|
|
406
|
+
action: s => {
|
|
407
|
+
const a = s.pop(), b = s.pop();
|
|
408
|
+
s.push(a, b, a);
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
|
|
412
|
+
/* ==================== DATA MANIPULATION ==================== */
|
|
413
|
+
OP_CAT: {
|
|
414
|
+
code: 0x7e,
|
|
415
|
+
category: 'data',
|
|
416
|
+
description: 'Concatenate top two items',
|
|
417
|
+
action: s => {
|
|
418
|
+
const a = s.pop(), b = s.pop();
|
|
419
|
+
s.push(Buffer.concat([b, a]));
|
|
420
|
+
}
|
|
421
|
+
},
|
|
422
|
+
OP_SPLIT: {
|
|
423
|
+
code: 0x7f,
|
|
424
|
+
category: 'data',
|
|
425
|
+
description: 'Split item at position n',
|
|
426
|
+
action: s => {
|
|
427
|
+
const n = scriptNum.decode(s.pop()), x = s.pop();
|
|
428
|
+
s.push(x.slice(0, n), x.slice(n));
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
OP_NUM2BIN: {
|
|
432
|
+
code: 0x80,
|
|
433
|
+
category: 'data',
|
|
434
|
+
description: 'Convert number to byte sequence of length n',
|
|
435
|
+
action: s => {
|
|
436
|
+
const len = scriptNum.decode(s.pop()), num = scriptNum.decode(s.pop());
|
|
437
|
+
const result = Buffer.alloc(len);
|
|
438
|
+
const encoded = scriptNum.encode(num);
|
|
439
|
+
encoded.copy(result, 0, 0, Math.min(encoded.length, len));
|
|
440
|
+
s.push(result);
|
|
441
|
+
}
|
|
442
|
+
},
|
|
443
|
+
OP_BIN2NUM: {
|
|
444
|
+
code: 0x81,
|
|
445
|
+
category: 'data',
|
|
446
|
+
description: 'Convert byte sequence to number',
|
|
447
|
+
action: s => {
|
|
448
|
+
const x = s.pop();
|
|
449
|
+
s.push(scriptNum.encode(scriptNum.decode(x)));
|
|
450
|
+
}
|
|
451
|
+
},
|
|
452
|
+
OP_SIZE: {
|
|
453
|
+
code: 0x82,
|
|
454
|
+
category: 'data',
|
|
455
|
+
description: 'Push size of top item (without removing item)',
|
|
456
|
+
action: s => s.push(scriptNum.encode(s[s.length - 1].length))
|
|
457
|
+
},
|
|
458
|
+
|
|
459
|
+
/* ==================== BITWISE OPERATIONS ==================== */
|
|
460
|
+
OP_INVERT: {
|
|
461
|
+
code: 0x83,
|
|
462
|
+
category: 'bitwise',
|
|
463
|
+
description: 'Bitwise NOT of top item',
|
|
464
|
+
action: s => {
|
|
465
|
+
const x = s.pop();
|
|
466
|
+
s.push(Buffer.from(x.map(b => ~b)));
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
OP_AND: {
|
|
470
|
+
code: 0x84,
|
|
471
|
+
category: 'bitwise',
|
|
472
|
+
description: 'Bitwise AND of top two items',
|
|
473
|
+
action: s => {
|
|
474
|
+
const a = s.pop(), b = s.pop();
|
|
475
|
+
const maxLen = Math.max(a.length, b.length);
|
|
476
|
+
const result = Buffer.alloc(maxLen);
|
|
477
|
+
for (let i = 0; i < maxLen; i++) {
|
|
478
|
+
result[i] = (a[i] || 0) & (b[i] || 0);
|
|
479
|
+
}
|
|
480
|
+
s.push(result);
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
OP_OR: {
|
|
484
|
+
code: 0x85,
|
|
485
|
+
category: 'bitwise',
|
|
486
|
+
description: 'Bitwise OR of top two items',
|
|
487
|
+
action: s => {
|
|
488
|
+
const a = s.pop(), b = s.pop();
|
|
489
|
+
const maxLen = Math.max(a.length, b.length);
|
|
490
|
+
const result = Buffer.alloc(maxLen);
|
|
491
|
+
for (let i = 0; i < maxLen; i++) {
|
|
492
|
+
result[i] = (a[i] || 0) | (b[i] || 0);
|
|
493
|
+
}
|
|
494
|
+
s.push(result);
|
|
495
|
+
}
|
|
496
|
+
},
|
|
497
|
+
OP_XOR: {
|
|
498
|
+
code: 0x86,
|
|
499
|
+
category: 'bitwise',
|
|
500
|
+
description: 'Bitwise XOR of top two items',
|
|
501
|
+
action: s => {
|
|
502
|
+
const a = s.pop(), b = s.pop();
|
|
503
|
+
const maxLen = Math.max(a.length, b.length);
|
|
504
|
+
const result = Buffer.alloc(maxLen);
|
|
505
|
+
for (let i = 0; i < maxLen; i++) {
|
|
506
|
+
result[i] = (a[i] || 0) ^ (b[i] || 0);
|
|
507
|
+
}
|
|
508
|
+
s.push(result);
|
|
509
|
+
}
|
|
510
|
+
},
|
|
511
|
+
OP_EQUAL: {
|
|
512
|
+
code: 0x87,
|
|
513
|
+
category: 'bitwise',
|
|
514
|
+
description: 'Push 1 if top two items are equal, 0 otherwise',
|
|
515
|
+
action: s => {
|
|
516
|
+
const a = s.pop(), b = s.pop();
|
|
517
|
+
s.push(a.equals(b) ? scriptNum.encode(1) : scriptNum.encode(0));
|
|
518
|
+
}
|
|
519
|
+
},
|
|
520
|
+
OP_EQUALVERIFY: {
|
|
521
|
+
code: 0x88,
|
|
522
|
+
category: 'bitwise',
|
|
523
|
+
description: 'Fail if top two items are not equal',
|
|
524
|
+
action: s => {
|
|
525
|
+
const a = s.pop(), b = s.pop();
|
|
526
|
+
if (!a.equals(b)) throw new Error('OP_EQUALVERIFY failed');
|
|
527
|
+
}
|
|
528
|
+
},
|
|
529
|
+
|
|
530
|
+
/* ==================== ARITHMETIC ==================== */
|
|
531
|
+
OP_1ADD: {
|
|
532
|
+
code: 0x8b,
|
|
533
|
+
category: 'arithmetic',
|
|
534
|
+
description: 'Add 1 to top item',
|
|
535
|
+
action: s => {
|
|
536
|
+
const x = scriptNum.decode(s.pop());
|
|
537
|
+
s.push(scriptNum.encode(x + 1));
|
|
538
|
+
}
|
|
539
|
+
},
|
|
540
|
+
OP_1SUB: {
|
|
541
|
+
code: 0x8c,
|
|
542
|
+
category: 'arithmetic',
|
|
543
|
+
description: 'Subtract 1 from top item',
|
|
544
|
+
action: s => {
|
|
545
|
+
const x = scriptNum.decode(s.pop());
|
|
546
|
+
s.push(scriptNum.encode(x - 1));
|
|
547
|
+
}
|
|
548
|
+
},
|
|
549
|
+
OP_2MUL: {
|
|
550
|
+
code: 0x8d,
|
|
551
|
+
category: 'arithmetic',
|
|
552
|
+
description: 'Multiply top item by 2 (disabled)',
|
|
553
|
+
action: () => { throw new Error('OP_2MUL is disabled'); }
|
|
554
|
+
},
|
|
555
|
+
OP_2DIV: {
|
|
556
|
+
code: 0x8e,
|
|
557
|
+
category: 'arithmetic',
|
|
558
|
+
description: 'Divide top item by 2 (disabled)',
|
|
559
|
+
action: () => { throw new Error('OP_2DIV is disabled'); }
|
|
560
|
+
},
|
|
561
|
+
OP_NEGATE: {
|
|
562
|
+
code: 0x8f,
|
|
563
|
+
category: 'arithmetic',
|
|
564
|
+
description: 'Negate top item',
|
|
565
|
+
action: s => {
|
|
566
|
+
const x = scriptNum.decode(s.pop());
|
|
567
|
+
s.push(scriptNum.encode(-x));
|
|
568
|
+
}
|
|
569
|
+
},
|
|
570
|
+
OP_ABS: {
|
|
571
|
+
code: 0x90,
|
|
572
|
+
category: 'arithmetic',
|
|
573
|
+
description: 'Absolute value of top item',
|
|
574
|
+
action: s => {
|
|
575
|
+
const x = scriptNum.decode(s.pop());
|
|
576
|
+
s.push(scriptNum.encode(Math.abs(x)));
|
|
577
|
+
}
|
|
578
|
+
},
|
|
579
|
+
OP_NOT: {
|
|
580
|
+
code: 0x91,
|
|
581
|
+
category: 'arithmetic',
|
|
582
|
+
description: 'Logical NOT (0โ1, non-zeroโ0)',
|
|
583
|
+
action: s => {
|
|
584
|
+
const x = scriptNum.decode(s.pop());
|
|
585
|
+
s.push(scriptNum.encode(x === 0 ? 1 : 0));
|
|
586
|
+
}
|
|
587
|
+
},
|
|
588
|
+
OP_0NOTEQUAL: {
|
|
589
|
+
code: 0x92,
|
|
590
|
+
category: 'arithmetic',
|
|
591
|
+
description: 'Push 1 if top item is not zero, 0 otherwise',
|
|
592
|
+
action: s => {
|
|
593
|
+
const x = scriptNum.decode(s.pop());
|
|
594
|
+
s.push(scriptNum.encode(x !== 0 ? 1 : 0));
|
|
595
|
+
}
|
|
596
|
+
},
|
|
597
|
+
OP_ADD: {
|
|
598
|
+
code: 0x93,
|
|
599
|
+
category: 'arithmetic',
|
|
600
|
+
description: 'Add top two items',
|
|
601
|
+
action: s => {
|
|
602
|
+
const a = scriptNum.decode(s.pop()), b = scriptNum.decode(s.pop());
|
|
603
|
+
s.push(scriptNum.encode(b + a));
|
|
604
|
+
}
|
|
605
|
+
},
|
|
606
|
+
OP_SUB: {
|
|
607
|
+
code: 0x94,
|
|
608
|
+
category: 'arithmetic',
|
|
609
|
+
description: 'Subtract: second - first',
|
|
610
|
+
action: s => {
|
|
611
|
+
const a = scriptNum.decode(s.pop()), b = scriptNum.decode(s.pop());
|
|
612
|
+
s.push(scriptNum.encode(b - a));
|
|
613
|
+
}
|
|
614
|
+
},
|
|
615
|
+
OP_MUL: {
|
|
616
|
+
code: 0x95,
|
|
617
|
+
category: 'arithmetic',
|
|
618
|
+
description: 'Multiply top two items',
|
|
619
|
+
action: s => {
|
|
620
|
+
const a = scriptNum.decode(s.pop()), b = scriptNum.decode(s.pop());
|
|
621
|
+
s.push(scriptNum.encode(b * a));
|
|
622
|
+
}
|
|
623
|
+
},
|
|
624
|
+
OP_DIV: {
|
|
625
|
+
code: 0x96,
|
|
626
|
+
category: 'arithmetic',
|
|
627
|
+
description: 'Divide: second / first',
|
|
628
|
+
action: s => {
|
|
629
|
+
const a = scriptNum.decode(s.pop()), b = scriptNum.decode(s.pop());
|
|
630
|
+
if (a === 0) throw new Error('Division by zero');
|
|
631
|
+
s.push(scriptNum.encode(Math.floor(b / a)));
|
|
632
|
+
}
|
|
633
|
+
},
|
|
634
|
+
OP_MOD: {
|
|
635
|
+
code: 0x97,
|
|
636
|
+
category: 'arithmetic',
|
|
637
|
+
description: 'Modulo: second % first',
|
|
638
|
+
action: s => {
|
|
639
|
+
const a = scriptNum.decode(s.pop()), b = scriptNum.decode(s.pop());
|
|
640
|
+
if (a === 0) throw new Error('Division by zero');
|
|
641
|
+
s.push(scriptNum.encode(b % a));
|
|
642
|
+
}
|
|
643
|
+
},
|
|
644
|
+
OP_LSHIFT: {
|
|
645
|
+
code: 0x98,
|
|
646
|
+
category: 'arithmetic',
|
|
647
|
+
description: 'Left shift: second << first',
|
|
648
|
+
action: s => {
|
|
649
|
+
const n = scriptNum.decode(s.pop()), x = scriptNum.decode(s.pop());
|
|
650
|
+
s.push(scriptNum.encode(x << n));
|
|
651
|
+
}
|
|
652
|
+
},
|
|
653
|
+
OP_RSHIFT: {
|
|
654
|
+
code: 0x99,
|
|
655
|
+
category: 'arithmetic',
|
|
656
|
+
description: 'Right shift: second >> first',
|
|
657
|
+
action: s => {
|
|
658
|
+
const n = scriptNum.decode(s.pop()), x = scriptNum.decode(s.pop());
|
|
659
|
+
s.push(scriptNum.encode(x >> n));
|
|
660
|
+
}
|
|
661
|
+
},
|
|
662
|
+
OP_BOOLAND: {
|
|
663
|
+
code: 0x9a,
|
|
664
|
+
category: 'arithmetic',
|
|
665
|
+
description: 'Boolean AND: 1 if both non-zero, 0 otherwise',
|
|
666
|
+
action: s => {
|
|
667
|
+
const a = scriptNum.decode(s.pop()), b = scriptNum.decode(s.pop());
|
|
668
|
+
s.push(scriptNum.encode((a !== 0 && b !== 0) ? 1 : 0));
|
|
669
|
+
}
|
|
670
|
+
},
|
|
671
|
+
OP_BOOLOR: {
|
|
672
|
+
code: 0x9b,
|
|
673
|
+
category: 'arithmetic',
|
|
674
|
+
description: 'Boolean OR: 1 if either non-zero, 0 otherwise',
|
|
675
|
+
action: s => {
|
|
676
|
+
const a = scriptNum.decode(s.pop()), b = scriptNum.decode(s.pop());
|
|
677
|
+
s.push(scriptNum.encode((a !== 0 || b !== 0) ? 1 : 0));
|
|
678
|
+
}
|
|
679
|
+
},
|
|
680
|
+
OP_NUMEQUAL: {
|
|
681
|
+
code: 0x9c,
|
|
682
|
+
category: 'arithmetic',
|
|
683
|
+
description: 'Push 1 if numbers are equal, 0 otherwise',
|
|
684
|
+
action: s => {
|
|
685
|
+
const a = scriptNum.decode(s.pop()), b = scriptNum.decode(s.pop());
|
|
686
|
+
s.push(scriptNum.encode(a === b ? 1 : 0));
|
|
687
|
+
}
|
|
688
|
+
},
|
|
689
|
+
OP_NUMEQUALVERIFY: {
|
|
690
|
+
code: 0x9d,
|
|
691
|
+
category: 'arithmetic',
|
|
692
|
+
description: 'Fail if numbers are not equal',
|
|
693
|
+
action: s => {
|
|
694
|
+
const a = scriptNum.decode(s.pop()), b = scriptNum.decode(s.pop());
|
|
695
|
+
if (a !== b) throw new Error('OP_NUMEQUALVERIFY failed');
|
|
696
|
+
}
|
|
697
|
+
},
|
|
698
|
+
OP_NUMNOTEQUAL: {
|
|
699
|
+
code: 0x9e,
|
|
700
|
+
category: 'arithmetic',
|
|
701
|
+
description: 'Push 1 if numbers are not equal, 0 otherwise',
|
|
702
|
+
action: s => {
|
|
703
|
+
const a = scriptNum.decode(s.pop()), b = scriptNum.decode(s.pop());
|
|
704
|
+
s.push(scriptNum.encode(a !== b ? 1 : 0));
|
|
705
|
+
}
|
|
706
|
+
},
|
|
707
|
+
OP_LESSTHAN: {
|
|
708
|
+
code: 0x9f,
|
|
709
|
+
category: 'arithmetic',
|
|
710
|
+
description: 'Push 1 if second < first, 0 otherwise',
|
|
711
|
+
action: s => {
|
|
712
|
+
const a = scriptNum.decode(s.pop()), b = scriptNum.decode(s.pop());
|
|
713
|
+
s.push(scriptNum.encode(b < a ? 1 : 0));
|
|
714
|
+
}
|
|
715
|
+
},
|
|
716
|
+
OP_GREATERTHAN: {
|
|
717
|
+
code: 0xa0,
|
|
718
|
+
category: 'arithmetic',
|
|
719
|
+
description: 'Push 1 if second > first, 0 otherwise',
|
|
720
|
+
action: s => {
|
|
721
|
+
const a = scriptNum.decode(s.pop()), b = scriptNum.decode(s.pop());
|
|
722
|
+
s.push(scriptNum.encode(b > a ? 1 : 0));
|
|
723
|
+
}
|
|
724
|
+
},
|
|
725
|
+
OP_LESSTHANOREQUAL: {
|
|
726
|
+
code: 0xa1,
|
|
727
|
+
category: 'arithmetic',
|
|
728
|
+
description: 'Push 1 if second <= first, 0 otherwise',
|
|
729
|
+
action: s => {
|
|
730
|
+
const a = scriptNum.decode(s.pop()), b = scriptNum.decode(s.pop());
|
|
731
|
+
s.push(scriptNum.encode(b <= a ? 1 : 0));
|
|
732
|
+
}
|
|
733
|
+
},
|
|
734
|
+
OP_GREATERTHANOREQUAL: {
|
|
735
|
+
code: 0xa2,
|
|
736
|
+
category: 'arithmetic',
|
|
737
|
+
description: 'Push 1 if second >= first, 0 otherwise',
|
|
738
|
+
action: s => {
|
|
739
|
+
const a = scriptNum.decode(s.pop()), b = scriptNum.decode(s.pop());
|
|
740
|
+
s.push(scriptNum.encode(b >= a ? 1 : 0));
|
|
741
|
+
}
|
|
742
|
+
},
|
|
743
|
+
OP_MIN: {
|
|
744
|
+
code: 0xa3,
|
|
745
|
+
category: 'arithmetic',
|
|
746
|
+
description: 'Push smaller of top two numbers',
|
|
747
|
+
action: s => {
|
|
748
|
+
const a = scriptNum.decode(s.pop()), b = scriptNum.decode(s.pop());
|
|
749
|
+
s.push(scriptNum.encode(Math.min(a, b)));
|
|
750
|
+
}
|
|
751
|
+
},
|
|
752
|
+
OP_MAX: {
|
|
753
|
+
code: 0xa4,
|
|
754
|
+
category: 'arithmetic',
|
|
755
|
+
description: 'Push larger of top two numbers',
|
|
756
|
+
action: s => {
|
|
757
|
+
const a = scriptNum.decode(s.pop()), b = scriptNum.decode(s.pop());
|
|
758
|
+
s.push(scriptNum.encode(Math.max(a, b)));
|
|
759
|
+
}
|
|
760
|
+
},
|
|
761
|
+
OP_WITHIN: {
|
|
762
|
+
code: 0xa5,
|
|
763
|
+
category: 'arithmetic',
|
|
764
|
+
description: 'Push 1 if x is within [min, max), 0 otherwise',
|
|
765
|
+
action: s => {
|
|
766
|
+
const max = scriptNum.decode(s.pop()), min = scriptNum.decode(s.pop()), x = scriptNum.decode(s.pop());
|
|
767
|
+
s.push(scriptNum.encode((x >= min && x < max) ? 1 : 0));
|
|
768
|
+
}
|
|
769
|
+
},
|
|
770
|
+
|
|
771
|
+
/* ==================== CRYPTOGRAPHIC OPERATIONS ==================== */
|
|
772
|
+
OP_RIPEMD160: {
|
|
773
|
+
code: 0xa6,
|
|
774
|
+
category: 'crypto',
|
|
775
|
+
description: 'RIPEMD-160 hash of top item',
|
|
776
|
+
action: s => {
|
|
777
|
+
const crypto = require("crypto");
|
|
778
|
+
const hash = crypto.createHash("ripemd160").update(s.pop()).digest();
|
|
779
|
+
s.push(hash);
|
|
780
|
+
}
|
|
781
|
+
},
|
|
782
|
+
OP_SHA1: {
|
|
783
|
+
code: 0xa7,
|
|
784
|
+
category: 'crypto',
|
|
785
|
+
description: 'SHA-1 hash of top item',
|
|
786
|
+
action: s => {
|
|
787
|
+
const crypto = require("crypto");
|
|
788
|
+
const hash = crypto.createHash("sha1").update(s.pop()).digest();
|
|
789
|
+
s.push(hash);
|
|
790
|
+
}
|
|
791
|
+
},
|
|
792
|
+
OP_SHA256: {
|
|
793
|
+
code: 0xa8,
|
|
794
|
+
category: 'crypto',
|
|
795
|
+
description: 'SHA-256 hash of top item',
|
|
796
|
+
action: s => {
|
|
797
|
+
const crypto = require("crypto");
|
|
798
|
+
const hash = crypto.createHash("sha256").update(s.pop()).digest();
|
|
799
|
+
s.push(hash);
|
|
800
|
+
}
|
|
801
|
+
},
|
|
802
|
+
OP_HASH160: {
|
|
803
|
+
code: 0xa9,
|
|
804
|
+
category: 'crypto',
|
|
805
|
+
description: 'SHA-256 then RIPEMD-160 hash of top item',
|
|
806
|
+
action: s => {
|
|
807
|
+
const crypto = require("crypto");
|
|
808
|
+
const sha256 = crypto.createHash("sha256").update(s.pop()).digest();
|
|
809
|
+
const hash160 = crypto.createHash("ripemd160").update(sha256).digest();
|
|
810
|
+
s.push(hash160);
|
|
811
|
+
}
|
|
812
|
+
},
|
|
813
|
+
OP_HASH256: {
|
|
814
|
+
code: 0xaa,
|
|
815
|
+
category: 'crypto',
|
|
816
|
+
description: 'Double SHA-256 hash of top item',
|
|
817
|
+
action: s => {
|
|
818
|
+
const crypto = require("crypto");
|
|
819
|
+
const firstHash = crypto.createHash("sha256").update(s.pop()).digest();
|
|
820
|
+
const doubleHash = crypto.createHash("sha256").update(firstHash).digest();
|
|
821
|
+
s.push(doubleHash);
|
|
822
|
+
}
|
|
823
|
+
},
|
|
824
|
+
OP_CODESEPARATOR: {
|
|
825
|
+
code: 0xab,
|
|
826
|
+
category: 'crypto',
|
|
827
|
+
description: 'Mark signature checking boundary',
|
|
828
|
+
action: s => {}
|
|
829
|
+
},
|
|
830
|
+
OP_CHECKSIG: {
|
|
831
|
+
code: 0xac,
|
|
832
|
+
category: 'crypto',
|
|
833
|
+
description: 'Verify signature against public key',
|
|
834
|
+
action: "// Signature verification (requires transaction context)"
|
|
835
|
+
},
|
|
836
|
+
OP_CHECKSIGVERIFY: {
|
|
837
|
+
code: 0xad,
|
|
838
|
+
category: 'crypto',
|
|
839
|
+
description: 'Verify signature, then fail if invalid',
|
|
840
|
+
action: "// Signature verification with VERIFY (requires transaction context)"
|
|
841
|
+
},
|
|
842
|
+
OP_CHECKMULTISIG: {
|
|
843
|
+
code: 0xae,
|
|
844
|
+
category: 'crypto',
|
|
845
|
+
description: 'Verify multiple signatures against public keys',
|
|
846
|
+
action: "// Multi-signature verification (requires transaction context)"
|
|
847
|
+
},
|
|
848
|
+
OP_CHECKMULTISIGVERIFY: {
|
|
849
|
+
code: 0xaf,
|
|
850
|
+
category: 'crypto',
|
|
851
|
+
description: 'Verify multiple signatures, then fail if invalid',
|
|
852
|
+
action: "// Multi-signature verification with VERIFY (requires transaction context)"
|
|
853
|
+
},
|
|
854
|
+
|
|
855
|
+
/* ==================== STRING OPERATIONS ==================== */
|
|
856
|
+
OP_SUBSTR: {
|
|
857
|
+
code: 0xb3,
|
|
858
|
+
category: 'string',
|
|
859
|
+
description: 'Extract substring: string[start:start+length]',
|
|
860
|
+
action: s => {
|
|
861
|
+
const length = scriptNum.decode(s.pop()), start = scriptNum.decode(s.pop()), str = s.pop();
|
|
862
|
+
s.push(str.slice(start, start + length));
|
|
863
|
+
}
|
|
864
|
+
},
|
|
865
|
+
OP_LEFT: {
|
|
866
|
+
code: 0xb4,
|
|
867
|
+
category: 'string',
|
|
868
|
+
description: 'Extract leftmost n bytes',
|
|
869
|
+
action: s => {
|
|
870
|
+
const n = scriptNum.decode(s.pop()), str = s.pop();
|
|
871
|
+
s.push(str.slice(0, n));
|
|
872
|
+
}
|
|
873
|
+
},
|
|
874
|
+
OP_RIGHT: {
|
|
875
|
+
code: 0xb5,
|
|
876
|
+
category: 'string',
|
|
877
|
+
description: 'Extract rightmost n bytes',
|
|
878
|
+
action: s => {
|
|
879
|
+
const n = scriptNum.decode(s.pop()), str = s.pop();
|
|
880
|
+
s.push(str.slice(-n));
|
|
881
|
+
}
|
|
882
|
+
},
|
|
883
|
+
|
|
884
|
+
/* ==================== NOP OPERATIONS ==================== */
|
|
885
|
+
OP_NOP1: {
|
|
886
|
+
code: 0xb0,
|
|
887
|
+
category: 'nop',
|
|
888
|
+
description: 'No operation (reserved for future use)',
|
|
889
|
+
action: s => {}
|
|
890
|
+
},
|
|
891
|
+
OP_NOP2: {
|
|
892
|
+
code: 0xb1,
|
|
893
|
+
category: 'nop',
|
|
894
|
+
description: 'No operation (formerly CHECKLOCKTIMEVERIFY)',
|
|
895
|
+
action: s => {}
|
|
896
|
+
},
|
|
897
|
+
OP_NOP3: {
|
|
898
|
+
code: 0xb2,
|
|
899
|
+
category: 'nop',
|
|
900
|
+
description: 'No operation (formerly CHECKSEQUENCEVERIFY)',
|
|
901
|
+
action: s => {}
|
|
902
|
+
},
|
|
903
|
+
OP_NOP4: {
|
|
904
|
+
code: 0xb6,
|
|
905
|
+
category: 'nop',
|
|
906
|
+
description: 'No operation (reserved for future use)',
|
|
907
|
+
action: s => {}
|
|
908
|
+
},
|
|
909
|
+
OP_NOP5: {
|
|
910
|
+
code: 0xb7,
|
|
911
|
+
category: 'nop',
|
|
912
|
+
description: 'No operation (reserved for future use)',
|
|
913
|
+
action: s => {}
|
|
914
|
+
},
|
|
915
|
+
OP_NOP6: {
|
|
916
|
+
code: 0xb8,
|
|
917
|
+
category: 'nop',
|
|
918
|
+
description: 'No operation (reserved for future use)',
|
|
919
|
+
action: s => {}
|
|
920
|
+
},
|
|
921
|
+
OP_NOP7: {
|
|
922
|
+
code: 0xb9,
|
|
923
|
+
category: 'nop',
|
|
924
|
+
description: 'No operation (reserved for future use)',
|
|
925
|
+
action: s => {}
|
|
926
|
+
},
|
|
927
|
+
OP_NOP8: {
|
|
928
|
+
code: 0xba,
|
|
929
|
+
category: 'nop',
|
|
930
|
+
description: 'No operation (reserved for future use)',
|
|
931
|
+
action: s => {}
|
|
932
|
+
},
|
|
933
|
+
OP_NOP9: {
|
|
934
|
+
code: 0xbb,
|
|
935
|
+
category: 'nop',
|
|
936
|
+
description: 'No operation (reserved for future use)',
|
|
937
|
+
action: s => {}
|
|
938
|
+
},
|
|
939
|
+
OP_NOP10: {
|
|
940
|
+
code: 0xbc,
|
|
941
|
+
category: 'nop',
|
|
942
|
+
description: 'No operation (reserved for future use)',
|
|
943
|
+
action: s => {}
|
|
944
|
+
},
|
|
945
|
+
|
|
946
|
+
/* ==================== RESERVED/DISABLED OPERATIONS ==================== */
|
|
947
|
+
OP_RESERVED1: {
|
|
948
|
+
code: 0x89,
|
|
949
|
+
category: 'reserved',
|
|
950
|
+
description: 'Reserved opcode (makes transaction invalid)',
|
|
951
|
+
action: () => { throw new Error('OP_RESERVED1 encountered'); }
|
|
952
|
+
},
|
|
953
|
+
OP_RESERVED2: {
|
|
954
|
+
code: 0x8a,
|
|
955
|
+
category: 'reserved',
|
|
956
|
+
description: 'Reserved opcode (makes transaction invalid)',
|
|
957
|
+
action: () => { throw new Error('OP_RESERVED2 encountered'); }
|
|
958
|
+
},
|
|
959
|
+
OP_CHECKLOCKTIMEVERIFY: {
|
|
960
|
+
code: 0xb1,
|
|
961
|
+
category: 'disabled',
|
|
962
|
+
description: 'Check locktime (disabled post-Genesis)',
|
|
963
|
+
action: s => {}
|
|
964
|
+
},
|
|
965
|
+
OP_CHECKSEQUENCEVERIFY: {
|
|
966
|
+
code: 0xb2,
|
|
967
|
+
category: 'disabled',
|
|
968
|
+
description: 'Check sequence (disabled post-Genesis)',
|
|
969
|
+
action: s => {}
|
|
970
|
+
},
|
|
971
|
+
|
|
972
|
+
/* ==================== PSEUDO OPERATIONS ==================== */
|
|
973
|
+
OP_PUBKEYHASH: {
|
|
974
|
+
code: 0xfd,
|
|
975
|
+
category: 'pseudo',
|
|
976
|
+
description: 'Template matching: public key hash',
|
|
977
|
+
action: "// Template matching only"
|
|
978
|
+
},
|
|
979
|
+
OP_PUBKEY: {
|
|
980
|
+
code: 0xfe,
|
|
981
|
+
category: 'pseudo',
|
|
982
|
+
description: 'Template matching: public key',
|
|
983
|
+
action: "// Template matching only"
|
|
984
|
+
},
|
|
985
|
+
OP_INVALIDOPCODE: {
|
|
986
|
+
code: 0xff,
|
|
987
|
+
category: 'pseudo',
|
|
988
|
+
description: 'Invalid opcode placeholder',
|
|
989
|
+
action: () => { throw new Error('Invalid opcode encountered'); }
|
|
990
|
+
}
|
|
991
|
+
};
|
|
992
|
+
|
|
993
|
+
// Utility functions for working with the opcode map
|
|
994
|
+
const utils = {
|
|
995
|
+
// Get opcodes by category
|
|
996
|
+
getByCategory: (category) => {
|
|
997
|
+
return Object.entries(opcodeMap)
|
|
998
|
+
.filter(([name, op]) => op.category === category)
|
|
999
|
+
.reduce((acc, [name, op]) => ({ ...acc, [name]: op }), {});
|
|
1000
|
+
},
|
|
1001
|
+
|
|
1002
|
+
// Get all categories
|
|
1003
|
+
getCategories: () => {
|
|
1004
|
+
const categories = new Set();
|
|
1005
|
+
Object.values(opcodeMap).forEach(op => categories.add(op.category));
|
|
1006
|
+
return Array.from(categories).sort();
|
|
1007
|
+
},
|
|
1008
|
+
|
|
1009
|
+
// Create ASM from JavaScript operations
|
|
1010
|
+
createASM: (operations) => {
|
|
1011
|
+
const asm = [];
|
|
1012
|
+
operations.forEach(op => {
|
|
1013
|
+
if (typeof op === 'string' && opcodeMap[op]) {
|
|
1014
|
+
asm.push(op);
|
|
1015
|
+
} else if (typeof op === 'number') {
|
|
1016
|
+
// Convert number to appropriate opcode or push data
|
|
1017
|
+
if (op >= 1 && op <= 16) {
|
|
1018
|
+
asm.push(`OP_${op}`);
|
|
1019
|
+
} else if (op === 0) {
|
|
1020
|
+
asm.push('OP_0');
|
|
1021
|
+
} else if (op === -1) {
|
|
1022
|
+
asm.push('OP_1NEGATE');
|
|
1023
|
+
} else {
|
|
1024
|
+
// For larger numbers, we need to push the encoded bytes
|
|
1025
|
+
const encoded = scriptNum.encode(op);
|
|
1026
|
+
asm.push(encoded.toString('hex'));
|
|
1027
|
+
}
|
|
1028
|
+
} else if (Buffer.isBuffer(op)) {
|
|
1029
|
+
// Push buffer data
|
|
1030
|
+
asm.push(op.toString('hex'));
|
|
1031
|
+
} else {
|
|
1032
|
+
throw new Error(`Invalid operation: ${op}`);
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
1035
|
+
return asm.join(' ');
|
|
1036
|
+
},
|
|
1037
|
+
|
|
1038
|
+
// Simulate script execution with JavaScript
|
|
1039
|
+
simulate: (operations, initialStack = []) => {
|
|
1040
|
+
const stack = [...initialStack];
|
|
1041
|
+
const altStack = [];
|
|
1042
|
+
const history = [];
|
|
1043
|
+
|
|
1044
|
+
operations.forEach((opName, index) => {
|
|
1045
|
+
if (!opcodeMap[opName]) {
|
|
1046
|
+
throw new Error(`Unknown opcode: ${opName}`);
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
const opcode = opcodeMap[opName];
|
|
1050
|
+
const beforeStack = [...stack];
|
|
1051
|
+
|
|
1052
|
+
if (typeof opcode.action === 'function') {
|
|
1053
|
+
try {
|
|
1054
|
+
opcode.action(stack, altStack);
|
|
1055
|
+
history.push({
|
|
1056
|
+
step: index + 1,
|
|
1057
|
+
opcode: opName,
|
|
1058
|
+
beforeStack: beforeStack.map(b => b.toString('hex')),
|
|
1059
|
+
afterStack: stack.map(b => b.toString('hex')),
|
|
1060
|
+
description: opcode.description
|
|
1061
|
+
});
|
|
1062
|
+
} catch (error) {
|
|
1063
|
+
history.push({
|
|
1064
|
+
step: index + 1,
|
|
1065
|
+
opcode: opName,
|
|
1066
|
+
beforeStack: beforeStack.map(b => b.toString('hex')),
|
|
1067
|
+
afterStack: [],
|
|
1068
|
+
error: error.message,
|
|
1069
|
+
description: opcode.description
|
|
1070
|
+
});
|
|
1071
|
+
throw error;
|
|
1072
|
+
}
|
|
1073
|
+
} else {
|
|
1074
|
+
// String descriptions for complex opcodes
|
|
1075
|
+
history.push({
|
|
1076
|
+
step: index + 1,
|
|
1077
|
+
opcode: opName,
|
|
1078
|
+
beforeStack: beforeStack.map(b => b.toString('hex')),
|
|
1079
|
+
afterStack: beforeStack.map(b => b.toString('hex')),
|
|
1080
|
+
note: opcode.action,
|
|
1081
|
+
description: opcode.description
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
});
|
|
1085
|
+
|
|
1086
|
+
return {
|
|
1087
|
+
finalStack: stack.map(b => b.toString('hex')),
|
|
1088
|
+
finalAltStack: altStack.map(b => b.toString('hex')),
|
|
1089
|
+
history
|
|
1090
|
+
};
|
|
1091
|
+
},
|
|
1092
|
+
|
|
1093
|
+
// Convert hex opcode to name
|
|
1094
|
+
opcodeToName: (code) => {
|
|
1095
|
+
const name = Object.entries(opcodeMap).find(([name, op]) => op.code === code);
|
|
1096
|
+
return name ? name[0] : `UNKNOWN_${code.toString(16).padStart(2, '0')}`;
|
|
1097
|
+
},
|
|
1098
|
+
|
|
1099
|
+
// Convert name to hex opcode
|
|
1100
|
+
nameToOpcode: (name) => {
|
|
1101
|
+
return opcodeMap[name] && opcodeMap[name].code;
|
|
1102
|
+
},
|
|
1103
|
+
|
|
1104
|
+
// Generate covenant template
|
|
1105
|
+
generateCovenantTemplate: (fieldName, expectedValue) => {
|
|
1106
|
+
// This generates a basic field extraction and comparison template
|
|
1107
|
+
const operations = [];
|
|
1108
|
+
|
|
1109
|
+
// For preimage field extraction (example for 'value' field)
|
|
1110
|
+
if (fieldName === 'value') {
|
|
1111
|
+
operations.push('OP_SIZE'); // Get preimage size
|
|
1112
|
+
operations.push(52); // Push 52 (bytes to subtract)
|
|
1113
|
+
operations.push('OP_SUB'); // Calculate split position
|
|
1114
|
+
operations.push('OP_SPLIT'); // Split preimage
|
|
1115
|
+
operations.push('OP_DROP'); // Drop left part
|
|
1116
|
+
operations.push(8); // Push 8 (value field length)
|
|
1117
|
+
operations.push('OP_SPLIT'); // Split to get value field
|
|
1118
|
+
operations.push('OP_DROP'); // Drop remaining
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// Add expected value and comparison
|
|
1122
|
+
if (expectedValue) {
|
|
1123
|
+
operations.push(Buffer.from(expectedValue, 'hex'));
|
|
1124
|
+
operations.push('OP_EQUAL');
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
return {
|
|
1128
|
+
operations,
|
|
1129
|
+
asm: utils.createASM(operations),
|
|
1130
|
+
description: `Extract ${fieldName} field and compare with expected value`
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
};
|
|
1134
|
+
|
|
1135
|
+
module.exports = {
|
|
1136
|
+
opcodeMap,
|
|
1137
|
+
scriptNum,
|
|
1138
|
+
utils
|
|
1139
|
+
};
|
|
1140
|
+
|
|
1141
|
+
// CLI test and demonstration
|
|
1142
|
+
if (require.main === module) {
|
|
1143
|
+
console.log("๐ง Comprehensive Bitcoin Script Opcode Map");
|
|
1144
|
+
console.log("==========================================");
|
|
1145
|
+
console.log(`Total opcodes mapped: ${Object.keys(opcodeMap).length}`);
|
|
1146
|
+
|
|
1147
|
+
console.log("\n๐ Categories:");
|
|
1148
|
+
utils.getCategories().forEach(category => {
|
|
1149
|
+
const count = Object.values(opcodeMap).filter(op => op.category === category).length;
|
|
1150
|
+
console.log(` ${category}: ${count} opcodes`);
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
console.log("\n๐งช Example: Stack manipulation simulation");
|
|
1154
|
+
try {
|
|
1155
|
+
const operations = ['OP_1', 'OP_2', 'OP_ADD', 'OP_3', 'OP_EQUAL'];
|
|
1156
|
+
const result = utils.simulate(operations);
|
|
1157
|
+
console.log("Operations:", operations.join(' '));
|
|
1158
|
+
console.log("Final stack:", result.finalStack);
|
|
1159
|
+
console.log("Expected: ['01'] (true, since 1+2=3)");
|
|
1160
|
+
|
|
1161
|
+
console.log("\n๐ Step-by-step execution:");
|
|
1162
|
+
result.history.forEach(step => {
|
|
1163
|
+
console.log(` ${step.step}. ${step.opcode}: ${step.description}`);
|
|
1164
|
+
console.log(` Stack: [${step.beforeStack.join(', ')}] โ [${step.afterStack.join(', ')}]`);
|
|
1165
|
+
});
|
|
1166
|
+
} catch (error) {
|
|
1167
|
+
console.log("โ Simulation error:", error.message);
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
console.log("\n๐ฏ Example: Covenant template generation");
|
|
1171
|
+
const template = utils.generateCovenantTemplate('value', '50c3000000000000');
|
|
1172
|
+
console.log("Generated ASM:", template.asm);
|
|
1173
|
+
console.log("Description:", template.description);
|
|
1174
|
+
}
|