@btc-vision/btc-runtime 1.8.1 → 1.9.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/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 +643 -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 +2 -7
  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,208 @@
1
+ import { CsvP2wshResult, MultisigP2wshResult } from './ScriptUtils';
2
+ import { Segwit } from './Segwit';
3
+ import { BitcoinScript } from './Script';
4
+ import { sha256 } from '../env/global';
5
+
6
+ /**
7
+ * Ct provides constant-time comparison functions
8
+ * This is important for cryptographic operations to avoid timing attacks
9
+ */
10
+ @final
11
+ export class Ct {
12
+ /**
13
+ * Compare two 32-byte arrays in constant time
14
+ * Returns true if they are equal, false otherwise
15
+ *
16
+ * The function always takes the same amount of time regardless of where
17
+ * the arrays differ, which prevents timing-based attacks
18
+ */
19
+ @inline public static eq32(a: Uint8Array, b: Uint8Array): bool {
20
+ // XOR the lengths with 32 to detect length mismatches
21
+ let d: i32 = (a.length ^ 32) | (b.length ^ 32);
22
+
23
+ // Compare each byte, accumulating differences in d
24
+ for (let i = 0; i < 32; i++) {
25
+ // Handle arrays shorter than 32 bytes by treating missing bytes as 0
26
+ const ai: u8 = i < a.length ? a[i] : 0;
27
+ const bi: u8 = i < b.length ? b[i] : 0;
28
+ // XOR accumulates any differences without early exit
29
+ d |= ai ^ bi;
30
+ }
31
+
32
+ // d will be 0 only if all bytes matched
33
+ return d == 0;
34
+ }
35
+ }
36
+
37
+ /**
38
+ * BitcoinAddresses provides high-level functions for creating and verifying
39
+ * various types of Bitcoin addresses, particularly those using witness scripts
40
+ */
41
+ @final
42
+ export class BitcoinAddresses {
43
+ /**
44
+ * Create a witness script for a CSV (CheckSequenceVerify) timelock
45
+ * This script requires a certain number of blocks to pass before spending
46
+ *
47
+ * @param pubkey - The public key that can spend after the timelock
48
+ * @param csvBlocks - Number of blocks to wait (must be 0-65535)
49
+ * @returns The witness script bytes
50
+ */
51
+ public static csvWitnessScript(pubkey: Uint8Array, csvBlocks: i32): Uint8Array {
52
+ return BitcoinScript.csvTimelock(pubkey, csvBlocks);
53
+ }
54
+
55
+ /**
56
+ * Create a P2WSH (Pay to Witness Script Hash) address with CSV timelock
57
+ * This creates both the address and the witness script needed to spend it
58
+ *
59
+ * @param pubkey - The public key that can spend after the timelock
60
+ * @param csvBlocks - Number of blocks to wait
61
+ * @param hrp - Human-readable part for the address (e.g., "bc" for mainnet)
62
+ * @returns Object containing both the address and witness script
63
+ */
64
+ public static csvP2wshAddress(pubkey: Uint8Array, csvBlocks: i32, hrp: string): CsvP2wshResult {
65
+ const ws = BitcoinAddresses.csvWitnessScript(pubkey, csvBlocks);
66
+ const addr = Segwit.p2wsh(hrp, ws);
67
+ return new CsvP2wshResult(addr, ws);
68
+ }
69
+
70
+ /**
71
+ * Verify that a given address corresponds to a specific CSV timelock setup
72
+ * This is useful for validating that an address was created with expected parameters
73
+ *
74
+ * @param pubkey - The expected public key
75
+ * @param csvBlocks - The expected number of CSV blocks
76
+ * @param address - The address to verify
77
+ * @param hrp - Expected human-readable part
78
+ * @param strictMinimal - Whether to enforce strict minimal encoding rules
79
+ * @returns true if the address matches the expected parameters
80
+ */
81
+ public static verifyCsvP2wshAddress(
82
+ pubkey: Uint8Array,
83
+ csvBlocks: i32,
84
+ address: string,
85
+ hrp: string,
86
+ strictMinimal: bool = true,
87
+ ): bool {
88
+ // Try to decode the address - this replaces the try-catch block
89
+ const dec = Segwit.decodeOrNull(address);
90
+ if (!dec) return false;
91
+
92
+ // Verify it's a v0 witness program with 32-byte hash
93
+ if (dec.version != 0 || dec.hrp != hrp || dec.program.length != 32) return false;
94
+
95
+ // Reconstruct the witness script and verify it matches
96
+ const ws = BitcoinAddresses.csvWitnessScript(pubkey, csvBlocks);
97
+
98
+ // Parse the witness script to ensure it's well-formed
99
+ const rec = BitcoinScript.recognizeCsvTimelock(ws, strictMinimal);
100
+ if (!rec.ok || rec.csvBlocks != csvBlocks) return false;
101
+
102
+ // Compute the script hash and compare with the address program
103
+ const prog = sha256(ws);
104
+ return Ct.eq32(dec.program, prog);
105
+ }
106
+
107
+ /**
108
+ * Create a witness script for a multisig setup
109
+ * This creates a script requiring m-of-n signatures to spend
110
+ *
111
+ * @param m - Number of required signatures
112
+ * @param pubkeys - Array of public keys (n total)
113
+ * @returns The witness script bytes
114
+ */
115
+ public static multisigWitnessScript(m: i32, pubkeys: Array<Uint8Array>): Uint8Array {
116
+ return BitcoinScript.multisig(m, pubkeys);
117
+ }
118
+
119
+ /**
120
+ * Create a P2WSH multisig address
121
+ * This creates both the address and the witness script for a multisig setup
122
+ *
123
+ * @param m - Number of required signatures
124
+ * @param pubkeys - Array of public keys
125
+ * @param hrp - Human-readable part for the address
126
+ * @returns Object containing both the address and witness script
127
+ */
128
+ public static multisigP2wshAddress(
129
+ m: i32,
130
+ pubkeys: Array<Uint8Array>,
131
+ hrp: string,
132
+ ): MultisigP2wshResult {
133
+ const ws = BitcoinAddresses.multisigWitnessScript(m, pubkeys);
134
+ const addr = Segwit.p2wsh(hrp, ws);
135
+ return new MultisigP2wshResult(addr, ws);
136
+ }
137
+
138
+ /**
139
+ * Verify that a given address corresponds to a specific multisig setup
140
+ * This validates that an address was created with the expected m-of-n parameters
141
+ *
142
+ * @param m - Expected number of required signatures
143
+ * @param pubkeys - Expected array of public keys
144
+ * @param address - The address to verify
145
+ * @param hrp - Expected human-readable part
146
+ * @returns true if the address matches the expected parameters
147
+ */
148
+ public static verifyMultisigP2wshAddress(
149
+ m: i32,
150
+ pubkeys: Array<Uint8Array>,
151
+ address: string,
152
+ hrp: string,
153
+ ): bool {
154
+ // Decode the address safely
155
+ const dec = Segwit.decodeOrNull(address);
156
+ if (!dec) return false;
157
+
158
+ // Verify it's a v0 witness program with 32-byte hash
159
+ if (dec.version != 0 || dec.hrp != hrp || dec.program.length != 32) return false;
160
+
161
+ // Reconstruct the witness script and compare
162
+ const ws = BitcoinAddresses.multisigWitnessScript(m, pubkeys);
163
+ const prog = sha256(ws);
164
+ return Ct.eq32(dec.program, prog);
165
+ }
166
+
167
+ /**
168
+ * Create a Taproot (P2TR) key-path spend address
169
+ * This is the simplest form of Taproot address, spendable with a single key
170
+ *
171
+ * @param outputKeyX32 - The 32-byte X coordinate of the output key
172
+ * @param hrp - Human-readable part for the address
173
+ * @returns The Bech32m-encoded address
174
+ */
175
+ public static p2trKeyPathAddress(outputKeyX32: Uint8Array, hrp: string): string {
176
+ return Segwit.p2tr(hrp, outputKeyX32);
177
+ }
178
+
179
+ /**
180
+ * Create a Pay-to-Witness-Public-Key-Hash (P2WPKH) address
181
+ * @param pubkey - The public key (33 bytes compressed or 65 bytes uncompressed)
182
+ * @param hrp - Human-readable part (e.g., "bc" for mainnet)
183
+ * @returns The Bech32-encoded address
184
+ */
185
+ public static p2wpkh(pubkey: Uint8Array, hrp: string): string {
186
+ return Segwit.p2wpkh(hrp, pubkey);
187
+ }
188
+
189
+ /**
190
+ * Verify that a given address corresponds to a specific Taproot output key
191
+ *
192
+ * @param outputKeyX32 - The expected 32-byte X coordinate
193
+ * @param address - The address to verify
194
+ * @param hrp - Expected human-readable part
195
+ * @returns true if the address matches the expected output key
196
+ */
197
+ public static verifyP2trAddress(outputKeyX32: Uint8Array, address: string, hrp: string): bool {
198
+ // Decode the address safely
199
+ const dec = Segwit.decodeOrNull(address);
200
+ if (!dec) return false;
201
+
202
+ // Verify it's a v1 witness program with 32-byte key
203
+ if (dec.version != 1 || dec.hrp != hrp || dec.program.length != 32) return false;
204
+
205
+ // Compare the output key
206
+ return Ct.eq32(dec.program, outputKeyX32);
207
+ }
208
+ }
@@ -0,0 +1,395 @@
1
+ import { BitcoinAddresses, Ct } from './BitcoinAddresses';
2
+ import { BytesWriter } from '../buffer/BytesWriter';
3
+ import { BytesReader } from '../buffer/BytesReader';
4
+ import { CsvPairCrossCheck, MultisigPairCrossCheck } from './ScriptUtils';
5
+ import { Segwit } from './Segwit';
6
+ import { sha256 } from '../env/global';
7
+ import { BitcoinScript } from './Script';
8
+ import { Revert } from '../types/Revert';
9
+
10
+ /**
11
+ * Result type for codec operations that can fail
12
+ * This provides detailed error information when operations fail
13
+ */
14
+ @final
15
+ export class CodecResult<T> {
16
+ public readonly success: bool;
17
+ public readonly value: T | null;
18
+ public readonly error: string | null;
19
+
20
+ public constructor(success: bool, value: T | null, error: string | null) {
21
+ this.success = success;
22
+ this.value = value;
23
+ this.error = error;
24
+ }
25
+
26
+ public static ok<T>(value: T): CodecResult<T> {
27
+ return new CodecResult<T>(true, value, null);
28
+ }
29
+
30
+ public static err<T>(error: string): CodecResult<T> {
31
+ return new CodecResult<T>(false, null, error);
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Represents a verified address read from a byte stream
37
+ */
38
+ @final
39
+ export class VerifiedAddress {
40
+ public readonly address: string;
41
+ public readonly witnessScript: Uint8Array | null;
42
+
43
+ constructor(address: string, witnessScript: Uint8Array | null = null) {
44
+ this.address = address;
45
+ this.witnessScript = witnessScript;
46
+ }
47
+ }
48
+
49
+ /**
50
+ * BitcoinCodec provides serialization and deserialization functions for
51
+ * various Bitcoin address types and witness scripts. All methods use
52
+ * explicit error handling suitable for AssemblyScript/WebAssembly.
53
+ */
54
+ @final
55
+ export class BitcoinCodec {
56
+ /**
57
+ * Write a CSV P2WSH address and witness script to a byte stream
58
+ *
59
+ * @param out - The output writer to write to
60
+ * @param pubkey - The public key for the CSV timelock
61
+ * @param csvBlocks - Number of blocks for the timelock
62
+ * @param hrp - Human-readable part for the address
63
+ */
64
+ public static writeCsvP2wsh(
65
+ out: BytesWriter,
66
+ pubkey: Uint8Array,
67
+ csvBlocks: i32,
68
+ hrp: string,
69
+ ): void {
70
+ const res = BitcoinAddresses.csvP2wshAddress(pubkey, csvBlocks, hrp);
71
+ out.writeStringWithLength(res.address);
72
+ out.writeBytesWithLength(res.witnessScript);
73
+ }
74
+
75
+ /**
76
+ * Read a CSV P2WSH address and verify it matches expected parameters
77
+ *
78
+ * @param inp - The input reader to read from
79
+ * @param pubkey - The expected public key
80
+ * @param csvBlocks - The expected CSV blocks
81
+ * @param hrp - The expected human-readable part
82
+ * @param strictMinimal - Whether to enforce strict minimal encoding
83
+ * @returns A result containing the verified address or an error
84
+ */
85
+ public static readAndVerifyCsvP2wsh(
86
+ inp: BytesReader,
87
+ pubkey: Uint8Array,
88
+ csvBlocks: i32,
89
+ hrp: string,
90
+ strictMinimal: bool = true,
91
+ ): CodecResult<VerifiedAddress> {
92
+ const addr = inp.readStringWithLength();
93
+
94
+ if (BitcoinAddresses.verifyCsvP2wshAddress(pubkey, csvBlocks, addr, hrp, strictMinimal)) {
95
+ const ws = BitcoinAddresses.csvWitnessScript(pubkey, csvBlocks);
96
+ return CodecResult.ok<VerifiedAddress>(new VerifiedAddress(addr, ws));
97
+ }
98
+
99
+ return CodecResult.err<VerifiedAddress>(
100
+ 'CSV P2WSH verification failed: address does not match expected parameters',
101
+ );
102
+ }
103
+
104
+ /**
105
+ * Write a multisig P2WSH address and witness script to a byte stream
106
+ *
107
+ * @param out - The output writer
108
+ * @param m - Number of required signatures
109
+ * @param pubkeys - Array of public keys
110
+ * @param hrp - Human-readable part for the address
111
+ */
112
+ public static writeMultisigP2wsh(
113
+ out: BytesWriter,
114
+ m: i32,
115
+ pubkeys: Array<Uint8Array>,
116
+ hrp: string,
117
+ ): void {
118
+ const res = BitcoinAddresses.multisigP2wshAddress(m, pubkeys, hrp);
119
+ out.writeStringWithLength(res.address);
120
+ out.writeBytesWithLength(res.witnessScript);
121
+ }
122
+
123
+ /**
124
+ * Read and verify a multisig P2WSH address
125
+ *
126
+ * @param inp - The input reader
127
+ * @param m - Expected number of required signatures
128
+ * @param pubkeys - Expected array of public keys
129
+ * @param hrp - Expected human-readable part
130
+ * @returns A result containing the verified address or an error
131
+ */
132
+ public static readAndVerifyMultisigP2wsh(
133
+ inp: BytesReader,
134
+ m: i32,
135
+ pubkeys: Array<Uint8Array>,
136
+ hrp: string,
137
+ ): CodecResult<VerifiedAddress> {
138
+ const addr = inp.readStringWithLength();
139
+
140
+ if (BitcoinAddresses.verifyMultisigP2wshAddress(m, pubkeys, addr, hrp)) {
141
+ const ws = BitcoinAddresses.multisigWitnessScript(m, pubkeys);
142
+ return CodecResult.ok<VerifiedAddress>(new VerifiedAddress(addr, ws));
143
+ }
144
+
145
+ return CodecResult.err<VerifiedAddress>(
146
+ 'Multisig P2WSH verification failed: address does not match expected parameters',
147
+ );
148
+ }
149
+
150
+ /**
151
+ * Write a Taproot address to a byte stream
152
+ *
153
+ * @param out - The output writer
154
+ * @param outputKeyX32 - The 32-byte X coordinate of the output key
155
+ * @param hrp - Human-readable part for the address
156
+ */
157
+ public static writeP2tr(out: BytesWriter, outputKeyX32: Uint8Array, hrp: string): void {
158
+ const addr = BitcoinAddresses.p2trKeyPathAddress(outputKeyX32, hrp);
159
+ out.writeStringWithLength(addr);
160
+ }
161
+
162
+ /**
163
+ * Read and verify a Taproot address
164
+ *
165
+ * @param inp - The input reader
166
+ * @param outputKeyX32 - Expected 32-byte X coordinate
167
+ * @param hrp - Expected human-readable part
168
+ * @returns A result containing the verified address or an error
169
+ */
170
+ public static readAndVerifyP2tr(
171
+ inp: BytesReader,
172
+ outputKeyX32: Uint8Array,
173
+ hrp: string,
174
+ ): CodecResult<VerifiedAddress> {
175
+ const addr = inp.readStringWithLength();
176
+
177
+ if (BitcoinAddresses.verifyP2trAddress(outputKeyX32, addr, hrp)) {
178
+ return CodecResult.ok<VerifiedAddress>(new VerifiedAddress(addr));
179
+ }
180
+
181
+ return CodecResult.err<VerifiedAddress>(
182
+ 'P2TR verification failed: address does not match expected output key',
183
+ );
184
+ }
185
+
186
+ /**
187
+ * Read a P2WSH address/witness script pair and verify they match
188
+ *
189
+ * @param inp - The input reader
190
+ * @param hrp - Expected human-readable part
191
+ * @returns A result containing verification details
192
+ */
193
+ public static readP2wshPairAndVerify(
194
+ inp: BytesReader,
195
+ hrp: string,
196
+ ): CodecResult<VerifiedAddress> {
197
+ const address = inp.readStringWithLength();
198
+ const witnessScript = inp.readBytesWithLength();
199
+
200
+ // Decode the address
201
+ const dec = Segwit.decodeOrNull(address);
202
+ if (!dec) {
203
+ return CodecResult.err<VerifiedAddress>('Failed to decode address');
204
+ }
205
+
206
+ // Verify it's a valid P2WSH address
207
+ if (dec.version != 0) {
208
+ return CodecResult.err<VerifiedAddress>('Invalid witness version: expected v0');
209
+ }
210
+
211
+ if (dec.hrp != hrp) {
212
+ return CodecResult.err<VerifiedAddress>(
213
+ `HRP mismatch: expected ${hrp}, got ${dec.hrp}`,
214
+ );
215
+ }
216
+
217
+ if (dec.program.length != 32) {
218
+ return CodecResult.err<VerifiedAddress>(
219
+ 'Invalid program length: P2WSH must be 32 bytes',
220
+ );
221
+ }
222
+
223
+ // Verify the witness script hashes to the witness program
224
+ const prog = sha256(witnessScript);
225
+ if (!Ct.eq32(dec.program, prog)) {
226
+ return CodecResult.err<VerifiedAddress>('Witness script hash mismatch');
227
+ }
228
+
229
+ return CodecResult.ok<VerifiedAddress>(new VerifiedAddress(address, witnessScript));
230
+ }
231
+
232
+ /**
233
+ * Read a CSV P2WSH pair and cross-check all components
234
+ *
235
+ * @param inp - The input reader
236
+ * @param hrp - Expected human-readable part
237
+ * @param strictMinimal - Whether to enforce strict minimal encoding
238
+ * @returns Detailed cross-check results including extracted parameters
239
+ */
240
+ public static readCsvP2wshPairAndCrossCheck(
241
+ inp: BytesReader,
242
+ hrp: string,
243
+ strictMinimal: bool = true,
244
+ ): CsvPairCrossCheck {
245
+ const address = inp.readStringWithLength();
246
+ const witnessScript = inp.readBytesWithLength();
247
+
248
+ // Decode the address
249
+ const dec = Segwit.decodeOrNull(address);
250
+ if (!dec) {
251
+ return new CsvPairCrossCheck(false, address, witnessScript, -1, null);
252
+ }
253
+
254
+ // Verify it's a valid P2WSH address
255
+ if (dec.version != 0 || dec.hrp != hrp || dec.program.length != 32) {
256
+ return new CsvPairCrossCheck(false, address, witnessScript, -1, null);
257
+ }
258
+
259
+ // Verify the witness script hashes to the witness program
260
+ const prog = sha256(witnessScript);
261
+ if (!Ct.eq32(dec.program, prog)) {
262
+ return new CsvPairCrossCheck(false, address, witnessScript, -1, null);
263
+ }
264
+
265
+ // Parse the witness script to extract CSV parameters
266
+ const rec = BitcoinScript.recognizeCsvTimelock(witnessScript, strictMinimal);
267
+ if (!rec.ok) {
268
+ return new CsvPairCrossCheck(false, address, witnessScript, -1, null);
269
+ }
270
+
271
+ // Everything checks out
272
+ return new CsvPairCrossCheck(true, address, witnessScript, rec.csvBlocks, rec.pubkey);
273
+ }
274
+
275
+ /**
276
+ * Read a multisig P2WSH pair and cross-check all components
277
+ *
278
+ * @param inp - The input reader
279
+ * @param hrp - Expected human-readable part
280
+ * @param expectedM - Optional: verify the m value matches
281
+ * @param expectedPubkeys - Optional: verify the public keys match
282
+ * @param strictMinimal - Whether to enforce strict minimal encoding
283
+ * @returns Detailed cross-check results
284
+ */
285
+ public static readMultisigP2wshPairAndCrossCheck(
286
+ inp: BytesReader,
287
+ hrp: string,
288
+ expectedM: i32 = -1,
289
+ expectedPubkeys: Array<Uint8Array> | null = null,
290
+ strictMinimal: bool = true,
291
+ ): MultisigPairCrossCheck {
292
+ const address = inp.readStringWithLength();
293
+ const witnessScript = inp.readBytesWithLength();
294
+
295
+ // Decode the address
296
+ const dec = Segwit.decodeOrNull(address);
297
+ if (!dec) {
298
+ return new MultisigPairCrossCheck(false, 0, 0, address);
299
+ }
300
+
301
+ // Verify it's a valid P2WSH address
302
+ if (dec.version != 0 || dec.hrp != hrp || dec.program.length != 32) {
303
+ return new MultisigPairCrossCheck(false, 0, 0, address);
304
+ }
305
+
306
+ // Verify the witness script hashes to the witness program
307
+ const prog = sha256(witnessScript);
308
+ if (!Ct.eq32(dec.program, prog)) {
309
+ return new MultisigPairCrossCheck(false, 0, 0, address);
310
+ }
311
+
312
+ // Parse the witness script to extract multisig parameters
313
+ const rec = BitcoinScript.recognizeMultisig(witnessScript, strictMinimal);
314
+ if (!rec.ok) {
315
+ return new MultisigPairCrossCheck(false, 0, 0, address);
316
+ }
317
+
318
+ // Check if m matches expected value (if provided)
319
+ if (expectedM >= 0 && rec.m != expectedM) {
320
+ return new MultisigPairCrossCheck(false, rec.m, rec.n, address);
321
+ }
322
+
323
+ // Check if public keys match expected values (if provided)
324
+ if (expectedPubkeys !== null) {
325
+ if (!rec.pubkeys) throw new Revert('Public keys not found in multisig script');
326
+
327
+ if (!BitcoinCodec.comparePublicKeyArrays(rec.pubkeys, expectedPubkeys)) {
328
+ return new MultisigPairCrossCheck(false, rec.m, rec.n, address);
329
+ }
330
+ }
331
+
332
+ // All checks passed
333
+ return new MultisigPairCrossCheck(true, rec.m, rec.n, address);
334
+ }
335
+
336
+ /**
337
+ * Write a generic witness script and its address
338
+ *
339
+ * @param out - The output writer
340
+ * @param witnessScript - The witness script bytes
341
+ * @param hrp - Human-readable part for the address
342
+ */
343
+ public static writeWitnessScriptAndAddress(
344
+ out: BytesWriter,
345
+ witnessScript: Uint8Array,
346
+ hrp: string,
347
+ ): void {
348
+ const address = Segwit.p2wsh(hrp, witnessScript);
349
+ out.writeStringWithLength(address);
350
+ out.writeBytesWithLength(witnessScript);
351
+ }
352
+
353
+ /**
354
+ * Validate that a witness script is within size limits
355
+ * Bitcoin consensus rules limit witness scripts to 10,000 bytes
356
+ *
357
+ * @param witnessScript - The script to validate
358
+ * @returns true if the script is within limits
359
+ */
360
+ public static isValidWitnessScriptSize(witnessScript: Uint8Array): bool {
361
+ return witnessScript.length <= 10000;
362
+ }
363
+
364
+ /**
365
+ * Compare two arrays of public keys for equality
366
+ * Uses constant-time comparison for each key
367
+ *
368
+ * @param a - First array of public keys
369
+ * @param b - Second array of public keys
370
+ * @returns true if arrays are identical
371
+ */
372
+ private static comparePublicKeyArrays(a: Array<Uint8Array>, b: Array<Uint8Array>): bool {
373
+ // Check array lengths first
374
+ if (a.length != b.length) return false;
375
+
376
+ // Compare each key
377
+ for (let i = 0; i < a.length; i++) {
378
+ const keyA = a[i];
379
+ const keyB = b[i];
380
+
381
+ // Check key lengths
382
+ if (keyA.length != keyB.length) return false;
383
+
384
+ // Constant-time byte comparison
385
+ let diff = 0;
386
+ for (let j = 0; j < keyA.length; j++) {
387
+ diff |= keyA[j] ^ keyB[j];
388
+ }
389
+
390
+ if (diff != 0) return false;
391
+ }
392
+
393
+ return true;
394
+ }
395
+ }
@@ -0,0 +1,94 @@
1
+ import { Revert } from '../types/Revert';
2
+
3
+ export enum Networks {
4
+ Unknown = -1,
5
+ Mainnet = 0,
6
+ Testnet = 1,
7
+ Regtest = 2,
8
+ }
9
+
10
+ @final
11
+ export class NetworkManager {
12
+ private readonly mainnet: Uint8Array;
13
+ private readonly testnet: Uint8Array;
14
+ private readonly regtest: Uint8Array;
15
+
16
+ constructor() {
17
+ const mainnet = new Uint8Array(32);
18
+ mainnet.set([
19
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0xd6, 0x68, 0x9c, 0x08, 0x5a, 0xe1, 0x65, 0x83,
20
+ 0x1e, 0x93, 0x4f, 0xf7, 0x63, 0xae, 0x46, 0xa2, 0xa6, 0xc1, 0x72, 0xb3, 0xf1, 0xb6,
21
+ 0x0a, 0x8c, 0xe2, 0x6f,
22
+ ]);
23
+
24
+ const testnet = new Uint8Array(32);
25
+ testnet.set([
26
+ 0x00, 0x00, 0x00, 0x00, 0x09, 0x33, 0xea, 0x01, 0xad, 0x0e, 0xe9, 0x84, 0x20, 0x97,
27
+ 0x79, 0xba, 0xae, 0xc3, 0xce, 0xd9, 0x0f, 0xa3, 0xf4, 0x08, 0x71, 0x95, 0x26, 0xf8,
28
+ 0xd7, 0x7f, 0x49, 0x43,
29
+ ]);
30
+
31
+ const regtest = new Uint8Array(32);
32
+ regtest.set([
33
+ 0x0f, 0x91, 0x88, 0xf1, 0x3c, 0xb7, 0xb2, 0xc7, 0x1f, 0x2a, 0x33, 0x5e, 0x3a, 0x4f,
34
+ 0xc3, 0x28, 0xbf, 0x5b, 0xeb, 0x43, 0x60, 0x12, 0xaf, 0xca, 0x59, 0x0b, 0x1a, 0x11,
35
+ 0x46, 0x6e, 0x22, 0x06,
36
+ ]);
37
+
38
+ this.mainnet = mainnet;
39
+ this.testnet = testnet;
40
+ this.regtest = regtest;
41
+ }
42
+
43
+ public hrp(n: Networks): string {
44
+ switch (n) {
45
+ case Networks.Mainnet:
46
+ return 'bc';
47
+ case Networks.Testnet:
48
+ return 'tb';
49
+ case Networks.Regtest:
50
+ return 'bcrt';
51
+ default:
52
+ throw new Revert('Unknown network');
53
+ }
54
+ }
55
+
56
+ public getChainId(network: Networks): Uint8Array {
57
+ const out = new Uint8Array(32);
58
+ switch (network) {
59
+ case Networks.Mainnet:
60
+ out.set(this.mainnet);
61
+ return out;
62
+ case Networks.Testnet:
63
+ out.set(this.testnet);
64
+ return out;
65
+ case Networks.Regtest:
66
+ out.set(this.regtest);
67
+ return out;
68
+ default:
69
+ throw new Revert('Unknown network');
70
+ }
71
+ }
72
+
73
+ public fromChainId(chainId: Uint8Array): Networks {
74
+ if (chainId.length !== 32) {
75
+ throw new Revert('Invalid chain id length');
76
+ }
77
+
78
+ if (this.equals(chainId, this.mainnet)) return Networks.Mainnet;
79
+ if (this.equals(chainId, this.testnet)) return Networks.Testnet;
80
+ if (this.equals(chainId, this.regtest)) return Networks.Regtest;
81
+
82
+ throw new Revert('Unknown chain id');
83
+ }
84
+
85
+ private equals(a: Uint8Array, b: Uint8Array): boolean {
86
+ if (a.length !== b.length) return false;
87
+ for (let i = 0; i < a.length; i++) {
88
+ if (a[i] !== b[i]) return false;
89
+ }
90
+ return true;
91
+ }
92
+ }
93
+
94
+ export const Network: NetworkManager = new NetworkManager();