@scure/btc-signer 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +19 -3
  2. package/lib/_type_test.d.ts +2 -0
  3. package/lib/_type_test.d.ts.map +1 -0
  4. package/lib/_type_test.js +59 -0
  5. package/lib/_type_test.js.map +1 -0
  6. package/lib/esm/_type_test.js +57 -0
  7. package/lib/esm/_type_test.js.map +1 -0
  8. package/lib/esm/index.js +13 -3007
  9. package/lib/esm/index.js.map +1 -1
  10. package/lib/esm/payment.js +681 -0
  11. package/lib/esm/payment.js.map +1 -0
  12. package/lib/esm/psbt.js +441 -0
  13. package/lib/esm/psbt.js.map +1 -0
  14. package/lib/esm/script.js +347 -0
  15. package/lib/esm/script.js.map +1 -0
  16. package/lib/esm/transaction.js +1013 -0
  17. package/lib/esm/transaction.js.map +1 -0
  18. package/lib/esm/utils.js +115 -0
  19. package/lib/esm/utils.js.map +1 -0
  20. package/lib/esm/utxo.js +491 -0
  21. package/lib/esm/utxo.js.map +1 -0
  22. package/lib/index.d.ts +18 -1439
  23. package/lib/index.d.ts.map +1 -1
  24. package/lib/index.js +53 -3042
  25. package/lib/index.js.map +1 -0
  26. package/lib/payment.d.ts +167 -0
  27. package/lib/payment.d.ts.map +1 -0
  28. package/lib/payment.js +704 -0
  29. package/lib/payment.js.map +1 -0
  30. package/lib/psbt.d.ts +834 -0
  31. package/lib/psbt.d.ts.map +1 -0
  32. package/lib/psbt.js +446 -0
  33. package/lib/psbt.js.map +1 -0
  34. package/lib/script.d.ts +154 -0
  35. package/lib/script.d.ts.map +1 -0
  36. package/lib/script.js +353 -0
  37. package/lib/script.js.map +1 -0
  38. package/lib/transaction.d.ts +223 -0
  39. package/lib/transaction.d.ts.map +1 -0
  40. package/lib/transaction.js +1022 -0
  41. package/lib/transaction.js.map +1 -0
  42. package/lib/utils.d.ts +30 -0
  43. package/lib/utils.d.ts.map +1 -0
  44. package/lib/utils.js +128 -0
  45. package/lib/utils.js.map +1 -0
  46. package/lib/utxo.d.ts +251 -0
  47. package/lib/utxo.d.ts.map +1 -0
  48. package/lib/utxo.js +501 -0
  49. package/lib/utxo.js.map +1 -0
  50. package/package.json +40 -10
  51. package/src/_type_test.ts +69 -0
  52. package/src/index.ts +28 -0
  53. package/src/package.json +3 -0
  54. package/src/payment.ts +749 -0
  55. package/src/psbt.ts +512 -0
  56. package/src/script.ts +236 -0
  57. package/src/transaction.ts +1065 -0
  58. package/src/utils.ts +118 -0
  59. package/src/utxo.ts +517 -0
  60. package/index.ts +0 -3067
