@scure/btc-signer 2.0.0 → 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 +25 -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/src/payment.ts
CHANGED
|
@@ -1,43 +1,57 @@
|
|
|
1
1
|
import { bech32, bech32m, type Coder, 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, type TransactionInput } from './psbt.ts';
|
|
4
|
-
import { OpToNum, Script, type ScriptType, VarBytes } from './script.ts';
|
|
6
|
+
import { MAX_SCRIPT_BYTE_LENGTH, OpToNum, Script, type ScriptType, VarBytes } from './script.ts';
|
|
5
7
|
import * as u from './utils.ts';
|
|
6
|
-
import { type BTC_NETWORK, type Bytes, NETWORK } from './utils.ts';
|
|
8
|
+
import { type BTC_NETWORK, type Bytes, NETWORK, type TArg, type TRet } from './utils.ts';
|
|
7
9
|
|
|
8
10
|
// We need following items:
|
|
9
11
|
// - encode/decode output script
|
|
10
12
|
// - generate input script
|
|
11
13
|
// - generate address/output/redeem from user input
|
|
12
14
|
// P2ret represents generic interface for all p2* methods
|
|
15
|
+
/** Common shape returned by payment helper constructors. */
|
|
13
16
|
export type P2Ret = {
|
|
17
|
+
/** Payment-script tag such as `pkh`, `wpkh`, or `tr`. */
|
|
14
18
|
type: string;
|
|
19
|
+
/** Serialized output script for the payment descriptor. */
|
|
15
20
|
script: Bytes;
|
|
21
|
+
/** Encoded address when the script has a standard address form. */
|
|
16
22
|
address?: string;
|
|
23
|
+
/** Redeem script for wrapped script-hash descriptors. */
|
|
17
24
|
redeemScript?: Bytes;
|
|
25
|
+
/** Witness script for SegWit script-hash descriptors. */
|
|
18
26
|
witnessScript?: Bytes;
|
|
27
|
+
/** Hash committed by the output script when applicable. */
|
|
19
28
|
hash?: Bytes;
|
|
20
29
|
};
|
|
21
30
|
|
|
22
31
|
// Pay to Anchor (P2A)
|
|
23
32
|
type OutP2AType = { type: 'p2a'; script: Bytes };
|
|
24
33
|
const OutP2A: Coder<OptScript, OutP2AType | undefined> = {
|
|
25
|
-
encode(from: ScriptType): OutP2AType | undefined {
|
|
34
|
+
encode(from: TArg<ScriptType>): TRet<OutP2AType | undefined> {
|
|
35
|
+
// BIP433 defines P2A as the exact OP_1 <0x4e73> scriptPubKey.
|
|
26
36
|
if (from.length !== 2 || from[0] !== 1 || !u.isBytes(from[1]) || hex.encode(from[1]) !== '4e73')
|
|
27
37
|
return;
|
|
28
|
-
return { type: 'p2a', script: Script.encode(from) }
|
|
38
|
+
return { type: 'p2a', script: Script.encode(from) } as TRet<OutP2AType | undefined>;
|
|
29
39
|
},
|
|
30
|
-
decode: (to: OutP2AType): OptScript => {
|
|
40
|
+
decode: (to: TArg<OutP2AType>): TRet<OptScript> => {
|
|
31
41
|
if (to.type !== 'p2a') return;
|
|
32
|
-
|
|
42
|
+
// The decoded object keeps `script` for caller convenience, but the `p2a`
|
|
43
|
+
// tag always canonicalizes back to the fixed BIP433 script.
|
|
44
|
+
return [1, hex.decode('4e73')] as TRet<OptScript>;
|
|
33
45
|
},
|
|
34
46
|
};
|
|
35
47
|
|
|
36
48
|
// Public Key (P2PK)
|
|
37
49
|
type OutPKType = { type: 'pk'; pubkey: Bytes };
|
|
50
|
+
/** Optional parsed script result used by output-script coders. */
|
|
38
51
|
export type OptScript = ScriptType | undefined;
|
|
39
52
|
|
|
40
|
-
function isValidPubkey(pub: Bytes
|
|
53
|
+
function isValidPubkey(pub: TArg<Bytes>, type: u.PubT): boolean {
|
|
54
|
+
// Payment coders use a boolean guard here and normalize validatePubkey failures to false.
|
|
41
55
|
try {
|
|
42
56
|
u.validatePubkey(pub, type);
|
|
43
57
|
return true;
|
|
@@ -47,7 +61,9 @@ function isValidPubkey(pub: Bytes, type: u.PubT): boolean {
|
|
|
47
61
|
}
|
|
48
62
|
|
|
49
63
|
const OutPK: Coder<OptScript, OutPKType | undefined> = {
|
|
50
|
-
encode(from: ScriptType): OutPKType | undefined {
|
|
64
|
+
encode(from: TArg<ScriptType>): TRet<OutPKType | undefined> {
|
|
65
|
+
// BIP380/BIP381 `pk(KEY)` only admits SEC1 ECDSA pubkeys here; x-only
|
|
66
|
+
// 32-byte CHECKSIG scripts are left for the later tapscript coders.
|
|
51
67
|
if (
|
|
52
68
|
from.length !== 2 ||
|
|
53
69
|
!u.isBytes(from[0]) ||
|
|
@@ -55,61 +71,87 @@ const OutPK: Coder<OptScript, OutPKType | undefined> = {
|
|
|
55
71
|
from[1] !== 'CHECKSIG'
|
|
56
72
|
)
|
|
57
73
|
return;
|
|
58
|
-
return { type: 'pk', pubkey: from[0] }
|
|
74
|
+
return { type: 'pk', pubkey: from[0] } as TRet<OutPKType | undefined>;
|
|
75
|
+
},
|
|
76
|
+
decode: (to: TArg<OutPKType>): TRet<OptScript> => {
|
|
77
|
+
if (to.type !== 'pk') return;
|
|
78
|
+
// OutScript validates `pk.pubkey` before this branch emits the canonical
|
|
79
|
+
// `<pubkey> CHECKSIG` script.
|
|
80
|
+
return [to.pubkey, 'CHECKSIG'] as TRet<OptScript>;
|
|
59
81
|
},
|
|
60
|
-
decode: (to: OutPKType): OptScript => (to.type === 'pk' ? [to.pubkey, 'CHECKSIG'] : undefined),
|
|
61
82
|
};
|
|
62
83
|
|
|
63
84
|
// Public Key Hash (P2PKH)
|
|
64
85
|
type OutPKHType = { type: 'pkh'; hash: Bytes };
|
|
65
86
|
const OutPKH: Coder<OptScript, OutPKHType | undefined> = {
|
|
66
|
-
encode(from: ScriptType): OutPKHType | undefined {
|
|
87
|
+
encode(from: TArg<ScriptType>): TRet<OutPKHType | undefined> {
|
|
67
88
|
if (from.length !== 5 || from[0] !== 'DUP' || from[1] !== 'HASH160' || !u.isBytes(from[2]))
|
|
68
89
|
return;
|
|
90
|
+
// OutScript validates that the pushed HASH160 is exactly 20 bytes.
|
|
91
|
+
// This child matcher only recognizes the canonical P2PKH opcode skeleton.
|
|
69
92
|
if (from[3] !== 'EQUALVERIFY' || from[4] !== 'CHECKSIG') return;
|
|
70
|
-
return { type: 'pkh', hash: from[2] }
|
|
93
|
+
return { type: 'pkh', hash: from[2] } as TRet<OutPKHType | undefined>;
|
|
71
94
|
},
|
|
72
|
-
|
|
73
|
-
|
|
95
|
+
// OutScript validates `pkh.hash` before this branch emits the canonical
|
|
96
|
+
// `DUP HASH160 <hash> EQUALVERIFY CHECKSIG` script.
|
|
97
|
+
decode: (to: TArg<OutPKHType>): TRet<OptScript> =>
|
|
98
|
+
(to.type === 'pkh'
|
|
99
|
+
? ['DUP', 'HASH160', to.hash, 'EQUALVERIFY', 'CHECKSIG']
|
|
100
|
+
: undefined) as TRet<OptScript>,
|
|
74
101
|
};
|
|
75
102
|
// Script Hash (P2SH)
|
|
76
103
|
type OutSHType = { type: 'sh'; hash: Bytes };
|
|
77
104
|
const OutSH: Coder<OptScript, OutSHType | undefined> = {
|
|
78
|
-
encode(from: ScriptType): OutSHType | undefined {
|
|
105
|
+
encode(from: TArg<ScriptType>): TRet<OutSHType | undefined> {
|
|
79
106
|
if (from.length !== 3 || from[0] !== 'HASH160' || !u.isBytes(from[1]) || from[2] !== 'EQUAL')
|
|
80
107
|
return;
|
|
81
|
-
|
|
108
|
+
// OutScript validates that the pushed HASH160 is exactly 20 bytes.
|
|
109
|
+
// This child matcher only recognizes the canonical P2SH opcode skeleton.
|
|
110
|
+
return { type: 'sh', hash: from[1] } as TRet<OutSHType | undefined>;
|
|
82
111
|
},
|
|
83
|
-
|
|
84
|
-
|
|
112
|
+
// OutScript validates `sh.hash` before this branch emits the canonical
|
|
113
|
+
// `HASH160 <hash> EQUAL` script.
|
|
114
|
+
decode: (to: TArg<OutSHType>): TRet<OptScript> =>
|
|
115
|
+
(to.type === 'sh' ? ['HASH160', to.hash, 'EQUAL'] : undefined) as TRet<OptScript>,
|
|
85
116
|
};
|
|
86
117
|
|
|
87
118
|
// Witness Script Hash (P2WSH)
|
|
88
119
|
type OutWSHType = { type: 'wsh'; hash: Bytes };
|
|
89
120
|
const OutWSH: Coder<OptScript, OutWSHType | undefined> = {
|
|
90
|
-
encode(from: ScriptType): OutWSHType | undefined {
|
|
121
|
+
encode(from: TArg<ScriptType>): TRet<OutWSHType | undefined> {
|
|
91
122
|
if (from.length !== 2 || from[0] !== 0 || !u.isBytes(from[1])) return;
|
|
123
|
+
// BIP382 `wsh()` is specifically the version-0 32-byte witness program.
|
|
124
|
+
// Other witness versions stay with the later coders.
|
|
92
125
|
if (from[1].length !== 32) return;
|
|
93
|
-
return { type: 'wsh', hash: from[1] }
|
|
126
|
+
return { type: 'wsh', hash: from[1] } as TRet<OutWSHType | undefined>;
|
|
94
127
|
},
|
|
95
|
-
|
|
128
|
+
// OutScript validates `wsh.hash` before this branch emits the canonical
|
|
129
|
+
// version-0 32-byte witness program.
|
|
130
|
+
decode: (to: TArg<OutWSHType>): TRet<OptScript> =>
|
|
131
|
+
(to.type === 'wsh' ? [0, to.hash] : undefined) as TRet<OptScript>,
|
|
96
132
|
};
|
|
97
133
|
|
|
98
134
|
// Witness Public Key Hash (P2WPKH)
|
|
99
135
|
type OutWPKHType = { type: 'wpkh'; hash: Bytes };
|
|
100
136
|
const OutWPKH: Coder<OptScript, OutWPKHType | undefined> = {
|
|
101
|
-
encode(from: ScriptType): OutWPKHType | undefined {
|
|
137
|
+
encode(from: TArg<ScriptType>): TRet<OutWPKHType | undefined> {
|
|
102
138
|
if (from.length !== 2 || from[0] !== 0 || !u.isBytes(from[1])) return;
|
|
139
|
+
// BIP382 `wpkh()` is specifically the version-0 20-byte witness program.
|
|
140
|
+
// Compressed-key restrictions are enforced upstream, and other witness
|
|
141
|
+
// versions stay with the later coders.
|
|
103
142
|
if (from[1].length !== 20) return;
|
|
104
|
-
return { type: 'wpkh', hash: from[1] }
|
|
143
|
+
return { type: 'wpkh', hash: from[1] } as TRet<OutWPKHType | undefined>;
|
|
105
144
|
},
|
|
106
|
-
|
|
145
|
+
// OutScript validates `wpkh.hash` before this branch emits the canonical
|
|
146
|
+
// version-0 20-byte witness program.
|
|
147
|
+
decode: (to: TArg<OutWPKHType>): TRet<OptScript> =>
|
|
148
|
+
(to.type === 'wpkh' ? [0, to.hash] : undefined) as TRet<OptScript>,
|
|
107
149
|
};
|
|
108
150
|
|
|
109
151
|
// Multisig (P2MS)
|
|
110
152
|
type OutMSType = { type: 'ms'; pubkeys: Bytes[]; m: number };
|
|
111
153
|
const OutMS: Coder<OptScript, OutMSType | undefined> = {
|
|
112
|
-
encode(from: ScriptType): OutMSType | undefined {
|
|
154
|
+
encode(from: TArg<ScriptType>): TRet<OutMSType | undefined> {
|
|
113
155
|
const last = from.length - 1;
|
|
114
156
|
if (from[last] !== 'CHECKMULTISIG') return;
|
|
115
157
|
const m = from[0];
|
|
@@ -118,26 +160,40 @@ const OutMS: Coder<OptScript, OutMSType | undefined> = {
|
|
|
118
160
|
const pubkeys = from.slice(1, -2);
|
|
119
161
|
if (n !== pubkeys.length) return;
|
|
120
162
|
for (const pub of pubkeys) if (!u.isBytes(pub)) return;
|
|
121
|
-
|
|
163
|
+
// OutScript validates pubkey encodings and `0 < m <= n <= 16`.
|
|
164
|
+
// This child matcher only recognizes the canonical CHECKMULTISIG skeleton.
|
|
165
|
+
// We don't need n here because it is the same as pubkeys.length.
|
|
166
|
+
return { type: 'ms', m, pubkeys: pubkeys as Bytes[] } as TRet<OutMSType | undefined>;
|
|
122
167
|
},
|
|
123
168
|
// checkmultisig(n, ..pubkeys, m)
|
|
124
|
-
decode: (to: OutMSType): OptScript =>
|
|
125
|
-
|
|
169
|
+
decode: (to: TArg<OutMSType>): TRet<OptScript> =>
|
|
170
|
+
// OutScript validates multisig pubkeys and `0 < m <= n <= 16`.
|
|
171
|
+
// This branch only emits the canonical `m <pubkeys...> n CHECKMULTISIG`
|
|
172
|
+
// script.
|
|
173
|
+
(to.type === 'ms'
|
|
174
|
+
? [to.m, ...to.pubkeys, to.pubkeys.length, 'CHECKMULTISIG']
|
|
175
|
+
: undefined) as TRet<OptScript>,
|
|
126
176
|
};
|
|
127
177
|
// Taproot (P2TR)
|
|
128
178
|
type OutTRType = { type: 'tr'; pubkey: Bytes };
|
|
129
179
|
const OutTR: Coder<OptScript, OutTRType | undefined> = {
|
|
130
|
-
encode(from: ScriptType): OutTRType | undefined {
|
|
131
|
-
|
|
132
|
-
|
|
180
|
+
encode(from: TArg<ScriptType>): TRet<OutTRType | undefined> {
|
|
181
|
+
// BIP141 witness programs are `OP_0..OP_16` followed by a direct 2..40-byte push.
|
|
182
|
+
// BIP341 assigns native taproot meaning only to version 1 with a 32-byte x-only program;
|
|
183
|
+
// other OP_1 program lengths remain reserved future witness programs and should fall through.
|
|
184
|
+
if (from.length !== 2 || from[0] !== 1 || !u.isBytes(from[1]) || from[1].length !== 32) return;
|
|
185
|
+
return { type: 'tr', pubkey: from[1] } as TRet<OutTRType | undefined>;
|
|
133
186
|
},
|
|
134
|
-
|
|
187
|
+
// OutScript validates `tr.pubkey` before this branch emits the canonical
|
|
188
|
+
// version-1 32-byte witness program.
|
|
189
|
+
decode: (to: TArg<OutTRType>): TRet<OptScript> =>
|
|
190
|
+
(to.type === 'tr' ? [1, to.pubkey] : undefined) as TRet<OptScript>,
|
|
135
191
|
};
|
|
136
192
|
|
|
137
193
|
// Taproot N-of-N multisig (P2TR_NS)
|
|
138
194
|
type OutTRNSType = { type: 'tr_ns'; pubkeys: Bytes[] };
|
|
139
195
|
const OutTRNS: Coder<OptScript, OutTRNSType | undefined> = {
|
|
140
|
-
encode(from: ScriptType): OutTRNSType | undefined {
|
|
196
|
+
encode(from: TArg<ScriptType>): TRet<OutTRNSType | undefined> {
|
|
141
197
|
const last = from.length - 1;
|
|
142
198
|
if (from[last] !== 'CHECKSIG') return;
|
|
143
199
|
const pubkeys = [];
|
|
@@ -148,24 +204,32 @@ const OutTRNS: Coder<OptScript, OutTRNSType | undefined> = {
|
|
|
148
204
|
if (elm !== 'CHECKSIGVERIFY' || i === last - 1) return;
|
|
149
205
|
continue;
|
|
150
206
|
}
|
|
151
|
-
|
|
207
|
+
// Require actual Schnorr pubkeys here so near-miss `<bytes> CHECKSIG`
|
|
208
|
+
// scripts fall through to OutUnknown instead of failing later.
|
|
209
|
+
if (!u.isBytes(elm) || !isValidPubkey(elm, u.PubT.schnorr)) return;
|
|
152
210
|
pubkeys.push(elm);
|
|
153
211
|
}
|
|
154
|
-
|
|
212
|
+
// BIP342 "Using a k-of-k script for every combination" documents the shape
|
|
213
|
+
// `<pubkey_1> CHECKSIGVERIFY ... <pubkey_n> CHECKSIG`; this matcher only
|
|
214
|
+
// classifies that embedded-pubkey form, so bare CHECKSIG stays unknown.
|
|
215
|
+
if (!pubkeys.length) return;
|
|
216
|
+
return { type: 'tr_ns', pubkeys } as TRet<OutTRNSType | undefined>;
|
|
155
217
|
},
|
|
156
|
-
decode: (to: OutTRNSType): OptScript => {
|
|
218
|
+
decode: (to: TArg<OutTRNSType>): TRet<OptScript> => {
|
|
157
219
|
if (to.type !== 'tr_ns') return;
|
|
158
220
|
const out: ScriptType = [];
|
|
159
221
|
for (let i = 0; i < to.pubkeys.length - 1; i++) out.push(to.pubkeys[i], 'CHECKSIGVERIFY');
|
|
222
|
+
// This branch assumes at least one Schnorr pubkey; [] would otherwise emit
|
|
223
|
+
// `[undefined, CHECKSIG]` and only fail later in Script.encode.
|
|
160
224
|
out.push(to.pubkeys[to.pubkeys.length - 1], 'CHECKSIG');
|
|
161
|
-
return out
|
|
225
|
+
return out as TRet<OptScript>;
|
|
162
226
|
},
|
|
163
227
|
};
|
|
164
228
|
|
|
165
229
|
// Taproot M-of-N Multisig (P2TR_MS)
|
|
166
230
|
type OutTRMSType = { type: 'tr_ms'; pubkeys: Bytes[]; m: number };
|
|
167
231
|
const OutTRMS: Coder<OptScript, OutTRMSType | undefined> = {
|
|
168
|
-
encode(from: ScriptType): OutTRMSType | undefined {
|
|
232
|
+
encode(from: TArg<ScriptType>): TRet<OutTRMSType | undefined> {
|
|
169
233
|
const last = from.length - 1;
|
|
170
234
|
if (from[last] !== 'NUMEQUAL' || from[1] !== 'CHECKSIG') return;
|
|
171
235
|
const pubkeys = [];
|
|
@@ -173,37 +237,48 @@ const OutTRMS: Coder<OptScript, OutTRMSType | undefined> = {
|
|
|
173
237
|
if (typeof m !== 'number') return;
|
|
174
238
|
for (let i = 0; i < last - 1; i++) {
|
|
175
239
|
const elm = from[i];
|
|
240
|
+
// Structural mismatches should fall through to OutUnknown instead of
|
|
241
|
+
// throwing from the tr_ms matcher.
|
|
176
242
|
if (i & 1) {
|
|
177
|
-
if (elm !== (i === 1 ? 'CHECKSIG' : 'CHECKSIGADD'))
|
|
178
|
-
throw new Error('OutScript.encode/tr_ms: wrong element');
|
|
243
|
+
if (elm !== (i === 1 ? 'CHECKSIG' : 'CHECKSIGADD')) return;
|
|
179
244
|
continue;
|
|
180
245
|
}
|
|
181
|
-
if (!u.isBytes(elm))
|
|
246
|
+
if (!u.isBytes(elm)) return;
|
|
182
247
|
pubkeys.push(elm);
|
|
183
248
|
}
|
|
184
|
-
return { type: 'tr_ms', pubkeys, m }
|
|
249
|
+
return { type: 'tr_ms', pubkeys, m } as TRet<OutTRMSType | undefined>;
|
|
185
250
|
},
|
|
186
|
-
decode: (to: OutTRMSType): OptScript => {
|
|
251
|
+
decode: (to: TArg<OutTRMSType>): TRet<OptScript> => {
|
|
187
252
|
if (to.type !== 'tr_ms') return;
|
|
188
253
|
const out: ScriptType = [to.pubkeys[0], 'CHECKSIG'];
|
|
189
254
|
for (let i = 1; i < to.pubkeys.length; i++) out.push(to.pubkeys[i], 'CHECKSIGADD');
|
|
255
|
+
// This branch assumes `m` was already validated as an integer ScriptNum;
|
|
256
|
+
// fractional JS numbers would otherwise serialize as a different threshold.
|
|
190
257
|
out.push(to.m, 'NUMEQUAL');
|
|
191
|
-
return out
|
|
258
|
+
return out as TRet<OptScript>;
|
|
192
259
|
},
|
|
193
260
|
};
|
|
194
261
|
|
|
195
262
|
// Unknown output type
|
|
196
263
|
type OutUnknownType = { type: 'unknown'; script: Bytes };
|
|
197
264
|
const OutUnknown: Coder<OptScript, OutUnknownType | undefined> = {
|
|
198
|
-
encode(from: ScriptType): OutUnknownType | undefined {
|
|
199
|
-
|
|
265
|
+
encode(from: TArg<ScriptType>): TRet<OutUnknownType | undefined> {
|
|
266
|
+
// This is the catch-all fallback for scripts no structured coder recognized,
|
|
267
|
+
// so earlier matchers must return `undefined` instead of throwing on mismatch.
|
|
268
|
+
// Because this reserializes the parsed Script AST, unknown scripts preserve
|
|
269
|
+
// semantics but not original non-minimal push spellings.
|
|
270
|
+
return { type: 'unknown', script: Script.encode(from) } as TRet<OutUnknownType | undefined>;
|
|
200
271
|
},
|
|
201
|
-
decode: (to: OutUnknownType): OptScript =>
|
|
202
|
-
|
|
272
|
+
decode: (to: TArg<OutUnknownType>): TRet<OptScript> =>
|
|
273
|
+
// This reparses `unknown.script` through the semantic Script codec, so raw
|
|
274
|
+
// bytes must still be syntactically parseable and may canonicalize on re-encode.
|
|
275
|
+
(to.type === 'unknown' ? Script.decode(to.script) : undefined) as TRet<OptScript>,
|
|
203
276
|
};
|
|
204
277
|
// /Payments
|
|
205
278
|
|
|
206
|
-
const OutScripts = [
|
|
279
|
+
const OutScripts = /* @__PURE__ */ (() => [
|
|
280
|
+
// Order is semantic: specific structured coders run first and the catch-all
|
|
281
|
+
// unknown fallback must stay last.
|
|
207
282
|
OutP2A,
|
|
208
283
|
OutPK,
|
|
209
284
|
OutPKH,
|
|
@@ -215,22 +290,26 @@ const OutScripts = [
|
|
|
215
290
|
OutTRNS,
|
|
216
291
|
OutTRMS,
|
|
217
292
|
OutUnknown,
|
|
218
|
-
];
|
|
293
|
+
])();
|
|
219
294
|
// TODO: we can support user supplied output scripts now
|
|
220
295
|
// - addOutScript
|
|
221
296
|
// - removeOutScript
|
|
222
297
|
// - We can do that as log we modify array in-place
|
|
223
298
|
// - Actually is very hard, since there is sign/finalize logic
|
|
224
|
-
|
|
299
|
+
// Raw composition of semantic Script parsing with the ordered output-script
|
|
300
|
+
// matcher; OutScript adds the higher-level validation layer on top.
|
|
301
|
+
const _OutScript = /* @__PURE__ */ (() => P.apply(Script, P.coders.match(OutScripts)))();
|
|
225
302
|
|
|
226
303
|
/*
|
|
227
304
|
* UNSAFE: Custom scripts: mostly ordinals, be very careful when crafting new scripts
|
|
228
305
|
* Only taproot supported for now.
|
|
229
|
-
* NOTE: we can use same to move finalization logic from Transaction,
|
|
306
|
+
* NOTE: we can use same to move finalization logic from Transaction,
|
|
307
|
+
* but it will significantly change audited code.
|
|
230
308
|
*/
|
|
231
309
|
|
|
232
310
|
type FinalizeSignature = [{ pubKey: Bytes; leafHash: Bytes }, Bytes];
|
|
233
311
|
type CustomScriptOut = { type: string } & Record<string, any>;
|
|
312
|
+
/** Custom taproot script coder/finalizer hook. */
|
|
234
313
|
export type CustomScript = Coder<OptScript, CustomScriptOut | undefined> & {
|
|
235
314
|
finalizeTaproot?: (
|
|
236
315
|
script: Bytes,
|
|
@@ -240,90 +319,173 @@ export type CustomScript = Coder<OptScript, CustomScriptOut | undefined> & {
|
|
|
240
319
|
};
|
|
241
320
|
|
|
242
321
|
// We can validate this once, because of packed & coders
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
322
|
+
/**
|
|
323
|
+
* Coder for recognized Bitcoin output scripts.
|
|
324
|
+
* @example
|
|
325
|
+
* Decode a serialized output script back into the tagged payment descriptor.
|
|
326
|
+
* ```ts
|
|
327
|
+
* import { OutScript, p2wpkh } from '@scure/btc-signer/payment.js';
|
|
328
|
+
* import { pubECDSA, randomPrivateKeyBytes } from '@scure/btc-signer/utils.js';
|
|
329
|
+
* const pay = p2wpkh(pubECDSA(randomPrivateKeyBytes()));
|
|
330
|
+
* OutScript.decode(pay.script);
|
|
331
|
+
* ```
|
|
332
|
+
*/
|
|
333
|
+
export const OutScript: TRet<
|
|
334
|
+
P.CoderType<
|
|
335
|
+
NonNullable<
|
|
336
|
+
| OutP2AType
|
|
337
|
+
| OutPKType
|
|
338
|
+
| OutPKHType
|
|
339
|
+
| OutSHType
|
|
340
|
+
| OutWSHType
|
|
341
|
+
| OutWPKHType
|
|
342
|
+
| OutMSType
|
|
343
|
+
| OutTRType
|
|
344
|
+
| OutTRNSType
|
|
345
|
+
| OutTRMSType
|
|
346
|
+
| OutUnknownType
|
|
347
|
+
| undefined
|
|
348
|
+
>
|
|
257
349
|
>
|
|
258
|
-
> =
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
350
|
+
> = /* @__PURE__ */ (() =>
|
|
351
|
+
Object.freeze(
|
|
352
|
+
P.validate(_OutScript, (i) => {
|
|
353
|
+
if (i.type === 'pk' && !isValidPubkey(i.pubkey, u.PubT.ecdsa))
|
|
354
|
+
throw new Error('OutScript/pk: wrong key');
|
|
355
|
+
if (
|
|
356
|
+
(i.type === 'pkh' || i.type === 'sh' || i.type === 'wpkh') &&
|
|
357
|
+
(!u.isBytes(i.hash) || i.hash.length !== 20)
|
|
358
|
+
)
|
|
359
|
+
throw new Error(`OutScript/${i.type}: wrong hash`);
|
|
360
|
+
if (i.type === 'wsh' && (!u.isBytes(i.hash) || i.hash.length !== 32))
|
|
361
|
+
throw new Error(`OutScript/wsh: wrong hash`);
|
|
362
|
+
if (i.type === 'tr' && (!u.isBytes(i.pubkey) || !isValidPubkey(i.pubkey, u.PubT.schnorr)))
|
|
363
|
+
throw new Error('OutScript/tr: wrong taproot public key');
|
|
364
|
+
if (i.type === 'ms' || i.type === 'tr_ns' || i.type === 'tr_ms')
|
|
365
|
+
if (!Array.isArray(i.pubkeys)) throw new Error('OutScript/multisig: wrong pubkeys array');
|
|
366
|
+
if (i.type === 'ms') {
|
|
367
|
+
const n = i.pubkeys.length;
|
|
368
|
+
for (const p of i.pubkeys)
|
|
369
|
+
if (!isValidPubkey(p, u.PubT.ecdsa)) throw new Error('OutScript/multisig: wrong pubkey');
|
|
370
|
+
// Range checks are not enough here: non-integer JS numbers like 1.5 would
|
|
371
|
+
// otherwise slip through and serialize as a different ScriptNum threshold.
|
|
372
|
+
anumber(i.m, 'm');
|
|
373
|
+
if (i.m <= 0 || n > 16 || i.m > n) throw new Error('OutScript/multisig: invalid params');
|
|
374
|
+
}
|
|
375
|
+
if (i.type === 'tr_ns' || i.type === 'tr_ms') {
|
|
376
|
+
for (const p of i.pubkeys)
|
|
377
|
+
if (!isValidPubkey(p, u.PubT.schnorr))
|
|
378
|
+
throw new Error(`OutScript/${i.type}: wrong pubkey`);
|
|
379
|
+
}
|
|
380
|
+
if (i.type === 'tr_ms') {
|
|
381
|
+
const n = i.pubkeys.length;
|
|
382
|
+
// BIP 342 keeps the 1000-element stack limit. This CHECKSIG/CHECKSIGADD form
|
|
383
|
+
// momentarily has n witness items plus one pushed pubkey on the stack, so n must stay <= 999.
|
|
384
|
+
anumber(i.m, 'm');
|
|
385
|
+
if (i.m <= 0 || n > 999 || i.m > n) throw new Error('OutScript/tr_ms: invalid params');
|
|
386
|
+
}
|
|
387
|
+
return i;
|
|
388
|
+
})
|
|
389
|
+
))() as TRet<
|
|
390
|
+
P.CoderType<
|
|
391
|
+
NonNullable<
|
|
392
|
+
| OutP2AType
|
|
393
|
+
| OutPKType
|
|
394
|
+
| OutPKHType
|
|
395
|
+
| OutSHType
|
|
396
|
+
| OutWSHType
|
|
397
|
+
| OutWPKHType
|
|
398
|
+
| OutMSType
|
|
399
|
+
| OutTRType
|
|
400
|
+
| OutTRNSType
|
|
401
|
+
| OutTRMSType
|
|
402
|
+
| OutUnknownType
|
|
403
|
+
| undefined
|
|
404
|
+
>
|
|
405
|
+
>
|
|
406
|
+
>;
|
|
407
|
+
/** Type of the output-script coder. */
|
|
288
408
|
export type OutScriptType = typeof OutScript;
|
|
409
|
+
// TRet-wrapping OutScript changes decode() to the normalized descriptor surface, but the local
|
|
410
|
+
// checkScript/Address caches still need an explicit alias that can carry the decode-side `undefined`.
|
|
411
|
+
type OutScriptValue = ReturnType<OutScriptType['decode']> | undefined;
|
|
289
412
|
|
|
290
413
|
// Basic sanity check for scripts
|
|
291
|
-
function checkWSH(s: OutWSHType
|
|
414
|
+
function checkWSH(s: TArg<OutWSHType>, witnessScript: TArg<Bytes>) {
|
|
292
415
|
if (!u.equalBytes(s.hash, u.sha256(witnessScript)))
|
|
293
416
|
throw new Error('checkScript: wsh wrong witnessScript hash');
|
|
417
|
+
// BIP141 only requires the witnessScript hash match; the type-based rejects
|
|
418
|
+
// below are an extra descriptor sanity layer for BIP382 invalid-descriptor
|
|
419
|
+
// bullets `wpkh() nested in wsh()` and `wsh() nested in wsh()`.
|
|
294
420
|
const w = OutScript.decode(witnessScript);
|
|
295
421
|
if (w.type === 'tr' || w.type === 'tr_ns' || w.type === 'tr_ms')
|
|
296
422
|
throw new Error(`checkScript: P2${w.type} cannot be wrapped in P2SH`);
|
|
297
|
-
if (w.type === 'wpkh' || w.type === 'sh')
|
|
423
|
+
if (w.type === 'wpkh' || w.type === 'wsh' || w.type === 'sh')
|
|
298
424
|
throw new Error(`checkScript: P2${w.type} cannot be wrapped in P2WSH`);
|
|
299
425
|
}
|
|
300
426
|
|
|
301
|
-
|
|
427
|
+
/**
|
|
428
|
+
* Validates that nested redeem and witness scripts match their wrappers.
|
|
429
|
+
* @param script - top-level output script
|
|
430
|
+
* @param redeemScript - optional redeem script for P2SH wrappers
|
|
431
|
+
* @param witnessScript - optional witness script for P2WSH wrappers
|
|
432
|
+
* @throws If the script nesting is invalid or unsupported. {@link Error}
|
|
433
|
+
* @example
|
|
434
|
+
* Verify that wrapped scripts and hashes still match after custom edits.
|
|
435
|
+
* ```ts
|
|
436
|
+
* import { hex } from '@scure/base';
|
|
437
|
+
* import { checkScript, p2pkh, p2sh } from '@scure/btc-signer/payment.js';
|
|
438
|
+
* const wrapped = p2sh(
|
|
439
|
+
* p2pkh(hex.decode('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'))
|
|
440
|
+
* );
|
|
441
|
+
* checkScript(wrapped.script, wrapped.redeemScript);
|
|
442
|
+
* ```
|
|
443
|
+
*/
|
|
444
|
+
export function checkScript(
|
|
445
|
+
script?: TArg<Bytes>,
|
|
446
|
+
redeemScript?: TArg<Bytes>,
|
|
447
|
+
witnessScript?: TArg<Bytes>
|
|
448
|
+
): void {
|
|
449
|
+
let hasWsh = false;
|
|
450
|
+
let r: OutScriptValue = undefined;
|
|
302
451
|
if (script) {
|
|
303
452
|
const s = OutScript.decode(script);
|
|
453
|
+
// BIP174 Data Signers Check For bullets: provided redeemScript must match
|
|
454
|
+
// the scriptPubKey, and provided witnessScript must match the scriptPubKey
|
|
455
|
+
// or redeemScript instead of being silently ignored as stray metadata.
|
|
304
456
|
// ms||pk maybe work, but there will be no address, hard to spend
|
|
305
457
|
if (s.type === 'tr_ns' || s.type === 'tr_ms' || s.type === 'ms' || s.type == 'pk')
|
|
306
458
|
throw new Error(`checkScript: non-wrapped ${s.type}`);
|
|
307
|
-
if (
|
|
459
|
+
if (redeemScript) {
|
|
460
|
+
if (s.type !== 'sh') throw new Error('checkScript: redeemScript without P2SH');
|
|
308
461
|
if (!u.equalBytes(s.hash, u.hash160(redeemScript)))
|
|
309
462
|
throw new Error('checkScript: sh wrong redeemScript hash');
|
|
310
|
-
|
|
311
|
-
if (r
|
|
463
|
+
r = OutScript.decode(redeemScript) as OutScriptValue;
|
|
464
|
+
if (r?.type === 'tr' || r?.type === 'tr_ns' || r?.type === 'tr_ms')
|
|
312
465
|
throw new Error(`checkScript: P2${r.type} cannot be wrapped in P2SH`);
|
|
313
466
|
// Not sure if this unspendable, but we cannot represent this via PSBT
|
|
314
|
-
if (r
|
|
467
|
+
if (r?.type === 'sh') throw new Error('checkScript: P2SH cannot be wrapped in P2SH');
|
|
468
|
+
}
|
|
469
|
+
if (s.type === 'wsh') {
|
|
470
|
+
hasWsh = true;
|
|
471
|
+
if (witnessScript) checkWSH(s, witnessScript);
|
|
315
472
|
}
|
|
316
|
-
if (s.type === 'wsh' && witnessScript) checkWSH(s, witnessScript);
|
|
317
473
|
}
|
|
318
474
|
if (redeemScript) {
|
|
319
|
-
|
|
320
|
-
if (r
|
|
475
|
+
if (r === undefined) r = OutScript.decode(redeemScript) as OutScriptValue;
|
|
476
|
+
if (r?.type === 'wsh') {
|
|
477
|
+
hasWsh = true;
|
|
478
|
+
if (witnessScript) checkWSH(r as TArg<OutWSHType>, witnessScript);
|
|
479
|
+
}
|
|
321
480
|
}
|
|
481
|
+
if (witnessScript && !hasWsh) throw new Error('checkScript: witnessScript without P2WSH');
|
|
322
482
|
}
|
|
323
483
|
|
|
324
|
-
function uniqPubkey(pubkeys: Bytes[]) {
|
|
484
|
+
function uniqPubkey(pubkeys: TArg<Bytes[]>) {
|
|
325
485
|
const map: Record<string, boolean> = {};
|
|
326
486
|
for (const pub of pubkeys) {
|
|
487
|
+
// Exact-byte duplicate filter only: BIP383 valid vectors still permit the
|
|
488
|
+
// same point to appear in compressed and uncompressed SEC1 form in multi().
|
|
327
489
|
const key = hex.encode(pub);
|
|
328
490
|
if (map[key]) throw new Error(`Multisig: non-uniq pubkey: ${pubkeys.map(hex.encode)}`);
|
|
329
491
|
map[key] = true;
|
|
@@ -333,15 +495,68 @@ function uniqPubkey(pubkeys: Bytes[]) {
|
|
|
333
495
|
// Also we use satisfies for additional check (ts 4.9+)
|
|
334
496
|
type Extends<T, U> = T extends U ? T : never;
|
|
335
497
|
|
|
336
|
-
|
|
337
|
-
export
|
|
498
|
+
/** Pay-to-public-key output descriptor. */
|
|
499
|
+
export type P2PK = {
|
|
500
|
+
/** Payment-script tag for pay-to-public-key outputs. */
|
|
501
|
+
type: 'pk';
|
|
502
|
+
/** Serialized `pubkey CHECKSIG` script. */
|
|
503
|
+
script: TRet<Bytes>;
|
|
504
|
+
};
|
|
505
|
+
/**
|
|
506
|
+
* Builds a pay-to-public-key script.
|
|
507
|
+
* @param pubkey - compressed or uncompressed ECDSA public key
|
|
508
|
+
* @param _network - unused network placeholder for API consistency
|
|
509
|
+
* @returns P2PK descriptor.
|
|
510
|
+
* @throws If the public key cannot be encoded as a P2PK output. {@link Error}
|
|
511
|
+
* @example
|
|
512
|
+
* Build a bare pay-to-public-key output.
|
|
513
|
+
* ```ts
|
|
514
|
+
* import { hex } from '@scure/base';
|
|
515
|
+
* import { p2pk } from '@scure/btc-signer/payment.js';
|
|
516
|
+
* p2pk(hex.decode('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'));
|
|
517
|
+
* ```
|
|
518
|
+
*/
|
|
519
|
+
export const p2pk = (
|
|
520
|
+
pubkey: TArg<Bytes>,
|
|
521
|
+
_network: BTC_NETWORK = NETWORK
|
|
522
|
+
): TRet<Extends<P2PK, P2Ret>> => {
|
|
338
523
|
// network is unused
|
|
339
524
|
if (!isValidPubkey(pubkey, u.PubT.ecdsa)) throw new Error('P2PK: invalid publicKey');
|
|
340
|
-
return {
|
|
525
|
+
return {
|
|
526
|
+
type: 'pk',
|
|
527
|
+
script: OutScript.encode({ type: 'pk', pubkey }),
|
|
528
|
+
} as const as TRet<Extends<P2PK, P2Ret>>;
|
|
341
529
|
};
|
|
342
530
|
|
|
343
|
-
|
|
344
|
-
export
|
|
531
|
+
/** Pay-to-public-key-hash output descriptor. */
|
|
532
|
+
export type P2PKH = {
|
|
533
|
+
/** Payment-script tag for pay-to-public-key-hash outputs. */
|
|
534
|
+
type: 'pkh';
|
|
535
|
+
/** Serialized P2PKH script. */
|
|
536
|
+
script: TRet<Bytes>;
|
|
537
|
+
/** Base58Check address for the descriptor. */
|
|
538
|
+
address: string;
|
|
539
|
+
/** HASH160 committed by the script. */
|
|
540
|
+
hash: TRet<Bytes>;
|
|
541
|
+
};
|
|
542
|
+
/**
|
|
543
|
+
* Builds a P2PKH output from a public key.
|
|
544
|
+
* @param publicKey - compressed or uncompressed ECDSA public key bytes; HASH160 commits to the exact encoding
|
|
545
|
+
* @param network - address network parameters
|
|
546
|
+
* @returns P2PKH descriptor.
|
|
547
|
+
* @throws If the public key cannot be encoded as a P2PKH output. {@link Error}
|
|
548
|
+
* @example
|
|
549
|
+
* Build a classic pay-to-public-key-hash output.
|
|
550
|
+
* ```ts
|
|
551
|
+
* import { hex } from '@scure/base';
|
|
552
|
+
* import { p2pkh } from '@scure/btc-signer/payment.js';
|
|
553
|
+
* p2pkh(hex.decode('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'));
|
|
554
|
+
* ```
|
|
555
|
+
*/
|
|
556
|
+
export const p2pkh = (
|
|
557
|
+
publicKey: TArg<Bytes>,
|
|
558
|
+
network: BTC_NETWORK = NETWORK
|
|
559
|
+
): TRet<Extends<P2PKH, P2Ret>> => {
|
|
345
560
|
if (!isValidPubkey(publicKey, u.PubT.ecdsa)) throw new Error('P2PKH: invalid publicKey');
|
|
346
561
|
const hash = u.hash160(publicKey);
|
|
347
562
|
return {
|
|
@@ -349,79 +564,162 @@ export const p2pkh = (publicKey: Bytes, network: BTC_NETWORK = NETWORK): Extends
|
|
|
349
564
|
script: OutScript.encode({ type: 'pkh', hash }),
|
|
350
565
|
address: Address(network).encode({ type: 'pkh', hash }),
|
|
351
566
|
hash,
|
|
352
|
-
} as const
|
|
567
|
+
} as const as TRet<Extends<P2PKH, P2Ret>>;
|
|
353
568
|
};
|
|
354
569
|
|
|
570
|
+
/** Shared fields for pay-to-script-hash outputs. */
|
|
355
571
|
export type P2SHBase = {
|
|
572
|
+
/** Payment-script tag for pay-to-script-hash outputs. */
|
|
356
573
|
type: 'sh';
|
|
357
|
-
|
|
358
|
-
|
|
574
|
+
/** Child script wrapped by the P2SH output. */
|
|
575
|
+
redeemScript: TRet<Bytes>;
|
|
576
|
+
/** Serialized P2SH script. */
|
|
577
|
+
script: TRet<Bytes>;
|
|
578
|
+
/** Base58Check address for the descriptor. */
|
|
359
579
|
address: string;
|
|
360
|
-
|
|
580
|
+
/** HASH160 committed by the script. */
|
|
581
|
+
hash: TRet<Bytes>;
|
|
361
582
|
};
|
|
362
|
-
|
|
583
|
+
/** P2SH descriptor with an embedded witness script. */
|
|
584
|
+
export type P2SHWithWitness = P2SHBase & { witnessScript: TRet<Bytes> };
|
|
585
|
+
/** P2SH descriptor without an embedded witness script. */
|
|
363
586
|
export type P2SHWithoutWitness = Omit<P2SHBase, 'witnessScript'>;
|
|
587
|
+
/** Conditional P2SH return type for wrapped scripts. */
|
|
364
588
|
export type P2SHReturn<T extends P2Ret> = T extends { witnessScript: Bytes }
|
|
365
589
|
? P2SHWithWitness
|
|
366
590
|
: P2SHWithoutWitness;
|
|
591
|
+
/**
|
|
592
|
+
* Wraps a child script inside P2SH.
|
|
593
|
+
* @param child - child payment descriptor to wrap
|
|
594
|
+
* @param network - address network parameters
|
|
595
|
+
* @returns P2SH descriptor preserving witness metadata when present.
|
|
596
|
+
* @throws If the wrapped script combination is invalid or unsupported. {@link Error}
|
|
597
|
+
* @example
|
|
598
|
+
* Wrap a child script in P2SH so it gets a base58 address form.
|
|
599
|
+
* ```ts
|
|
600
|
+
* import { hex } from '@scure/base';
|
|
601
|
+
* import { p2pk, p2sh, p2wsh } from '@scure/btc-signer/payment.js';
|
|
602
|
+
* p2sh(p2wsh(p2pk(hex.decode('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'))));
|
|
603
|
+
* ```
|
|
604
|
+
*/
|
|
367
605
|
export const p2sh = <T extends P2Ret>(
|
|
368
|
-
child: T
|
|
606
|
+
child: TArg<T>,
|
|
369
607
|
network: BTC_NETWORK = NETWORK
|
|
370
|
-
): Extends<P2SHReturn<T>, P2Ret
|
|
608
|
+
): TRet<Extends<P2SHReturn<T>, P2Ret>> => {
|
|
371
609
|
// It is already tested inside noble-hashes and checkScript
|
|
372
|
-
|
|
373
|
-
|
|
610
|
+
// BIP16 redeemScripts are pushed by scriptSig, so anything over the 520-byte pushed-element
|
|
611
|
+
// limit would be fundable by HASH160 but unspendable once wrapped in P2SH.
|
|
612
|
+
const c = child as T;
|
|
613
|
+
const cs = c.script;
|
|
614
|
+
if (!u.isBytes(cs)) throw new Error(`Wrong script: ${typeof c.script}, expected Uint8Array`);
|
|
615
|
+
if (cs.length > MAX_SCRIPT_BYTE_LENGTH)
|
|
616
|
+
throw new Error(
|
|
617
|
+
`P2SH: redeemScript exceeds ${MAX_SCRIPT_BYTE_LENGTH}-byte push limit: len=${cs.length}`
|
|
618
|
+
);
|
|
374
619
|
const hash = u.hash160(cs);
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
|
|
620
|
+
const out = { type: 'sh', hash } as const;
|
|
621
|
+
const script = OutScript.encode(out);
|
|
622
|
+
const address = Address(network).encode(out);
|
|
623
|
+
checkScript(script, cs, c.witnessScript);
|
|
624
|
+
if (c.witnessScript) {
|
|
378
625
|
return {
|
|
379
626
|
type: 'sh',
|
|
380
627
|
redeemScript: cs,
|
|
381
|
-
script
|
|
382
|
-
address
|
|
628
|
+
script,
|
|
629
|
+
address,
|
|
383
630
|
hash,
|
|
384
|
-
witnessScript:
|
|
385
|
-
} as Extends<P2SHReturn<T>, P2Ret
|
|
631
|
+
witnessScript: c.witnessScript,
|
|
632
|
+
} as unknown as TRet<Extends<P2SHReturn<T>, P2Ret>>;
|
|
386
633
|
} else {
|
|
387
634
|
return {
|
|
388
635
|
type: 'sh',
|
|
389
636
|
redeemScript: cs,
|
|
390
|
-
script
|
|
391
|
-
address
|
|
637
|
+
script,
|
|
638
|
+
address,
|
|
392
639
|
hash,
|
|
393
|
-
} as Extends<P2SHReturn<T>, P2Ret
|
|
640
|
+
} as unknown as TRet<Extends<P2SHReturn<T>, P2Ret>>;
|
|
394
641
|
}
|
|
395
642
|
};
|
|
396
643
|
|
|
644
|
+
/** Pay-to-witness-script-hash descriptor. */
|
|
397
645
|
export type P2WSH = {
|
|
646
|
+
/** Payment-script tag for pay-to-witness-script-hash outputs. */
|
|
398
647
|
type: 'wsh';
|
|
399
|
-
|
|
400
|
-
|
|
648
|
+
/** Child script committed by the witness program. */
|
|
649
|
+
witnessScript: TRet<Bytes>;
|
|
650
|
+
/** Serialized P2WSH script. */
|
|
651
|
+
script: TRet<Bytes>;
|
|
652
|
+
/** Bech32 address for the descriptor. */
|
|
401
653
|
address: string;
|
|
402
|
-
|
|
654
|
+
/** SHA256 committed by the witness program. */
|
|
655
|
+
hash: TRet<Bytes>;
|
|
403
656
|
};
|
|
404
|
-
|
|
657
|
+
/**
|
|
658
|
+
* Wraps a child script inside native SegWit P2WSH.
|
|
659
|
+
* @param child - child payment descriptor to wrap
|
|
660
|
+
* @param network - address network parameters
|
|
661
|
+
* @returns P2WSH descriptor.
|
|
662
|
+
* @throws If the wrapped script combination is invalid or unsupported. {@link Error}
|
|
663
|
+
* @example
|
|
664
|
+
* Wrap a child script in native SegWit P2WSH.
|
|
665
|
+
* ```ts
|
|
666
|
+
* import { hex } from '@scure/base';
|
|
667
|
+
* import { p2pk, p2wsh } from '@scure/btc-signer/payment.js';
|
|
668
|
+
* p2wsh(p2pk(hex.decode('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')));
|
|
669
|
+
* ```
|
|
670
|
+
*/
|
|
671
|
+
export const p2wsh = (
|
|
672
|
+
child: TArg<P2Ret>,
|
|
673
|
+
network: BTC_NETWORK = NETWORK
|
|
674
|
+
): TRet<Extends<P2WSH, P2Ret>> => {
|
|
405
675
|
const cs = child.script;
|
|
406
676
|
if (!u.isBytes(cs)) throw new Error(`Wrong script: ${typeof cs}, expected Uint8Array`);
|
|
677
|
+
// BIP141 P2WSH says the witness "must consist of ... a serialized script (witnessScript)"
|
|
678
|
+
// and that witnessScript is limited to 10,000 bytes, so larger wrapped scripts must reject.
|
|
679
|
+
if (cs.length > 10000) throw new Error('P2WSH: witnessScript exceeds 10,000 bytes');
|
|
407
680
|
const hash = u.sha256(cs);
|
|
408
681
|
const script = OutScript.encode({ type: 'wsh', hash });
|
|
409
682
|
checkScript(script, undefined, cs);
|
|
410
683
|
return {
|
|
411
684
|
type: 'wsh',
|
|
412
685
|
witnessScript: cs,
|
|
413
|
-
script
|
|
686
|
+
script,
|
|
414
687
|
address: Address(network).encode({ type: 'wsh', hash }),
|
|
415
688
|
hash,
|
|
416
|
-
} as const
|
|
689
|
+
} as const as TRet<Extends<P2WSH, P2Ret>>;
|
|
417
690
|
};
|
|
418
691
|
|
|
419
|
-
|
|
692
|
+
/** Pay-to-witness-public-key-hash descriptor. */
|
|
693
|
+
export type P2WPKH = {
|
|
694
|
+
/** Payment-script tag for pay-to-witness-public-key-hash outputs. */
|
|
695
|
+
type: 'wpkh';
|
|
696
|
+
/** Serialized P2WPKH script. */
|
|
697
|
+
script: TRet<Bytes>;
|
|
698
|
+
/** Bech32 address for the descriptor. */
|
|
699
|
+
address: string;
|
|
700
|
+
/** HASH160 committed by the witness program. */
|
|
701
|
+
hash: TRet<Bytes>;
|
|
702
|
+
};
|
|
703
|
+
/**
|
|
704
|
+
* Builds a native SegWit P2WPKH output from a public key.
|
|
705
|
+
* @param publicKey - compressed ECDSA public key
|
|
706
|
+
* @param network - address network parameters
|
|
707
|
+
* @returns P2WPKH descriptor.
|
|
708
|
+
* @throws If the public key cannot be encoded as a P2WPKH output. {@link Error}
|
|
709
|
+
* @example
|
|
710
|
+
* Build a native SegWit pay-to-public-key-hash output.
|
|
711
|
+
* ```ts
|
|
712
|
+
* import { hex } from '@scure/base';
|
|
713
|
+
* import { p2wpkh } from '@scure/btc-signer/payment.js';
|
|
714
|
+
* p2wpkh(hex.decode('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'));
|
|
715
|
+
* ```
|
|
716
|
+
*/
|
|
420
717
|
export const p2wpkh = (
|
|
421
|
-
publicKey: Bytes
|
|
718
|
+
publicKey: TArg<Bytes>,
|
|
422
719
|
network: BTC_NETWORK = NETWORK
|
|
423
|
-
): Extends<P2WPKH, P2Ret
|
|
720
|
+
): TRet<Extends<P2WPKH, P2Ret>> => {
|
|
424
721
|
if (!isValidPubkey(publicKey, u.PubT.ecdsa)) throw new Error('P2WPKH: invalid publicKey');
|
|
722
|
+
// BIP 143 default policy: version-0 witness programs MUST use 33-byte compressed ECDSA keys.
|
|
425
723
|
if (publicKey.length === 65) throw new Error('P2WPKH: uncompressed public key');
|
|
426
724
|
const hash = u.hash160(publicKey);
|
|
427
725
|
return {
|
|
@@ -429,30 +727,54 @@ export const p2wpkh = (
|
|
|
429
727
|
script: OutScript.encode({ type: 'wpkh', hash }),
|
|
430
728
|
address: Address(network).encode({ type: 'wpkh', hash }),
|
|
431
729
|
hash,
|
|
432
|
-
} as const
|
|
730
|
+
} as const as TRet<Extends<P2WPKH, P2Ret>>;
|
|
433
731
|
};
|
|
434
732
|
|
|
435
|
-
|
|
733
|
+
/** Bare multisig output descriptor. */
|
|
734
|
+
export type P2MS = {
|
|
735
|
+
/** Payment-script tag for bare multisig outputs. */
|
|
736
|
+
type: 'ms';
|
|
737
|
+
/** Serialized bare multisig script. */
|
|
738
|
+
script: TRet<Bytes>;
|
|
739
|
+
};
|
|
740
|
+
/**
|
|
741
|
+
* Builds a bare multisig script.
|
|
742
|
+
* @param m - number of required signatures
|
|
743
|
+
* @param pubkeys - participating public keys
|
|
744
|
+
* @param allowSamePubkeys - whether duplicate keys are allowed
|
|
745
|
+
* @returns P2MS descriptor.
|
|
746
|
+
* @throws If the multisig parameters are invalid. {@link Error}
|
|
747
|
+
* @example
|
|
748
|
+
* Build a bare multisig output script.
|
|
749
|
+
* ```ts
|
|
750
|
+
* import { hex } from '@scure/base';
|
|
751
|
+
* import { p2ms } from '@scure/btc-signer/payment.js';
|
|
752
|
+
* p2ms(1, [hex.decode('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')], true);
|
|
753
|
+
* ```
|
|
754
|
+
*/
|
|
436
755
|
export const p2ms = (
|
|
437
756
|
m: number,
|
|
438
|
-
pubkeys: Bytes[]
|
|
757
|
+
pubkeys: TArg<Bytes[]>,
|
|
439
758
|
allowSamePubkeys = false
|
|
440
|
-
): Extends<P2MS, P2Ret
|
|
759
|
+
): TRet<Extends<P2MS, P2Ret>> => {
|
|
760
|
+
// BIP 11 only standardized bare multisig up to 3 keys; this helper still permits up to 16
|
|
761
|
+
// because the same script shape is commonly wrapped by p2sh()/p2wsh() instead of used bare.
|
|
441
762
|
if (!allowSamePubkeys) uniqPubkey(pubkeys);
|
|
442
763
|
return {
|
|
443
764
|
type: 'ms',
|
|
444
765
|
script: OutScript.encode({ type: 'ms', pubkeys, m }),
|
|
445
|
-
} as const
|
|
766
|
+
} as const as TRet<Extends<P2MS, P2Ret>>;
|
|
446
767
|
};
|
|
447
768
|
|
|
769
|
+
/** Internal taproot hash tree without merkle paths. */
|
|
448
770
|
export type HashedTree =
|
|
449
771
|
| { type: 'leaf'; version?: number; script: Bytes; hash: Bytes }
|
|
450
772
|
| { type: 'branch'; left: HashedTree; right: HashedTree; hash: Bytes };
|
|
451
773
|
function checkTaprootScript(
|
|
452
|
-
script: Bytes
|
|
453
|
-
internalPubKey: Bytes
|
|
774
|
+
script: TArg<Bytes>,
|
|
775
|
+
internalPubKey: TArg<Bytes>,
|
|
454
776
|
allowUnknownOutputs = false,
|
|
455
|
-
customScripts?: CustomScript[]
|
|
777
|
+
customScripts?: TArg<CustomScript[]>
|
|
456
778
|
) {
|
|
457
779
|
const out = OutScript.decode(script);
|
|
458
780
|
if (out.type === 'unknown') {
|
|
@@ -492,25 +814,35 @@ function checkTaprootScript(
|
|
|
492
814
|
}
|
|
493
815
|
}
|
|
494
816
|
|
|
817
|
+
/** Taproot key-path descriptor. */
|
|
495
818
|
export type P2TR = {
|
|
819
|
+
/** Payment-script tag for taproot outputs. */
|
|
496
820
|
type: 'tr';
|
|
821
|
+
/** Serialized v1 witness-program script. */
|
|
497
822
|
script: Bytes;
|
|
823
|
+
/** Bech32m address for the descriptor. */
|
|
498
824
|
address: string;
|
|
825
|
+
/** Tweaked x-only output key committed by the address and script. */
|
|
499
826
|
tweakedPubkey: Bytes;
|
|
827
|
+
/** Internal x-only taproot key before tweaking. */
|
|
500
828
|
tapInternalKey: Bytes;
|
|
501
829
|
};
|
|
830
|
+
/** Taproot descriptor with a script tree attached. */
|
|
502
831
|
export type P2TR_TREE = P2TR & {
|
|
503
832
|
tapMerkleRoot: Bytes;
|
|
504
833
|
tapLeafScript: TransactionInput['tapLeafScript'];
|
|
505
834
|
leaves: TaprootLeaf[];
|
|
506
835
|
};
|
|
507
836
|
|
|
837
|
+
/** Node accepted when constructing a taproot script tree. */
|
|
508
838
|
export type TaprootNode = {
|
|
509
839
|
script: Bytes | string;
|
|
510
840
|
leafVersion?: number;
|
|
511
841
|
weight?: number;
|
|
512
842
|
} & Partial<P2TR_TREE>;
|
|
843
|
+
/** Recursive taproot tree input. */
|
|
513
844
|
export type TaprootScriptTree = TaprootNode | TaprootScriptTree[];
|
|
845
|
+
/** Flat list of weighted taproot leaves. */
|
|
514
846
|
export type TaprootScriptList = TaprootNode[];
|
|
515
847
|
type _TaprootTreeInternal = {
|
|
516
848
|
weight?: number;
|
|
@@ -518,7 +850,27 @@ type _TaprootTreeInternal = {
|
|
|
518
850
|
};
|
|
519
851
|
|
|
520
852
|
// Helper for generating binary tree from list, with weights
|
|
521
|
-
|
|
853
|
+
/**
|
|
854
|
+
* Converts a flat list of weighted leaves into a binary taproot tree.
|
|
855
|
+
* @param taprootList - weighted leaves to arrange
|
|
856
|
+
* @returns Binary taproot script tree.
|
|
857
|
+
* @throws If the list is empty and cannot describe any tree. {@link Error}
|
|
858
|
+
* @example
|
|
859
|
+
* Start from a flat weighted list, then let the helper build the binary tree shape.
|
|
860
|
+
* ```ts
|
|
861
|
+
* import { hex } from '@scure/base';
|
|
862
|
+
* import { p2tr_pk, taprootListToTree } from '@scure/btc-signer/payment.js';
|
|
863
|
+
* taprootListToTree([
|
|
864
|
+
* p2tr_pk(hex.decode('f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9')),
|
|
865
|
+
* p2tr_pk(hex.decode('dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659')),
|
|
866
|
+
* ]);
|
|
867
|
+
* ```
|
|
868
|
+
*/
|
|
869
|
+
export function taprootListToTree(taprootList: TArg<TaprootScriptList>): TRet<TaprootScriptTree> {
|
|
870
|
+
// Empty flat lists cannot represent a taproot script tree; omit the tree entirely for
|
|
871
|
+
// key-path-only outputs instead of passing [] here, otherwise this helper would return
|
|
872
|
+
// undefined and downstream taproot tree walkers would fail much later on a non-tree value.
|
|
873
|
+
if (!taprootList.length) throw new Error('taprootListToTree: empty tree');
|
|
522
874
|
// Clone input in order to not corrupt it
|
|
523
875
|
const lst = Array.from(taprootList) as _TaprootTreeInternal[];
|
|
524
876
|
// We have at least 2 elements => can create branch
|
|
@@ -537,17 +889,24 @@ export function taprootListToTree(taprootList: TaprootScriptList): TaprootScript
|
|
|
537
889
|
}
|
|
538
890
|
// At this point there is always 1 element in lst
|
|
539
891
|
const last = lst[0];
|
|
540
|
-
return (last?.childs || last) as TaprootScriptTree
|
|
892
|
+
return (last?.childs || last) as TRet<TaprootScriptTree>;
|
|
541
893
|
}
|
|
542
894
|
|
|
895
|
+
/** Taproot leaf with its merkle path. */
|
|
543
896
|
export type TaprootLeaf = {
|
|
897
|
+
/** Leaf marker inside the annotated taproot tree. */
|
|
544
898
|
type: 'leaf';
|
|
899
|
+
/** Tapleaf version committed by the merkle tree. */
|
|
545
900
|
version?: number;
|
|
901
|
+
/** Serialized leaf script. */
|
|
546
902
|
script: Bytes;
|
|
903
|
+
/** Tagged tapleaf hash for the script and version. */
|
|
547
904
|
hash: Bytes;
|
|
905
|
+
/** Merkle path hashes required for script-path spending. */
|
|
548
906
|
path: Bytes[];
|
|
549
907
|
};
|
|
550
908
|
|
|
909
|
+
/** Internal taproot tree annotated with merkle paths. */
|
|
551
910
|
export type HashedTreeWithPath =
|
|
552
911
|
| TaprootLeaf
|
|
553
912
|
| {
|
|
@@ -558,36 +917,42 @@ export type HashedTreeWithPath =
|
|
|
558
917
|
path: Bytes[];
|
|
559
918
|
};
|
|
560
919
|
|
|
561
|
-
function taprootAddPath(
|
|
920
|
+
function taprootAddPath(
|
|
921
|
+
tree: TArg<HashedTree>,
|
|
922
|
+
path: TArg<Bytes[]> = []
|
|
923
|
+
): TRet<HashedTreeWithPath> {
|
|
562
924
|
if (!tree) throw new Error(`taprootAddPath: empty tree`);
|
|
563
|
-
if (tree.type === 'leaf') return { ...tree, path }
|
|
925
|
+
if (tree.type === 'leaf') return { ...tree, path } as TRet<HashedTreeWithPath>;
|
|
564
926
|
if (tree.type !== 'branch') throw new Error(`taprootAddPath: wrong type=${tree}`);
|
|
565
927
|
return {
|
|
566
928
|
...tree,
|
|
567
929
|
path,
|
|
568
|
-
//
|
|
930
|
+
// BIP 341 control blocks serialize sibling hashes from leaf to root, so prepend the
|
|
931
|
+
// current sibling before descending into the child subtree.
|
|
569
932
|
left: taprootAddPath(tree.left, [tree.right.hash, ...path]),
|
|
570
933
|
right: taprootAddPath(tree.right, [tree.left.hash, ...path]),
|
|
571
|
-
}
|
|
934
|
+
} as TRet<HashedTreeWithPath>;
|
|
572
935
|
}
|
|
573
|
-
function taprootWalkTree(tree: HashedTreeWithPath): TaprootLeaf[] {
|
|
936
|
+
function taprootWalkTree(tree: TArg<HashedTreeWithPath>): TRet<TaprootLeaf[]> {
|
|
574
937
|
if (!tree) throw new Error(`taprootAddPath: empty tree`);
|
|
575
|
-
if (tree.type === 'leaf') return [tree]
|
|
938
|
+
if (tree.type === 'leaf') return [tree] as TRet<TaprootLeaf[]>;
|
|
576
939
|
if (tree.type !== 'branch') throw new Error(`taprootWalkTree: wrong type=${tree}`);
|
|
577
|
-
|
|
940
|
+
// Keep a stable left-to-right DFS leaf order when flattening the annotated tree.
|
|
941
|
+
return [...taprootWalkTree(tree.left), ...taprootWalkTree(tree.right)] as TRet<TaprootLeaf[]>;
|
|
578
942
|
}
|
|
579
943
|
|
|
580
944
|
function taprootHashTree(
|
|
581
|
-
tree: TaprootScriptTree
|
|
582
|
-
internalPubKey: Bytes
|
|
945
|
+
tree: TArg<TaprootScriptTree>,
|
|
946
|
+
internalPubKey: TArg<Bytes>,
|
|
583
947
|
allowUnknownOutputs = false,
|
|
584
|
-
customScripts?: CustomScript[]
|
|
585
|
-
): HashedTree {
|
|
948
|
+
customScripts?: TArg<CustomScript[]>
|
|
949
|
+
): TRet<HashedTree> {
|
|
586
950
|
if (!tree) throw new Error('taprootHashTree: empty tree');
|
|
587
951
|
if (Array.isArray(tree) && tree.length === 1) tree = tree[0];
|
|
588
952
|
// Terminal node (leaf)
|
|
589
953
|
if (!Array.isArray(tree)) {
|
|
590
|
-
const
|
|
954
|
+
const version = tree.leafVersion;
|
|
955
|
+
const { script: leafScript } = tree;
|
|
591
956
|
// Earliest tree walk where we can validate tapScripts
|
|
592
957
|
if (tree.tapLeafScript || (tree.tapMerkleRoot && !u.equalBytes(tree.tapMerkleRoot, P.EMPTY)))
|
|
593
958
|
throw new Error('P2TR: tapRoot leafScript cannot have tree');
|
|
@@ -598,8 +963,8 @@ function taprootHashTree(
|
|
|
598
963
|
type: 'leaf',
|
|
599
964
|
version,
|
|
600
965
|
script,
|
|
601
|
-
hash: tapLeafHash(script, version),
|
|
602
|
-
}
|
|
966
|
+
hash: tapLeafHash(script, tapLeafVersion(version)),
|
|
967
|
+
} as TRet<HashedTree>;
|
|
603
968
|
}
|
|
604
969
|
// If tree / branch is not binary tree, convert it
|
|
605
970
|
if (tree.length !== 2) tree = taprootListToTree(tree as TaprootNode[]) as TaprootNode[];
|
|
@@ -608,41 +973,91 @@ function taprootHashTree(
|
|
|
608
973
|
// Both nodes should exist
|
|
609
974
|
const left = taprootHashTree(tree[0], internalPubKey, allowUnknownOutputs, customScripts);
|
|
610
975
|
const right = taprootHashTree(tree[1], internalPubKey, allowUnknownOutputs, customScripts);
|
|
611
|
-
//
|
|
976
|
+
// BIP 341 sorts TapBranch child hashes lexicographically for hashing, but the original
|
|
977
|
+
// left/right structure still determines the control-block sibling paths for each leaf.
|
|
612
978
|
let [lH, rH] = [left.hash, right.hash];
|
|
613
979
|
if (u.compareBytes(rH, lH) === -1) [lH, rH] = [rH, lH];
|
|
614
|
-
return {
|
|
980
|
+
return {
|
|
981
|
+
type: 'branch',
|
|
982
|
+
left,
|
|
983
|
+
right,
|
|
984
|
+
hash: u.tagSchnorr('TapBranch', lH, rH),
|
|
985
|
+
} as TRet<HashedTree>;
|
|
615
986
|
}
|
|
616
987
|
|
|
988
|
+
/** Default tapleaf version used by taproot script-path outputs before adding the parity bit. */
|
|
617
989
|
export const TAP_LEAF_VERSION = 0xc0;
|
|
618
|
-
|
|
619
|
-
|
|
990
|
+
const tapLeafVersion = (version: number | undefined): number => {
|
|
991
|
+
if (version === undefined) return TAP_LEAF_VERSION;
|
|
992
|
+
anumber(version, 'leafVersion');
|
|
993
|
+
// BIP341 script-path validation defines the effective leaf version as `v = c[0] & 0xfe`
|
|
994
|
+
// and says it cannot be odd or `0x50`; tapleaf hashes also serialize this as one byte.
|
|
995
|
+
if (version > 0xfe || version === 0x50 || !!(version & 1))
|
|
996
|
+
throw new Error(`P2TR: invalid leafVersion=${version}`);
|
|
997
|
+
return version;
|
|
998
|
+
};
|
|
999
|
+
/**
|
|
1000
|
+
* Computes the tagged hash of a tapleaf script.
|
|
1001
|
+
* @param script - tapleaf script bytes
|
|
1002
|
+
* @param version - base even tapleaf version byte (for tapscript, `0xc0`)
|
|
1003
|
+
* @returns Tapleaf hash.
|
|
1004
|
+
* @throws If the tapleaf version is not a valid even one-byte version.
|
|
1005
|
+
* {@link Error}
|
|
1006
|
+
* @example
|
|
1007
|
+
* Hash a finalized tapscript leaf before placing it into a Merkle tree.
|
|
1008
|
+
* ```ts
|
|
1009
|
+
* tapLeafHash(new Uint8Array([0x51]));
|
|
1010
|
+
* ```
|
|
1011
|
+
*/
|
|
1012
|
+
export const tapLeafHash = (script: TArg<Bytes>, version: number = TAP_LEAF_VERSION): TRet<Bytes> =>
|
|
1013
|
+
u.tagSchnorr('TapLeaf', new Uint8Array([tapLeafVersion(version)]), VarBytes.encode(script));
|
|
620
1014
|
|
|
621
1015
|
// Works as key OR tree.
|
|
622
1016
|
// If we only have tree, need to add unspendable key, otherwise
|
|
623
1017
|
// complex multisig wallet can be spent by owner of key only. See TAPROOT_UNSPENDABLE_KEY
|
|
1018
|
+
/** Conditional taproot return type for key-only or tree-backed outputs. */
|
|
624
1019
|
export type P2TRRet<T> = T extends TaprootScriptTree ? P2TR_TREE : P2TR;
|
|
1020
|
+
/**
|
|
1021
|
+
* Builds a taproot output from an internal key and optional script tree.
|
|
1022
|
+
* @param internalPubKey - x-only internal public key, hex string, or `undefined` for script-only outputs
|
|
1023
|
+
* @param tree - optional taproot script tree
|
|
1024
|
+
* @param network - address network parameters
|
|
1025
|
+
* @param allowUnknownOutputs - whether unknown leaf scripts are allowed
|
|
1026
|
+
* @param customScripts - optional custom script codecs for taproot leaves
|
|
1027
|
+
* @returns Taproot descriptor with optional script-path metadata.
|
|
1028
|
+
* @throws If the internal key or taproot script tree is invalid. {@link Error}
|
|
1029
|
+
* @example
|
|
1030
|
+
* Combine script leaves into a final taproot output descriptor and address.
|
|
1031
|
+
* ```ts
|
|
1032
|
+
* import { hex } from '@scure/base';
|
|
1033
|
+
* import { p2tr, p2tr_pk } from '@scure/btc-signer/payment.js';
|
|
1034
|
+
* p2tr(
|
|
1035
|
+
* undefined,
|
|
1036
|
+
* [p2tr_pk(hex.decode('f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9'))]
|
|
1037
|
+
* );
|
|
1038
|
+
* ```
|
|
1039
|
+
*/
|
|
625
1040
|
export function p2tr(
|
|
626
|
-
internalPubKey: Bytes | string
|
|
1041
|
+
internalPubKey: TArg<Bytes | string>,
|
|
627
1042
|
tree?: undefined,
|
|
628
1043
|
network?: BTC_NETWORK,
|
|
629
1044
|
allowUnknownOutputs?: boolean,
|
|
630
|
-
customScripts?: CustomScript[]
|
|
631
|
-
): Extends<P2TR, P2Ret
|
|
1045
|
+
customScripts?: TArg<CustomScript[]>
|
|
1046
|
+
): TRet<Extends<P2TR, P2Ret>>;
|
|
632
1047
|
export function p2tr(
|
|
633
|
-
internalPubKey: Bytes | string
|
|
634
|
-
tree: TaprootScriptTree
|
|
1048
|
+
internalPubKey: TArg<Bytes | string | undefined>,
|
|
1049
|
+
tree: TArg<TaprootScriptTree>,
|
|
635
1050
|
network?: BTC_NETWORK,
|
|
636
1051
|
allowUnknownOutputs?: boolean,
|
|
637
|
-
customScripts?: CustomScript[]
|
|
638
|
-
): Extends<P2TR_TREE, P2Ret
|
|
1052
|
+
customScripts?: TArg<CustomScript[]>
|
|
1053
|
+
): TRet<Extends<P2TR_TREE, P2Ret>>;
|
|
639
1054
|
export function p2tr(
|
|
640
|
-
internalPubKey?: Bytes | string
|
|
641
|
-
tree?: TaprootScriptTree
|
|
1055
|
+
internalPubKey?: TArg<Bytes | string>,
|
|
1056
|
+
tree?: TArg<TaprootScriptTree>,
|
|
642
1057
|
network: BTC_NETWORK = NETWORK,
|
|
643
1058
|
allowUnknownOutputs = false,
|
|
644
|
-
customScripts?: CustomScript[]
|
|
645
|
-
): Extends<P2TR & Partial<P2TR_TREE>, P2Ret
|
|
1059
|
+
customScripts?: TArg<CustomScript[]>
|
|
1060
|
+
): TRet<Extends<P2TR & Partial<P2TR_TREE>, P2Ret>> {
|
|
646
1061
|
// Unspendable
|
|
647
1062
|
if (!internalPubKey && !tree) throw new Error('p2tr: should have pubKey or scriptTree (or both)');
|
|
648
1063
|
const pubKey =
|
|
@@ -656,14 +1071,19 @@ export function p2tr(
|
|
|
656
1071
|
);
|
|
657
1072
|
const tapMerkleRoot = hashedTree.hash;
|
|
658
1073
|
const [tweakedPubkey, parity] = u.taprootTweakPubkey(pubKey, tapMerkleRoot);
|
|
659
|
-
const leaves = taprootWalkTree(hashedTree).map((l) =>
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
1074
|
+
const leaves = taprootWalkTree(hashedTree).map((l) => {
|
|
1075
|
+
const version = tapLeafVersion(l.version);
|
|
1076
|
+
return {
|
|
1077
|
+
...l,
|
|
1078
|
+
// Leaf versions are stored as the base even byte; only the control block adds the
|
|
1079
|
+
// output-key parity bit required by BIP 341 script-path spending.
|
|
1080
|
+
controlBlock: TaprootControlBlock.encode({
|
|
1081
|
+
version: version + parity,
|
|
1082
|
+
internalKey: pubKey,
|
|
1083
|
+
merklePath: l.path,
|
|
1084
|
+
}),
|
|
1085
|
+
};
|
|
1086
|
+
});
|
|
667
1087
|
return {
|
|
668
1088
|
type: 'tr',
|
|
669
1089
|
script: OutScript.encode({ type: 'tr', pubkey: tweakedPubkey }),
|
|
@@ -675,11 +1095,13 @@ export function p2tr(
|
|
|
675
1095
|
leaves,
|
|
676
1096
|
tapLeafScript: leaves.map((l) => [
|
|
677
1097
|
TaprootControlBlock.decode(l.controlBlock),
|
|
678
|
-
u.concatBytes(l.script, new Uint8Array([l.version
|
|
1098
|
+
u.concatBytes(l.script, new Uint8Array([tapLeafVersion(l.version)])),
|
|
679
1099
|
]),
|
|
680
1100
|
tapMerkleRoot,
|
|
681
|
-
} as const
|
|
1101
|
+
} as const as TRet<Extends<P2TR_TREE, P2Ret>>;
|
|
682
1102
|
} else {
|
|
1103
|
+
// BIP 341 / BIP 86: key-only Taproot still tweaks with the empty Merkle root so the
|
|
1104
|
+
// output commits to an unspendable script path instead of leaving the key untweaked.
|
|
683
1105
|
const tweakedPubkey = u.taprootTweakPubkey(pubKey, P.EMPTY)[0];
|
|
684
1106
|
return {
|
|
685
1107
|
type: 'tr',
|
|
@@ -689,21 +1111,36 @@ export function p2tr(
|
|
|
689
1111
|
tweakedPubkey,
|
|
690
1112
|
// PSBT stuff
|
|
691
1113
|
tapInternalKey: pubKey,
|
|
692
|
-
} as const
|
|
1114
|
+
} as const as TRet<Extends<P2TR, P2Ret>>;
|
|
693
1115
|
}
|
|
694
1116
|
}
|
|
695
1117
|
|
|
696
1118
|
// Returns all combinations of size M from lst
|
|
1119
|
+
/**
|
|
1120
|
+
* Returns all size-`m` combinations from a list.
|
|
1121
|
+
* @param m - size of each combination
|
|
1122
|
+
* @param list - input items to combine
|
|
1123
|
+
* @returns Array of combinations.
|
|
1124
|
+
* @throws If the combination size or input list is invalid. {@link Error}
|
|
1125
|
+
* @example
|
|
1126
|
+
* Enumerate all size-two subsets of a short list.
|
|
1127
|
+
* ```ts
|
|
1128
|
+
* combinations(2, [1, 2, 3]);
|
|
1129
|
+
* ```
|
|
1130
|
+
*/
|
|
697
1131
|
export function combinations<T>(m: number, list: T[]): T[][] {
|
|
698
1132
|
const res: T[][] = [];
|
|
699
1133
|
if (!Array.isArray(list)) throw new Error('combinations: lst arg should be array');
|
|
700
1134
|
const n = list.length;
|
|
701
|
-
|
|
1135
|
+
anumber(m, 'm');
|
|
1136
|
+
if (m < 1 || m > n) throw new Error('combinations: m must satisfy 1 <= m <= lst.length');
|
|
702
1137
|
/*
|
|
703
1138
|
Basically works as M nested loops like:
|
|
704
1139
|
for (;idx[0]<lst.length;idx[0]++) for (idx[1]=idx[0]+1;idx[1]<lst.length;idx[1]++)
|
|
705
1140
|
but since we cannot create nested loops dynamically, we unroll it to a single loop
|
|
706
1141
|
*/
|
|
1142
|
+
// This unrolled-loop implementation assumes an integer 1 <= m <= n; zero, negative, and
|
|
1143
|
+
// fractional m values need an explicit guard before entering the loop.
|
|
707
1144
|
const idx = Array.from({ length: m }, (_, i) => i);
|
|
708
1145
|
const last = idx.length - 1;
|
|
709
1146
|
main: for (;;) {
|
|
@@ -727,119 +1164,274 @@ export function combinations<T>(m: number, list: T[]): T[][] {
|
|
|
727
1164
|
/**
|
|
728
1165
|
* M-of-N multi-leaf wallet via p2tr_ns. If m == n, single script is emitted.
|
|
729
1166
|
* Takes O(n^2) if m != n. 99-of-100 is ok, 5-of-100 is not.
|
|
1167
|
+
* It materializes C(n, m) leaves, so middle-of-the-range thresholds blow up combinatorially.
|
|
730
1168
|
* `2-of-[A,B,C] => [A,B] | [A,C] | [B,C]`
|
|
731
1169
|
*/
|
|
732
|
-
export type P2TR_NS = {
|
|
1170
|
+
export type P2TR_NS = {
|
|
1171
|
+
/** Payment-script tag for taproot `CHECKSIGVERIFY` leaf scripts. */
|
|
1172
|
+
type: 'tr_ns';
|
|
1173
|
+
/** Serialized tapscript leaf. */
|
|
1174
|
+
script: TRet<Bytes>;
|
|
1175
|
+
};
|
|
1176
|
+
/**
|
|
1177
|
+
* Builds the leaf set for an M-of-N `CHECKSIGVERIFY` taproot policy.
|
|
1178
|
+
* @param m - number of required signatures
|
|
1179
|
+
* @param pubkeys - participating Schnorr public keys
|
|
1180
|
+
* @param allowSamePubkeys - whether duplicate keys are allowed
|
|
1181
|
+
* @returns Array of taproot leaf descriptors.
|
|
1182
|
+
* @throws If the taproot multisig parameters are invalid. {@link Error}
|
|
1183
|
+
* @example
|
|
1184
|
+
* Build the leaf set for an M-of-N taproot `CHECKSIGVERIFY` policy.
|
|
1185
|
+
* ```ts
|
|
1186
|
+
* import { hex } from '@scure/base';
|
|
1187
|
+
* import { p2tr_ns } from '@scure/btc-signer/payment.js';
|
|
1188
|
+
* p2tr_ns(1, [hex.decode('f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9')], true);
|
|
1189
|
+
* ```
|
|
1190
|
+
*/
|
|
733
1191
|
export const p2tr_ns = (
|
|
734
1192
|
m: number,
|
|
735
|
-
pubkeys: Bytes[]
|
|
1193
|
+
pubkeys: TArg<Bytes[]>,
|
|
736
1194
|
allowSamePubkeys = false
|
|
737
|
-
): Extends<P2TR_NS, P2Ret>[] => {
|
|
1195
|
+
): TRet<Extends<P2TR_NS, P2Ret>[]> => {
|
|
738
1196
|
if (!allowSamePubkeys) uniqPubkey(pubkeys);
|
|
739
1197
|
return combinations(m, pubkeys).map(
|
|
740
1198
|
(i) =>
|
|
741
1199
|
({
|
|
742
1200
|
type: 'tr_ns',
|
|
743
1201
|
script: OutScript.encode({ type: 'tr_ns', pubkeys: i }),
|
|
744
|
-
}) as const
|
|
745
|
-
)
|
|
1202
|
+
}) as const as TRet<Extends<P2TR_NS, P2Ret>>
|
|
1203
|
+
) as TRet<Extends<P2TR_NS, P2Ret>[]>;
|
|
746
1204
|
};
|
|
747
1205
|
// Taproot public key (case of p2tr_ns)
|
|
1206
|
+
/** Single-key taproot leaf descriptor. */
|
|
748
1207
|
export type P2TR_PK = P2TR_NS;
|
|
749
|
-
|
|
750
|
-
|
|
1208
|
+
/**
|
|
1209
|
+
* Builds a single-key taproot leaf script.
|
|
1210
|
+
* BIP 341 design guidance: if this is the most likely single-key spend path, prefer
|
|
1211
|
+
* using that key as the `p2tr()` internal key instead of forcing it into a script leaf.
|
|
1212
|
+
* @param pubkey - Schnorr public key
|
|
1213
|
+
* @returns Taproot single-key leaf descriptor.
|
|
1214
|
+
* @throws If the taproot single-key leaf cannot be encoded. {@link Error}
|
|
1215
|
+
* @example
|
|
1216
|
+
* Build a single-key tapscript leaf.
|
|
1217
|
+
* ```ts
|
|
1218
|
+
* import { hex } from '@scure/base';
|
|
1219
|
+
* import { p2tr_pk } from '@scure/btc-signer/payment.js';
|
|
1220
|
+
* p2tr_pk(hex.decode('f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9'));
|
|
1221
|
+
* ```
|
|
1222
|
+
*/
|
|
1223
|
+
export const p2tr_pk = (pubkey: TArg<Bytes>): TRet<Extends<P2TR_PK, P2Ret>> =>
|
|
1224
|
+
p2tr_ns(1, [pubkey], undefined)[0];
|
|
751
1225
|
|
|
752
|
-
|
|
1226
|
+
/** Taproot `CHECKSIGADD` multisig leaf descriptor. */
|
|
1227
|
+
export type P2TR_MS = {
|
|
1228
|
+
/** Payment-script tag for taproot `CHECKSIGADD` leaf scripts. */
|
|
1229
|
+
type: 'tr_ms';
|
|
1230
|
+
/** Serialized tapscript leaf. */
|
|
1231
|
+
script: TRet<Bytes>;
|
|
1232
|
+
};
|
|
1233
|
+
/**
|
|
1234
|
+
* Builds a `CHECKSIGADD` taproot multisig leaf.
|
|
1235
|
+
* @param m - number of required signatures
|
|
1236
|
+
* @param pubkeys - participating Schnorr public keys
|
|
1237
|
+
* @param allowSamePubkeys - whether duplicate keys are allowed
|
|
1238
|
+
* @returns Taproot multisig leaf descriptor.
|
|
1239
|
+
* @throws If the taproot multisig parameters are invalid. {@link Error}
|
|
1240
|
+
* @example
|
|
1241
|
+
* Build a `CHECKSIGADD` taproot multisig leaf.
|
|
1242
|
+
* ```ts
|
|
1243
|
+
* import { hex } from '@scure/base';
|
|
1244
|
+
* import { p2tr_ms } from '@scure/btc-signer/payment.js';
|
|
1245
|
+
* p2tr_ms(1, [hex.decode('f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9')], true);
|
|
1246
|
+
* ```
|
|
1247
|
+
*/
|
|
753
1248
|
export function p2tr_ms(
|
|
754
1249
|
m: number,
|
|
755
|
-
pubkeys: Bytes[]
|
|
1250
|
+
pubkeys: TArg<Bytes[]>,
|
|
756
1251
|
allowSamePubkeys = false
|
|
757
|
-
): Extends<P2TR_MS, P2Ret
|
|
1252
|
+
): TRet<Extends<P2TR_MS, P2Ret>> {
|
|
758
1253
|
if (!allowSamePubkeys) uniqPubkey(pubkeys);
|
|
759
1254
|
return {
|
|
760
1255
|
type: 'tr_ms',
|
|
761
1256
|
script: OutScript.encode({ type: 'tr_ms', pubkeys, m }),
|
|
762
|
-
} as const
|
|
1257
|
+
} as const as TRet<Extends<P2TR_MS, P2Ret>>;
|
|
763
1258
|
}
|
|
764
1259
|
|
|
765
1260
|
// Simple pubkey address, without complex scripts
|
|
1261
|
+
/**
|
|
1262
|
+
* Derives a simple address from a private key.
|
|
1263
|
+
* @param type - address type to derive
|
|
1264
|
+
* @param privKey - private key bytes
|
|
1265
|
+
* @param network - address network parameters
|
|
1266
|
+
* @returns Encoded Bitcoin address.
|
|
1267
|
+
* @throws If the requested address type is unknown. {@link Error}
|
|
1268
|
+
* @example
|
|
1269
|
+
* Pick the output type first, then derive the matching address from the private key.
|
|
1270
|
+
* ```ts
|
|
1271
|
+
* import { getAddress } from '@scure/btc-signer/payment.js';
|
|
1272
|
+
* import { randomPrivateKeyBytes } from '@scure/btc-signer/utils.js';
|
|
1273
|
+
* getAddress('wpkh', randomPrivateKeyBytes());
|
|
1274
|
+
* ```
|
|
1275
|
+
*/
|
|
766
1276
|
export function getAddress(
|
|
767
1277
|
type: 'pkh' | 'wpkh' | 'tr',
|
|
768
|
-
privKey: Bytes
|
|
1278
|
+
privKey: TArg<Bytes>,
|
|
769
1279
|
network: BTC_NETWORK = NETWORK
|
|
770
|
-
): string
|
|
1280
|
+
): string {
|
|
771
1281
|
if (type === 'tr') {
|
|
772
1282
|
return p2tr(u.pubSchnorr(privKey), undefined, network).address;
|
|
773
1283
|
}
|
|
1284
|
+
// This convenience wrapper always uses the compressed ECDSA public key; derive
|
|
1285
|
+
// `pubECDSA(privKey, false)` and call `p2pkh(...)` directly for legacy uncompressed P2PKH.
|
|
774
1286
|
const pubKey = u.pubECDSA(privKey);
|
|
775
1287
|
if (type === 'pkh') return p2pkh(pubKey, network).address;
|
|
776
1288
|
if (type === 'wpkh') return p2wpkh(pubKey, network).address;
|
|
777
1289
|
throw new Error(`getAddress: unknown type=${type}`);
|
|
778
1290
|
}
|
|
779
1291
|
|
|
780
|
-
|
|
1292
|
+
// BIP67 defines canonical multisig ordering only for compressed pubkeys; this helper still sorts
|
|
1293
|
+
// raw bytes generically, and higher-level callers may accept uncompressed participants for compat.
|
|
1294
|
+
export const _sortPubkeys = (pubkeys: TArg<Bytes[]>): TRet<Bytes[]> =>
|
|
1295
|
+
Array.from(pubkeys).sort(u.compareBytes) as TRet<Bytes[]>;
|
|
781
1296
|
|
|
1297
|
+
/**
|
|
1298
|
+
* Builds a classic M-of-N multisig output, wrapped in P2SH or P2WSH.
|
|
1299
|
+
* @param m - number of required signatures
|
|
1300
|
+
* @param pubkeys - participating public keys
|
|
1301
|
+
* @param sorted - whether to sort the public keys first
|
|
1302
|
+
* @param witness - whether to wrap the result as native SegWit
|
|
1303
|
+
* @param network - address network parameters
|
|
1304
|
+
* @returns Multisig payment descriptor.
|
|
1305
|
+
* @throws If the multisig parameters or wrapped script are invalid. {@link Error}
|
|
1306
|
+
* @example
|
|
1307
|
+
* Wrap a classic 2-of-2 script into an addressable multisig output.
|
|
1308
|
+
* ```ts
|
|
1309
|
+
* import { multisig } from '@scure/btc-signer/payment.js';
|
|
1310
|
+
* import { pubECDSA, randomPrivateKeyBytes } from '@scure/btc-signer/utils.js';
|
|
1311
|
+
* multisig(
|
|
1312
|
+
* 2,
|
|
1313
|
+
* [pubECDSA(randomPrivateKeyBytes()), pubECDSA(randomPrivateKeyBytes())],
|
|
1314
|
+
* true,
|
|
1315
|
+
* true
|
|
1316
|
+
* );
|
|
1317
|
+
* ```
|
|
1318
|
+
*/
|
|
782
1319
|
export function multisig(
|
|
783
1320
|
m: number,
|
|
784
|
-
pubkeys: Bytes[]
|
|
1321
|
+
pubkeys: TArg<Bytes[]>,
|
|
785
1322
|
sorted = false,
|
|
786
1323
|
witness = false,
|
|
787
1324
|
network: BTC_NETWORK = NETWORK
|
|
788
|
-
): P2Ret {
|
|
1325
|
+
): TRet<P2Ret> {
|
|
1326
|
+
// BIP 143 default policy: version-0 witness programs should use compressed ECDSA pubkeys only;
|
|
1327
|
+
// witness multisig callers must avoid uncompressed keys because p2ms accepts generic ECDSA encodings.
|
|
1328
|
+
// BIP 16 caps spendable compressed-key P2SH multisig at 15 pubkeys because larger redeem scripts
|
|
1329
|
+
// exceed the 520-byte push limit; use witness=true for larger classic multisig sets.
|
|
789
1330
|
const ms = p2ms(m, sorted ? _sortPubkeys(pubkeys) : pubkeys);
|
|
790
|
-
return witness ? p2wsh(ms, network) : p2sh(ms, network)
|
|
1331
|
+
return (witness ? p2wsh(ms, network) : p2sh(ms, network)) as TRet<P2Ret>;
|
|
791
1332
|
}
|
|
792
1333
|
|
|
1334
|
+
/**
|
|
1335
|
+
* Builds a multisig output after lexicographically sorting the keys.
|
|
1336
|
+
* @param m - number of required signatures
|
|
1337
|
+
* @param pubkeys - participating public keys
|
|
1338
|
+
* @param witness - whether to wrap the result as native SegWit
|
|
1339
|
+
* @param network - address network parameters
|
|
1340
|
+
* @returns Sorted multisig payment descriptor.
|
|
1341
|
+
* @throws If the multisig parameters or wrapped script are invalid. {@link Error}
|
|
1342
|
+
* @example
|
|
1343
|
+
* Sort public keys deterministically before constructing the multisig address.
|
|
1344
|
+
* ```ts
|
|
1345
|
+
* import { sortedMultisig } from '@scure/btc-signer/payment.js';
|
|
1346
|
+
* import { pubECDSA, randomPrivateKeyBytes } from '@scure/btc-signer/utils.js';
|
|
1347
|
+
* sortedMultisig(
|
|
1348
|
+
* 2,
|
|
1349
|
+
* [pubECDSA(randomPrivateKeyBytes()), pubECDSA(randomPrivateKeyBytes())],
|
|
1350
|
+
* true
|
|
1351
|
+
* );
|
|
1352
|
+
* ```
|
|
1353
|
+
*/
|
|
793
1354
|
export function sortedMultisig(
|
|
794
1355
|
m: number,
|
|
795
|
-
pubkeys: Bytes[]
|
|
1356
|
+
pubkeys: TArg<Bytes[]>,
|
|
796
1357
|
witness = false,
|
|
797
1358
|
network: BTC_NETWORK = NETWORK
|
|
798
|
-
): P2Ret {
|
|
799
|
-
|
|
1359
|
+
): TRet<P2Ret> {
|
|
1360
|
+
// BIP67 canonical multisig is compressed-only, but this wrapper intentionally keeps the generic
|
|
1361
|
+
// sorted-multisig behavior and still allows uncompressed participant keys for compatibility.
|
|
1362
|
+
return multisig(m, pubkeys, true, witness, network) as TRet<P2Ret>;
|
|
800
1363
|
}
|
|
801
1364
|
|
|
802
|
-
const base58check = createBase58check(u.sha256);
|
|
1365
|
+
const base58check = /* @__PURE__ */ createBase58check(u.sha256);
|
|
803
1366
|
|
|
804
|
-
function validateWitness(version: number, data: Bytes) {
|
|
1367
|
+
function validateWitness(version: number, data: TArg<Bytes>) {
|
|
805
1368
|
if (data.length < 2 || data.length > 40) throw new Error('Witness: invalid length');
|
|
806
1369
|
if (version > 16) throw new Error('Witness: invalid version');
|
|
807
1370
|
if (version === 0 && !(data.length === 20 || data.length === 32))
|
|
808
1371
|
throw new Error('Witness: invalid length for version');
|
|
809
1372
|
}
|
|
810
1373
|
|
|
811
|
-
function programToWitness(version: number, data: Bytes
|
|
1374
|
+
function programToWitness(version: number, data: TArg<Bytes>, network = NETWORK) {
|
|
812
1375
|
validateWitness(version, data);
|
|
1376
|
+
// BIP 350 keeps segwit v0 on Bech32, while witness versions 1+ switch to Bech32m.
|
|
813
1377
|
const coder = version === 0 ? bech32 : bech32m;
|
|
814
1378
|
return coder.encode(network.bech32, [version].concat(coder.toWords(data)));
|
|
815
1379
|
}
|
|
816
1380
|
|
|
817
|
-
function formatKey(hashed: Bytes
|
|
1381
|
+
function formatKey(hashed: TArg<Bytes>, prefix: number[]): string {
|
|
1382
|
+
// Legacy Base58Check paths all serialize [version-byte || payload] before checksumming.
|
|
818
1383
|
return base58check.encode(u.concatBytes(Uint8Array.from(prefix), hashed));
|
|
819
1384
|
}
|
|
820
1385
|
|
|
821
|
-
|
|
1386
|
+
/**
|
|
1387
|
+
* Wallet-import-format coder for private keys.
|
|
1388
|
+
* @param network - address network parameters
|
|
1389
|
+
* @returns WIF coder.
|
|
1390
|
+
* @example
|
|
1391
|
+
* Encode or decode wallet-import-format private keys.
|
|
1392
|
+
* ```ts
|
|
1393
|
+
* const coder = WIF();
|
|
1394
|
+
* coder.encode(new Uint8Array(32).fill(1));
|
|
1395
|
+
* ```
|
|
1396
|
+
*/
|
|
1397
|
+
export function WIF(network: BTC_NETWORK = NETWORK): TRet<Coder<Bytes, string>> {
|
|
822
1398
|
return {
|
|
823
|
-
encode(privKey: Bytes) {
|
|
1399
|
+
encode(privKey: TArg<Bytes>) {
|
|
1400
|
+
// Compressed WIF is exactly 32 private-key bytes plus the 0x01 suffix; shorter or longer
|
|
1401
|
+
// inputs must be rejected instead of being silently padded or truncated by subarray().
|
|
1402
|
+
abytes(privKey, 32, 'privKey');
|
|
824
1403
|
const compressed = u.concatBytes(privKey, new Uint8Array([0x01]));
|
|
825
1404
|
return formatKey(compressed.subarray(0, 33), [network.wif]);
|
|
826
1405
|
},
|
|
827
|
-
decode(wif: string) {
|
|
1406
|
+
decode(wif: string): TRet<Bytes> {
|
|
828
1407
|
let parsed = base58check.decode(wif);
|
|
829
1408
|
if (parsed[0] !== network.wif) throw new Error('Wrong WIF prefix');
|
|
830
1409
|
parsed = parsed.subarray(1);
|
|
831
1410
|
// Check what it is. Compressed flag?
|
|
832
1411
|
if (parsed.length !== 33) throw new Error('Wrong WIF length');
|
|
833
1412
|
if (parsed[32] !== 0x01) throw new Error('Wrong WIF postfix');
|
|
834
|
-
return parsed.subarray(0, -1)
|
|
1413
|
+
return parsed.subarray(0, -1) as TRet<Bytes>;
|
|
835
1414
|
},
|
|
836
1415
|
};
|
|
837
1416
|
}
|
|
838
1417
|
|
|
839
1418
|
// Returns OutType, which can be used to create outscript
|
|
1419
|
+
/**
|
|
1420
|
+
* Address encoder/decoder for a specific Bitcoin network.
|
|
1421
|
+
* @param network - address network parameters
|
|
1422
|
+
* @returns Address coder backed by the provided network.
|
|
1423
|
+
* @example
|
|
1424
|
+
* Create a network-specific address coder and encode a payment descriptor.
|
|
1425
|
+
* ```ts
|
|
1426
|
+
* import { Address, p2wpkh } from '@scure/btc-signer/payment.js';
|
|
1427
|
+
* import { pubECDSA, randomPrivateKeyBytes } from '@scure/btc-signer/utils.js';
|
|
1428
|
+
* const coder = Address();
|
|
1429
|
+
* coder.encode(p2wpkh(pubECDSA(randomPrivateKeyBytes())));
|
|
1430
|
+
* ```
|
|
1431
|
+
*/
|
|
840
1432
|
export function Address(network: BTC_NETWORK = NETWORK) {
|
|
841
1433
|
return {
|
|
842
|
-
encode(from:
|
|
1434
|
+
encode(from: Exclude<OutScriptValue, undefined>): string {
|
|
843
1435
|
const { type } = from;
|
|
844
1436
|
if (type === 'wpkh') return programToWitness(0, from.hash, network);
|
|
845
1437
|
else if (type === 'wsh') return programToWitness(0, from.hash, network);
|
|
@@ -848,7 +1440,7 @@ export function Address(network: BTC_NETWORK = NETWORK) {
|
|
|
848
1440
|
else if (type === 'sh') return formatKey(from.hash, [network.scriptHash]);
|
|
849
1441
|
throw new Error(`Unknown address type=${type}`);
|
|
850
1442
|
},
|
|
851
|
-
decode(address: string):
|
|
1443
|
+
decode(address: string): OutScriptValue {
|
|
852
1444
|
if (address.length < 14 || address.length > 74) throw new Error('Invalid address length');
|
|
853
1445
|
// Bech32
|
|
854
1446
|
if (network.bech32 && address.toLowerCase().startsWith(`${network.bech32}1`)) {
|
|
@@ -865,21 +1457,26 @@ export function Address(network: BTC_NETWORK = NETWORK) {
|
|
|
865
1457
|
const [version, ...program] = res.words;
|
|
866
1458
|
const data = bech32.fromWords(program);
|
|
867
1459
|
validateWitness(version, data);
|
|
868
|
-
if (version === 0 && data.length === 32)
|
|
869
|
-
|
|
870
|
-
else if (version ===
|
|
1460
|
+
if (version === 0 && data.length === 32)
|
|
1461
|
+
return { type: 'wsh', hash: data } as OutScriptValue;
|
|
1462
|
+
else if (version === 0 && data.length === 20)
|
|
1463
|
+
return { type: 'wpkh', hash: data } as OutScriptValue;
|
|
1464
|
+
else if (version === 1 && data.length === 32)
|
|
1465
|
+
return { type: 'tr', pubkey: data } as OutScriptValue;
|
|
1466
|
+
// Future witness versions can still be valid addresses, but this helper
|
|
1467
|
+
// only returns typed descriptors for recognized v0 and taproot templates.
|
|
871
1468
|
else throw new Error('Unknown witness program');
|
|
872
1469
|
}
|
|
873
1470
|
const data = base58check.decode(address);
|
|
874
1471
|
if (data.length !== 21) throw new Error('Invalid base58 address');
|
|
875
1472
|
// Pay To Public Key Hash
|
|
876
1473
|
if (data[0] === network.pubKeyHash) {
|
|
877
|
-
return { type: 'pkh', hash: data.slice(1) };
|
|
1474
|
+
return { type: 'pkh', hash: data.slice(1) } as OutScriptValue;
|
|
878
1475
|
} else if (data[0] === network.scriptHash) {
|
|
879
1476
|
return {
|
|
880
1477
|
type: 'sh',
|
|
881
1478
|
hash: data.slice(1),
|
|
882
|
-
};
|
|
1479
|
+
} as OutScriptValue;
|
|
883
1480
|
}
|
|
884
1481
|
throw new Error(`Invalid address prefix=${data[0]}`);
|
|
885
1482
|
},
|