@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.
- package/README.md +112 -13
- package/benchmark-compare/BENCHMARK.md +74 -59
- package/benchmark-compare/compare.bench.ts +249 -96
- package/benchmark-compare/harness.ts +23 -25
- package/benchmark-compare/package.json +1 -0
- package/browser/address.d.ts +4 -4
- package/browser/address.d.ts.map +1 -1
- package/browser/chunks/{psbt-parallel-B-dfm5GZ.js → psbt-parallel-jZ6QcCnM.js} +3128 -2731
- package/browser/index.d.ts +1 -1
- package/browser/index.d.ts.map +1 -1
- package/browser/index.js +603 -585
- package/browser/io/base58check.d.ts +1 -25
- package/browser/io/base58check.d.ts.map +1 -1
- package/browser/io/base64.d.ts.map +1 -1
- package/browser/networks.d.ts +1 -0
- package/browser/networks.d.ts.map +1 -1
- package/browser/payments/bip341.d.ts +17 -0
- package/browser/payments/bip341.d.ts.map +1 -1
- package/browser/payments/index.d.ts +3 -2
- package/browser/payments/index.d.ts.map +1 -1
- package/browser/payments/p2mr.d.ts +169 -0
- package/browser/payments/p2mr.d.ts.map +1 -0
- package/browser/payments/types.d.ts +11 -1
- package/browser/payments/types.d.ts.map +1 -1
- package/browser/psbt/bip371.d.ts +30 -0
- package/browser/psbt/bip371.d.ts.map +1 -1
- package/browser/psbt/psbtutils.d.ts +1 -0
- package/browser/psbt/psbtutils.d.ts.map +1 -1
- package/browser/psbt.d.ts.map +1 -1
- package/browser/workers/index.js +9 -9
- package/build/address.d.ts +4 -4
- package/build/address.d.ts.map +1 -1
- package/build/address.js +11 -1
- package/build/address.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js.map +1 -1
- package/build/io/base58check.d.ts +1 -25
- package/build/io/base58check.d.ts.map +1 -1
- package/build/io/base58check.js +1 -31
- package/build/io/base58check.js.map +1 -1
- package/build/io/base64.d.ts.map +1 -1
- package/build/io/base64.js +3 -0
- package/build/io/base64.js.map +1 -1
- package/build/networks.d.ts +1 -0
- package/build/networks.d.ts.map +1 -1
- package/build/networks.js +12 -0
- package/build/networks.js.map +1 -1
- package/build/payments/bip341.d.ts +17 -0
- package/build/payments/bip341.d.ts.map +1 -1
- package/build/payments/bip341.js +32 -1
- package/build/payments/bip341.js.map +1 -1
- package/build/payments/index.d.ts +3 -2
- package/build/payments/index.d.ts.map +1 -1
- package/build/payments/index.js +2 -1
- package/build/payments/index.js.map +1 -1
- package/build/payments/p2mr.d.ts +178 -0
- package/build/payments/p2mr.d.ts.map +1 -0
- package/build/payments/p2mr.js +555 -0
- package/build/payments/p2mr.js.map +1 -0
- package/build/payments/types.d.ts +11 -1
- package/build/payments/types.d.ts.map +1 -1
- package/build/payments/types.js +1 -0
- package/build/payments/types.js.map +1 -1
- package/build/psbt/bip371.d.ts +30 -0
- package/build/psbt/bip371.d.ts.map +1 -1
- package/build/psbt/bip371.js +80 -15
- package/build/psbt/bip371.js.map +1 -1
- package/build/psbt/psbtutils.d.ts +1 -0
- package/build/psbt/psbtutils.d.ts.map +1 -1
- package/build/psbt/psbtutils.js +2 -0
- package/build/psbt/psbtutils.js.map +1 -1
- package/build/psbt.d.ts.map +1 -1
- package/build/psbt.js +3 -2
- package/build/psbt.js.map +1 -1
- package/build/pubkey.js +1 -1
- package/build/pubkey.js.map +1 -1
- package/build/tsconfig.build.tsbuildinfo +1 -1
- package/documentation/README.md +122 -0
- package/documentation/address.md +820 -0
- package/documentation/block.md +679 -0
- package/documentation/crypto.md +461 -0
- package/documentation/ecc.md +584 -0
- package/documentation/errors.md +656 -0
- package/documentation/io.md +942 -0
- package/documentation/networks.md +625 -0
- package/documentation/p2mr.md +380 -0
- package/documentation/payments.md +1485 -0
- package/documentation/psbt.md +1400 -0
- package/documentation/script.md +730 -0
- package/documentation/taproot.md +670 -0
- package/documentation/transaction.md +943 -0
- package/documentation/types.md +587 -0
- package/documentation/workers.md +1007 -0
- package/eslint.config.js +3 -0
- package/package.json +17 -14
- package/src/address.ts +22 -10
- package/src/index.ts +1 -0
- package/src/io/base58check.ts +1 -35
- package/src/io/base64.ts +5 -0
- package/src/networks.ts +13 -0
- package/src/payments/bip341.ts +36 -1
- package/src/payments/index.ts +4 -0
- package/src/payments/p2mr.ts +660 -0
- package/src/payments/types.ts +12 -0
- package/src/psbt/bip371.ts +84 -13
- package/src/psbt/psbtutils.ts +2 -0
- package/src/psbt.ts +4 -2
- package/src/pubkey.ts +1 -1
- package/test/bitcoin.core.spec.ts +1 -1
- package/test/fixtures/p2mr.json +270 -0
- package/test/integration/taproot.spec.ts +7 -3
- package/test/opnetTestnet.spec.ts +302 -0
- package/test/payments.spec.ts +3 -1
- package/test/psbt.spec.ts +297 -2
- 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
|
+
}
|
package/src/payments/types.ts
CHANGED
|
@@ -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;
|