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