@@ -0,0 +1,681 @@
1
+ import { hex, bech32, bech32m, createBase58check } from '@scure/base';
2
+ import * as P from 'micro-packed';
3
+ import { TaprootControlBlock } from './psbt.js';
4
+ import { OpToNum, Script, VarBytes } from './script.js';
5
+ import { NETWORK } from './utils.js';
6
+ import * as u from './utils.js';
7
+ function isValidPubkey(pub, type) {
8
+ try {
9
+ u.validatePubkey(pub, type);
10
+ return true;
11
+ }
12
+ catch (e) {
13
+ return false;
14
+ }
15
+ }
16
+ const OutPK = {
17
+ encode(from) {
18
+ if (from.length !== 2 ||
19
+ !u.isBytes(from[0]) ||
20
+ !isValidPubkey(from[0], u.PubT.ecdsa) ||
21
+ from[1] !== 'CHECKSIG')
22
+ return;
23
+ return { type: 'pk', pubkey: from[0] };
24
+ },
25
+ decode: (to) => (to.type === 'pk' ? [to.pubkey, 'CHECKSIG'] : undefined),
26
+ };
27
+ const OutPKH = {
28
+ encode(from) {
29
+ if (from.length !== 5 || from[0] !== 'DUP' || from[1] !== 'HASH160' || !u.isBytes(from[2]))
30
+ return;
31
+ if (from[3] !== 'EQUALVERIFY' || from[4] !== 'CHECKSIG')
32
+ return;
33
+ return { type: 'pkh', hash: from[2] };
34
+ },
35
+ decode: (to) => to.type === 'pkh' ? ['DUP', 'HASH160', to.hash, 'EQUALVERIFY', 'CHECKSIG'] : undefined,
36
+ };
37
+ const OutSH = {
38
+ encode(from) {
39
+ if (from.length !== 3 || from[0] !== 'HASH160' || !u.isBytes(from[1]) || from[2] !== 'EQUAL')
40
+ return;
41
+ return { type: 'sh', hash: from[1] };
42
+ },
43
+ decode: (to) => to.type === 'sh' ? ['HASH160', to.hash, 'EQUAL'] : undefined,
44
+ };
45
+ const OutWSH = {
46
+ encode(from) {
47
+ if (from.length !== 2 || from[0] !== 0 || !u.isBytes(from[1]))
48
+ return;
49
+ if (from[1].length !== 32)
50
+ return;
51
+ return { type: 'wsh', hash: from[1] };
52
+ },
53
+ decode: (to) => (to.type === 'wsh' ? [0, to.hash] : undefined),
54
+ };
55
+ const OutWPKH = {
56
+ encode(from) {
57
+ if (from.length !== 2 || from[0] !== 0 || !u.isBytes(from[1]))
58
+ return;
59
+ if (from[1].length !== 20)
60
+ return;
61
+ return { type: 'wpkh', hash: from[1] };
62
+ },
63
+ decode: (to) => (to.type === 'wpkh' ? [0, to.hash] : undefined),
64
+ };
65
+ const OutMS = {
66
+ encode(from) {
67
+ const last = from.length - 1;
68
+ if (from[last] !== 'CHECKMULTISIG')
69
+ return;
70
+ const m = from[0];
71
+ const n = from[last - 1];
72
+ if (typeof m !== 'number' || typeof n !== 'number')
73
+ return;
74
+ const pubkeys = from.slice(1, -2);
75
+ if (n !== pubkeys.length)
76
+ return;
77
+ for (const pub of pubkeys)
78
+ if (!u.isBytes(pub))
79
+ return;
80
+ return { type: 'ms', m, pubkeys: pubkeys }; // we don't need n, since it is the same as pubkeys
81
+ },
82
+ // checkmultisig(n, ..pubkeys, m)
83
+ decode: (to) => to.type === 'ms' ? [to.m, ...to.pubkeys, to.pubkeys.length, 'CHECKMULTISIG'] : undefined,
84
+ };
85
+ const OutTR = {
86
+ encode(from) {
87
+ if (from.length !== 2 || from[0] !== 1 || !u.isBytes(from[1]))
88
+ return;
89
+ return { type: 'tr', pubkey: from[1] };
90
+ },
91
+ decode: (to) => (to.type === 'tr' ? [1, to.pubkey] : undefined),
92
+ };
93
+ const OutTRNS = {
94
+ encode(from) {
95
+ const last = from.length - 1;
96
+ if (from[last] !== 'CHECKSIG')
97
+ return;
98
+ const pubkeys = [];
99
+ // On error return, since it can be different script
100
+ for (let i = 0; i < last; i++) {
101
+ const elm = from[i];
102
+ if (i & 1) {
103
+ if (elm !== 'CHECKSIGVERIFY' || i === last - 1)
104
+ return;
105
+ continue;
106
+ }
107
+ if (!u.isBytes(elm))
108
+ return;
109
+ pubkeys.push(elm);
110
+ }
111
+ return { type: 'tr_ns', pubkeys };
112
+ },
113
+ decode: (to) => {
114
+ if (to.type !== 'tr_ns')
115
+ return;
116
+ const out = [];
117
+ for (let i = 0; i < to.pubkeys.length - 1; i++)
118
+ out.push(to.pubkeys[i], 'CHECKSIGVERIFY');
119
+ out.push(to.pubkeys[to.pubkeys.length - 1], 'CHECKSIG');
120
+ return out;
121
+ },
122
+ };
123
+ const OutTRMS = {
124
+ encode(from) {
125
+ const last = from.length - 1;
126
+ if (from[last] !== 'NUMEQUAL' || from[1] !== 'CHECKSIG')
127
+ return;
128
+ const pubkeys = [];
129
+ const m = OpToNum(from[last - 1]);
130
+ if (typeof m !== 'number')
131
+ return;
132
+ for (let i = 0; i < last - 1; i++) {
133
+ const elm = from[i];
134
+ if (i & 1) {
135
+ if (elm !== (i === 1 ? 'CHECKSIG' : 'CHECKSIGADD'))
136
+ throw new Error('OutScript.encode/tr_ms: wrong element');
137
+ continue;
138
+ }
139
+ if (!u.isBytes(elm))
140
+ throw new Error('OutScript.encode/tr_ms: wrong key element');
141
+ pubkeys.push(elm);
142
+ }
143
+ return { type: 'tr_ms', pubkeys, m };
144
+ },
145
+ decode: (to) => {
146
+ if (to.type !== 'tr_ms')
147
+ return;
148
+ const out = [to.pubkeys[0], 'CHECKSIG'];
149
+ for (let i = 1; i < to.pubkeys.length; i++)
150
+ out.push(to.pubkeys[i], 'CHECKSIGADD');
151
+ out.push(to.m, 'NUMEQUAL');
152
+ return out;
153
+ },
154
+ };
155
+ const OutUnknown = {
156
+ encode(from) {
157
+ return { type: 'unknown', script: Script.encode(from) };
158
+ },
159
+ decode: (to) => to.type === 'unknown' ? Script.decode(to.script) : undefined,
160
+ };
161
+ // /Payments
162
+ const OutScripts = [
163
+ OutPK,
164
+ OutPKH,
165
+ OutSH,
166
+ OutWSH,
167
+ OutWPKH,
168
+ OutMS,
169
+ OutTR,
170
+ OutTRNS,
171
+ OutTRMS,
172
+ OutUnknown,
173
+ ];
174
+ // TODO: we can support user supplied output scripts now
175
+ // - addOutScript
176
+ // - removeOutScript
177
+ // - We can do that as log we modify array in-place
178
+ // - Actually is very hard, since there is sign/finalize logic
179
+ const _OutScript = P.apply(Script, P.coders.match(OutScripts));
180
+ // We can validate this once, because of packed & coders
181
+ export const OutScript = P.validate(_OutScript, (i) => {
182
+ if (i.type === 'pk' && !isValidPubkey(i.pubkey, u.PubT.ecdsa))
183
+ throw new Error('OutScript/pk: wrong key');
184
+ if ((i.type === 'pkh' || i.type === 'sh' || i.type === 'wpkh') &&
185
+ (!u.isBytes(i.hash) || i.hash.length !== 20))
186
+ throw new Error(`OutScript/${i.type}: wrong hash`);
187
+ if (i.type === 'wsh' && (!u.isBytes(i.hash) || i.hash.length !== 32))
188
+ throw new Error(`OutScript/wsh: wrong hash`);
189
+ if (i.type === 'tr' && (!u.isBytes(i.pubkey) || !isValidPubkey(i.pubkey, u.PubT.schnorr)))
190
+ throw new Error('OutScript/tr: wrong taproot public key');
191
+ if (i.type === 'ms' || i.type === 'tr_ns' || i.type === 'tr_ms')
192
+ if (!Array.isArray(i.pubkeys))
193
+ throw new Error('OutScript/multisig: wrong pubkeys array');
194
+ if (i.type === 'ms') {
195
+ const n = i.pubkeys.length;
196
+ for (const p of i.pubkeys)
197
+ if (!isValidPubkey(p, u.PubT.ecdsa))
198
+ throw new Error('OutScript/multisig: wrong pubkey');
199
+ if (i.m <= 0 || n > 16 || i.m > n)
200
+ throw new Error('OutScript/multisig: invalid params');
201
+ }
202
+ if (i.type === 'tr_ns' || i.type === 'tr_ms') {
203
+ for (const p of i.pubkeys)
204
+ if (!isValidPubkey(p, u.PubT.schnorr))
205
+ throw new Error(`OutScript/${i.type}: wrong pubkey`);
206
+ }
207
+ if (i.type === 'tr_ms') {
208
+ const n = i.pubkeys.length;
209
+ if (i.m <= 0 || n > 999 || i.m > n)
210
+ throw new Error('OutScript/tr_ms: invalid params');
211
+ }
212
+ return i;
213
+ });
214
+ // Basic sanity check for scripts
215
+ function checkWSH(s, witnessScript) {
216
+ if (!P.equalBytes(s.hash, u.sha256(witnessScript)))
217
+ throw new Error('checkScript: wsh wrong witnessScript hash');
218
+ const w = OutScript.decode(witnessScript);
219
+ if (w.type === 'tr' || w.type === 'tr_ns' || w.type === 'tr_ms')
220
+ throw new Error(`checkScript: P2${w.type} cannot be wrapped in P2SH`);
221
+ if (w.type === 'wpkh' || w.type === 'sh')
222
+ throw new Error(`checkScript: P2${w.type} cannot be wrapped in P2WSH`);
223
+ }
224
+ export function checkScript(script, redeemScript, witnessScript) {
225
+ if (script) {
226
+ const s = OutScript.decode(script);
227
+ // ms||pk maybe work, but there will be no address, hard to spend
228
+ if (s.type === 'tr_ns' || s.type === 'tr_ms' || s.type === 'ms' || s.type == 'pk')
229
+ throw new Error(`checkScript: non-wrapped ${s.type}`);
230
+ if (s.type === 'sh' && redeemScript) {
231
+ if (!P.equalBytes(s.hash, u.hash160(redeemScript)))
232
+ throw new Error('checkScript: sh wrong redeemScript hash');
233
+ const r = OutScript.decode(redeemScript);
234
+ if (r.type === 'tr' || r.type === 'tr_ns' || r.type === 'tr_ms')
235
+ throw new Error(`checkScript: P2${r.type} cannot be wrapped in P2SH`);
236
+ // Not sure if this unspendable, but we cannot represent this via PSBT
237
+ if (r.type === 'sh')
238
+ throw new Error('checkScript: P2SH cannot be wrapped in P2SH');
239
+ }
240
+ if (s.type === 'wsh' && witnessScript)
241
+ checkWSH(s, witnessScript);
242
+ }
243
+ if (redeemScript) {
244
+ const r = OutScript.decode(redeemScript);
245
+ if (r.type === 'wsh' && witnessScript)
246
+ checkWSH(r, witnessScript);
247
+ }
248
+ }
249
+ function uniqPubkey(pubkeys) {
250
+ const map = {};
251
+ for (const pub of pubkeys) {
252
+ const key = hex.encode(pub);
253
+ if (map[key])
254
+ throw new Error(`Multisig: non-uniq pubkey: ${pubkeys.map(hex.encode)}`);
255
+ map[key] = true;
256
+ }
257
+ }
258
+ // @ts-ignore
259
+ export const p2pk = (pubkey, network = NETWORK) => {
260
+ // network is unused
261
+ if (!isValidPubkey(pubkey, u.PubT.ecdsa))
262
+ throw new Error('P2PK: invalid publicKey');
263
+ return {
264
+ type: 'pk',
265
+ script: OutScript.encode({ type: 'pk', pubkey }),
266
+ };
267
+ };
268
+ export const p2pkh = (publicKey, network = NETWORK) => {
269
+ if (!isValidPubkey(publicKey, u.PubT.ecdsa))
270
+ throw new Error('P2PKH: invalid publicKey');
271
+ const hash = u.hash160(publicKey);
272
+ return {
273
+ type: 'pkh',
274
+ script: OutScript.encode({ type: 'pkh', hash }),
275
+ address: Address(network).encode({ type: 'pkh', hash }),
276
+ };
277
+ };
278
+ export const p2sh = (child, network = NETWORK) => {
279
+ // It is already tested inside noble-hashes and checkScript
280
+ const cs = child.script;
281
+ if (!u.isBytes(cs))
282
+ throw new Error(`Wrong script: ${typeof child.script}, expected Uint8Array`);
283
+ const hash = u.hash160(cs);
284
+ const script = OutScript.encode({ type: 'sh', hash });
285
+ checkScript(script, cs, child.witnessScript);
286
+ const res = {
287
+ type: 'sh',
288
+ redeemScript: cs,
289
+ script: OutScript.encode({ type: 'sh', hash }),
290
+ address: Address(network).encode({ type: 'sh', hash }),
291
+ };
292
+ if (child.witnessScript)
293
+ res.witnessScript = child.witnessScript;
294
+ return res;
295
+ };
296
+ export const p2wsh = (child, network = NETWORK) => {
297
+ const cs = child.script;
298
+ if (!u.isBytes(cs))
299
+ throw new Error(`Wrong script: ${typeof cs}, expected Uint8Array`);
300
+ const hash = u.sha256(cs);
301
+ const script = OutScript.encode({ type: 'wsh', hash });
302
+ checkScript(script, undefined, cs);
303
+ return {
304
+ type: 'wsh',
305
+ witnessScript: cs,
306
+ script: OutScript.encode({ type: 'wsh', hash }),
307
+ address: Address(network).encode({ type: 'wsh', hash }),
308
+ };
309
+ };
310
+ export const p2wpkh = (publicKey, network = NETWORK) => {
311
+ if (!isValidPubkey(publicKey, u.PubT.ecdsa))
312
+ throw new Error('P2WPKH: invalid publicKey');
313
+ if (publicKey.length === 65)
314
+ throw new Error('P2WPKH: uncompressed public key');
315
+ const hash = u.hash160(publicKey);
316
+ return {
317
+ type: 'wpkh',
318
+ script: OutScript.encode({ type: 'wpkh', hash }),
319
+ address: Address(network).encode({ type: 'wpkh', hash }),
320
+ };
321
+ };
322
+ export const p2ms = (m, pubkeys, allowSamePubkeys = false) => {
323
+ if (!allowSamePubkeys)
324
+ uniqPubkey(pubkeys);
325
+ return { type: 'ms', script: OutScript.encode({ type: 'ms', pubkeys, m }) };
326
+ };
327
+ function checkTaprootScript(script, internalPubKey, allowUnknownOutputs = false, customScripts) {
328
+ const out = OutScript.decode(script);
329
+ if (out.type === 'unknown') {
330
+ // NOTE: this check should be before allowUnknownOutputs, otherwise it will
331
+ // disable custom. All custom scripts for taproot should have prefix 'tr_'
332
+ if (customScripts) {
333
+ const cs = P.apply(Script, P.coders.match(customScripts));
334
+ const c = cs.decode(script);
335
+ if (c !== undefined) {
336
+ if (typeof c.type !== 'string' || !c.type.startsWith('tr_'))
337
+ throw new Error(`P2TR: invalid custom type=${c.type}`);
338
+ return;
339
+ }
340
+ }
341
+ if (allowUnknownOutputs)
342
+ return;
343
+ }
344
+ if (!['tr_ns', 'tr_ms'].includes(out.type))
345
+ throw new Error(`P2TR: invalid leaf script=${out.type}`);
346
+ const outms = out;
347
+ if (!allowUnknownOutputs && outms.pubkeys) {
348
+ for (const p of outms.pubkeys) {
349
+ if (P.equalBytes(p, u.TAPROOT_UNSPENDABLE_KEY))
350
+ throw new Error('Unspendable taproot key in leaf script');
351
+ // It's likely a mistake at this point:
352
+ // 1. p2tr(A, p2tr_ns(2, [A, B])) == p2tr(A, p2tr_pk(B)) (A or B key)
353
+ // but will take more space and fees.
354
+ // 2. For multi-sig p2tr(A, p2tr_ns(2, [A, B, C])) it's probably a security issue:
355
+ // User creates 2 of 3 multisig of keys [A, B, C],
356
+ // but key A always can spend whole output without signatures from other keys.
357
+ // p2tr(A, p2tr_ns(2, [B, C, D])) is ok: A or (B and C) or (B and D) or (C and D)
358
+ if (P.equalBytes(p, internalPubKey)) {
359
+ throw new Error('Using P2TR with leaf script with same key as internal key is not supported');
360
+ }
361
+ }
362
+ }
363
+ }
364
+ // Helper for generating binary tree from list, with weights
365
+ export function taprootListToTree(taprootList) {
366
+ // Clone input in order to not corrupt it
367
+ const lst = Array.from(taprootList);
368
+ // We have at least 2 elements => can create branch
369
+ while (lst.length >= 2) {
370
+ // Sort: elements with smallest weight are in the end of queue
371
+ lst.sort((a, b) => (b.weight || 1) - (a.weight || 1));
372
+ const b = lst.pop();
373
+ const a = lst.pop();
374
+ const weight = (a?.weight || 1) + (b?.weight || 1);
375
+ lst.push({
376
+ weight,
377
+ // Unwrap children array
378
+ // TODO: Very hard to remove any here
379
+ childs: [a?.childs || a, b?.childs || b],
380
+ });
381
+ }
382
+ // At this point there is always 1 element in lst
383
+ const last = lst[0];
384
+ return (last?.childs || last);
385
+ }
386
+ function taprootAddPath(tree, path = []) {
387
+ if (!tree)
388
+ throw new Error(`taprootAddPath: empty tree`);
389
+ if (tree.type === 'leaf')
390
+ return { ...tree, path };
391
+ if (tree.type !== 'branch')
392
+ throw new Error(`taprootAddPath: wrong type=${tree}`);
393
+ return {
394
+ ...tree,
395
+ path,
396
+ // Left element has right hash in path and otherwise
397
+ left: taprootAddPath(tree.left, [tree.right.hash, ...path]),
398
+ right: taprootAddPath(tree.right, [tree.left.hash, ...path]),
399
+ };
400
+ }
401
+ function taprootWalkTree(tree) {
402
+ if (!tree)
403
+ throw new Error(`taprootAddPath: empty tree`);
404
+ if (tree.type === 'leaf')
405
+ return [tree];
406
+ if (tree.type !== 'branch')
407
+ throw new Error(`taprootWalkTree: wrong type=${tree}`);
408
+ return [...taprootWalkTree(tree.left), ...taprootWalkTree(tree.right)];
409
+ }
410
+ function taprootHashTree(tree, internalPubKey, allowUnknownOutputs = false, customScripts) {
411
+ if (!tree)
412
+ throw new Error('taprootHashTree: empty tree');
413
+ if (Array.isArray(tree) && tree.length === 1)
414
+ tree = tree[0];
415
+ // Terminal node (leaf)
416
+ if (!Array.isArray(tree)) {
417
+ const { leafVersion: version, script: leafScript } = tree;
418
+ // Earliest tree walk where we can validate tapScripts
419
+ if (tree.tapLeafScript || (tree.tapMerkleRoot && !P.equalBytes(tree.tapMerkleRoot, P.EMPTY)))
420
+ throw new Error('P2TR: tapRoot leafScript cannot have tree');
421
+ const script = typeof leafScript === 'string' ? hex.decode(leafScript) : leafScript;
422
+ if (!u.isBytes(script))
423
+ throw new Error(`checkScript: wrong script type=${script}`);
424
+ checkTaprootScript(script, internalPubKey, allowUnknownOutputs, customScripts);
425
+ return {
426
+ type: 'leaf',
427
+ version,
428
+ script,
429
+ hash: tapLeafHash(script, version),
430
+ };
431
+ }
432
+ // If tree / branch is not binary tree, convert it
433
+ if (tree.length !== 2)
434
+ tree = taprootListToTree(tree);
435
+ if (tree.length !== 2)
436
+ throw new Error('hashTree: non binary tree!');
437
+ // branch
438
+ // Both nodes should exist
439
+ const left = taprootHashTree(tree[0], internalPubKey, allowUnknownOutputs, customScripts);
440
+ const right = taprootHashTree(tree[1], internalPubKey, allowUnknownOutputs, customScripts);
441
+ // We cannot swap left/right here, since it will change structure of tree
442
+ let [lH, rH] = [left.hash, right.hash];
443
+ if (u.compareBytes(rH, lH) === -1)
444
+ [lH, rH] = [rH, lH];
445
+ return { type: 'branch', left, right, hash: u.tagSchnorr('TapBranch', lH, rH) };
446
+ }
447
+ export const TAP_LEAF_VERSION = 0xc0;
448
+ export const tapLeafHash = (script, version = TAP_LEAF_VERSION) => u.tagSchnorr('TapLeaf', new Uint8Array([version]), VarBytes.encode(script));
449
+ // Works as key OR tree.
450
+ // If we only have tree, need to add unspendable key, otherwise
451
+ // complex multisig wallet can be spent by owner of key only. See TAPROOT_UNSPENDABLE_KEY
452
+ export function p2tr(internalPubKey, tree, network = NETWORK, allowUnknownOutputs = false, customScripts) {
453
+ // Unspendable
454
+ if (!internalPubKey && !tree)
455
+ throw new Error('p2tr: should have pubKey or scriptTree (or both)');
456
+ const pubKey = typeof internalPubKey === 'string'
457
+ ? hex.decode(internalPubKey)
458
+ : internalPubKey || u.TAPROOT_UNSPENDABLE_KEY;
459
+ if (!isValidPubkey(pubKey, u.PubT.schnorr))
460
+ throw new Error('p2tr: non-schnorr pubkey');
461
+ let hashedTree = tree
462
+ ? taprootAddPath(taprootHashTree(tree, pubKey, allowUnknownOutputs, customScripts))
463
+ : undefined;
464
+ const tapMerkleRoot = hashedTree ? hashedTree.hash : undefined;
465
+ const [tweakedPubkey, parity] = u.taprootTweakPubkey(pubKey, tapMerkleRoot || P.EMPTY);
466
+ let leaves;
467
+ if (hashedTree) {
468
+ leaves = taprootWalkTree(hashedTree).map((l) => ({
469
+ ...l,
470
+ controlBlock: TaprootControlBlock.encode({
471
+ version: (l.version || TAP_LEAF_VERSION) + parity,
472
+ internalKey: pubKey,
473
+ merklePath: l.path,
474
+ }),
475
+ }));
476
+ }
477
+ let tapLeafScript;
478
+ if (leaves) {
479
+ tapLeafScript = leaves.map((l) => [
480
+ TaprootControlBlock.decode(l.controlBlock),
481
+ u.concatBytes(l.script, new Uint8Array([l.version || TAP_LEAF_VERSION])),
482
+ ]);
483
+ }
484
+ const res = {
485
+ type: 'tr',
486
+ script: OutScript.encode({ type: 'tr', pubkey: tweakedPubkey }),
487
+ address: Address(network).encode({ type: 'tr', pubkey: tweakedPubkey }),
488
+ // For tests
489
+ tweakedPubkey,
490
+ // PSBT stuff
491
+ tapInternalKey: pubKey,
492
+ };
493
+ // Just in case someone would want to select a specific script
494
+ if (leaves)
495
+ res.leaves = leaves;
496
+ if (tapLeafScript)
497
+ res.tapLeafScript = tapLeafScript;
498
+ if (tapMerkleRoot)
499
+ res.tapMerkleRoot = tapMerkleRoot;
500
+ return res;
501
+ }
502
+ // Returns all combinations of size M from lst
503
+ export function combinations(m, list) {
504
+ const res = [];
505
+ if (!Array.isArray(list))
506
+ throw new Error('combinations: lst arg should be array');
507
+ const n = list.length;
508
+ if (m > n)
509
+ throw new Error('combinations: m > lst.length, no combinations possible');
510
+ /*
511
+ Basically works as M nested loops like:
512
+ for (;idx[0]<lst.length;idx[0]++) for (idx[1]=idx[0]+1;idx[1]<lst.length;idx[1]++)
513
+ but since we cannot create nested loops dynamically, we unroll it to a single loop
514
+ */
515
+ const idx = Array.from({ length: m }, (_, i) => i);
516
+ const last = idx.length - 1;
517
+ main: for (;;) {
518
+ res.push(idx.map((i) => list[i]));
519
+ idx[last] += 1;
520
+ let i = last;
521
+ // Propagate increment
522
+ // idx[i] cannot be bigger than n-m+i, otherwise last elements in right part will overflow
523
+ for (; i >= 0 && idx[i] > n - m + i; i--) {
524
+ idx[i] = 0;
525
+ // Overflow in idx[0], break
526
+ if (i === 0)
527
+ break main;
528
+ idx[i - 1] += 1;
529
+ }
530
+ // Propagate: idx[i+1] = idx[idx]+1
531
+ for (i += 1; i < idx.length; i++)
532
+ idx[i] = idx[i - 1] + 1;
533
+ }
534
+ return res;
535
+ }
536
+ /**
537
+ * M-of-N multi-leaf wallet via p2tr_ns. If m == n, single script is emitted.
538
+ * Takes O(n^2) if m != n. 99-of-100 is ok, 5-of-100 is not.
539
+ * `2-of-[A,B,C] => [A,B] | [A,C] | [B,C]`
540
+ */
541
+ export const p2tr_ns = (m, pubkeys, allowSamePubkeys = false) => {
542
+ if (!allowSamePubkeys)
543
+ uniqPubkey(pubkeys);
544
+ return combinations(m, pubkeys).map((i) => ({
545
+ type: 'tr_ns',
546
+ script: OutScript.encode({ type: 'tr_ns', pubkeys: i }),
547
+ }));
548
+ };
549
+ // Taproot public key (case of p2tr_ns)
550
+ export const p2tr_pk = (pubkey) => p2tr_ns(1, [pubkey], undefined)[0];
551
+ export function p2tr_ms(m, pubkeys, allowSamePubkeys = false) {
552
+ if (!allowSamePubkeys)
553
+ uniqPubkey(pubkeys);
554
+ return {
555
+ type: 'tr_ms',
556
+ script: OutScript.encode({ type: 'tr_ms', pubkeys, m }),
557
+ };
558
+ }
559
+ // Simple pubkey address, without complex scripts
560
+ export function getAddress(type, privKey, network = NETWORK) {
561
+ if (type === 'tr') {
562
+ return p2tr(u.pubSchnorr(privKey), undefined, network).address;
563
+ }
564
+ const pubKey = u.pubECDSA(privKey);
565
+ if (type === 'pkh')
566
+ return p2pkh(pubKey, network).address;
567
+ if (type === 'wpkh')
568
+ return p2wpkh(pubKey, network).address;
569
+ throw new Error(`getAddress: unknown type=${type}`);
570
+ }
571
+ export const _sortPubkeys = (pubkeys) => Array.from(pubkeys).sort(u.compareBytes);
572
+ export function multisig(m, pubkeys, sorted = false, witness = false) {
573
+ const ms = p2ms(m, sorted ? _sortPubkeys(pubkeys) : pubkeys);
574
+ return witness ? p2wsh(ms) : p2sh(ms);
575
+ }
576
+ export function sortedMultisig(m, pubkeys, witness = false) {
577
+ return multisig(m, pubkeys, true, witness);
578
+ }
579
+ const base58check = createBase58check(u.sha256);
580
+ function validateWitness(version, data) {
581
+ if (data.length < 2 || data.length > 40)
582
+ throw new Error('Witness: invalid length');
583
+ if (version > 16)
584
+ throw new Error('Witness: invalid version');
585
+ if (version === 0 && !(data.length === 20 || data.length === 32))
586
+ throw new Error('Witness: invalid length for version');
587
+ }
588
+ function programToWitness(version, data, network = NETWORK) {
589
+ validateWitness(version, data);
590
+ const coder = version === 0 ? bech32 : bech32m;
591
+ return coder.encode(network.bech32, [version].concat(coder.toWords(data)));
592
+ }
593
+ function formatKey(hashed, prefix) {
594
+ return base58check.encode(u.concatBytes(Uint8Array.from(prefix), hashed));
595
+ }
596
+ export function WIF(network = NETWORK) {
597
+ return {
598
+ encode(privKey) {
599
+ const compressed = u.concatBytes(privKey, new Uint8Array([0x01]));
600
+ return formatKey(compressed.subarray(0, 33), [network.wif]);
601
+ },
602
+ decode(wif) {
603
+ let parsed = base58check.decode(wif);
604
+ if (parsed[0] !== network.wif)
605
+ throw new Error('Wrong WIF prefix');
606
+ parsed = parsed.subarray(1);
607
+ // Check what it is. Compressed flag?
608
+ if (parsed.length !== 33)
609
+ throw new Error('Wrong WIF length');
610
+ if (parsed[32] !== 0x01)
611
+ throw new Error('Wrong WIF postfix');
612
+ return parsed.subarray(0, -1);
613
+ },
614
+ };
615
+ }
616
+ // Returns OutType, which can be used to create outscript
617
+ export function Address(network = NETWORK) {
618
+ return {
619
+ encode(from) {
620
+ const { type } = from;
621
+ if (type === 'wpkh')
622
+ return programToWitness(0, from.hash, network);
623
+ else if (type === 'wsh')
624
+ return programToWitness(0, from.hash, network);
625
+ else if (type === 'tr')
626
+ return programToWitness(1, from.pubkey, network);
627
+ else if (type === 'pkh')
628
+ return formatKey(from.hash, [network.pubKeyHash]);
629
+ else if (type === 'sh')
630
+ return formatKey(from.hash, [network.scriptHash]);
631
+ throw new Error(`Unknown address type=${type}`);
632
+ },
633
+ decode(address) {
634
+ if (address.length < 14 || address.length > 74)
635
+ throw new Error('Invalid address length');
636
+ // Bech32
637
+ if (network.bech32 && address.toLowerCase().startsWith(network.bech32)) {
638
+ let res;
639
+ try {
640
+ res = bech32.decode(address);
641
+ if (res.words[0] !== 0)
642
+ throw new Error(`bech32: wrong version=${res.words[0]}`);
643
+ }
644
+ catch (_) {
645
+ // Starting from version 1 it is decoded as bech32m
646
+ res = bech32m.decode(address);
647
+ if (res.words[0] === 0)
648
+ throw new Error(`bech32m: wrong version=${res.words[0]}`);
649
+ }
650
+ if (res.prefix !== network.bech32)
651
+ throw new Error(`wrong bech32 prefix=${res.prefix}`);
652
+ const [version, ...program] = res.words;
653
+ const data = bech32.fromWords(program);
654
+ validateWitness(version, data);
655
+ if (version === 0 && data.length === 32)
656
+ return { type: 'wsh', hash: data };
657
+ else if (version === 0 && data.length === 20)
658
+ return { type: 'wpkh', hash: data };
659
+ else if (version === 1 && data.length === 32)
660
+ return { type: 'tr', pubkey: data };
661
+ else
662
+ throw new Error('Unknown witness program');
663
+ }
664
+ const data = base58check.decode(address);
665
+ if (data.length !== 21)
666
+ throw new Error('Invalid base58 address');
667
+ // Pay To Public Key Hash
668
+ if (data[0] === network.pubKeyHash) {
669
+ return { type: 'pkh', hash: data.slice(1) };
670
+ }
671
+ else if (data[0] === network.scriptHash) {
672
+ return {
673
+ type: 'sh',
674
+ hash: data.slice(1),
675
+ };
676
+ }
677
+ throw new Error(`Invalid address prefix=${data[0]}`);
678
+ },
679
+ };
680
+ }
681
+ //# sourceMappingURL=payment.js.map