@btc-vision/btc-runtime 1.8.1 → 1.9.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.
Files changed (69) hide show
  1. package/README.md +57 -79
  2. package/package.json +8 -8
  3. package/runtime/buffer/BytesReader.ts +36 -45
  4. package/runtime/buffer/BytesWriter.ts +60 -20
  5. package/runtime/contracts/OP20.ts +636 -0
  6. package/runtime/contracts/OP_NET.ts +3 -3
  7. package/runtime/contracts/interfaces/IOP20.ts +15 -0
  8. package/runtime/contracts/interfaces/OP20InitParameters.ts +3 -1
  9. package/runtime/env/BlockchainEnvironment.ts +28 -7
  10. package/runtime/env/classes/Transaction.ts +1 -2
  11. package/runtime/env/global.ts +171 -23
  12. package/runtime/events/NetEvent.ts +1 -1
  13. package/runtime/events/predefined/{ApproveEvent.ts → ApprovedEvent.ts} +3 -3
  14. package/runtime/events/predefined/{MintEvent.ts → BurnedEvent.ts} +5 -6
  15. package/runtime/events/predefined/{TransferEvent.ts → MintedEvent.ts} +5 -6
  16. package/runtime/events/predefined/TransferredEvent.ts +18 -0
  17. package/runtime/events/predefined/index.ts +4 -4
  18. package/runtime/exports/index.ts +4 -4
  19. package/runtime/index.ts +13 -3
  20. package/runtime/math/abi.ts +10 -6
  21. package/runtime/math/bytes.ts +5 -17
  22. package/runtime/memory/AddressMemoryMap.ts +4 -5
  23. package/runtime/memory/KeyMerger.ts +7 -7
  24. package/runtime/memory/MapOfMap.ts +2 -7
  25. package/runtime/memory/Nested.ts +6 -8
  26. package/runtime/nested/PointerManager.ts +1 -1
  27. package/runtime/nested/codecs/AddressCodec.ts +1 -1
  28. package/runtime/nested/codecs/BooleanCodec.ts +1 -1
  29. package/runtime/nested/codecs/Ids.ts +1 -1
  30. package/runtime/nested/codecs/NumericCodec.ts +1 -1
  31. package/runtime/nested/codecs/StringCodec.ts +1 -1
  32. package/runtime/nested/codecs/VariableBytesCodec.ts +3 -3
  33. package/runtime/nested/storage/StorageMap.ts +3 -3
  34. package/runtime/nested/storage/StorageSet.ts +2 -2
  35. package/runtime/plugins/Plugin.ts +5 -7
  36. package/runtime/script/Bech32.ts +369 -0
  37. package/runtime/script/BitcoinAddresses.ts +208 -0
  38. package/runtime/script/BitcoinCodec.ts +395 -0
  39. package/runtime/script/Networks.ts +94 -0
  40. package/runtime/script/Opcodes.ts +155 -0
  41. package/runtime/script/Script.ts +463 -0
  42. package/runtime/script/ScriptUtils.ts +101 -0
  43. package/runtime/script/Segwit.ts +185 -0
  44. package/runtime/script/reader/ScriptReader.ts +247 -0
  45. package/runtime/secp256k1/ECPoint.ts +6 -12
  46. package/runtime/shared-libraries/TransferHelper.ts +72 -31
  47. package/runtime/storage/AdvancedStoredString.ts +1 -1
  48. package/runtime/storage/StoredAddress.ts +1 -1
  49. package/runtime/storage/StoredBoolean.ts +2 -4
  50. package/runtime/storage/StoredString.ts +6 -3
  51. package/runtime/storage/StoredU256.ts +1 -3
  52. package/runtime/storage/StoredU32.ts +1 -6
  53. package/runtime/storage/StoredU64.ts +1 -4
  54. package/runtime/storage/arrays/StoredBooleanArray.ts +7 -12
  55. package/runtime/storage/arrays/StoredPackedArray.ts +3 -13
  56. package/runtime/storage/maps/StoredMapU256.ts +5 -5
  57. package/runtime/types/Address.ts +49 -39
  58. package/runtime/types/SafeMath.ts +19 -18
  59. package/runtime/types/SafeMathI128.ts +14 -13
  60. package/runtime/utils/hex.ts +41 -19
  61. package/runtime/utils/index.ts +0 -2
  62. package/runtime/contracts/DeployableOP_20.ts +0 -415
  63. package/runtime/contracts/OP_20.ts +0 -9
  64. package/runtime/contracts/interfaces/IOP_20.ts +0 -19
  65. package/runtime/events/predefined/BurnEvent.ts +0 -14
  66. package/runtime/utils/b32.ts +0 -243
  67. package/runtime/utils/box.ts +0 -134
  68. package/runtime/utils/encodings.ts +0 -45
  69. /package/{LICENSE → LICENSE.md} +0 -0
