@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,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
+ }