@btc-vision/bitcoin 7.0.0-beta.0 → 7.0.0-beta.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 (116) hide show
  1. package/README.md +112 -13
  2. package/benchmark-compare/BENCHMARK.md +74 -59
  3. package/benchmark-compare/compare.bench.ts +249 -96
  4. package/benchmark-compare/harness.ts +23 -25
  5. package/benchmark-compare/package.json +1 -0
  6. package/browser/address.d.ts +4 -4
  7. package/browser/address.d.ts.map +1 -1
  8. package/browser/chunks/{psbt-parallel-B-dfm5GZ.js → psbt-parallel-jZ6QcCnM.js} +3128 -2731
  9. package/browser/index.d.ts +1 -1
  10. package/browser/index.d.ts.map +1 -1
  11. package/browser/index.js +603 -585
  12. package/browser/io/base58check.d.ts +1 -25
  13. package/browser/io/base58check.d.ts.map +1 -1
  14. package/browser/io/base64.d.ts.map +1 -1
  15. package/browser/networks.d.ts +1 -0
  16. package/browser/networks.d.ts.map +1 -1
  17. package/browser/payments/bip341.d.ts +17 -0
  18. package/browser/payments/bip341.d.ts.map +1 -1
  19. package/browser/payments/index.d.ts +3 -2
  20. package/browser/payments/index.d.ts.map +1 -1
  21. package/browser/payments/p2mr.d.ts +169 -0
  22. package/browser/payments/p2mr.d.ts.map +1 -0
  23. package/browser/payments/types.d.ts +11 -1
  24. package/browser/payments/types.d.ts.map +1 -1
  25. package/browser/psbt/bip371.d.ts +30 -0
  26. package/browser/psbt/bip371.d.ts.map +1 -1
  27. package/browser/psbt/psbtutils.d.ts +1 -0
  28. package/browser/psbt/psbtutils.d.ts.map +1 -1
  29. package/browser/psbt.d.ts.map +1 -1
  30. package/browser/workers/index.js +9 -9
  31. package/build/address.d.ts +4 -4
  32. package/build/address.d.ts.map +1 -1
  33. package/build/address.js +11 -1
  34. package/build/address.js.map +1 -1
  35. package/build/index.d.ts +1 -1
  36. package/build/index.d.ts.map +1 -1
  37. package/build/index.js.map +1 -1
  38. package/build/io/base58check.d.ts +1 -25
  39. package/build/io/base58check.d.ts.map +1 -1
  40. package/build/io/base58check.js +1 -31
  41. package/build/io/base58check.js.map +1 -1
  42. package/build/io/base64.d.ts.map +1 -1
  43. package/build/io/base64.js +3 -0
  44. package/build/io/base64.js.map +1 -1
  45. package/build/networks.d.ts +1 -0
  46. package/build/networks.d.ts.map +1 -1
  47. package/build/networks.js +12 -0
  48. package/build/networks.js.map +1 -1
  49. package/build/payments/bip341.d.ts +17 -0
  50. package/build/payments/bip341.d.ts.map +1 -1
  51. package/build/payments/bip341.js +32 -1
  52. package/build/payments/bip341.js.map +1 -1
  53. package/build/payments/index.d.ts +3 -2
  54. package/build/payments/index.d.ts.map +1 -1
  55. package/build/payments/index.js +2 -1
  56. package/build/payments/index.js.map +1 -1
  57. package/build/payments/p2mr.d.ts +178 -0
  58. package/build/payments/p2mr.d.ts.map +1 -0
  59. package/build/payments/p2mr.js +555 -0
  60. package/build/payments/p2mr.js.map +1 -0
  61. package/build/payments/types.d.ts +11 -1
  62. package/build/payments/types.d.ts.map +1 -1
  63. package/build/payments/types.js +1 -0
  64. package/build/payments/types.js.map +1 -1
  65. package/build/psbt/bip371.d.ts +30 -0
  66. package/build/psbt/bip371.d.ts.map +1 -1
  67. package/build/psbt/bip371.js +80 -15
  68. package/build/psbt/bip371.js.map +1 -1
  69. package/build/psbt/psbtutils.d.ts +1 -0
  70. package/build/psbt/psbtutils.d.ts.map +1 -1
  71. package/build/psbt/psbtutils.js +2 -0
  72. package/build/psbt/psbtutils.js.map +1 -1
  73. package/build/psbt.d.ts.map +1 -1
  74. package/build/psbt.js +3 -2
  75. package/build/psbt.js.map +1 -1
  76. package/build/pubkey.js +1 -1
  77. package/build/pubkey.js.map +1 -1
  78. package/build/tsconfig.build.tsbuildinfo +1 -1
  79. package/documentation/README.md +122 -0
  80. package/documentation/address.md +820 -0
  81. package/documentation/block.md +679 -0
  82. package/documentation/crypto.md +461 -0
  83. package/documentation/ecc.md +584 -0
  84. package/documentation/errors.md +656 -0
  85. package/documentation/io.md +942 -0
  86. package/documentation/networks.md +625 -0
  87. package/documentation/p2mr.md +380 -0
  88. package/documentation/payments.md +1485 -0
  89. package/documentation/psbt.md +1400 -0
  90. package/documentation/script.md +730 -0
  91. package/documentation/taproot.md +670 -0
  92. package/documentation/transaction.md +943 -0
  93. package/documentation/types.md +587 -0
  94. package/documentation/workers.md +1007 -0
  95. package/eslint.config.js +3 -0
  96. package/package.json +17 -14
  97. package/src/address.ts +22 -10
  98. package/src/index.ts +1 -0
  99. package/src/io/base58check.ts +1 -35
  100. package/src/io/base64.ts +5 -0
  101. package/src/networks.ts +13 -0
  102. package/src/payments/bip341.ts +36 -1
  103. package/src/payments/index.ts +4 -0
  104. package/src/payments/p2mr.ts +660 -0
  105. package/src/payments/types.ts +12 -0
  106. package/src/psbt/bip371.ts +84 -13
  107. package/src/psbt/psbtutils.ts +2 -0
  108. package/src/psbt.ts +4 -2
  109. package/src/pubkey.ts +1 -1
  110. package/test/bitcoin.core.spec.ts +1 -1
  111. package/test/fixtures/p2mr.json +270 -0
  112. package/test/integration/taproot.spec.ts +7 -3
  113. package/test/opnetTestnet.spec.ts +302 -0
  114. package/test/payments.spec.ts +3 -1
  115. package/test/psbt.spec.ts +297 -2
  116. package/test/tsconfig.json +2 -2