@@ -0,0 +1,155 @@
1
+ import { Revert } from '../types/Revert';
2
+
3
+ @final
4
+ export class BitcoinOpcodes {
5
+ public static readonly OP_FALSE: u8 = 0;
6
+ public static readonly OP_0: u8 = 0;
7
+ public static readonly OP_PUSHDATA1: u8 = 76;
8
+ public static readonly OP_PUSHDATA2: u8 = 77;
9
+ public static readonly OP_PUSHDATA4: u8 = 78;
10
+ public static readonly OP_1NEGATE: u8 = 79;
11
+ public static readonly OP_RESERVED: u8 = 80;
12
+ public static readonly OP_TRUE: u8 = 81;
13
+ public static readonly OP_1: u8 = 81;
14
+ public static readonly OP_2: u8 = 82;
15
+ public static readonly OP_3: u8 = 83;
16
+ public static readonly OP_4: u8 = 84;
17
+ public static readonly OP_5: u8 = 85;
18
+ public static readonly OP_6: u8 = 86;
19
+ public static readonly OP_7: u8 = 87;
20
+ public static readonly OP_8: u8 = 88;
21
+ public static readonly OP_9: u8 = 89;
22
+ public static readonly OP_10: u8 = 90;
23
+ public static readonly OP_11: u8 = 91;
24
+ public static readonly OP_12: u8 = 92;
25
+ public static readonly OP_13: u8 = 93;
26
+ public static readonly OP_14: u8 = 94;
27
+ public static readonly OP_15: u8 = 95;
28
+ public static readonly OP_16: u8 = 96;
29
+ public static readonly OP_NOP: u8 = 97;
30
+ public static readonly OP_VER: u8 = 98;
31
+ public static readonly OP_IF: u8 = 99;
32
+ public static readonly OP_NOTIF: u8 = 100;
33
+ public static readonly OP_VERIF: u8 = 101;
34
+ public static readonly OP_VERNOTIF: u8 = 102;
35
+ public static readonly OP_ELSE: u8 = 103;
36
+ public static readonly OP_ENDIF: u8 = 104;
37
+ public static readonly OP_VERIFY: u8 = 105;
38
+ public static readonly OP_RETURN: u8 = 106;
39
+ public static readonly OP_TOALTSTACK: u8 = 107;
40
+ public static readonly OP_FROMALTSTACK: u8 = 108;
41
+ public static readonly OP_2DROP: u8 = 109;
42
+ public static readonly OP_2DUP: u8 = 110;
43
+ public static readonly OP_3DUP: u8 = 111;
44
+ public static readonly OP_2OVER: u8 = 112;
45
+ public static readonly OP_2ROT: u8 = 113;
46
+ public static readonly OP_2SWAP: u8 = 114;
47
+ public static readonly OP_IFDUP: u8 = 115;
48
+ public static readonly OP_DEPTH: u8 = 116;
49
+ public static readonly OP_DROP: u8 = 117;
50
+ public static readonly OP_DUP: u8 = 118;
51
+ public static readonly OP_NIP: u8 = 119;
52
+ public static readonly OP_OVER: u8 = 120;
53
+ public static readonly OP_PICK: u8 = 121;
54
+ public static readonly OP_ROLL: u8 = 122;
55
+ public static readonly OP_ROT: u8 = 123;
56
+ public static readonly OP_SWAP: u8 = 124;
57
+ public static readonly OP_TUCK: u8 = 125;
58
+ public static readonly OP_CAT: u8 = 126;
59
+ public static readonly OP_SUBSTR: u8 = 127;
60
+ public static readonly OP_LEFT: u8 = 128;
61
+ public static readonly OP_RIGHT: u8 = 129;
62
+ public static readonly OP_SIZE: u8 = 130;
63
+ public static readonly OP_INVERT: u8 = 131;
64
+ public static readonly OP_AND: u8 = 132;
65
+ public static readonly OP_OR: u8 = 133;
66
+ public static readonly OP_XOR: u8 = 134;
67
+ public static readonly OP_EQUAL: u8 = 135;
68
+ public static readonly OP_EQUALVERIFY: u8 = 136;
69
+ public static readonly OP_RESERVED1: u8 = 137;
70
+ public static readonly OP_RESERVED2: u8 = 138;
71
+ public static readonly OP_1ADD: u8 = 139;
72
+ public static readonly OP_1SUB: u8 = 140;
73
+ public static readonly OP_2MUL: u8 = 141;
74
+ public static readonly OP_2DIV: u8 = 142;
75
+ public static readonly OP_NEGATE: u8 = 143;
76
+ public static readonly OP_ABS: u8 = 144;
77
+ public static readonly OP_NOT: u8 = 145;
78
+ public static readonly OP_0NOTEQUAL: u8 = 146;
79
+ public static readonly OP_ADD: u8 = 147;
80
+ public static readonly OP_SUB: u8 = 148;
81
+ public static readonly OP_MUL: u8 = 149;
82
+ public static readonly OP_DIV: u8 = 150;
83
+ public static readonly OP_MOD: u8 = 151;
84
+ public static readonly OP_LSHIFT: u8 = 152;
85
+ public static readonly OP_RSHIFT: u8 = 153;
86
+ public static readonly OP_BOOLAND: u8 = 154;
87
+ public static readonly OP_BOOLOR: u8 = 155;
88
+ public static readonly OP_NUMEQUAL: u8 = 156;
89
+ public static readonly OP_NUMEQUALVERIFY: u8 = 157;
90
+ public static readonly OP_NUMNOTEQUAL: u8 = 158;
91
+ public static readonly OP_LESSTHAN: u8 = 159;
92
+ public static readonly OP_GREATERTHAN: u8 = 160;
93
+ public static readonly OP_LESSTHANOREQUAL: u8 = 161;
94
+ public static readonly OP_GREATERTHANOREQUAL: u8 = 162;
95
+ public static readonly OP_MIN: u8 = 163;
96
+ public static readonly OP_MAX: u8 = 164;
97
+ public static readonly OP_WITHIN: u8 = 165;
98
+ public static readonly OP_RIPEMD160: u8 = 166;
99
+ public static readonly OP_SHA1: u8 = 167;
100
+ public static readonly OP_SHA256: u8 = 168;
101
+ public static readonly OP_HASH160: u8 = 169;
102
+ public static readonly OP_HASH256: u8 = 170;
103
+ public static readonly OP_CODESEPARATOR: u8 = 171;
104
+ public static readonly OP_CHECKSIG: u8 = 172;
105
+ public static readonly OP_CHECKSIGVERIFY: u8 = 173;
106
+ public static readonly OP_CHECKMULTISIG: u8 = 174;
107
+ public static readonly OP_CHECKMULTISIGVERIFY: u8 = 175;
108
+ public static readonly OP_NOP1: u8 = 176;
109
+ public static readonly OP_NOP2: u8 = 177;
110
+ public static readonly OP_CHECKLOCKTIMEVERIFY: u8 = 177;
111
+ public static readonly OP_NOP3: u8 = 178;
112
+ public static readonly OP_CHECKSEQUENCEVERIFY: u8 = 178;
113
+ public static readonly OP_NOP4: u8 = 179;
114
+ public static readonly OP_NOP5: u8 = 180;
115
+ public static readonly OP_NOP6: u8 = 181;
116
+ public static readonly OP_NOP7: u8 = 182;
117
+ public static readonly OP_NOP8: u8 = 183;
118
+ public static readonly OP_NOP9: u8 = 184;
119
+ public static readonly OP_NOP10: u8 = 185;
120
+ public static readonly OP_CHECKSIGADD: u8 = 186;
121
+ public static readonly OP_PUBKEYHASH: u8 = 253;
122
+ public static readonly OP_PUBKEY: u8 = 254;
123
+ public static readonly OP_INVALIDOPCODE: u8 = 255;
124
+
125
+ public static opN(n: i32): u8 {
126
+ if (n == 0) return 0;
127
+ if (n < 0 || n > 16) throw new Revert('OP_N out of range');
128
+ return <u8>(0x50 + n);
129
+ }
130
+
131
+ public static isPushdataOpcode(op: u8): bool {
132
+ // true for explicit data-push opcodes: 0x01..0x4b, OP_PUSHDATA1/2/4
133
+ if (op >= <u8>1 && op <= <u8>75) return true;
134
+ return (
135
+ op == BitcoinOpcodes.OP_PUSHDATA1 ||
136
+ op == BitcoinOpcodes.OP_PUSHDATA2 ||
137
+ op == BitcoinOpcodes.OP_PUSHDATA4
138
+ );
139
+ }
140
+
141
+ public static isOpSuccessTaproot(op: u8): bool {
142
+ // BIP342 OP_SUCCESSx set for Tapscript (witness v1)
143
+ if (op == BitcoinOpcodes.OP_RESERVED /* 0x50 */) return true;
144
+ if (op == BitcoinOpcodes.OP_VER /* 0x62 */) return true;
145
+
146
+ // Disabled legacy ops that become OP_SUCCESSx in Tapscript
147
+ if (op >= BitcoinOpcodes.OP_CAT && op <= BitcoinOpcodes.OP_RIGHT) return true;
148
+ if (op >= BitcoinOpcodes.OP_INVERT && op <= BitcoinOpcodes.OP_XOR) return true;
149
+ if (op >= BitcoinOpcodes.OP_2MUL && op <= BitcoinOpcodes.OP_2DIV) return true;
150
+ if (op >= <u8>149 && op <= <u8>153) return true;
151
+
152
+ // Unknown/reserved range that is OP_SUCCESSx in Tapscript
153
+ return op >= <u8>187 && op <= <u8>254;
154
+ }
155
+ }
@@ -0,0 +1,463 @@
1
+ import { BytesWriter } from '../buffer/BytesWriter';
2
+ import { BitcoinOpcodes } from './Opcodes';
3
+ import { ScriptReader } from './reader/ScriptReader';
4
+ import { CsvRecognize, MultisigRecognize } from './ScriptUtils';
5
+ import { Revert } from '../types/Revert';
6
+
7
+ /**
8
+ * ScriptNumber handles Bitcoin Script's unique number encoding format
9
+ * Bitcoin uses a variable-length, little-endian encoding with a sign bit
10
+ */
11
+ @final
12
+ export class ScriptNumber {
13
+ /**
14
+ * Calculate how many bytes are needed to encode a number
15
+ * in Bitcoin Script format
16
+ */
17
+ public static encodedLen(x: i64): i32 {
18
+ if (x == 0) return 0;
19
+
20
+ // Work with absolute value to count bytes
21
+ let n = x < 0 ? -x : x;
22
+ let bytes = 0;
23
+
24
+ while (n > 0) {
25
+ bytes++;
26
+ n >>= 8;
27
+ }
28
+
29
+ // Check if we need an extra byte for the sign bit
30
+ // This happens when the most significant bit is already set
31
+ const msb: u8 = <u8>(((x < 0 ? -x : x) >> (<i64>((bytes - 1) * 8))) & 0xff);
32
+ return (msb & 0x80) != 0 ? bytes + 1 : bytes;
33
+ }
34
+
35
+ /**
36
+ * Encode a number into Bitcoin Script format
37
+ * Returns a little-endian byte array with sign in the MSB
38
+ */
39
+ public static encode(x: i64): Uint8Array {
40
+ const L = ScriptNumber.encodedLen(x);
41
+ if (L == 0) return new Uint8Array(0);
42
+
43
+ const neg = x < 0;
44
+ let n = neg ? -x : x;
45
+ const out = new Uint8Array(L);
46
+
47
+ // Write bytes in little-endian order
48
+ for (let i = 0; i < L; i++) {
49
+ out[i] = <u8>(n & 0xff);
50
+ n >>= 8;
51
+ }
52
+
53
+ // Handle sign bit
54
+ if (neg) {
55
+ out[L - 1] |= 0x80;
56
+ } else if ((out[L - 1] & 0x80) != 0) {
57
+ // If MSB is set on a positive number, we need an extra byte
58
+ out[L - 1] = 0x00;
59
+ }
60
+
61
+ return out;
62
+ }
63
+
64
+ /**
65
+ * Decode result type for safe error handling
66
+ */
67
+ static decodeResult(data: Uint8Array, minimal: bool = true): DecodeNumberResult {
68
+ const L = data.length;
69
+ if (L == 0) return DecodeNumberResult.ok(0);
70
+
71
+ // Script numbers are limited to 4 bytes
72
+ if (L > 4) return DecodeNumberResult.err('ScriptNumber too large');
73
+
74
+ // Check minimal encoding if required
75
+ if (minimal) {
76
+ const msb = data[L - 1];
77
+ if ((msb & 0x7f) == 0) {
78
+ if (L == 1) return DecodeNumberResult.err('non-minimal zero');
79
+ if ((data[L - 2] & 0x80) == 0) {
80
+ return DecodeNumberResult.err('non-minimal sign byte');
81
+ }
82
+ }
83
+ }
84
+
85
+ // Decode the number
86
+ let res: i64 = 0;
87
+ for (let i = 0; i < L; i++) {
88
+ res |= (<i64>((<i64>data[i]) & 0xff)) << (<i64>8 * i);
89
+ }
90
+
91
+ // Handle negative numbers
92
+ const neg = (data[L - 1] & 0x80) != 0;
93
+ if (neg) {
94
+ // Clear the sign bit and negate
95
+ const mask: i64 = ~((<i64>0x80) << (<i64>8 * (L - 1)));
96
+ res &= mask;
97
+ res = -res;
98
+ }
99
+
100
+ return DecodeNumberResult.ok(res);
101
+ }
102
+
103
+ /**
104
+ * Consider using decodeResult for safe decoding
105
+ * This method will throw an error if decoding fails
106
+ */
107
+ public static decode(data: Uint8Array, minimal: bool = true): i64 {
108
+ const result = ScriptNumber.decodeResult(data, minimal);
109
+ if (!result.success) {
110
+ if (!result.error) {
111
+ throw new Revert('Unexpected error in ScriptNumber.decode');
112
+ }
113
+
114
+ throw new Revert(result.error);
115
+ }
116
+ return result.value;
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Result type for number decoding operations
122
+ */
123
+ @final
124
+ export class DecodeNumberResult {
125
+ public readonly success: bool;
126
+ public readonly value: i64;
127
+ public readonly error: string | null;
128
+
129
+ public constructor(success: bool, value: i64, error: string | null) {
130
+ this.success = success;
131
+ this.value = value;
132
+ this.error = error;
133
+ }
134
+
135
+ static ok(value: i64): DecodeNumberResult {
136
+ return new DecodeNumberResult(true, value, null);
137
+ }
138
+
139
+ static err(error: string): DecodeNumberResult {
140
+ return new DecodeNumberResult(false, 0, error);
141
+ }
142
+ }
143
+
144
+ /**
145
+ * ScriptIO handles the serialization of Script operations
146
+ * It knows how to write push operations in the most efficient format
147
+ */
148
+ @final
149
+ export class ScriptIO {
150
+ /**
151
+ * Calculate the size needed for a push operation
152
+ * This includes the opcode and any length prefixes
153
+ */
154
+ public static pushPrefixSize(len: i32): i32 {
155
+ if (len == 0) return 1; // OP_0
156
+ if (len <= 75) return 1; // Direct push
157
+ if (len < 0x100) return 2; // OP_PUSHDATA1 + 1 byte length
158
+ if (len < 0x10000) return 3; // OP_PUSHDATA2 + 2 byte length
159
+ return 5; // OP_PUSHDATA4 + 4 byte length
160
+ }
161
+
162
+ /**
163
+ * Write a push operation in the most efficient format
164
+ * Bitcoin Script has several ways to push data onto the stack
165
+ */
166
+ public static writePush(w: BytesWriter, data: Uint8Array): void {
167
+ const len = data.length;
168
+
169
+ if (len == 0) {
170
+ // Empty pushes use OP_0
171
+ w.writeU8(BitcoinOpcodes.OP_0);
172
+ return;
173
+ }
174
+
175
+ if (len <= 75) {
176
+ // Small pushes: the length itself is the opcode
177
+ w.writeU8(<u8>len);
178
+ w.writeBytes(data);
179
+ return;
180
+ }
181
+
182
+ if (len < 0x100) {
183
+ // Medium pushes: OP_PUSHDATA1 followed by 1-byte length
184
+ w.writeU8(BitcoinOpcodes.OP_PUSHDATA1);
185
+ w.writeU8(<u8>len);
186
+ w.writeBytes(data);
187
+ return;
188
+ }
189
+
190
+ if (len < 0x10000) {
191
+ // Large pushes: OP_PUSHDATA2 followed by 2-byte length
192
+ w.writeU8(BitcoinOpcodes.OP_PUSHDATA2);
193
+ w.writeU8(<u8>len);
194
+ w.writeU8(<u8>(len >>> 8));
195
+ w.writeBytes(data);
196
+ return;
197
+ }
198
+
199
+ // Very large pushes: OP_PUSHDATA4 followed by 4-byte length
200
+ w.writeU8(BitcoinOpcodes.OP_PUSHDATA4);
201
+ w.writeU8(<u8>len);
202
+ w.writeU8(<u8>(len >>> 8));
203
+ w.writeU8(<u8>(len >>> 16));
204
+ w.writeU8(<u8>(len >>> 24));
205
+ w.writeBytes(data);
206
+ }
207
+ }
208
+
209
+ @final
210
+ export class BitcoinScript {
211
+ /**
212
+ * Create a CSV (CheckSequenceVerify) timelock script
213
+ * This allows coins to be locked for a certain number of blocks
214
+ */
215
+ public static csvTimelock(pubkey: Uint8Array, csvBlocks: i32): Uint8Array {
216
+ // Validate inputs
217
+ if (csvBlocks < 0) throw new Revert('csvBlocks must be >= 0');
218
+ if (csvBlocks > 65535) throw new Revert('csvBlocks exceeds 16-bit BIP-68 field');
219
+
220
+ const pubLen = pubkey.length;
221
+
222
+ // Calculate size needed for the number part
223
+ let nPartSize = 0;
224
+ if (csvBlocks == 0) {
225
+ nPartSize = 1; // OP_0
226
+ } else if (csvBlocks <= 16) {
227
+ nPartSize = 1; // OP_1 through OP_16
228
+ } else {
229
+ // Need to encode as a push operation
230
+ const nBytes = ScriptNumber.encodedLen(csvBlocks);
231
+ nPartSize = ScriptIO.pushPrefixSize(nBytes) + nBytes;
232
+ }
233
+
234
+ // Calculate total script size
235
+ const sz =
236
+ nPartSize +
237
+ 1 + // OP_CHECKSEQUENCEVERIFY
238
+ 1 + // OP_DROP
239
+ ScriptIO.pushPrefixSize(pubLen) +
240
+ pubLen + // pubkey push
241
+ 1; // OP_CHECKSIG
242
+
243
+ if (sz > 10000) throw new Revert('script too large');
244
+
245
+ // Build the script
246
+ const w = new BytesWriter(sz);
247
+
248
+ // Write the CSV blocks value
249
+ if (csvBlocks == 0) {
250
+ w.writeU8(BitcoinOpcodes.OP_0);
251
+ } else if (csvBlocks <= 16) {
252
+ w.writeU8(BitcoinOpcodes.opN(csvBlocks));
253
+ } else {
254
+ const enc = ScriptNumber.encode(csvBlocks);
255
+ ScriptIO.writePush(w, enc);
256
+ }
257
+
258
+ // Write the rest of the script
259
+ w.writeU8(BitcoinOpcodes.OP_CHECKSEQUENCEVERIFY);
260
+ w.writeU8(BitcoinOpcodes.OP_DROP);
261
+ ScriptIO.writePush(w, pubkey);
262
+ w.writeU8(BitcoinOpcodes.OP_CHECKSIG);
263
+
264
+ return w.getBuffer().subarray(0, <i32>w.getOffset());
265
+ }
266
+
267
+ /**
268
+ * Create a multisig script
269
+ * Requires m-of-n signatures to spend
270
+ */
271
+ public static multisig(m: i32, pubkeys: Array<Uint8Array>): Uint8Array {
272
+ const n = pubkeys.length;
273
+
274
+ // Validate parameters
275
+ if (m < 1 || m > 16) throw new Revert('m out of range');
276
+ if (n < m || n > 16) throw new Revert('n out of range');
277
+
278
+ // Calculate total size
279
+ let sz = 1; // OP_m
280
+ for (let i = 0; i < n; i++) {
281
+ const L = pubkeys[i].length;
282
+ sz += ScriptIO.pushPrefixSize(L) + L;
283
+ }
284
+ sz += 1 + 1; // OP_n + OP_CHECKMULTISIG
285
+
286
+ if (sz > 10000) throw new Revert('script too large');
287
+
288
+ // Build the script
289
+ const w = new BytesWriter(sz);
290
+ w.writeU8(BitcoinOpcodes.opN(m));
291
+
292
+ for (let i = 0; i < n; i++) {
293
+ ScriptIO.writePush(w, pubkeys[i]);
294
+ }
295
+
296
+ w.writeU8(BitcoinOpcodes.opN(n));
297
+ w.writeU8(BitcoinOpcodes.OP_CHECKMULTISIG);
298
+
299
+ return w.getBuffer().subarray(0, <i32>w.getOffset());
300
+ }
301
+
302
+ /**
303
+ * Recognize and parse a CSV timelock script
304
+ * Returns the CSV blocks and pubkey if the script matches the pattern
305
+ */
306
+ public static recognizeCsvTimelock(
307
+ script: Uint8Array,
308
+ strictMinimal: bool = true,
309
+ ): CsvRecognize {
310
+ const r = new ScriptReader(script);
311
+
312
+ if (r.done()) return new CsvRecognize(false, -1, null);
313
+
314
+ // Read the CSV blocks value
315
+ let result = r.nextSafe(strictMinimal);
316
+ if (!result.success) return new CsvRecognize(false, -1, null);
317
+
318
+ let t = result.value!;
319
+ let csvBlocks: i64 = -1;
320
+ let pubkey: Uint8Array | null = null;
321
+
322
+ // Parse the number from the first instruction
323
+ if (t.data !== null) {
324
+ // It's a push operation - decode the number
325
+ const numResult = ScriptNumber.decodeResult(t.data as Uint8Array, strictMinimal);
326
+ if (!numResult.success || numResult.value < 0) {
327
+ return new CsvRecognize(false, -1, null);
328
+ }
329
+ csvBlocks = numResult.value;
330
+ } else {
331
+ // It's a direct opcode
332
+ const op = t.op;
333
+ if (op == <i32>BitcoinOpcodes.OP_1NEGATE) {
334
+ return new CsvRecognize(false, -1, null);
335
+ }
336
+ if (op >= <i32>BitcoinOpcodes.OP_1 && op <= <i32>BitcoinOpcodes.OP_16) {
337
+ csvBlocks = <i64>(op - 0x50);
338
+ } else if (op == <i32>BitcoinOpcodes.OP_0) {
339
+ csvBlocks = 0;
340
+ } else {
341
+ return new CsvRecognize(false, -1, null);
342
+ }
343
+ }
344
+
345
+ // Check for OP_CHECKSEQUENCEVERIFY
346
+ if (r.done()) return new CsvRecognize(false, -1, null);
347
+ result = r.nextSafe(strictMinimal);
348
+ if (!result.success) return new CsvRecognize(false, -1, null);
349
+ t = result.value!;
350
+ if (t.op != <i32>BitcoinOpcodes.OP_CHECKSEQUENCEVERIFY) {
351
+ return new CsvRecognize(false, -1, null);
352
+ }
353
+
354
+ // Check for OP_DROP
355
+ if (r.done()) return new CsvRecognize(false, -1, null);
356
+ result = r.nextSafe(strictMinimal);
357
+ if (!result.success) return new CsvRecognize(false, -1, null);
358
+ t = result.value!;
359
+ if (t.op != <i32>BitcoinOpcodes.OP_DROP) {
360
+ return new CsvRecognize(false, -1, null);
361
+ }
362
+
363
+ // Read the pubkey
364
+ if (r.done()) return new CsvRecognize(false, -1, null);
365
+ result = r.nextSafe(strictMinimal);
366
+ if (!result.success) return new CsvRecognize(false, -1, null);
367
+ t = result.value!;
368
+ if (t.data === null) return new CsvRecognize(false, -1, null);
369
+
370
+ const pk = t.data as Uint8Array;
371
+ const Lpk = pk.length;
372
+ // Valid pubkeys are either 33 bytes (compressed) or 65 bytes (uncompressed)
373
+ if (Lpk != 33 && Lpk != 65) return new CsvRecognize(false, -1, null);
374
+ pubkey = pk;
375
+
376
+ // Check for OP_CHECKSIG
377
+ if (r.done()) return new CsvRecognize(false, -1, null);
378
+ result = r.nextSafe(strictMinimal);
379
+ if (!result.success) return new CsvRecognize(false, -1, null);
380
+ t = result.value!;
381
+ if (t.op != <i32>BitcoinOpcodes.OP_CHECKSIG) {
382
+ return new CsvRecognize(false, -1, null);
383
+ }
384
+
385
+ // Script should be exactly consumed
386
+ if (!r.done()) return new CsvRecognize(false, -1, null);
387
+
388
+ return new CsvRecognize(true, csvBlocks, pubkey);
389
+ }
390
+
391
+ /**
392
+ * Recognize and parse a multisig script
393
+ * Returns m, n, and the list of pubkeys if the script matches
394
+ */
395
+ public static recognizeMultisig(
396
+ script: Uint8Array,
397
+ strictMinimal: bool = true,
398
+ ): MultisigRecognize {
399
+ const r = new ScriptReader(script);
400
+
401
+ if (r.done()) return new MultisigRecognize(false, 0, 0, null);
402
+
403
+ // Read m value
404
+ let result = r.nextSafe(strictMinimal);
405
+ if (!result.success) return new MultisigRecognize(false, 0, 0, null);
406
+
407
+ let t = result.value!;
408
+ const opm = t.op;
409
+ if (opm < <i32>BitcoinOpcodes.OP_1 || opm > <i32>BitcoinOpcodes.OP_16) {
410
+ return new MultisigRecognize(false, 0, 0, null);
411
+ }
412
+ const m = opm - 0x50;
413
+
414
+ // Collect pubkeys
415
+ const keys = new Array<Uint8Array>();
416
+
417
+ while (!r.done()) {
418
+ result = r.nextSafe(strictMinimal);
419
+ if (!result.success) return new MultisigRecognize(false, 0, 0, null);
420
+ t = result.value!;
421
+
422
+ if (t.data !== null) {
423
+ // This is a pubkey
424
+ const pk = t.data as Uint8Array;
425
+ const L = pk.length;
426
+ if (L != 33 && L != 65) {
427
+ return new MultisigRecognize(false, 0, 0, null);
428
+ }
429
+ keys.push(pk);
430
+ continue;
431
+ }
432
+
433
+ // Must be the n value
434
+ if (t.op < <i32>BitcoinOpcodes.OP_1 || t.op > <i32>BitcoinOpcodes.OP_16) {
435
+ return new MultisigRecognize(false, 0, 0, null);
436
+ }
437
+ const n = t.op - 0x50;
438
+
439
+ // Validate n
440
+ if (n != keys.length || n < m) {
441
+ return new MultisigRecognize(false, 0, 0, null);
442
+ }
443
+
444
+ // Next must be OP_CHECKMULTISIG
445
+ if (r.done()) return new MultisigRecognize(false, 0, 0, null);
446
+
447
+ const result2 = r.nextSafe(strictMinimal);
448
+ if (!result2.success) return new MultisigRecognize(false, 0, 0, null);
449
+ const t2 = result2.value!;
450
+
451
+ if (t2.op != <i32>BitcoinOpcodes.OP_CHECKMULTISIG) {
452
+ return new MultisigRecognize(false, 0, 0, null);
453
+ }
454
+
455
+ // Script should be fully consumed
456
+ if (!r.done()) return new MultisigRecognize(false, 0, 0, null);
457
+
458
+ return new MultisigRecognize(true, m, n, keys);
459
+ }
460
+
461
+ return new MultisigRecognize(false, 0, 0, null);
462
+ }
463
+ }