@scure/btc-signer 2.0.1 → 2.3.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 +334 -64
- package/index.d.ts +15 -6
- package/index.js +16 -7
- package/musig2.d.ts +212 -69
- package/musig2.js +352 -99
- package/net.d.ts +355 -0
- package/net.js +875 -0
- package/p2p.d.ts +17 -8
- package/p2p.js +63 -11
- package/package.json +17 -17
- package/payment.d.ts +406 -41
- package/payment.js +570 -69
- package/psbt.d.ts +2958 -560
- package/psbt.js +475 -119
- package/script.d.ts +311 -133
- package/script.js +313 -90
- package/src/_type_test.ts +69 -0
- package/src/index.ts +34 -11
- package/src/musig2.ts +424 -155
- package/src/net.ts +1106 -0
- package/src/p2p.ts +76 -24
- package/src/payment.ts +882 -235
- package/src/psbt.ts +648 -229
- package/src/script.ts +397 -139
- package/src/transaction.ts +667 -196
- package/src/utils.ts +392 -47
- package/src/utxo.ts +182 -83
- package/transaction.d.ts +242 -32
- package/transaction.js +531 -121
- package/utils.d.ts +296 -25
- package/utils.js +337 -30
- package/utxo.d.ts +438 -76
- package/utxo.js +150 -59
- package/index.d.ts.map +0 -1
- package/index.js.map +0 -1
- package/musig2.d.ts.map +0 -1
- package/musig2.js.map +0 -1
- package/p2p.d.ts.map +0 -1
- package/p2p.js.map +0 -1
- package/payment.d.ts.map +0 -1
- package/payment.js.map +0 -1
- package/psbt.d.ts.map +0 -1
- package/psbt.js.map +0 -1
- package/script.d.ts.map +0 -1
- package/script.js.map +0 -1
- package/transaction.d.ts.map +0 -1
- package/transaction.js.map +0 -1
- package/utils.d.ts.map +0 -1
- package/utils.js.map +0 -1
- package/utxo.d.ts.map +0 -1
- package/utxo.js.map +0 -1
package/payment.js
CHANGED
|
@@ -1,22 +1,34 @@
|
|
|
1
1
|
import { bech32, bech32m, createBase58check, hex } from '@scure/base';
|
|
2
|
+
import { anumber } from '@noble/hashes/utils.js';
|
|
3
|
+
import { abytes } from '@noble/curves/utils.js';
|
|
2
4
|
import * as P from 'micro-packed';
|
|
3
5
|
import { TaprootControlBlock } from "./psbt.js";
|
|
4
|
-
import { OpToNum, Script, VarBytes } from "./script.js";
|
|
6
|
+
import { MAX_SCRIPT_BYTE_LENGTH, OpToNum, Script, VarBytes } from "./script.js";
|
|
5
7
|
import * as u from "./utils.js";
|
|
6
8
|
import { NETWORK } from "./utils.js";
|
|
9
|
+
// Pay to Anchor (P2A)
|
|
10
|
+
// BIP433 Pay-to-Anchor witness program bytes; the scriptPubKey is `OP_1 <0x4e73>`.
|
|
11
|
+
const P2A_PROGRAM = /* @__PURE__ */ Uint8Array.from([0x4e, 0x73]);
|
|
7
12
|
const OutP2A = {
|
|
8
13
|
encode(from) {
|
|
9
|
-
|
|
14
|
+
// BIP433 defines P2A as the exact OP_1 <0x4e73> scriptPubKey.
|
|
15
|
+
if (from.length !== 2 ||
|
|
16
|
+
from[0] !== 1 ||
|
|
17
|
+
!u.isBytes(from[1]) ||
|
|
18
|
+
!u.equalBytes(from[1], P2A_PROGRAM))
|
|
10
19
|
return;
|
|
11
20
|
return { type: 'p2a', script: Script.encode(from) };
|
|
12
21
|
},
|
|
13
22
|
decode: (to) => {
|
|
14
23
|
if (to.type !== 'p2a')
|
|
15
24
|
return;
|
|
16
|
-
|
|
25
|
+
// The decoded object keeps `script` for caller convenience, but the `p2a`
|
|
26
|
+
// tag always canonicalizes back to the fixed BIP433 script.
|
|
27
|
+
return [1, Uint8Array.from(P2A_PROGRAM)];
|
|
17
28
|
},
|
|
18
29
|
};
|
|
19
30
|
function isValidPubkey(pub, type) {
|
|
31
|
+
// Payment coders use a boolean guard here and normalize validatePubkey failures to false.
|
|
20
32
|
try {
|
|
21
33
|
u.validatePubkey(pub, type);
|
|
22
34
|
return true;
|
|
@@ -27,6 +39,8 @@ function isValidPubkey(pub, type) {
|
|
|
27
39
|
}
|
|
28
40
|
const OutPK = {
|
|
29
41
|
encode(from) {
|
|
42
|
+
// BIP380/BIP381 `pk(KEY)` only admits SEC1 ECDSA pubkeys here; x-only
|
|
43
|
+
// 32-byte CHECKSIG scripts are left for the later tapscript coders.
|
|
30
44
|
if (from.length !== 2 ||
|
|
31
45
|
!u.isBytes(from[0]) ||
|
|
32
46
|
!isValidPubkey(from[0], u.PubT.ecdsa) ||
|
|
@@ -34,44 +48,73 @@ const OutPK = {
|
|
|
34
48
|
return;
|
|
35
49
|
return { type: 'pk', pubkey: from[0] };
|
|
36
50
|
},
|
|
37
|
-
decode: (to) =>
|
|
51
|
+
decode: (to) => {
|
|
52
|
+
if (to.type !== 'pk')
|
|
53
|
+
return;
|
|
54
|
+
// OutScript validates `pk.pubkey` before this branch emits the canonical
|
|
55
|
+
// `<pubkey> CHECKSIG` script.
|
|
56
|
+
return [to.pubkey, 'CHECKSIG'];
|
|
57
|
+
},
|
|
38
58
|
};
|
|
39
59
|
const OutPKH = {
|
|
40
60
|
encode(from) {
|
|
41
61
|
if (from.length !== 5 || from[0] !== 'DUP' || from[1] !== 'HASH160' || !u.isBytes(from[2]))
|
|
42
62
|
return;
|
|
63
|
+
// Require the exact 20-byte HASH160 here so near-miss scripts fall through
|
|
64
|
+
// to OutUnknown instead of throwing in the OutScript validator on decode.
|
|
65
|
+
if (from[2].length !== 20)
|
|
66
|
+
return;
|
|
43
67
|
if (from[3] !== 'EQUALVERIFY' || from[4] !== 'CHECKSIG')
|
|
44
68
|
return;
|
|
45
69
|
return { type: 'pkh', hash: from[2] };
|
|
46
70
|
},
|
|
47
|
-
|
|
71
|
+
// OutScript validates `pkh.hash` before this branch emits the canonical
|
|
72
|
+
// `DUP HASH160 <hash> EQUALVERIFY CHECKSIG` script.
|
|
73
|
+
decode: (to) => (to.type === 'pkh'
|
|
74
|
+
? ['DUP', 'HASH160', to.hash, 'EQUALVERIFY', 'CHECKSIG']
|
|
75
|
+
: undefined),
|
|
48
76
|
};
|
|
49
77
|
const OutSH = {
|
|
50
78
|
encode(from) {
|
|
51
79
|
if (from.length !== 3 || from[0] !== 'HASH160' || !u.isBytes(from[1]) || from[2] !== 'EQUAL')
|
|
52
80
|
return;
|
|
81
|
+
// Require the exact 20-byte HASH160 here so near-miss scripts fall through
|
|
82
|
+
// to OutUnknown instead of throwing in the OutScript validator on decode.
|
|
83
|
+
if (from[1].length !== 20)
|
|
84
|
+
return;
|
|
53
85
|
return { type: 'sh', hash: from[1] };
|
|
54
86
|
},
|
|
55
|
-
|
|
87
|
+
// OutScript validates `sh.hash` before this branch emits the canonical
|
|
88
|
+
// `HASH160 <hash> EQUAL` script.
|
|
89
|
+
decode: (to) => (to.type === 'sh' ? ['HASH160', to.hash, 'EQUAL'] : undefined),
|
|
56
90
|
};
|
|
57
91
|
const OutWSH = {
|
|
58
92
|
encode(from) {
|
|
59
93
|
if (from.length !== 2 || from[0] !== 0 || !u.isBytes(from[1]))
|
|
60
94
|
return;
|
|
95
|
+
// BIP382 `wsh()` is specifically the version-0 32-byte witness program.
|
|
96
|
+
// Other witness versions stay with the later coders.
|
|
61
97
|
if (from[1].length !== 32)
|
|
62
98
|
return;
|
|
63
99
|
return { type: 'wsh', hash: from[1] };
|
|
64
100
|
},
|
|
101
|
+
// OutScript validates `wsh.hash` before this branch emits the canonical
|
|
102
|
+
// version-0 32-byte witness program.
|
|
65
103
|
decode: (to) => (to.type === 'wsh' ? [0, to.hash] : undefined),
|
|
66
104
|
};
|
|
67
105
|
const OutWPKH = {
|
|
68
106
|
encode(from) {
|
|
69
107
|
if (from.length !== 2 || from[0] !== 0 || !u.isBytes(from[1]))
|
|
70
108
|
return;
|
|
109
|
+
// BIP382 `wpkh()` is specifically the version-0 20-byte witness program.
|
|
110
|
+
// Compressed-key restrictions are enforced upstream, and other witness
|
|
111
|
+
// versions stay with the later coders.
|
|
71
112
|
if (from[1].length !== 20)
|
|
72
113
|
return;
|
|
73
114
|
return { type: 'wpkh', hash: from[1] };
|
|
74
115
|
},
|
|
116
|
+
// OutScript validates `wpkh.hash` before this branch emits the canonical
|
|
117
|
+
// version-0 20-byte witness program.
|
|
75
118
|
decode: (to) => (to.type === 'wpkh' ? [0, to.hash] : undefined),
|
|
76
119
|
};
|
|
77
120
|
const OutMS = {
|
|
@@ -86,20 +129,43 @@ const OutMS = {
|
|
|
86
129
|
const pubkeys = from.slice(1, -2);
|
|
87
130
|
if (n !== pubkeys.length)
|
|
88
131
|
return;
|
|
132
|
+
// Require valid ECDSA pubkeys and `0 < m <= n` here so near-miss
|
|
133
|
+
// CHECKMULTISIG scripts (garbage keys, degenerate 0-of-0) fall through to
|
|
134
|
+
// OutUnknown instead of throwing in the OutScript validator on decode.
|
|
135
|
+
// Script.decode only yields 0..16 for opcode numbers, so n <= 16 holds.
|
|
89
136
|
for (const pub of pubkeys)
|
|
90
|
-
if (!u.isBytes(pub))
|
|
137
|
+
if (!u.isBytes(pub) || !isValidPubkey(pub, u.PubT.ecdsa))
|
|
91
138
|
return;
|
|
92
|
-
|
|
139
|
+
if (!Number.isSafeInteger(m) || m < 1 || m > n)
|
|
140
|
+
return;
|
|
141
|
+
// We don't need n here because it is the same as pubkeys.length.
|
|
142
|
+
return { type: 'ms', m, pubkeys: pubkeys };
|
|
93
143
|
},
|
|
94
144
|
// checkmultisig(n, ..pubkeys, m)
|
|
95
|
-
decode: (to) =>
|
|
145
|
+
decode: (to) =>
|
|
146
|
+
// OutScript validates multisig pubkeys and `0 < m <= n <= 16`.
|
|
147
|
+
// This branch only emits the canonical `m <pubkeys...> n CHECKMULTISIG`
|
|
148
|
+
// script.
|
|
149
|
+
(to.type === 'ms'
|
|
150
|
+
? [to.m, ...to.pubkeys, to.pubkeys.length, 'CHECKMULTISIG']
|
|
151
|
+
: undefined),
|
|
96
152
|
};
|
|
97
153
|
const OutTR = {
|
|
98
154
|
encode(from) {
|
|
99
|
-
|
|
155
|
+
// BIP141 witness programs are `OP_0..OP_16` followed by a direct 2..40-byte push.
|
|
156
|
+
// BIP341 assigns native taproot meaning only to version 1 with a 32-byte x-only program;
|
|
157
|
+
// other OP_1 program lengths remain reserved future witness programs and should fall through.
|
|
158
|
+
if (from.length !== 2 || from[0] !== 1 || !u.isBytes(from[1]) || from[1].length !== 32)
|
|
159
|
+
return;
|
|
160
|
+
// A 32-byte v1 program with an off-curve x coordinate is a fundable but
|
|
161
|
+
// taproot-unspendable output; classify it as unknown instead of throwing
|
|
162
|
+
// in the OutScript validator on decode.
|
|
163
|
+
if (!isValidPubkey(from[1], u.PubT.schnorr))
|
|
100
164
|
return;
|
|
101
165
|
return { type: 'tr', pubkey: from[1] };
|
|
102
166
|
},
|
|
167
|
+
// OutScript validates `tr.pubkey` before this branch emits the canonical
|
|
168
|
+
// version-1 32-byte witness program.
|
|
103
169
|
decode: (to) => (to.type === 'tr' ? [1, to.pubkey] : undefined),
|
|
104
170
|
};
|
|
105
171
|
const OutTRNS = {
|
|
@@ -116,10 +182,17 @@ const OutTRNS = {
|
|
|
116
182
|
return;
|
|
117
183
|
continue;
|
|
118
184
|
}
|
|
119
|
-
|
|
185
|
+
// Require actual Schnorr pubkeys here so near-miss `<bytes> CHECKSIG`
|
|
186
|
+
// scripts fall through to OutUnknown instead of failing later.
|
|
187
|
+
if (!u.isBytes(elm) || !isValidPubkey(elm, u.PubT.schnorr))
|
|
120
188
|
return;
|
|
121
189
|
pubkeys.push(elm);
|
|
122
190
|
}
|
|
191
|
+
// BIP342 "Using a k-of-k script for every combination" documents the shape
|
|
192
|
+
// `<pubkey_1> CHECKSIGVERIFY ... <pubkey_n> CHECKSIG`; this matcher only
|
|
193
|
+
// classifies that embedded-pubkey form, so bare CHECKSIG stays unknown.
|
|
194
|
+
if (!pubkeys.length)
|
|
195
|
+
return;
|
|
123
196
|
return { type: 'tr_ns', pubkeys };
|
|
124
197
|
},
|
|
125
198
|
decode: (to) => {
|
|
@@ -128,6 +201,8 @@ const OutTRNS = {
|
|
|
128
201
|
const out = [];
|
|
129
202
|
for (let i = 0; i < to.pubkeys.length - 1; i++)
|
|
130
203
|
out.push(to.pubkeys[i], 'CHECKSIGVERIFY');
|
|
204
|
+
// This branch assumes at least one Schnorr pubkey; [] would otherwise emit
|
|
205
|
+
// `[undefined, CHECKSIG]` and only fail later in Script.encode.
|
|
131
206
|
out.push(to.pubkeys[to.pubkeys.length - 1], 'CHECKSIG');
|
|
132
207
|
return out;
|
|
133
208
|
},
|
|
@@ -143,15 +218,22 @@ const OutTRMS = {
|
|
|
143
218
|
return;
|
|
144
219
|
for (let i = 0; i < last - 1; i++) {
|
|
145
220
|
const elm = from[i];
|
|
221
|
+
// Structural mismatches should fall through to OutUnknown instead of
|
|
222
|
+
// throwing from the tr_ms matcher.
|
|
146
223
|
if (i & 1) {
|
|
147
224
|
if (elm !== (i === 1 ? 'CHECKSIG' : 'CHECKSIGADD'))
|
|
148
|
-
|
|
225
|
+
return;
|
|
149
226
|
continue;
|
|
150
227
|
}
|
|
151
|
-
|
|
152
|
-
|
|
228
|
+
// Require actual Schnorr pubkeys here (same as tr_ns) so near-miss
|
|
229
|
+
// CHECKSIGADD scripts fall through to OutUnknown instead of throwing
|
|
230
|
+
// in the OutScript validator on decode.
|
|
231
|
+
if (!u.isBytes(elm) || !isValidPubkey(elm, u.PubT.schnorr))
|
|
232
|
+
return;
|
|
153
233
|
pubkeys.push(elm);
|
|
154
234
|
}
|
|
235
|
+
if (!Number.isSafeInteger(m) || m < 1 || m > pubkeys.length || pubkeys.length > 999)
|
|
236
|
+
return;
|
|
155
237
|
return { type: 'tr_ms', pubkeys, m };
|
|
156
238
|
},
|
|
157
239
|
decode: (to) => {
|
|
@@ -160,18 +242,29 @@ const OutTRMS = {
|
|
|
160
242
|
const out = [to.pubkeys[0], 'CHECKSIG'];
|
|
161
243
|
for (let i = 1; i < to.pubkeys.length; i++)
|
|
162
244
|
out.push(to.pubkeys[i], 'CHECKSIGADD');
|
|
245
|
+
// This branch assumes `m` was already validated as an integer ScriptNum;
|
|
246
|
+
// fractional JS numbers would otherwise serialize as a different threshold.
|
|
163
247
|
out.push(to.m, 'NUMEQUAL');
|
|
164
248
|
return out;
|
|
165
249
|
},
|
|
166
250
|
};
|
|
167
251
|
const OutUnknown = {
|
|
168
252
|
encode(from) {
|
|
253
|
+
// This is the catch-all fallback for scripts no structured coder recognized,
|
|
254
|
+
// so earlier matchers must return `undefined` instead of throwing on mismatch.
|
|
255
|
+
// Because this reserializes the parsed Script AST, unknown scripts preserve
|
|
256
|
+
// semantics but not original non-minimal push spellings.
|
|
169
257
|
return { type: 'unknown', script: Script.encode(from) };
|
|
170
258
|
},
|
|
171
|
-
decode: (to) =>
|
|
259
|
+
decode: (to) =>
|
|
260
|
+
// This reparses `unknown.script` through the semantic Script codec, so raw
|
|
261
|
+
// bytes must still be syntactically parseable and may canonicalize on re-encode.
|
|
262
|
+
(to.type === 'unknown' ? Script.decode(to.script) : undefined),
|
|
172
263
|
};
|
|
173
264
|
// /Payments
|
|
174
|
-
const OutScripts = [
|
|
265
|
+
const OutScripts = /* @__PURE__ */ (() => [
|
|
266
|
+
// Order is semantic: specific structured coders run first and the catch-all
|
|
267
|
+
// unknown fallback must stay last.
|
|
175
268
|
OutP2A,
|
|
176
269
|
OutPK,
|
|
177
270
|
OutPKH,
|
|
@@ -183,15 +276,28 @@ const OutScripts = [
|
|
|
183
276
|
OutTRNS,
|
|
184
277
|
OutTRMS,
|
|
185
278
|
OutUnknown,
|
|
186
|
-
];
|
|
279
|
+
])();
|
|
187
280
|
// TODO: we can support user supplied output scripts now
|
|
188
281
|
// - addOutScript
|
|
189
282
|
// - removeOutScript
|
|
190
283
|
// - We can do that as log we modify array in-place
|
|
191
284
|
// - Actually is very hard, since there is sign/finalize logic
|
|
192
|
-
|
|
285
|
+
// Raw composition of semantic Script parsing with the ordered output-script
|
|
286
|
+
// matcher; OutScript adds the higher-level validation layer on top.
|
|
287
|
+
const _OutScript = /* @__PURE__ */ (() => P.apply(Script, P.coders.match(OutScripts)))();
|
|
193
288
|
// We can validate this once, because of packed & coders
|
|
194
|
-
|
|
289
|
+
/**
|
|
290
|
+
* Coder for recognized Bitcoin output scripts.
|
|
291
|
+
* @example
|
|
292
|
+
* Decode a serialized output script back into the tagged payment descriptor.
|
|
293
|
+
* ```ts
|
|
294
|
+
* import { OutScript, p2wpkh } from '@scure/btc-signer/payment.js';
|
|
295
|
+
* import { pubECDSA, randomPrivateKeyBytes } from '@scure/btc-signer/utils.js';
|
|
296
|
+
* const pay = p2wpkh(pubECDSA(randomPrivateKeyBytes()));
|
|
297
|
+
* OutScript.decode(pay.script);
|
|
298
|
+
* ```
|
|
299
|
+
*/
|
|
300
|
+
export const OutScript = /* @__PURE__ */ (() => Object.freeze(P.validate(_OutScript, (i) => {
|
|
195
301
|
if (i.type === 'pk' && !isValidPubkey(i.pubkey, u.PubT.ecdsa))
|
|
196
302
|
throw new Error('OutScript/pk: wrong key');
|
|
197
303
|
if ((i.type === 'pkh' || i.type === 'sh' || i.type === 'wpkh') &&
|
|
@@ -209,6 +315,9 @@ export const OutScript = P.validate(_OutScript, (i) => {
|
|
|
209
315
|
for (const p of i.pubkeys)
|
|
210
316
|
if (!isValidPubkey(p, u.PubT.ecdsa))
|
|
211
317
|
throw new Error('OutScript/multisig: wrong pubkey');
|
|
318
|
+
// Range checks are not enough here: non-integer JS numbers like 1.5 would
|
|
319
|
+
// otherwise slip through and serialize as a different ScriptNum threshold.
|
|
320
|
+
anumber(i.m, 'm');
|
|
212
321
|
if (i.m <= 0 || n > 16 || i.m > n)
|
|
213
322
|
throw new Error('OutScript/multisig: invalid params');
|
|
214
323
|
}
|
|
@@ -219,61 +328,133 @@ export const OutScript = P.validate(_OutScript, (i) => {
|
|
|
219
328
|
}
|
|
220
329
|
if (i.type === 'tr_ms') {
|
|
221
330
|
const n = i.pubkeys.length;
|
|
331
|
+
// BIP 342 keeps the 1000-element stack limit. This CHECKSIG/CHECKSIGADD form
|
|
332
|
+
// momentarily has n witness items plus one pushed pubkey on the stack, so n must stay <= 999.
|
|
333
|
+
anumber(i.m, 'm');
|
|
222
334
|
if (i.m <= 0 || n > 999 || i.m > n)
|
|
223
335
|
throw new Error('OutScript/tr_ms: invalid params');
|
|
224
336
|
}
|
|
225
337
|
return i;
|
|
226
|
-
});
|
|
338
|
+
})))();
|
|
227
339
|
// Basic sanity check for scripts
|
|
228
340
|
function checkWSH(s, witnessScript) {
|
|
229
341
|
if (!u.equalBytes(s.hash, u.sha256(witnessScript)))
|
|
230
342
|
throw new Error('checkScript: wsh wrong witnessScript hash');
|
|
343
|
+
// BIP141 only requires the witnessScript hash match; the type-based rejects
|
|
344
|
+
// below are an extra descriptor sanity layer for BIP382 invalid-descriptor
|
|
345
|
+
// bullets `wpkh() nested in wsh()` and `wsh() nested in wsh()`.
|
|
231
346
|
const w = OutScript.decode(witnessScript);
|
|
232
347
|
if (w.type === 'tr' || w.type === 'tr_ns' || w.type === 'tr_ms')
|
|
233
348
|
throw new Error(`checkScript: P2${w.type} cannot be wrapped in P2SH`);
|
|
234
|
-
if (w.type === 'wpkh' || w.type === 'sh')
|
|
349
|
+
if (w.type === 'wpkh' || w.type === 'wsh' || w.type === 'sh')
|
|
235
350
|
throw new Error(`checkScript: P2${w.type} cannot be wrapped in P2WSH`);
|
|
236
351
|
}
|
|
352
|
+
/**
|
|
353
|
+
* Validates that nested redeem and witness scripts match their wrappers.
|
|
354
|
+
* @param script - top-level output script
|
|
355
|
+
* @param redeemScript - optional redeem script for P2SH wrappers
|
|
356
|
+
* @param witnessScript - optional witness script for P2WSH wrappers
|
|
357
|
+
* @throws If the script nesting is invalid or unsupported. {@link Error}
|
|
358
|
+
* @example
|
|
359
|
+
* Verify that wrapped scripts and hashes still match after custom edits.
|
|
360
|
+
* ```ts
|
|
361
|
+
* import { hex } from '@scure/base';
|
|
362
|
+
* import { checkScript, p2pkh, p2sh } from '@scure/btc-signer/payment.js';
|
|
363
|
+
* const wrapped = p2sh(
|
|
364
|
+
* p2pkh(hex.decode('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'))
|
|
365
|
+
* );
|
|
366
|
+
* checkScript(wrapped.script, wrapped.redeemScript);
|
|
367
|
+
* ```
|
|
368
|
+
*/
|
|
237
369
|
export function checkScript(script, redeemScript, witnessScript) {
|
|
370
|
+
let hasWsh = false;
|
|
371
|
+
let r = undefined;
|
|
238
372
|
if (script) {
|
|
239
373
|
const s = OutScript.decode(script);
|
|
374
|
+
// BIP174 Data Signers Check For bullets: provided redeemScript must match
|
|
375
|
+
// the scriptPubKey, and provided witnessScript must match the scriptPubKey
|
|
376
|
+
// or redeemScript instead of being silently ignored as stray metadata.
|
|
240
377
|
// ms||pk maybe work, but there will be no address, hard to spend
|
|
241
378
|
if (s.type === 'tr_ns' || s.type === 'tr_ms' || s.type === 'ms' || s.type == 'pk')
|
|
242
379
|
throw new Error(`checkScript: non-wrapped ${s.type}`);
|
|
243
|
-
if (
|
|
380
|
+
if (redeemScript) {
|
|
381
|
+
if (s.type !== 'sh')
|
|
382
|
+
throw new Error('checkScript: redeemScript without P2SH');
|
|
244
383
|
if (!u.equalBytes(s.hash, u.hash160(redeemScript)))
|
|
245
384
|
throw new Error('checkScript: sh wrong redeemScript hash');
|
|
246
|
-
|
|
247
|
-
if (r
|
|
385
|
+
r = OutScript.decode(redeemScript);
|
|
386
|
+
if (r?.type === 'tr' || r?.type === 'tr_ns' || r?.type === 'tr_ms')
|
|
248
387
|
throw new Error(`checkScript: P2${r.type} cannot be wrapped in P2SH`);
|
|
249
388
|
// Not sure if this unspendable, but we cannot represent this via PSBT
|
|
250
|
-
if (r
|
|
389
|
+
if (r?.type === 'sh')
|
|
251
390
|
throw new Error('checkScript: P2SH cannot be wrapped in P2SH');
|
|
252
391
|
}
|
|
253
|
-
if (s.type === 'wsh'
|
|
254
|
-
|
|
392
|
+
if (s.type === 'wsh') {
|
|
393
|
+
hasWsh = true;
|
|
394
|
+
if (witnessScript)
|
|
395
|
+
checkWSH(s, witnessScript);
|
|
396
|
+
}
|
|
255
397
|
}
|
|
256
398
|
if (redeemScript) {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
399
|
+
if (r === undefined)
|
|
400
|
+
r = OutScript.decode(redeemScript);
|
|
401
|
+
if (r?.type === 'wsh') {
|
|
402
|
+
hasWsh = true;
|
|
403
|
+
if (witnessScript)
|
|
404
|
+
checkWSH(r, witnessScript);
|
|
405
|
+
}
|
|
260
406
|
}
|
|
407
|
+
if (witnessScript && !hasWsh)
|
|
408
|
+
throw new Error('checkScript: witnessScript without P2WSH');
|
|
261
409
|
}
|
|
262
410
|
function uniqPubkey(pubkeys) {
|
|
263
411
|
const map = {};
|
|
264
412
|
for (const pub of pubkeys) {
|
|
413
|
+
// Exact-byte duplicate filter only: BIP383 valid vectors still permit the
|
|
414
|
+
// same point to appear in compressed and uncompressed SEC1 form in multi().
|
|
265
415
|
const key = hex.encode(pub);
|
|
266
416
|
if (map[key])
|
|
267
417
|
throw new Error(`Multisig: non-uniq pubkey: ${pubkeys.map(hex.encode)}`);
|
|
268
418
|
map[key] = true;
|
|
269
419
|
}
|
|
270
420
|
}
|
|
421
|
+
/**
|
|
422
|
+
* Builds a pay-to-public-key script.
|
|
423
|
+
* @param pubkey - compressed or uncompressed ECDSA public key
|
|
424
|
+
* @param _network - unused network placeholder for API consistency
|
|
425
|
+
* @returns P2PK descriptor.
|
|
426
|
+
* @throws If the public key cannot be encoded as a P2PK output. {@link Error}
|
|
427
|
+
* @example
|
|
428
|
+
* Build a bare pay-to-public-key output.
|
|
429
|
+
* ```ts
|
|
430
|
+
* import { hex } from '@scure/base';
|
|
431
|
+
* import { p2pk } from '@scure/btc-signer/payment.js';
|
|
432
|
+
* p2pk(hex.decode('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'));
|
|
433
|
+
* ```
|
|
434
|
+
*/
|
|
271
435
|
export const p2pk = (pubkey, _network = NETWORK) => {
|
|
272
436
|
// network is unused
|
|
273
437
|
if (!isValidPubkey(pubkey, u.PubT.ecdsa))
|
|
274
438
|
throw new Error('P2PK: invalid publicKey');
|
|
275
|
-
return {
|
|
439
|
+
return {
|
|
440
|
+
type: 'pk',
|
|
441
|
+
script: OutScript.encode({ type: 'pk', pubkey }),
|
|
442
|
+
};
|
|
276
443
|
};
|
|
444
|
+
/**
|
|
445
|
+
* Builds a P2PKH output from a public key.
|
|
446
|
+
* @param publicKey - compressed or uncompressed ECDSA public key bytes; HASH160 commits to the exact encoding
|
|
447
|
+
* @param network - address network parameters
|
|
448
|
+
* @returns P2PKH descriptor.
|
|
449
|
+
* @throws If the public key cannot be encoded as a P2PKH output. {@link Error}
|
|
450
|
+
* @example
|
|
451
|
+
* Build a classic pay-to-public-key-hash output.
|
|
452
|
+
* ```ts
|
|
453
|
+
* import { hex } from '@scure/base';
|
|
454
|
+
* import { p2pkh } from '@scure/btc-signer/payment.js';
|
|
455
|
+
* p2pkh(hex.decode('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'));
|
|
456
|
+
* ```
|
|
457
|
+
*/
|
|
277
458
|
export const p2pkh = (publicKey, network = NETWORK) => {
|
|
278
459
|
if (!isValidPubkey(publicKey, u.PubT.ecdsa))
|
|
279
460
|
throw new Error('P2PKH: invalid publicKey');
|
|
@@ -285,52 +466,108 @@ export const p2pkh = (publicKey, network = NETWORK) => {
|
|
|
285
466
|
hash,
|
|
286
467
|
};
|
|
287
468
|
};
|
|
469
|
+
/**
|
|
470
|
+
* Wraps a child script inside P2SH.
|
|
471
|
+
* @param child - child payment descriptor to wrap
|
|
472
|
+
* @param network - address network parameters
|
|
473
|
+
* @returns P2SH descriptor preserving witness metadata when present.
|
|
474
|
+
* @throws If the wrapped script combination is invalid or unsupported. {@link Error}
|
|
475
|
+
* @example
|
|
476
|
+
* Wrap a child script in P2SH so it gets a base58 address form.
|
|
477
|
+
* ```ts
|
|
478
|
+
* import { hex } from '@scure/base';
|
|
479
|
+
* import { p2pk, p2sh, p2wsh } from '@scure/btc-signer/payment.js';
|
|
480
|
+
* p2sh(p2wsh(p2pk(hex.decode('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'))));
|
|
481
|
+
* ```
|
|
482
|
+
*/
|
|
288
483
|
export const p2sh = (child, network = NETWORK) => {
|
|
484
|
+
u.validateObject(child, {}, {}, 'child');
|
|
289
485
|
// It is already tested inside noble-hashes and checkScript
|
|
290
|
-
|
|
486
|
+
// BIP16 redeemScripts are pushed by scriptSig, so anything over the 520-byte pushed-element
|
|
487
|
+
// limit would be fundable by HASH160 but unspendable once wrapped in P2SH.
|
|
488
|
+
const c = child;
|
|
489
|
+
const cs = c.script;
|
|
291
490
|
if (!u.isBytes(cs))
|
|
292
|
-
throw new Error(`Wrong script: ${typeof
|
|
491
|
+
throw new Error(`Wrong script: ${typeof c.script}, expected Uint8Array`);
|
|
492
|
+
if (cs.length > MAX_SCRIPT_BYTE_LENGTH)
|
|
493
|
+
throw new Error(`P2SH: redeemScript exceeds ${MAX_SCRIPT_BYTE_LENGTH}-byte push limit: len=${cs.length}`);
|
|
293
494
|
const hash = u.hash160(cs);
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
495
|
+
const out = { type: 'sh', hash };
|
|
496
|
+
const script = OutScript.encode(out);
|
|
497
|
+
const address = Address(network).encode(out);
|
|
498
|
+
checkScript(script, cs, c.witnessScript);
|
|
499
|
+
if (c.witnessScript) {
|
|
297
500
|
return {
|
|
298
501
|
type: 'sh',
|
|
299
502
|
redeemScript: cs,
|
|
300
|
-
script
|
|
301
|
-
address
|
|
503
|
+
script,
|
|
504
|
+
address,
|
|
302
505
|
hash,
|
|
303
|
-
witnessScript:
|
|
506
|
+
witnessScript: c.witnessScript,
|
|
304
507
|
};
|
|
305
508
|
}
|
|
306
509
|
else {
|
|
307
510
|
return {
|
|
308
511
|
type: 'sh',
|
|
309
512
|
redeemScript: cs,
|
|
310
|
-
script
|
|
311
|
-
address
|
|
513
|
+
script,
|
|
514
|
+
address,
|
|
312
515
|
hash,
|
|
313
516
|
};
|
|
314
517
|
}
|
|
315
518
|
};
|
|
519
|
+
/**
|
|
520
|
+
* Wraps a child script inside native SegWit P2WSH.
|
|
521
|
+
* @param child - child payment descriptor to wrap
|
|
522
|
+
* @param network - address network parameters
|
|
523
|
+
* @returns P2WSH descriptor.
|
|
524
|
+
* @throws If the wrapped script combination is invalid or unsupported. {@link Error}
|
|
525
|
+
* @example
|
|
526
|
+
* Wrap a child script in native SegWit P2WSH.
|
|
527
|
+
* ```ts
|
|
528
|
+
* import { hex } from '@scure/base';
|
|
529
|
+
* import { p2pk, p2wsh } from '@scure/btc-signer/payment.js';
|
|
530
|
+
* p2wsh(p2pk(hex.decode('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')));
|
|
531
|
+
* ```
|
|
532
|
+
*/
|
|
316
533
|
export const p2wsh = (child, network = NETWORK) => {
|
|
534
|
+
u.validateObject(child, {}, {}, 'child');
|
|
317
535
|
const cs = child.script;
|
|
318
536
|
if (!u.isBytes(cs))
|
|
319
537
|
throw new Error(`Wrong script: ${typeof cs}, expected Uint8Array`);
|
|
538
|
+
// BIP141 P2WSH says the witness "must consist of ... a serialized script (witnessScript)"
|
|
539
|
+
// and that witnessScript is limited to 10,000 bytes, so larger wrapped scripts must reject.
|
|
540
|
+
if (cs.length > 10000)
|
|
541
|
+
throw new Error('P2WSH: witnessScript exceeds 10,000 bytes');
|
|
320
542
|
const hash = u.sha256(cs);
|
|
321
543
|
const script = OutScript.encode({ type: 'wsh', hash });
|
|
322
544
|
checkScript(script, undefined, cs);
|
|
323
545
|
return {
|
|
324
546
|
type: 'wsh',
|
|
325
547
|
witnessScript: cs,
|
|
326
|
-
script
|
|
548
|
+
script,
|
|
327
549
|
address: Address(network).encode({ type: 'wsh', hash }),
|
|
328
550
|
hash,
|
|
329
551
|
};
|
|
330
552
|
};
|
|
553
|
+
/**
|
|
554
|
+
* Builds a native SegWit P2WPKH output from a public key.
|
|
555
|
+
* @param publicKey - compressed ECDSA public key
|
|
556
|
+
* @param network - address network parameters
|
|
557
|
+
* @returns P2WPKH descriptor.
|
|
558
|
+
* @throws If the public key cannot be encoded as a P2WPKH output. {@link Error}
|
|
559
|
+
* @example
|
|
560
|
+
* Build a native SegWit pay-to-public-key-hash output.
|
|
561
|
+
* ```ts
|
|
562
|
+
* import { hex } from '@scure/base';
|
|
563
|
+
* import { p2wpkh } from '@scure/btc-signer/payment.js';
|
|
564
|
+
* p2wpkh(hex.decode('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'));
|
|
565
|
+
* ```
|
|
566
|
+
*/
|
|
331
567
|
export const p2wpkh = (publicKey, network = NETWORK) => {
|
|
332
568
|
if (!isValidPubkey(publicKey, u.PubT.ecdsa))
|
|
333
569
|
throw new Error('P2WPKH: invalid publicKey');
|
|
570
|
+
// BIP 143 default policy: version-0 witness programs MUST use 33-byte compressed ECDSA keys.
|
|
334
571
|
if (publicKey.length === 65)
|
|
335
572
|
throw new Error('P2WPKH: uncompressed public key');
|
|
336
573
|
const hash = u.hash160(publicKey);
|
|
@@ -341,7 +578,24 @@ export const p2wpkh = (publicKey, network = NETWORK) => {
|
|
|
341
578
|
hash,
|
|
342
579
|
};
|
|
343
580
|
};
|
|
581
|
+
/**
|
|
582
|
+
* Builds a bare multisig script.
|
|
583
|
+
* @param m - number of required signatures
|
|
584
|
+
* @param pubkeys - participating public keys
|
|
585
|
+
* @param allowSamePubkeys - whether duplicate keys are allowed
|
|
586
|
+
* @returns P2MS descriptor.
|
|
587
|
+
* @throws If the multisig parameters are invalid. {@link Error}
|
|
588
|
+
* @example
|
|
589
|
+
* Build a bare multisig output script.
|
|
590
|
+
* ```ts
|
|
591
|
+
* import { hex } from '@scure/base';
|
|
592
|
+
* import { p2ms } from '@scure/btc-signer/payment.js';
|
|
593
|
+
* p2ms(1, [hex.decode('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')], true);
|
|
594
|
+
* ```
|
|
595
|
+
*/
|
|
344
596
|
export const p2ms = (m, pubkeys, allowSamePubkeys = false) => {
|
|
597
|
+
// BIP 11 only standardized bare multisig up to 3 keys; this helper still permits up to 16
|
|
598
|
+
// because the same script shape is commonly wrapped by p2sh()/p2wsh() instead of used bare.
|
|
345
599
|
if (!allowSamePubkeys)
|
|
346
600
|
uniqPubkey(pubkeys);
|
|
347
601
|
return {
|
|
@@ -356,9 +610,17 @@ function checkTaprootScript(script, internalPubKey, allowUnknownOutputs = false,
|
|
|
356
610
|
// disable custom. All custom scripts for taproot should have prefix 'tr_'
|
|
357
611
|
if (customScripts) {
|
|
358
612
|
const cs = P.apply(Script, P.coders.match(customScripts));
|
|
359
|
-
|
|
613
|
+
let c;
|
|
614
|
+
// match() throws when no custom coder matches; treat that as "not a custom
|
|
615
|
+
// script" so the allowUnknownOutputs escape below stays reachable.
|
|
616
|
+
try {
|
|
617
|
+
c = cs.decode(script);
|
|
618
|
+
}
|
|
619
|
+
catch (e) {
|
|
620
|
+
c = undefined;
|
|
621
|
+
}
|
|
360
622
|
if (c !== undefined) {
|
|
361
|
-
if (
|
|
623
|
+
if (!u.astring(c.type, 'c.type').startsWith('tr_'))
|
|
362
624
|
throw new Error(`P2TR: invalid custom type=${c.type}`);
|
|
363
625
|
return;
|
|
364
626
|
}
|
|
@@ -387,7 +649,37 @@ function checkTaprootScript(script, internalPubKey, allowUnknownOutputs = false,
|
|
|
387
649
|
}
|
|
388
650
|
}
|
|
389
651
|
// Helper for generating binary tree from list, with weights
|
|
652
|
+
/**
|
|
653
|
+
* Converts a flat list of weighted leaves into a binary taproot tree.
|
|
654
|
+
* @param taprootList - weighted leaves to arrange
|
|
655
|
+
* @returns Binary taproot script tree.
|
|
656
|
+
* @throws If the list is empty and cannot describe any tree. {@link Error}
|
|
657
|
+
* @example
|
|
658
|
+
* Start from a flat weighted list, then let the helper build the binary tree shape.
|
|
659
|
+
* ```ts
|
|
660
|
+
* import { hex } from '@scure/base';
|
|
661
|
+
* import { p2tr_pk, taprootListToTree } from '@scure/btc-signer/payment.js';
|
|
662
|
+
* taprootListToTree([
|
|
663
|
+
* p2tr_pk(hex.decode('f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9')),
|
|
664
|
+
* p2tr_pk(hex.decode('dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659')),
|
|
665
|
+
* ]);
|
|
666
|
+
* ```
|
|
667
|
+
*/
|
|
390
668
|
export function taprootListToTree(taprootList) {
|
|
669
|
+
u.aarray(taprootList, 'taprootList', (leaf, title) => {
|
|
670
|
+
// p2tr reduces non-binary trees through this helper, so nested branch arrays are valid here.
|
|
671
|
+
if (Array.isArray(leaf))
|
|
672
|
+
return;
|
|
673
|
+
u.validateObject(leaf, {}, {}, title);
|
|
674
|
+
// This helper only arranges weighted tree nodes; p2tr validates leaf scripts while hashing.
|
|
675
|
+
if (leaf.weight !== undefined)
|
|
676
|
+
anumber(leaf.weight, title + '.weight');
|
|
677
|
+
});
|
|
678
|
+
// Empty flat lists cannot represent a taproot script tree; omit the tree entirely for
|
|
679
|
+
// key-path-only outputs instead of passing [] here, otherwise this helper would return
|
|
680
|
+
// undefined and downstream taproot tree walkers would fail much later on a non-tree value.
|
|
681
|
+
if (!taprootList.length)
|
|
682
|
+
throw new Error('taprootListToTree: empty tree');
|
|
391
683
|
// Clone input in order to not corrupt it
|
|
392
684
|
const lst = Array.from(taprootList);
|
|
393
685
|
// We have at least 2 elements => can create branch
|
|
@@ -418,7 +710,8 @@ function taprootAddPath(tree, path = []) {
|
|
|
418
710
|
return {
|
|
419
711
|
...tree,
|
|
420
712
|
path,
|
|
421
|
-
//
|
|
713
|
+
// BIP 341 control blocks serialize sibling hashes from leaf to root, so prepend the
|
|
714
|
+
// current sibling before descending into the child subtree.
|
|
422
715
|
left: taprootAddPath(tree.left, [tree.right.hash, ...path]),
|
|
423
716
|
right: taprootAddPath(tree.right, [tree.left.hash, ...path]),
|
|
424
717
|
};
|
|
@@ -430,28 +723,33 @@ function taprootWalkTree(tree) {
|
|
|
430
723
|
return [tree];
|
|
431
724
|
if (tree.type !== 'branch')
|
|
432
725
|
throw new Error(`taprootWalkTree: wrong type=${tree}`);
|
|
726
|
+
// Keep a stable left-to-right DFS leaf order when flattening the annotated tree.
|
|
433
727
|
return [...taprootWalkTree(tree.left), ...taprootWalkTree(tree.right)];
|
|
434
728
|
}
|
|
435
729
|
function taprootHashTree(tree, internalPubKey, allowUnknownOutputs = false, customScripts) {
|
|
436
|
-
if (
|
|
730
|
+
if (tree === undefined)
|
|
437
731
|
throw new Error('taprootHashTree: empty tree');
|
|
732
|
+
if (!Array.isArray(tree) && !P.utils.isPlainObject(tree))
|
|
733
|
+
throw new TypeError('"tree" expected object or array, got type=' + typeof tree);
|
|
438
734
|
if (Array.isArray(tree) && tree.length === 1)
|
|
439
735
|
tree = tree[0];
|
|
440
736
|
// Terminal node (leaf)
|
|
441
737
|
if (!Array.isArray(tree)) {
|
|
442
|
-
|
|
738
|
+
u.validateObject(tree, {}, {}, 'tree');
|
|
739
|
+
const version = tree.leafVersion;
|
|
740
|
+
const { script: leafScript } = tree;
|
|
443
741
|
// Earliest tree walk where we can validate tapScripts
|
|
444
742
|
if (tree.tapLeafScript || (tree.tapMerkleRoot && !u.equalBytes(tree.tapMerkleRoot, P.EMPTY)))
|
|
445
743
|
throw new Error('P2TR: tapRoot leafScript cannot have tree');
|
|
446
|
-
const script = typeof leafScript === 'string'
|
|
447
|
-
|
|
448
|
-
|
|
744
|
+
const script = typeof leafScript === 'string'
|
|
745
|
+
? hex.decode(leafScript)
|
|
746
|
+
: abytes(leafScript, undefined, 'tree.script');
|
|
449
747
|
checkTaprootScript(script, internalPubKey, allowUnknownOutputs, customScripts);
|
|
450
748
|
return {
|
|
451
749
|
type: 'leaf',
|
|
452
750
|
version,
|
|
453
751
|
script,
|
|
454
|
-
hash: tapLeafHash(script, version),
|
|
752
|
+
hash: tapLeafHash(script, tapLeafVersion(version)),
|
|
455
753
|
};
|
|
456
754
|
}
|
|
457
755
|
// If tree / branch is not binary tree, convert it
|
|
@@ -463,14 +761,44 @@ function taprootHashTree(tree, internalPubKey, allowUnknownOutputs = false, cust
|
|
|
463
761
|
// Both nodes should exist
|
|
464
762
|
const left = taprootHashTree(tree[0], internalPubKey, allowUnknownOutputs, customScripts);
|
|
465
763
|
const right = taprootHashTree(tree[1], internalPubKey, allowUnknownOutputs, customScripts);
|
|
466
|
-
//
|
|
764
|
+
// BIP 341 sorts TapBranch child hashes lexicographically for hashing, but the original
|
|
765
|
+
// left/right structure still determines the control-block sibling paths for each leaf.
|
|
467
766
|
let [lH, rH] = [left.hash, right.hash];
|
|
468
767
|
if (u.compareBytes(rH, lH) === -1)
|
|
469
768
|
[lH, rH] = [rH, lH];
|
|
470
|
-
return {
|
|
769
|
+
return {
|
|
770
|
+
type: 'branch',
|
|
771
|
+
left,
|
|
772
|
+
right,
|
|
773
|
+
hash: u.tagSchnorr('TapBranch', lH, rH),
|
|
774
|
+
};
|
|
471
775
|
}
|
|
776
|
+
/** Default tapleaf version used by taproot script-path outputs before adding the parity bit. */
|
|
472
777
|
export const TAP_LEAF_VERSION = 0xc0;
|
|
473
|
-
|
|
778
|
+
const tapLeafVersion = (version) => {
|
|
779
|
+
if (version === undefined)
|
|
780
|
+
return TAP_LEAF_VERSION;
|
|
781
|
+
anumber(version, 'leafVersion');
|
|
782
|
+
// BIP341 script-path validation defines the effective leaf version as `v = c[0] & 0xfe`
|
|
783
|
+
// and says it cannot be odd or `0x50`; tapleaf hashes also serialize this as one byte.
|
|
784
|
+
if (version > 0xfe || version === 0x50 || !!(version & 1))
|
|
785
|
+
throw new Error(`P2TR: invalid leafVersion=${version}`);
|
|
786
|
+
return version;
|
|
787
|
+
};
|
|
788
|
+
/**
|
|
789
|
+
* Computes the tagged hash of a tapleaf script.
|
|
790
|
+
* @param script - tapleaf script bytes
|
|
791
|
+
* @param version - base even tapleaf version byte (for tapscript, `0xc0`)
|
|
792
|
+
* @returns Tapleaf hash.
|
|
793
|
+
* @throws If the tapleaf version is not a valid even one-byte version.
|
|
794
|
+
* {@link Error}
|
|
795
|
+
* @example
|
|
796
|
+
* Hash a finalized tapscript leaf before placing it into a Merkle tree.
|
|
797
|
+
* ```ts
|
|
798
|
+
* tapLeafHash(new Uint8Array([0x51]));
|
|
799
|
+
* ```
|
|
800
|
+
*/
|
|
801
|
+
export const tapLeafHash = (script, version = TAP_LEAF_VERSION) => u.tagSchnorr('TapLeaf', new Uint8Array([tapLeafVersion(version)]), VarBytes.encode(script));
|
|
474
802
|
export function p2tr(internalPubKey, tree, network = NETWORK, allowUnknownOutputs = false, customScripts) {
|
|
475
803
|
// Unspendable
|
|
476
804
|
if (!internalPubKey && !tree)
|
|
@@ -484,14 +812,21 @@ export function p2tr(internalPubKey, tree, network = NETWORK, allowUnknownOutput
|
|
|
484
812
|
let hashedTree = taprootAddPath(taprootHashTree(tree, pubKey, allowUnknownOutputs, customScripts));
|
|
485
813
|
const tapMerkleRoot = hashedTree.hash;
|
|
486
814
|
const [tweakedPubkey, parity] = u.taprootTweakPubkey(pubKey, tapMerkleRoot);
|
|
487
|
-
const
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
815
|
+
const tapLeafScript = [];
|
|
816
|
+
const leaves = taprootWalkTree(hashedTree).map((l) => {
|
|
817
|
+
const version = tapLeafVersion(l.version);
|
|
818
|
+
// Leaf versions are stored as the base even byte; only the control block adds the
|
|
819
|
+
// output-key parity bit required by BIP 341 script-path spending.
|
|
820
|
+
const controlBlock = {
|
|
821
|
+
version: version + parity,
|
|
491
822
|
internalKey: pubKey,
|
|
492
823
|
merklePath: l.path,
|
|
493
|
-
}
|
|
494
|
-
|
|
824
|
+
};
|
|
825
|
+
// Skip an encode/decode copy for performance; callers must treat returned metadata as
|
|
826
|
+
// immutable.
|
|
827
|
+
tapLeafScript.push([controlBlock, u.concatBytes(l.script, new Uint8Array([version]))]);
|
|
828
|
+
return { ...l, controlBlock: TaprootControlBlock.encode(controlBlock) };
|
|
829
|
+
});
|
|
495
830
|
return {
|
|
496
831
|
type: 'tr',
|
|
497
832
|
script: OutScript.encode({ type: 'tr', pubkey: tweakedPubkey }),
|
|
@@ -501,14 +836,13 @@ export function p2tr(internalPubKey, tree, network = NETWORK, allowUnknownOutput
|
|
|
501
836
|
// PSBT stuff
|
|
502
837
|
tapInternalKey: pubKey,
|
|
503
838
|
leaves,
|
|
504
|
-
tapLeafScript
|
|
505
|
-
TaprootControlBlock.decode(l.controlBlock),
|
|
506
|
-
u.concatBytes(l.script, new Uint8Array([l.version || TAP_LEAF_VERSION])),
|
|
507
|
-
]),
|
|
839
|
+
tapLeafScript,
|
|
508
840
|
tapMerkleRoot,
|
|
509
841
|
};
|
|
510
842
|
}
|
|
511
843
|
else {
|
|
844
|
+
// BIP 341 / BIP 86: key-only Taproot still tweaks with the empty Merkle root so the
|
|
845
|
+
// output commits to an unspendable script path instead of leaving the key untweaked.
|
|
512
846
|
const tweakedPubkey = u.taprootTweakPubkey(pubKey, P.EMPTY)[0];
|
|
513
847
|
return {
|
|
514
848
|
type: 'tr',
|
|
@@ -522,18 +856,33 @@ export function p2tr(internalPubKey, tree, network = NETWORK, allowUnknownOutput
|
|
|
522
856
|
}
|
|
523
857
|
}
|
|
524
858
|
// Returns all combinations of size M from lst
|
|
859
|
+
/**
|
|
860
|
+
* Returns all size-`m` combinations from a list.
|
|
861
|
+
* @param m - size of each combination
|
|
862
|
+
* @param list - input items to combine
|
|
863
|
+
* @returns Array of combinations.
|
|
864
|
+
* @throws If the combination size or input list is invalid. {@link Error}
|
|
865
|
+
* @example
|
|
866
|
+
* Enumerate all size-two subsets of a short list.
|
|
867
|
+
* ```ts
|
|
868
|
+
* combinations(2, [1, 2, 3]);
|
|
869
|
+
* ```
|
|
870
|
+
*/
|
|
525
871
|
export function combinations(m, list) {
|
|
526
872
|
const res = [];
|
|
527
873
|
if (!Array.isArray(list))
|
|
528
874
|
throw new Error('combinations: lst arg should be array');
|
|
529
875
|
const n = list.length;
|
|
530
|
-
|
|
531
|
-
|
|
876
|
+
anumber(m, 'm');
|
|
877
|
+
if (m < 1 || m > n)
|
|
878
|
+
throw new Error('combinations: m must satisfy 1 <= m <= lst.length');
|
|
532
879
|
/*
|
|
533
880
|
Basically works as M nested loops like:
|
|
534
881
|
for (;idx[0]<lst.length;idx[0]++) for (idx[1]=idx[0]+1;idx[1]<lst.length;idx[1]++)
|
|
535
882
|
but since we cannot create nested loops dynamically, we unroll it to a single loop
|
|
536
883
|
*/
|
|
884
|
+
// This unrolled-loop implementation assumes an integer 1 <= m <= n; zero, negative, and
|
|
885
|
+
// fractional m values need an explicit guard before entering the loop.
|
|
537
886
|
const idx = Array.from({ length: m }, (_, i) => i);
|
|
538
887
|
const last = idx.length - 1;
|
|
539
888
|
main: for (;;) {
|
|
@@ -555,6 +904,21 @@ export function combinations(m, list) {
|
|
|
555
904
|
}
|
|
556
905
|
return res;
|
|
557
906
|
}
|
|
907
|
+
/**
|
|
908
|
+
* Builds the leaf set for an M-of-N `CHECKSIGVERIFY` taproot policy.
|
|
909
|
+
* @param m - number of required signatures
|
|
910
|
+
* @param pubkeys - participating Schnorr public keys
|
|
911
|
+
* @param allowSamePubkeys - whether duplicate keys are allowed
|
|
912
|
+
* @returns Array of taproot leaf descriptors.
|
|
913
|
+
* @throws If the taproot multisig parameters are invalid. {@link Error}
|
|
914
|
+
* @example
|
|
915
|
+
* Build the leaf set for an M-of-N taproot `CHECKSIGVERIFY` policy.
|
|
916
|
+
* ```ts
|
|
917
|
+
* import { hex } from '@scure/base';
|
|
918
|
+
* import { p2tr_ns } from '@scure/btc-signer/payment.js';
|
|
919
|
+
* p2tr_ns(1, [hex.decode('f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9')], true);
|
|
920
|
+
* ```
|
|
921
|
+
*/
|
|
558
922
|
export const p2tr_ns = (m, pubkeys, allowSamePubkeys = false) => {
|
|
559
923
|
if (!allowSamePubkeys)
|
|
560
924
|
uniqPubkey(pubkeys);
|
|
@@ -563,7 +927,37 @@ export const p2tr_ns = (m, pubkeys, allowSamePubkeys = false) => {
|
|
|
563
927
|
script: OutScript.encode({ type: 'tr_ns', pubkeys: i }),
|
|
564
928
|
}));
|
|
565
929
|
};
|
|
930
|
+
/**
|
|
931
|
+
* Builds a single-key taproot leaf script.
|
|
932
|
+
* BIP 341 design guidance: if this is the most likely single-key spend path, prefer
|
|
933
|
+
* using that key as the `p2tr()` internal key instead of forcing it into a script leaf.
|
|
934
|
+
* @param pubkey - Schnorr public key
|
|
935
|
+
* @returns Taproot single-key leaf descriptor.
|
|
936
|
+
* @throws If the taproot single-key leaf cannot be encoded. {@link Error}
|
|
937
|
+
* @example
|
|
938
|
+
* Build a single-key tapscript leaf.
|
|
939
|
+
* ```ts
|
|
940
|
+
* import { hex } from '@scure/base';
|
|
941
|
+
* import { p2tr_pk } from '@scure/btc-signer/payment.js';
|
|
942
|
+
* p2tr_pk(hex.decode('f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9'));
|
|
943
|
+
* ```
|
|
944
|
+
*/
|
|
566
945
|
export const p2tr_pk = (pubkey) => p2tr_ns(1, [pubkey], undefined)[0];
|
|
946
|
+
/**
|
|
947
|
+
* Builds a `CHECKSIGADD` taproot multisig leaf.
|
|
948
|
+
* @param m - number of required signatures
|
|
949
|
+
* @param pubkeys - participating Schnorr public keys
|
|
950
|
+
* @param allowSamePubkeys - whether duplicate keys are allowed
|
|
951
|
+
* @returns Taproot multisig leaf descriptor.
|
|
952
|
+
* @throws If the taproot multisig parameters are invalid. {@link Error}
|
|
953
|
+
* @example
|
|
954
|
+
* Build a `CHECKSIGADD` taproot multisig leaf.
|
|
955
|
+
* ```ts
|
|
956
|
+
* import { hex } from '@scure/base';
|
|
957
|
+
* import { p2tr_ms } from '@scure/btc-signer/payment.js';
|
|
958
|
+
* p2tr_ms(1, [hex.decode('f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9')], true);
|
|
959
|
+
* ```
|
|
960
|
+
*/
|
|
567
961
|
export function p2tr_ms(m, pubkeys, allowSamePubkeys = false) {
|
|
568
962
|
if (!allowSamePubkeys)
|
|
569
963
|
uniqPubkey(pubkeys);
|
|
@@ -573,10 +967,28 @@ export function p2tr_ms(m, pubkeys, allowSamePubkeys = false) {
|
|
|
573
967
|
};
|
|
574
968
|
}
|
|
575
969
|
// Simple pubkey address, without complex scripts
|
|
970
|
+
/**
|
|
971
|
+
* Derives a simple address from a private key.
|
|
972
|
+
* @param type - address type to derive
|
|
973
|
+
* @param privKey - private key bytes
|
|
974
|
+
* @param network - address network parameters
|
|
975
|
+
* @returns Encoded Bitcoin address.
|
|
976
|
+
* @throws If the requested address type is unknown. {@link Error}
|
|
977
|
+
* @example
|
|
978
|
+
* Pick the output type first, then derive the matching address from the private key.
|
|
979
|
+
* ```ts
|
|
980
|
+
* import { getAddress } from '@scure/btc-signer/payment.js';
|
|
981
|
+
* import { randomPrivateKeyBytes } from '@scure/btc-signer/utils.js';
|
|
982
|
+
* getAddress('wpkh', randomPrivateKeyBytes());
|
|
983
|
+
* ```
|
|
984
|
+
*/
|
|
576
985
|
export function getAddress(type, privKey, network = NETWORK) {
|
|
986
|
+
u.astring(type, 'type');
|
|
577
987
|
if (type === 'tr') {
|
|
578
988
|
return p2tr(u.pubSchnorr(privKey), undefined, network).address;
|
|
579
989
|
}
|
|
990
|
+
// This convenience wrapper always uses the compressed ECDSA public key; derive
|
|
991
|
+
// `pubECDSA(privKey, false)` and call `p2pkh(...)` directly for legacy uncompressed P2PKH.
|
|
580
992
|
const pubKey = u.pubECDSA(privKey);
|
|
581
993
|
if (type === 'pkh')
|
|
582
994
|
return p2pkh(pubKey, network).address;
|
|
@@ -584,15 +996,65 @@ export function getAddress(type, privKey, network = NETWORK) {
|
|
|
584
996
|
return p2wpkh(pubKey, network).address;
|
|
585
997
|
throw new Error(`getAddress: unknown type=${type}`);
|
|
586
998
|
}
|
|
999
|
+
// BIP67 defines canonical multisig ordering only for compressed pubkeys; this helper still sorts
|
|
1000
|
+
// raw bytes generically, and higher-level callers may accept uncompressed participants for compat.
|
|
587
1001
|
export const _sortPubkeys = (pubkeys) => Array.from(pubkeys).sort(u.compareBytes);
|
|
1002
|
+
/**
|
|
1003
|
+
* Builds a classic M-of-N multisig output, wrapped in P2SH or P2WSH.
|
|
1004
|
+
* @param m - number of required signatures
|
|
1005
|
+
* @param pubkeys - participating public keys
|
|
1006
|
+
* @param sorted - whether to sort the public keys first
|
|
1007
|
+
* @param witness - whether to wrap the result as native SegWit
|
|
1008
|
+
* @param network - address network parameters
|
|
1009
|
+
* @returns Multisig payment descriptor.
|
|
1010
|
+
* @throws If the multisig parameters or wrapped script are invalid. {@link Error}
|
|
1011
|
+
* @example
|
|
1012
|
+
* Wrap a classic 2-of-2 script into an addressable multisig output.
|
|
1013
|
+
* ```ts
|
|
1014
|
+
* import { multisig } from '@scure/btc-signer/payment.js';
|
|
1015
|
+
* import { pubECDSA, randomPrivateKeyBytes } from '@scure/btc-signer/utils.js';
|
|
1016
|
+
* multisig(
|
|
1017
|
+
* 2,
|
|
1018
|
+
* [pubECDSA(randomPrivateKeyBytes()), pubECDSA(randomPrivateKeyBytes())],
|
|
1019
|
+
* true,
|
|
1020
|
+
* true
|
|
1021
|
+
* );
|
|
1022
|
+
* ```
|
|
1023
|
+
*/
|
|
588
1024
|
export function multisig(m, pubkeys, sorted = false, witness = false, network = NETWORK) {
|
|
1025
|
+
// BIP 143 default policy: version-0 witness programs should use compressed ECDSA pubkeys only;
|
|
1026
|
+
// witness multisig callers must avoid uncompressed keys because p2ms accepts generic ECDSA encodings.
|
|
1027
|
+
// BIP 16 caps spendable compressed-key P2SH multisig at 15 pubkeys because larger redeem scripts
|
|
1028
|
+
// exceed the 520-byte push limit; use witness=true for larger classic multisig sets.
|
|
589
1029
|
const ms = p2ms(m, sorted ? _sortPubkeys(pubkeys) : pubkeys);
|
|
590
|
-
return witness ? p2wsh(ms, network) : p2sh(ms, network);
|
|
1030
|
+
return (witness ? p2wsh(ms, network) : p2sh(ms, network));
|
|
591
1031
|
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Builds a multisig output after lexicographically sorting the keys.
|
|
1034
|
+
* @param m - number of required signatures
|
|
1035
|
+
* @param pubkeys - participating public keys
|
|
1036
|
+
* @param witness - whether to wrap the result as native SegWit
|
|
1037
|
+
* @param network - address network parameters
|
|
1038
|
+
* @returns Sorted multisig payment descriptor.
|
|
1039
|
+
* @throws If the multisig parameters or wrapped script are invalid. {@link Error}
|
|
1040
|
+
* @example
|
|
1041
|
+
* Sort public keys deterministically before constructing the multisig address.
|
|
1042
|
+
* ```ts
|
|
1043
|
+
* import { sortedMultisig } from '@scure/btc-signer/payment.js';
|
|
1044
|
+
* import { pubECDSA, randomPrivateKeyBytes } from '@scure/btc-signer/utils.js';
|
|
1045
|
+
* sortedMultisig(
|
|
1046
|
+
* 2,
|
|
1047
|
+
* [pubECDSA(randomPrivateKeyBytes()), pubECDSA(randomPrivateKeyBytes())],
|
|
1048
|
+
* true
|
|
1049
|
+
* );
|
|
1050
|
+
* ```
|
|
1051
|
+
*/
|
|
592
1052
|
export function sortedMultisig(m, pubkeys, witness = false, network = NETWORK) {
|
|
1053
|
+
// BIP67 canonical multisig is compressed-only, but this wrapper intentionally keeps the generic
|
|
1054
|
+
// sorted-multisig behavior and still allows uncompressed participant keys for compatibility.
|
|
593
1055
|
return multisig(m, pubkeys, true, witness, network);
|
|
594
1056
|
}
|
|
595
|
-
const base58check = createBase58check(u.sha256);
|
|
1057
|
+
const base58check = /* @__PURE__ */ createBase58check(u.sha256);
|
|
596
1058
|
function validateWitness(version, data) {
|
|
597
1059
|
if (data.length < 2 || data.length > 40)
|
|
598
1060
|
throw new Error('Witness: invalid length');
|
|
@@ -603,15 +1065,31 @@ function validateWitness(version, data) {
|
|
|
603
1065
|
}
|
|
604
1066
|
function programToWitness(version, data, network = NETWORK) {
|
|
605
1067
|
validateWitness(version, data);
|
|
1068
|
+
// BIP 350 keeps segwit v0 on Bech32, while witness versions 1+ switch to Bech32m.
|
|
606
1069
|
const coder = version === 0 ? bech32 : bech32m;
|
|
607
1070
|
return coder.encode(network.bech32, [version].concat(coder.toWords(data)));
|
|
608
1071
|
}
|
|
609
1072
|
function formatKey(hashed, prefix) {
|
|
1073
|
+
// Legacy Base58Check paths all serialize [version-byte || payload] before checksumming.
|
|
610
1074
|
return base58check.encode(u.concatBytes(Uint8Array.from(prefix), hashed));
|
|
611
1075
|
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Wallet-import-format coder for private keys.
|
|
1078
|
+
* @param network - address network parameters
|
|
1079
|
+
* @returns WIF coder.
|
|
1080
|
+
* @example
|
|
1081
|
+
* Encode or decode wallet-import-format private keys.
|
|
1082
|
+
* ```ts
|
|
1083
|
+
* const coder = WIF();
|
|
1084
|
+
* coder.encode(new Uint8Array(32).fill(1));
|
|
1085
|
+
* ```
|
|
1086
|
+
*/
|
|
612
1087
|
export function WIF(network = NETWORK) {
|
|
613
1088
|
return {
|
|
614
1089
|
encode(privKey) {
|
|
1090
|
+
// Compressed WIF is exactly 32 private-key bytes plus the 0x01 suffix; shorter or longer
|
|
1091
|
+
// inputs must be rejected instead of being silently padded or truncated by subarray().
|
|
1092
|
+
abytes(privKey, 32, 'privKey');
|
|
615
1093
|
const compressed = u.concatBytes(privKey, new Uint8Array([0x01]));
|
|
616
1094
|
return formatKey(compressed.subarray(0, 33), [network.wif]);
|
|
617
1095
|
},
|
|
@@ -630,16 +1108,35 @@ export function WIF(network = NETWORK) {
|
|
|
630
1108
|
};
|
|
631
1109
|
}
|
|
632
1110
|
// Returns OutType, which can be used to create outscript
|
|
1111
|
+
/**
|
|
1112
|
+
* Address encoder/decoder for a specific Bitcoin network.
|
|
1113
|
+
* @param network - address network parameters
|
|
1114
|
+
* @returns Address coder backed by the provided network.
|
|
1115
|
+
* @example
|
|
1116
|
+
* Create a network-specific address coder and encode a payment descriptor.
|
|
1117
|
+
* ```ts
|
|
1118
|
+
* import { Address, p2wpkh } from '@scure/btc-signer/payment.js';
|
|
1119
|
+
* import { pubECDSA, randomPrivateKeyBytes } from '@scure/btc-signer/utils.js';
|
|
1120
|
+
* const coder = Address();
|
|
1121
|
+
* coder.encode(p2wpkh(pubECDSA(randomPrivateKeyBytes())));
|
|
1122
|
+
* ```
|
|
1123
|
+
*/
|
|
633
1124
|
export function Address(network = NETWORK) {
|
|
1125
|
+
u.validateObject(network, {}, {}, 'network');
|
|
634
1126
|
return {
|
|
635
1127
|
encode(from) {
|
|
1128
|
+
u.validateObject(from, {}, {}, 'from');
|
|
636
1129
|
const { type } = from;
|
|
1130
|
+
u.astring(type, 'from.type');
|
|
637
1131
|
if (type === 'wpkh')
|
|
638
1132
|
return programToWitness(0, from.hash, network);
|
|
639
1133
|
else if (type === 'wsh')
|
|
640
1134
|
return programToWitness(0, from.hash, network);
|
|
641
1135
|
else if (type === 'tr')
|
|
642
1136
|
return programToWitness(1, from.pubkey, network);
|
|
1137
|
+
// BIP433 P2A is the fixed v1 witness program 0x4e73 ('bc1pfeessrawgf').
|
|
1138
|
+
else if (type === 'p2a')
|
|
1139
|
+
return programToWitness(1, P2A_PROGRAM, network);
|
|
643
1140
|
else if (type === 'pkh')
|
|
644
1141
|
return formatKey(from.hash, [network.pubKeyHash]);
|
|
645
1142
|
else if (type === 'sh')
|
|
@@ -647,6 +1144,7 @@ export function Address(network = NETWORK) {
|
|
|
647
1144
|
throw new Error(`Unknown address type=${type}`);
|
|
648
1145
|
},
|
|
649
1146
|
decode(address) {
|
|
1147
|
+
u.astring(address, 'address');
|
|
650
1148
|
if (address.length < 14 || address.length > 74)
|
|
651
1149
|
throw new Error('Invalid address length');
|
|
652
1150
|
// Bech32
|
|
@@ -674,6 +1172,10 @@ export function Address(network = NETWORK) {
|
|
|
674
1172
|
return { type: 'wpkh', hash: data };
|
|
675
1173
|
else if (version === 1 && data.length === 32)
|
|
676
1174
|
return { type: 'tr', pubkey: data };
|
|
1175
|
+
else if (version === 1 && u.equalBytes(data, P2A_PROGRAM))
|
|
1176
|
+
return { type: 'p2a', script: Script.encode([1, data]) };
|
|
1177
|
+
// Future witness versions can still be valid addresses, but this helper
|
|
1178
|
+
// only returns typed descriptors for recognized v0, taproot and P2A templates.
|
|
677
1179
|
else
|
|
678
1180
|
throw new Error('Unknown witness program');
|
|
679
1181
|
}
|
|
@@ -694,4 +1196,3 @@ export function Address(network = NETWORK) {
|
|
|
694
1196
|
},
|
|
695
1197
|
};
|
|
696
1198
|
}
|
|
697
|
-
//# sourceMappingURL=payment.js.map
|