@@ -0,0 +1,660 @@
1
+ /**
2
+ * Pay-to-Merkle-Root (P2MR) payment class.
3
+ *
4
+ * P2MR is a SegWit version 2 output type (BIP 360). It commits directly to
5
+ * the Merkle root of a script tree, removing the quantum-vulnerable key-path
6
+ * spend found in P2TR. There is no internal pubkey or tweaking.
7
+ *
8
+ * @packageDocumentation
9
+ */
10
+
11
+ import { bech32m } from 'bech32';
12
+ import { fromBech32 } from '../bech32utils.js';
13
+ import { bitcoin as BITCOIN_NETWORK, type Network } from '../networks.js';
14
+ import * as bscript from '../script.js';
15
+ import {
16
+ type Bytes32,
17
+ type Script,
18
+ stacksEqual,
19
+ TAPLEAF_VERSION_MASK,
20
+ type Taptree,
21
+ } from '../types.js';
22
+ import {
23
+ findScriptPath,
24
+ type HashTree,
25
+ LEAF_VERSION_TAPSCRIPT,
26
+ rootHashFromPathP2MR,
27
+ tapleafHash,
28
+ toHashTree,
29
+ } from './bip341.js';
30
+ import { concat, equals } from '../io/index.js';
31
+ import { type P2MRPayment, type PaymentOpts, PaymentType, type ScriptRedeem } from './types.js';
32
+
33
+ const OPS = bscript.opcodes;
34
+ const P2MR_WITNESS_VERSION = 0x02;
35
+ const ANNEX_PREFIX = 0x50;
36
+
37
+ /**
38
+ * Pay-to-Merkle-Root (P2MR) payment class.
39
+ *
40
+ * Creates locking scripts of the form: `OP_2 {32-byte merkle root}`
41
+ *
42
+ * Script-path spending witness: `[script inputs..., script, control block]`
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * import { P2MR } from '@btc-vision/bitcoin';
47
+ *
48
+ * // From script tree
49
+ * const p2mr = P2MR.fromScriptTree(scriptTree);
50
+ * console.log(p2mr.address); // bc1z... address
51
+ *
52
+ * // From merkle root hash
53
+ * const fromHash = P2MR.fromHash(merkleRoot);
54
+ *
55
+ * // Decode an existing output
56
+ * const decoded = P2MR.fromOutput(scriptPubKey);
57
+ * console.log(decoded.hash); // 32-byte merkle root
58
+ * ```
59
+ */
60
+ export class P2MR {
61
+ static readonly NAME = PaymentType.P2MR;
62
+
63
+ readonly #network: Network;
64
+ readonly #opts: Required<PaymentOpts>;
65
+
66
+ #inputAddress?: string | undefined;
67
+ #inputHash?: Uint8Array | undefined;
68
+ #inputScriptTree?: Taptree | undefined;
69
+ #inputOutput?: Uint8Array | undefined;
70
+ #inputWitness?: Uint8Array[] | undefined;
71
+ #inputRedeem?: ScriptRedeem | undefined;
72
+ #inputRedeemVersion?: number | undefined;
73
+
74
+ #address?: string | undefined;
75
+ #hash?: Bytes32 | undefined;
76
+ #output?: Script | undefined;
77
+ #redeem?: ScriptRedeem | undefined;
78
+ #redeemVersion?: number | undefined;
79
+ #witness?: Uint8Array[] | undefined;
80
+
81
+ #addressComputed = false;
82
+ #hashComputed = false;
83
+ #outputComputed = false;
84
+ #redeemComputed = false;
85
+ #redeemVersionComputed = false;
86
+ #witnessComputed = false;
87
+
88
+ #decodedAddress?: { version: number; prefix: string; data: Uint8Array } | undefined;
89
+ #decodedAddressComputed = false;
90
+
91
+ #witnessWithoutAnnex?: Uint8Array[] | undefined;
92
+ #witnessWithoutAnnexComputed = false;
93
+
94
+ #hashTree?: HashTree | undefined;
95
+ #hashTreeComputed = false;
96
+
97
+ /**
98
+ * Creates a new P2MR payment instance.
99
+ *
100
+ * @param params - Payment parameters
101
+ * @param params.address - Bech32m encoded address (bc1z...)
102
+ * @param params.hash - Merkle root (32 bytes, = witness program)
103
+ * @param params.scriptTree - Full script tree definition
104
+ * @param params.output - The scriptPubKey
105
+ * @param params.witness - The witness stack
106
+ * @param params.redeem - Redeem script for script-path spending
107
+ * @param params.redeemVersion - Leaf version (defaults to LEAF_VERSION_TAPSCRIPT)
108
+ * @param params.network - Network parameters (defaults to mainnet)
109
+ * @param opts - Payment options
110
+ * @param opts.validate - Whether to validate inputs (default: true)
111
+ *
112
+ * @throws {TypeError} If validation is enabled and data is invalid
113
+ */
114
+ constructor(
115
+ params: {
116
+ address?: string | undefined;
117
+ hash?: Uint8Array | undefined;
118
+ scriptTree?: Taptree | undefined;
119
+ output?: Uint8Array | undefined;
120
+ witness?: Uint8Array[] | undefined;
121
+ redeem?: ScriptRedeem | undefined;
122
+ redeemVersion?: number | undefined;
123
+ network?: Network | undefined;
124
+ },
125
+ opts?: PaymentOpts,
126
+ ) {
127
+ this.#network = params.network ?? BITCOIN_NETWORK;
128
+ this.#opts = {
129
+ validate: opts?.validate ?? true,
130
+ allowIncomplete: opts?.allowIncomplete ?? false,
131
+ };
132
+
133
+ // Store input data
134
+ this.#inputAddress = params.address;
135
+ this.#inputHash = params.hash;
136
+ this.#inputScriptTree = params.scriptTree;
137
+ this.#inputOutput = params.output;
138
+ this.#inputWitness = params.witness;
139
+ this.#inputRedeem = params.redeem;
140
+ this.#inputRedeemVersion = params.redeemVersion;
141
+
142
+ // Validate if requested
143
+ if (this.#opts.validate) {
144
+ this.#validate();
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Payment type discriminant.
150
+ *
151
+ * @returns The P2MR payment type constant.
152
+ */
153
+ get name(): typeof PaymentType.P2MR {
154
+ return PaymentType.P2MR;
155
+ }
156
+
157
+ /**
158
+ * Network parameters used for address encoding.
159
+ *
160
+ * @returns The network configuration (mainnet, testnet, or regtest).
161
+ */
162
+ get network(): Network {
163
+ return this.#network;
164
+ }
165
+
166
+ /**
167
+ * Bech32m encoded address (bc1z... for mainnet).
168
+ *
169
+ * @returns The bech32m-encoded address, or `undefined` if insufficient data.
170
+ */
171
+ get address(): string | undefined {
172
+ if (!this.#addressComputed) {
173
+ this.#address = this.#computeAddress();
174
+ this.#addressComputed = true;
175
+ }
176
+ return this.#address;
177
+ }
178
+
179
+ /**
180
+ * Merkle root hash (32 bytes). This is the witness program and directly
181
+ * appears in the output script.
182
+ *
183
+ * @returns The 32-byte merkle root, or `undefined` if insufficient data.
184
+ */
185
+ get hash(): Bytes32 | undefined {
186
+ if (!this.#hashComputed) {
187
+ this.#hash = this.#computeHash();
188
+ this.#hashComputed = true;
189
+ }
190
+ return this.#hash;
191
+ }
192
+
193
+ /**
194
+ * The scriptPubKey: `OP_2 <32-byte merkle root>` (34 bytes total).
195
+ *
196
+ * @returns The output script, or `undefined` if insufficient data.
197
+ */
198
+ get output(): Script | undefined {
199
+ if (!this.#outputComputed) {
200
+ this.#output = this.#computeOutput();
201
+ this.#outputComputed = true;
202
+ }
203
+ return this.#output;
204
+ }
205
+
206
+ /**
207
+ * Redeem script information for script-path spending.
208
+ *
209
+ * @returns The redeem script data, or `undefined` if not available.
210
+ */
211
+ get redeem(): ScriptRedeem | undefined {
212
+ if (!this.#redeemComputed) {
213
+ this.#redeem = this.#computeRedeem();
214
+ this.#redeemComputed = true;
215
+ }
216
+ return this.#redeem;
217
+ }
218
+
219
+ /**
220
+ * Leaf version used for script-path hashing.
221
+ *
222
+ * @returns The leaf version byte (defaults to LEAF_VERSION_TAPSCRIPT = 0xc0).
223
+ */
224
+ get redeemVersion(): number {
225
+ if (!this.#redeemVersionComputed) {
226
+ this.#redeemVersion = this.#computeRedeemVersion();
227
+ this.#redeemVersionComputed = true;
228
+ }
229
+ return this.#redeemVersion ?? LEAF_VERSION_TAPSCRIPT;
230
+ }
231
+
232
+ /**
233
+ * Witness stack for script-path spending.
234
+ * Format: `[script inputs..., script, control block]`
235
+ *
236
+ * @returns The witness stack array, or `undefined` if insufficient data.
237
+ */
238
+ get witness(): Uint8Array[] | undefined {
239
+ if (!this.#witnessComputed) {
240
+ this.#witness = this.#computeWitness();
241
+ this.#witnessComputed = true;
242
+ }
243
+ return this.#witness;
244
+ }
245
+
246
+ /**
247
+ * Creates a P2MR payment from a script tree.
248
+ *
249
+ * @param scriptTree - The script tree
250
+ * @param network - Network parameters (defaults to mainnet)
251
+ * @returns A new P2MR payment instance
252
+ */
253
+ static fromScriptTree(scriptTree: Taptree, network?: Network): P2MR {
254
+ return new P2MR({ scriptTree, network });
255
+ }
256
+
257
+ /**
258
+ * Creates a P2MR payment from a bech32m address.
259
+ *
260
+ * @param address - Bech32m encoded address (bc1z...)
261
+ * @param network - Network parameters (defaults to mainnet)
262
+ * @returns A new P2MR payment instance
263
+ */
264
+ static fromAddress(address: string, network?: Network): P2MR {
265
+ return new P2MR({ address, network });
266
+ }
267
+
268
+ /**
269
+ * Creates a P2MR payment from a scriptPubKey.
270
+ *
271
+ * @param output - The scriptPubKey
272
+ * @param network - Network parameters (defaults to mainnet)
273
+ * @returns A new P2MR payment instance
274
+ */
275
+ static fromOutput(output: Uint8Array, network?: Network): P2MR {
276
+ return new P2MR({ output, network });
277
+ }
278
+
279
+ /**
280
+ * Creates a P2MR payment from a merkle root hash.
281
+ *
282
+ * @param hash - The 32-byte merkle root
283
+ * @param network - Network parameters (defaults to mainnet)
284
+ * @returns A new P2MR payment instance
285
+ */
286
+ static fromHash(hash: Bytes32, network?: Network): P2MR {
287
+ return new P2MR({ hash, network });
288
+ }
289
+
290
+ /**
291
+ * Converts to a plain P2MRPayment object for backwards compatibility.
292
+ *
293
+ * @returns A P2MRPayment object
294
+ */
295
+ toPayment(): P2MRPayment {
296
+ return {
297
+ name: this.name,
298
+ network: this.network,
299
+ address: this.address,
300
+ hash: this.hash,
301
+ scriptTree: this.#inputScriptTree,
302
+ output: this.output,
303
+ redeem: this.redeem,
304
+ redeemVersion: this.redeemVersion,
305
+ witness: this.witness,
306
+ };
307
+ }
308
+
309
+ #getDecodedAddress(): { version: number; prefix: string; data: Uint8Array } | undefined {
310
+ if (!this.#decodedAddressComputed) {
311
+ if (this.#inputAddress) {
312
+ const decoded = fromBech32(this.#inputAddress);
313
+ if (decoded) {
314
+ this.#decodedAddress = {
315
+ version: decoded.version,
316
+ prefix: decoded.prefix,
317
+ data: decoded.data,
318
+ };
319
+ }
320
+ }
321
+ this.#decodedAddressComputed = true;
322
+ }
323
+ return this.#decodedAddress;
324
+ }
325
+
326
+ #getWitnessWithoutAnnex(): Uint8Array[] | undefined {
327
+ if (!this.#witnessWithoutAnnexComputed) {
328
+ if (this.#inputWitness && this.#inputWitness.length > 0) {
329
+ // Remove annex if present
330
+ const lastWitness = this.#inputWitness[this.#inputWitness.length - 1];
331
+ if (
332
+ this.#inputWitness.length >= 2 &&
333
+ lastWitness && lastWitness[0] === ANNEX_PREFIX
334
+ ) {
335
+ this.#witnessWithoutAnnex = this.#inputWitness.slice(0, -1);
336
+ } else {
337
+ this.#witnessWithoutAnnex = this.#inputWitness.slice();
338
+ }
339
+ }
340
+ this.#witnessWithoutAnnexComputed = true;
341
+ }
342
+ return this.#witnessWithoutAnnex;
343
+ }
344
+
345
+ #getHashTree(): HashTree | undefined {
346
+ if (!this.#hashTreeComputed) {
347
+ if (this.#inputScriptTree) {
348
+ this.#hashTree = toHashTree(this.#inputScriptTree);
349
+ } else if (this.#inputHash) {
350
+ this.#hashTree = { hash: this.#inputHash as Bytes32 };
351
+ }
352
+ this.#hashTreeComputed = true;
353
+ }
354
+ return this.#hashTree;
355
+ }
356
+
357
+ #computeAddress(): string | undefined {
358
+ if (this.#inputAddress) {
359
+ return this.#inputAddress;
360
+ }
361
+ const h = this.hash;
362
+ if (!h) return undefined;
363
+
364
+ const words = bech32m.toWords(h);
365
+ words.unshift(P2MR_WITNESS_VERSION);
366
+ return bech32m.encode(this.#network.bech32, words);
367
+ }
368
+
369
+ #computeHash(): Bytes32 | undefined {
370
+ if (this.#inputHash) {
371
+ return this.#inputHash as Bytes32;
372
+ }
373
+
374
+ const hashTree = this.#getHashTree();
375
+ if (hashTree) {
376
+ return hashTree.hash;
377
+ }
378
+
379
+ if (this.#inputOutput) {
380
+ return this.#inputOutput.subarray(2) as Bytes32;
381
+ }
382
+
383
+ if (this.#inputAddress) {
384
+ return this.#getDecodedAddress()?.data as Bytes32 | undefined;
385
+ }
386
+
387
+ const w = this.#getWitnessWithoutAnnex();
388
+ if (w && w.length > 1) {
389
+ const controlBlock = w[w.length - 1] as Uint8Array;
390
+ const leafVersion = (controlBlock[0] as number) & TAPLEAF_VERSION_MASK;
391
+ const script = w[w.length - 2] as Uint8Array;
392
+ const leafHash = tapleafHash({
393
+ output: script,
394
+ version: leafVersion,
395
+ });
396
+ return rootHashFromPathP2MR(controlBlock, leafHash);
397
+ }
398
+
399
+ return undefined;
400
+ }
401
+
402
+ #computeOutput(): Script | undefined {
403
+ if (this.#inputOutput) {
404
+ return this.#inputOutput as Script;
405
+ }
406
+ const h = this.hash;
407
+ if (!h) return undefined;
408
+
409
+ return bscript.compile([OPS.OP_2, h]);
410
+ }
411
+
412
+ #computeRedeem(): ScriptRedeem | undefined {
413
+ if (this.#inputRedeem) {
414
+ return this.#inputRedeem;
415
+ }
416
+ const witness = this.#getWitnessWithoutAnnex();
417
+ if (!witness || witness.length < 2) {
418
+ return undefined;
419
+ }
420
+ const lastWitness = witness[witness.length - 1] as Uint8Array;
421
+ return {
422
+ output: witness[witness.length - 2] as Script,
423
+ witness: witness.slice(0, -2),
424
+ redeemVersion: (lastWitness[0] as number) & TAPLEAF_VERSION_MASK,
425
+ };
426
+ }
427
+
428
+ #computeRedeemVersion(): number | undefined {
429
+ if (this.#inputRedeemVersion !== undefined) {
430
+ return this.#inputRedeemVersion;
431
+ }
432
+ if (
433
+ this.#inputRedeem &&
434
+ this.#inputRedeem.redeemVersion !== undefined &&
435
+ this.#inputRedeem.redeemVersion !== null
436
+ ) {
437
+ return this.#inputRedeem.redeemVersion;
438
+ }
439
+ return LEAF_VERSION_TAPSCRIPT;
440
+ }
441
+
442
+ #computeWitness(): Uint8Array[] | undefined {
443
+ if (this.#inputWitness) {
444
+ return this.#inputWitness;
445
+ }
446
+
447
+ const hashTree = this.#getHashTree();
448
+ if (hashTree && this.#inputRedeem?.output) {
449
+ const leafHash = tapleafHash({
450
+ output: this.#inputRedeem.output,
451
+ version: this.redeemVersion,
452
+ });
453
+ const path = findScriptPath(hashTree, leafHash);
454
+ if (!path) return undefined;
455
+
456
+ const version = this.redeemVersion ?? 0xc0;
457
+ // P2MR control block: [version | 0x01, ...merklePath]
458
+ // Parity bit is always 1, no internal pubkey
459
+ const controlBlock = concat([
460
+ new Uint8Array([version | 0x01]),
461
+ ...path,
462
+ ]);
463
+ return [this.#inputRedeem.output, controlBlock];
464
+ }
465
+
466
+ return undefined;
467
+ }
468
+
469
+ #validate(): void {
470
+ let knownHash: Uint8Array = new Uint8Array(0);
471
+
472
+ if (this.#inputAddress) {
473
+ const addr = this.#getDecodedAddress();
474
+ if (!addr) {
475
+ throw new TypeError('Invalid address');
476
+ }
477
+ if (this.#network && this.#network.bech32 !== addr.prefix) {
478
+ throw new TypeError('Invalid prefix or Network mismatch');
479
+ }
480
+ if (addr.version !== P2MR_WITNESS_VERSION) {
481
+ throw new TypeError('Invalid address version');
482
+ }
483
+ if (addr.data.length !== 32) {
484
+ throw new TypeError('Invalid address data');
485
+ }
486
+ knownHash = addr.data;
487
+ }
488
+
489
+ if (this.#inputOutput) {
490
+ if (
491
+ this.#inputOutput.length !== 34 ||
492
+ this.#inputOutput[0] !== OPS.OP_2 ||
493
+ this.#inputOutput[1] !== 0x20
494
+ ) {
495
+ throw new TypeError('Output is invalid');
496
+ }
497
+ if (knownHash.length > 0 && !equals(knownHash, this.#inputOutput.subarray(2))) {
498
+ throw new TypeError('Hash mismatch');
499
+ } else {
500
+ knownHash = this.#inputOutput.subarray(2);
501
+ }
502
+ }
503
+
504
+ if (this.#inputHash) {
505
+ if (this.#inputHash.length !== 32) {
506
+ throw new TypeError('Invalid hash length');
507
+ }
508
+ if (knownHash.length > 0 && !equals(knownHash, this.#inputHash)) {
509
+ throw new TypeError('Hash mismatch');
510
+ }
511
+ }
512
+
513
+ const hashTree = this.#getHashTree();
514
+
515
+ if (this.#inputHash && hashTree) {
516
+ if (!equals(this.#inputHash, hashTree.hash)) {
517
+ throw new TypeError('Hash mismatch');
518
+ }
519
+ }
520
+
521
+ if (this.#inputRedeem?.output && hashTree) {
522
+ const leafHash = tapleafHash({
523
+ output: this.#inputRedeem.output,
524
+ version: this.redeemVersion,
525
+ });
526
+ if (!findScriptPath(hashTree, leafHash)) {
527
+ throw new TypeError('Redeem script not in tree');
528
+ }
529
+ }
530
+
531
+ const witness = this.#getWitnessWithoutAnnex();
532
+
533
+ // Compare provided redeem with computed from witness
534
+ if (this.#inputRedeem && this.redeem) {
535
+ if (this.#inputRedeem.redeemVersion) {
536
+ if (this.#inputRedeem.redeemVersion !== this.redeem.redeemVersion) {
537
+ throw new TypeError('Redeem.redeemVersion and witness mismatch');
538
+ }
539
+ }
540
+
541
+ if (this.#inputRedeem.output) {
542
+ const decompiled = bscript.decompile(this.#inputRedeem.output);
543
+ if (!decompiled || decompiled.length === 0) {
544
+ throw new TypeError('Redeem.output is invalid');
545
+ }
546
+
547
+ if (this.redeem.output && !equals(this.#inputRedeem.output, this.redeem.output)) {
548
+ throw new TypeError('Redeem.output and witness mismatch');
549
+ }
550
+ }
551
+ if (this.#inputRedeem.witness) {
552
+ if (
553
+ this.redeem.witness &&
554
+ !stacksEqual(this.#inputRedeem.witness, this.redeem.witness)
555
+ ) {
556
+ throw new TypeError('Redeem.witness and witness mismatch');
557
+ }
558
+ }
559
+ }
560
+
561
+ if (witness && witness.length > 0) {
562
+ // P2MR only supports script-path spending (no key-path)
563
+ if (witness.length < 2) {
564
+ throw new TypeError(
565
+ 'P2MR requires at least 2 witness items (script + control block)',
566
+ );
567
+ }
568
+
569
+ const controlBlock = witness[witness.length - 1] as Uint8Array;
570
+ if (controlBlock.length < 1) {
571
+ throw new TypeError(
572
+ `The control-block length is too small. Got ${controlBlock.length}, expected min 1.`,
573
+ );
574
+ }
575
+
576
+ if ((controlBlock.length - 1) % 32 !== 0) {
577
+ throw new TypeError(
578
+ `The control-block length of ${controlBlock.length} is incorrect!`,
579
+ );
580
+ }
581
+
582
+ const m = (controlBlock.length - 1) / 32;
583
+ if (m > 128) {
584
+ throw new TypeError(`The script path is too long. Got ${m}, expected max 128.`);
585
+ }
586
+
587
+ const controlBlockFirstByte = controlBlock[0] as number;
588
+
589
+ // P2MR parity bit must be 1
590
+ if ((controlBlockFirstByte & 1) !== 1) {
591
+ throw new TypeError('P2MR control byte parity bit must be 1');
592
+ }
593
+
594
+ const leafVersion = controlBlockFirstByte & TAPLEAF_VERSION_MASK;
595
+ const script = witness[witness.length - 2] as Uint8Array;
596
+
597
+ const leafHash = tapleafHash({
598
+ output: script,
599
+ version: leafVersion,
600
+ });
601
+ const computedHash = rootHashFromPathP2MR(controlBlock, leafHash);
602
+
603
+ // Verify merkle root matches the witness program
604
+ if (knownHash.length > 0 && !equals(knownHash, computedHash)) {
605
+ throw new TypeError('Merkle root mismatch for p2mr witness');
606
+ }
607
+ }
608
+ }
609
+ }
610
+
611
+ /**
612
+ * Creates a Pay-to-Merkle-Root (P2MR) payment object.
613
+ *
614
+ * This is the legacy factory function for backwards compatibility.
615
+ * For new code, prefer using the P2MR class directly.
616
+ *
617
+ * @param a - The payment object containing the necessary data
618
+ * @param opts - Optional payment options
619
+ * @returns The P2MR payment object
620
+ * @throws {TypeError} If the required data is not provided or if the data is invalid
621
+ *
622
+ * @example
623
+ * ```typescript
624
+ * import { p2mr } from '@btc-vision/bitcoin';
625
+ *
626
+ * // From script tree
627
+ * const payment = p2mr({ scriptTree });
628
+ *
629
+ * // From merkle root hash
630
+ * const fromHash = p2mr({ hash: merkleRoot });
631
+ * ```
632
+ */
633
+ export function p2mr(a: Omit<P2MRPayment, 'name'>, opts?: PaymentOpts): P2MRPayment {
634
+ if (
635
+ !a.address &&
636
+ !a.output &&
637
+ !a.hash &&
638
+ !a.scriptTree &&
639
+ !(a.witness && a.witness.length > 1)
640
+ ) {
641
+ throw new TypeError('Not enough data');
642
+ }
643
+
644
+ const instance = new P2MR(
645
+ {
646
+ address: a.address,
647
+ hash: a.hash,
648
+ scriptTree: a.scriptTree,
649
+ output: a.output,
650
+ witness: a.witness,
651
+ redeem: a.redeem,
652
+ redeemVersion: a.redeemVersion,
653
+ network: a.network,
654
+ },
655
+ opts,
656
+ );
657
+
658
+ // Return a merged object for backwards compatibility
659
+ return Object.assign(instance.toPayment(), a);
660
+ }
@@ -23,6 +23,7 @@ export const PaymentType = {
23
23
  P2WPKH: 'p2wpkh',
24
24
  P2WSH: 'p2wsh',
25
25
  P2TR: 'p2tr',
26
+ P2MR: 'p2mr',
26
27
  P2OP: 'p2op',
27
28
  Embed: 'embed',
28
29
  ScriptRedeem: 'scriptRedeem',
@@ -127,6 +128,16 @@ export interface P2TRPayment extends BasePayment {
127
128
  readonly redeem?: ScriptRedeem | undefined;
128
129
  }
129
130
 
131
+ export interface P2MRPayment extends BasePayment {
132
+ readonly name: typeof PaymentType.P2MR;
133
+ /** Merkle root of the script tree (= witness program). */
134
+ readonly hash?: Bytes32 | undefined;
135
+ /** Full taptree description (optional, dev-side). */
136
+ readonly scriptTree?: Taptree | undefined;
137
+ readonly redeemVersion?: number | undefined;
138
+ readonly redeem?: ScriptRedeem | undefined;
139
+ }
140
+
130
141
  export interface P2OPPayment extends BasePayment {
131
142
  readonly name: typeof PaymentType.P2OP;
132
143
  /** <deploymentVersion || HASH160(payload)> (2–40 bytes). */
@@ -156,6 +167,7 @@ export type Payment =
156
167
  | P2WPKHPayment
157
168
  | P2WSHPayment
158
169
  | P2TRPayment
170
+ | P2MRPayment
159
171
  | P2OPPayment
160
172
  | EmbedPayment
161
173
  | ScriptRedeem;