@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.
- package/README.md +57 -79
- package/package.json +8 -8
- package/runtime/buffer/BytesReader.ts +36 -45
- package/runtime/buffer/BytesWriter.ts +60 -20
- package/runtime/contracts/OP20.ts +636 -0
- package/runtime/contracts/OP_NET.ts +3 -3
- package/runtime/contracts/interfaces/IOP20.ts +15 -0
- package/runtime/contracts/interfaces/OP20InitParameters.ts +3 -1
- package/runtime/env/BlockchainEnvironment.ts +28 -7
- package/runtime/env/classes/Transaction.ts +1 -2
- package/runtime/env/global.ts +171 -23
- package/runtime/events/NetEvent.ts +1 -1
- package/runtime/events/predefined/{ApproveEvent.ts → ApprovedEvent.ts} +3 -3
- package/runtime/events/predefined/{MintEvent.ts → BurnedEvent.ts} +5 -6
- package/runtime/events/predefined/{TransferEvent.ts → MintedEvent.ts} +5 -6
- package/runtime/events/predefined/TransferredEvent.ts +18 -0
- package/runtime/events/predefined/index.ts +4 -4
- package/runtime/exports/index.ts +4 -4
- package/runtime/index.ts +13 -3
- package/runtime/math/abi.ts +10 -6
- package/runtime/math/bytes.ts +5 -17
- package/runtime/memory/AddressMemoryMap.ts +4 -5
- package/runtime/memory/KeyMerger.ts +7 -7
- package/runtime/memory/MapOfMap.ts +2 -7
- package/runtime/memory/Nested.ts +6 -8
- package/runtime/nested/PointerManager.ts +1 -1
- package/runtime/nested/codecs/AddressCodec.ts +1 -1
- package/runtime/nested/codecs/BooleanCodec.ts +1 -1
- package/runtime/nested/codecs/Ids.ts +1 -1
- package/runtime/nested/codecs/NumericCodec.ts +1 -1
- package/runtime/nested/codecs/StringCodec.ts +1 -1
- package/runtime/nested/codecs/VariableBytesCodec.ts +3 -3
- package/runtime/nested/storage/StorageMap.ts +3 -3
- package/runtime/nested/storage/StorageSet.ts +2 -2
- package/runtime/plugins/Plugin.ts +5 -7
- package/runtime/script/Bech32.ts +369 -0
- package/runtime/script/BitcoinAddresses.ts +208 -0
- package/runtime/script/BitcoinCodec.ts +395 -0
- package/runtime/script/Networks.ts +94 -0
- package/runtime/script/Opcodes.ts +155 -0
- package/runtime/script/Script.ts +463 -0
- package/runtime/script/ScriptUtils.ts +101 -0
- package/runtime/script/Segwit.ts +185 -0
- package/runtime/script/reader/ScriptReader.ts +247 -0
- package/runtime/secp256k1/ECPoint.ts +6 -12
- package/runtime/shared-libraries/TransferHelper.ts +72 -31
- package/runtime/storage/AdvancedStoredString.ts +1 -1
- package/runtime/storage/StoredAddress.ts +1 -1
- package/runtime/storage/StoredBoolean.ts +2 -4
- package/runtime/storage/StoredString.ts +6 -3
- package/runtime/storage/StoredU256.ts +1 -3
- package/runtime/storage/StoredU32.ts +1 -6
- package/runtime/storage/StoredU64.ts +1 -4
- package/runtime/storage/arrays/StoredBooleanArray.ts +7 -12
- package/runtime/storage/arrays/StoredPackedArray.ts +3 -13
- package/runtime/storage/maps/StoredMapU256.ts +5 -5
- package/runtime/types/Address.ts +49 -39
- package/runtime/types/SafeMath.ts +19 -18
- package/runtime/types/SafeMathI128.ts +14 -13
- package/runtime/utils/hex.ts +41 -19
- package/runtime/utils/index.ts +0 -2
- package/runtime/contracts/DeployableOP_20.ts +0 -415
- package/runtime/contracts/OP_20.ts +0 -9
- package/runtime/contracts/interfaces/IOP_20.ts +0 -19
- package/runtime/events/predefined/BurnEvent.ts +0 -14
- package/runtime/utils/b32.ts +0 -243
- package/runtime/utils/box.ts +0 -134
- package/runtime/utils/encodings.ts +0 -45
- /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
|
+
}
|