@scure/btc-signer 1.5.0 → 1.7.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 (77) hide show
  1. package/README.md +146 -25
  2. package/_type_test.js +2 -2
  3. package/_type_test.js.map +1 -1
  4. package/esm/_type_test.js +2 -2
  5. package/esm/_type_test.js.map +1 -1
  6. package/esm/index.d.ts +8 -6
  7. package/esm/index.d.ts.map +1 -1
  8. package/esm/index.js +7 -10
  9. package/esm/index.js.map +1 -1
  10. package/esm/musig2.d.ts +148 -0
  11. package/esm/musig2.d.ts.map +1 -0
  12. package/esm/musig2.js +405 -0
  13. package/esm/musig2.js.map +1 -0
  14. package/esm/payment.d.ts +43 -76
  15. package/esm/payment.d.ts.map +1 -1
  16. package/esm/payment.js +15 -2
  17. package/esm/payment.js.map +1 -1
  18. package/esm/psbt.d.ts +463 -463
  19. package/esm/psbt.d.ts.map +1 -1
  20. package/esm/psbt.js +10 -7
  21. package/esm/psbt.js.map +1 -1
  22. package/esm/script.d.ts +19 -17
  23. package/esm/script.d.ts.map +1 -1
  24. package/esm/script.js.map +1 -1
  25. package/esm/transaction.d.ts +13 -123
  26. package/esm/transaction.d.ts.map +1 -1
  27. package/esm/transaction.js +8 -9
  28. package/esm/transaction.js.map +1 -1
  29. package/esm/utils.d.ts +17 -12
  30. package/esm/utils.d.ts.map +1 -1
  31. package/esm/utils.js +6 -4
  32. package/esm/utils.js.map +1 -1
  33. package/esm/utxo.d.ts +96 -94
  34. package/esm/utxo.d.ts.map +1 -1
  35. package/esm/utxo.js +19 -15
  36. package/esm/utxo.js.map +1 -1
  37. package/index.d.ts +8 -6
  38. package/index.d.ts.map +1 -1
  39. package/index.js +20 -22
  40. package/index.js.map +1 -1
  41. package/musig2.d.ts +148 -0
  42. package/musig2.d.ts.map +1 -0
  43. package/musig2.js +417 -0
  44. package/musig2.js.map +1 -0
  45. package/package.json +22 -14
  46. package/payment.d.ts +43 -76
  47. package/payment.d.ts.map +1 -1
  48. package/payment.js +14 -1
  49. package/payment.js.map +1 -1
  50. package/psbt.d.ts +463 -463
  51. package/psbt.d.ts.map +1 -1
  52. package/psbt.js +13 -10
  53. package/psbt.js.map +1 -1
  54. package/script.d.ts +19 -17
  55. package/script.d.ts.map +1 -1
  56. package/script.js.map +1 -1
  57. package/src/_type_test.ts +2 -2
  58. package/src/index.ts +25 -14
  59. package/src/musig2.ts +517 -0
  60. package/src/payment.ts +74 -27
  61. package/src/psbt.ts +36 -14
  62. package/src/script.ts +5 -5
  63. package/src/transaction.ts +61 -39
  64. package/src/utils.ts +26 -16
  65. package/src/utxo.ts +52 -31
  66. package/transaction.d.ts +13 -123
  67. package/transaction.d.ts.map +1 -1
  68. package/transaction.js +17 -18
  69. package/transaction.js.map +1 -1
  70. package/utils.d.ts +17 -12
  71. package/utils.d.ts.map +1 -1
  72. package/utils.js +6 -4
  73. package/utils.js.map +1 -1
  74. package/utxo.d.ts +96 -94
  75. package/utxo.d.ts.map +1 -1
  76. package/utxo.js +17 -13
  77. package/utxo.js.map +1 -1
package/src/payment.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { Coder, hex, bech32, bech32m, createBase58check } from '@scure/base';
1
+ import { bech32, bech32m, type Coder, createBase58check, hex } from '@scure/base';
2
2
  import * as P from 'micro-packed';
3
- import { TaprootControlBlock, TransactionInput } from './psbt.js';
4
- import { OpToNum, ScriptType, Script, VarBytes } from './script.js';
5
- import { Bytes, NETWORK } from './utils.js';
3
+ import { TaprootControlBlock, type TransactionInput } from './psbt.js';
4
+ import { OpToNum, Script, type ScriptType, VarBytes } from './script.js';
6
5
  import * as u from './utils.js';
6
+ import { type BTC_NETWORK, type Bytes, NETWORK } from './utils.js';
7
7
 
8
8
  // We need following items:
9
9
  // - encode/decode output script
@@ -18,6 +18,20 @@ export type P2Ret = {
18
18
  witnessScript?: Bytes;
19
19
  };
20
20
 
21
+ // Pay to Anchor (P2A)
22
+ type OutP2AType = { type: 'p2a'; script: Bytes };
23
+ const OutP2A: Coder<OptScript, OutP2AType | undefined> = {
24
+ encode(from: ScriptType): OutP2AType | undefined {
25
+ if (from.length !== 2 || from[0] !== 1 || !u.isBytes(from[1]) || hex.encode(from[1]) !== '4e73')
26
+ return;
27
+ return { type: 'p2a', script: Script.encode(from) };
28
+ },
29
+ decode: (to: OutP2AType): OptScript => {
30
+ if (to.type !== 'p2a') return;
31
+ return [1, hex.decode('4e73')];
32
+ },
33
+ };
34
+
21
35
  // Public Key (P2PK)
22
36
  type OutPKType = { type: 'pk'; pubkey: Bytes };
23
37
  export type OptScript = ScriptType | undefined;
@@ -189,6 +203,7 @@ const OutUnknown: Coder<OptScript, OutUnknownType | undefined> = {
189
203
  // /Payments
190
204
 
191
205
  const OutScripts = [
206
+ OutP2A,
192
207
  OutPK,
193
208
  OutPKH,
194
209
  OutSH,
@@ -224,7 +239,22 @@ export type CustomScript = Coder<OptScript, CustomScriptOut | undefined> & {
224
239
  };
225
240
 
226
241
  // We can validate this once, because of packed & coders
227
- export const OutScript = P.validate(_OutScript, (i) => {
242
+ export const OutScript: P.CoderType<
243
+ NonNullable<
244
+ | OutP2AType
245
+ | OutPKType
246
+ | OutPKHType
247
+ | OutSHType
248
+ | OutWSHType
249
+ | OutWPKHType
250
+ | OutMSType
251
+ | OutTRType
252
+ | OutTRNSType
253
+ | OutTRMSType
254
+ | OutUnknownType
255
+ | undefined
256
+ >
257
+ > = P.validate(_OutScript, (i) => {
228
258
  if (i.type === 'pk' && !isValidPubkey(i.pubkey, u.PubT.ecdsa))
229
259
  throw new Error('OutScript/pk: wrong key');
230
260
  if (
@@ -267,7 +297,7 @@ function checkWSH(s: OutWSHType, witnessScript: Bytes) {
267
297
  throw new Error(`checkScript: P2${w.type} cannot be wrapped in P2WSH`);
268
298
  }
269
299
 
270
- export function checkScript(script?: Bytes, redeemScript?: Bytes, witnessScript?: Bytes) {
300
+ export function checkScript(script?: Bytes, redeemScript?: Bytes, witnessScript?: Bytes): void {
271
301
  if (script) {
272
302
  const s = OutScript.decode(script);
273
303
  // ms||pk maybe work, but there will be no address, hard to spend
@@ -300,7 +330,7 @@ function uniqPubkey(pubkeys: Bytes[]) {
300
330
  }
301
331
 
302
332
  // @ts-ignore
303
- export const p2pk = (pubkey: Bytes, network = NETWORK): P2Ret => {
333
+ export const p2pk = (pubkey: Bytes, network: BTC_NETWORK = NETWORK): P2Ret => {
304
334
  // network is unused
305
335
  if (!isValidPubkey(pubkey, u.PubT.ecdsa)) throw new Error('P2PK: invalid publicKey');
306
336
  return {
@@ -308,7 +338,7 @@ export const p2pk = (pubkey: Bytes, network = NETWORK): P2Ret => {
308
338
  script: OutScript.encode({ type: 'pk', pubkey }),
309
339
  };
310
340
  };
311
- export const p2pkh = (publicKey: Bytes, network = NETWORK): P2Ret => {
341
+ export const p2pkh = (publicKey: Bytes, network: BTC_NETWORK = NETWORK): P2Ret => {
312
342
  if (!isValidPubkey(publicKey, u.PubT.ecdsa)) throw new Error('P2PKH: invalid publicKey');
313
343
  const hash = u.hash160(publicKey);
314
344
  return {
@@ -317,7 +347,7 @@ export const p2pkh = (publicKey: Bytes, network = NETWORK): P2Ret => {
317
347
  address: Address(network).encode({ type: 'pkh', hash }),
318
348
  };
319
349
  };
320
- export const p2sh = (child: P2Ret, network = NETWORK): P2Ret => {
350
+ export const p2sh = (child: P2Ret, network: BTC_NETWORK = NETWORK): P2Ret => {
321
351
  // It is already tested inside noble-hashes and checkScript
322
352
  const cs = child.script;
323
353
  if (!u.isBytes(cs)) throw new Error(`Wrong script: ${typeof child.script}, expected Uint8Array`);
@@ -333,7 +363,7 @@ export const p2sh = (child: P2Ret, network = NETWORK): P2Ret => {
333
363
  if (child.witnessScript) res.witnessScript = child.witnessScript;
334
364
  return res;
335
365
  };
336
- export const p2wsh = (child: P2Ret, network = NETWORK): P2Ret => {
366
+ export const p2wsh = (child: P2Ret, network: BTC_NETWORK = NETWORK): P2Ret => {
337
367
  const cs = child.script;
338
368
  if (!u.isBytes(cs)) throw new Error(`Wrong script: ${typeof cs}, expected Uint8Array`);
339
369
  const hash = u.sha256(cs);
@@ -346,7 +376,7 @@ export const p2wsh = (child: P2Ret, network = NETWORK): P2Ret => {
346
376
  address: Address(network).encode({ type: 'wsh', hash }),
347
377
  };
348
378
  };
349
- export const p2wpkh = (publicKey: Bytes, network = NETWORK): P2Ret => {
379
+ export const p2wpkh = (publicKey: Bytes, network: BTC_NETWORK = NETWORK): P2Ret => {
350
380
  if (!isValidPubkey(publicKey, u.PubT.ecdsa)) throw new Error('P2WPKH: invalid publicKey');
351
381
  if (publicKey.length === 65) throw new Error('P2WPKH: uncompressed public key');
352
382
  const hash = u.hash160(publicKey);
@@ -361,7 +391,7 @@ export const p2ms = (m: number, pubkeys: Bytes[], allowSamePubkeys = false): P2R
361
391
  return { type: 'ms', script: OutScript.encode({ type: 'ms', pubkeys, m }) };
362
392
  };
363
393
 
364
- type HashedTree =
394
+ export type HashedTree =
365
395
  | { type: 'leaf'; version?: number; script: Bytes; hash: Bytes }
366
396
  | { type: 'branch'; left: HashedTree; right: HashedTree; hash: Bytes };
367
397
  function checkTaprootScript(
@@ -409,9 +439,9 @@ function checkTaprootScript(
409
439
  }
410
440
 
411
441
  export type P2TROut = P2Ret & {
412
- tweakedPubkey: Uint8Array;
413
- tapInternalKey: Uint8Array;
414
- tapMerkleRoot?: Uint8Array;
442
+ tweakedPubkey: Bytes;
443
+ tapInternalKey: Bytes;
444
+ tapMerkleRoot?: Bytes;
415
445
  tapLeafScript?: TransactionInput['tapLeafScript'];
416
446
  leaves?: TaprootLeaf[];
417
447
  };
@@ -451,7 +481,7 @@ export function taprootListToTree(taprootList: TaprootScriptList): TaprootScript
451
481
  return (last?.childs || last) as TaprootScriptTree;
452
482
  }
453
483
 
454
- type TaprootLeaf = {
484
+ export type TaprootLeaf = {
455
485
  type: 'leaf';
456
486
  version?: number;
457
487
  script: Bytes;
@@ -459,7 +489,7 @@ type TaprootLeaf = {
459
489
  path: Bytes[];
460
490
  };
461
491
 
462
- type HashedTreeWithPath =
492
+ export type HashedTreeWithPath =
463
493
  | TaprootLeaf
464
494
  | {
465
495
  type: 'branch';
@@ -526,7 +556,7 @@ function taprootHashTree(
526
556
  }
527
557
 
528
558
  export const TAP_LEAF_VERSION = 0xc0;
529
- export const tapLeafHash = (script: Bytes, version = TAP_LEAF_VERSION) =>
559
+ export const tapLeafHash = (script: Bytes, version: number = TAP_LEAF_VERSION): Bytes =>
530
560
  u.tagSchnorr('TapLeaf', new Uint8Array([version]), VarBytes.encode(script));
531
561
 
532
562
  // Works as key OR tree.
@@ -535,7 +565,7 @@ export const tapLeafHash = (script: Bytes, version = TAP_LEAF_VERSION) =>
535
565
  export function p2tr(
536
566
  internalPubKey?: Bytes | string,
537
567
  tree?: TaprootScriptTree,
538
- network = NETWORK,
568
+ network: BTC_NETWORK = NETWORK,
539
569
  allowUnknownOutputs = false,
540
570
  customScripts?: CustomScript[]
541
571
  ): P2TROut {
@@ -631,7 +661,14 @@ export const p2tr_ns = (m: number, pubkeys: Bytes[], allowSamePubkeys = false):
631
661
  // Taproot public key (case of p2tr_ns)
632
662
  export const p2tr_pk = (pubkey: Bytes): P2Ret => p2tr_ns(1, [pubkey], undefined)[0];
633
663
 
634
- export function p2tr_ms(m: number, pubkeys: Bytes[], allowSamePubkeys = false) {
664
+ export function p2tr_ms(
665
+ m: number,
666
+ pubkeys: Bytes[],
667
+ allowSamePubkeys = false
668
+ ): {
669
+ type: string;
670
+ script: Uint8Array;
671
+ } {
635
672
  if (!allowSamePubkeys) uniqPubkey(pubkeys);
636
673
  return {
637
674
  type: 'tr_ms',
@@ -640,7 +677,11 @@ export function p2tr_ms(m: number, pubkeys: Bytes[], allowSamePubkeys = false) {
640
677
  }
641
678
 
642
679
  // Simple pubkey address, without complex scripts
643
- export function getAddress(type: 'pkh' | 'wpkh' | 'tr', privKey: Bytes, network = NETWORK) {
680
+ export function getAddress(
681
+ type: 'pkh' | 'wpkh' | 'tr',
682
+ privKey: Bytes,
683
+ network: BTC_NETWORK = NETWORK
684
+ ): string | undefined {
644
685
  if (type === 'tr') {
645
686
  return p2tr(u.pubSchnorr(privKey), undefined, network).address;
646
687
  }
@@ -650,20 +691,26 @@ export function getAddress(type: 'pkh' | 'wpkh' | 'tr', privKey: Bytes, network
650
691
  throw new Error(`getAddress: unknown type=${type}`);
651
692
  }
652
693
 
653
- export const _sortPubkeys = (pubkeys: Bytes[]) => Array.from(pubkeys).sort(u.compareBytes);
694
+ export const _sortPubkeys = (pubkeys: Bytes[]): Uint8Array[] =>
695
+ Array.from(pubkeys).sort(u.compareBytes);
654
696
 
655
697
  export function multisig(
656
698
  m: number,
657
699
  pubkeys: Bytes[],
658
700
  sorted = false,
659
701
  witness = false,
660
- network = NETWORK
661
- ) {
702
+ network: BTC_NETWORK = NETWORK
703
+ ): P2Ret {
662
704
  const ms = p2ms(m, sorted ? _sortPubkeys(pubkeys) : pubkeys);
663
705
  return witness ? p2wsh(ms, network) : p2sh(ms, network);
664
706
  }
665
707
 
666
- export function sortedMultisig(m: number, pubkeys: Bytes[], witness = false, network = NETWORK) {
708
+ export function sortedMultisig(
709
+ m: number,
710
+ pubkeys: Bytes[],
711
+ witness = false,
712
+ network: BTC_NETWORK = NETWORK
713
+ ): P2Ret {
667
714
  return multisig(m, pubkeys, true, witness, network);
668
715
  }
669
716
 
@@ -686,7 +733,7 @@ function formatKey(hashed: Bytes, prefix: number[]): string {
686
733
  return base58check.encode(u.concatBytes(Uint8Array.from(prefix), hashed));
687
734
  }
688
735
 
689
- export function WIF(network = NETWORK): Coder<Bytes, string> {
736
+ export function WIF(network: BTC_NETWORK = NETWORK): Coder<Bytes, string> {
690
737
  return {
691
738
  encode(privKey: Bytes) {
692
739
  const compressed = u.concatBytes(privKey, new Uint8Array([0x01]));
@@ -705,7 +752,7 @@ export function WIF(network = NETWORK): Coder<Bytes, string> {
705
752
  }
706
753
 
707
754
  // Returns OutType, which can be used to create outscript
708
- export function Address(network = NETWORK) {
755
+ export function Address(network: BTC_NETWORK = NETWORK) {
709
756
  return {
710
757
  encode(from: P.UnwrapCoder<OutScriptType>): string {
711
758
  const { type } = from;
package/src/psbt.ts CHANGED
@@ -1,16 +1,27 @@
1
1
  import { hex } from '@scure/base';
2
2
  import * as P from 'micro-packed';
3
- import { CompactSize, CompactSizeLen, VarBytes } from './script.js';
4
- import { RawOutput, RawTx, RawOldTx, RawWitness } from './script.js';
3
+ import {
4
+ CompactSize,
5
+ CompactSizeLen,
6
+ RawOldTx,
7
+ RawOutput,
8
+ RawTx,
9
+ RawWitness,
10
+ VarBytes,
11
+ } from './script.js';
5
12
  import { Transaction } from './transaction.js'; // circular
6
- import { Bytes, compareBytes, PubT, validatePubkey, equalBytes } from './utils.js';
13
+ import { type Bytes, compareBytes, equalBytes, PubT, validatePubkey } from './utils.js';
7
14
 
8
15
  // PSBT BIP174, BIP370, BIP371
9
16
 
10
17
  // Can be 33 or 64 bytes
11
- const PubKeyECDSA = P.validate(P.bytes(null), (pub) => validatePubkey(pub, PubT.ecdsa));
12
- const PubKeySchnorr = P.validate(P.bytes(32), (pub) => validatePubkey(pub, PubT.schnorr));
13
- const SignatureSchnorr = P.validate(P.bytes(null), (sig) => {
18
+ const PubKeyECDSA: P.CoderType<Uint8Array> = P.validate(P.bytes(null), (pub) =>
19
+ validatePubkey(pub, PubT.ecdsa)
20
+ );
21
+ const PubKeySchnorr: P.CoderType<Uint8Array> = P.validate(P.bytes(32), (pub) =>
22
+ validatePubkey(pub, PubT.schnorr)
23
+ );
24
+ const SignatureSchnorr: P.CoderType<Uint8Array> = P.validate(P.bytes(null), (sig) => {
14
25
  if (sig.length !== 64 && sig.length !== 65)
15
26
  throw new Error('Schnorr signature should be 64 or 65 bytes long');
16
27
  return sig;
@@ -51,9 +62,9 @@ const tapTree = P.array(
51
62
  })
52
63
  );
53
64
 
54
- const BytesInf = P.bytes(null); // Bytes will conflict with Bytes type
55
- const Bytes20 = P.bytes(20);
56
- const Bytes32 = P.bytes(32);
65
+ const BytesInf: P.CoderType<P.Bytes> = P.bytes(null); // Bytes will conflict with Bytes type
66
+ const Bytes20: P.CoderType<P.Bytes> = P.bytes(20);
67
+ const Bytes32: P.CoderType<P.Bytes> = P.bytes(32);
57
68
  // versionsRequiringExclusing = !versionsAllowsInclusion (as set)
58
69
  // {name: [tag, keyCoder, valueCoder, versionsRequiringInclusion, versionsRequiringExclusing, versionsAllowsInclusion, silentIgnore]}
59
70
  // SilentIgnore: we use some v2 fields for v1 representation too, so we just clean them before serialize
@@ -167,7 +178,12 @@ function PSBTKeyInfo(info: PSBTKeyMapInfo) {
167
178
 
168
179
  type PSBTKeyMap = Record<string, PSBTKeyMapInfo>;
169
180
 
170
- const PSBTUnknownKey = P.struct({ type: CompactSizeLen, key: P.bytes(null) });
181
+ const PSBTUnknownKey: P.CoderType<
182
+ P.StructInput<{
183
+ type: number;
184
+ key: Uint8Array;
185
+ }>
186
+ > = P.struct({ type: CompactSizeLen, key: P.bytes(null) });
171
187
  type PSBTUnknownFields = { unknown?: [P.UnwrapCoder<typeof PSBTUnknownKey>, Bytes][] };
172
188
  export type PSBTKeyMapKeys<T extends PSBTKeyMap> = {
173
189
  -readonly [K in keyof T]?: T[K][1] extends false
@@ -399,7 +415,7 @@ export function cleanPSBTFields<T extends PSBTKeyMap>(
399
415
  version: number,
400
416
  info: T,
401
417
  lst: PSBTKeyMapKeys<T>
402
- ) {
418
+ ): PSBTKeyMapKeys<T> {
403
419
  const out: PSBTKeyMapKeys<T> = {};
404
420
  for (const _k in lst) {
405
421
  const k = _k as string & keyof PSBTKeyMapKeys<T>;
@@ -442,7 +458,8 @@ export function mergeKeyMap<T extends PSBTKeyMap>(
442
458
  psbtEnum: T,
443
459
  val: PSBTKeyMapKeys<T>,
444
460
  cur?: PSBTKeyMapKeys<T>,
445
- allowedFields?: (keyof PSBTKeyMapKeys<T>)[]
461
+ allowedFields?: (keyof PSBTKeyMapKeys<T>)[],
462
+ allowUnknown?: boolean
446
463
  ): PSBTKeyMapKeys<T> {
447
464
  const res: PSBTKeyMapKeys<T> = { ...cur, ...val };
448
465
  // All arguments can be provided as hex
@@ -501,8 +518,13 @@ export function mergeKeyMap<T extends PSBTKeyMap>(
501
518
  throw new Error(`Cannot change signed field=${k}`);
502
519
  }
503
520
  }
504
- // Remove unknown keys
505
- for (const k in res) if (!psbtEnum[k]) delete res[k];
521
+ // Remove unknown keys except the "unknown" array if allowUnknown is true
522
+ for (const k in res) {
523
+ if (!psbtEnum[k]) {
524
+ if (allowUnknown && k === 'unknown') continue;
525
+ delete res[k];
526
+ }
527
+ }
506
528
  return res;
507
529
  }
508
530
 
package/src/script.ts CHANGED
@@ -81,7 +81,7 @@ export function ScriptNum(bytesLimit = 6, forceMinimal = false): P.CoderType<big
81
81
  });
82
82
  }
83
83
 
84
- export function OpToNum(op: ScriptOP, bytesLimit = 4, forceMinimal = true) {
84
+ export function OpToNum(op: ScriptOP, bytesLimit = 4, forceMinimal = true): number | undefined {
85
85
  if (typeof op === 'number') return op;
86
86
  if (isBytes(op)) {
87
87
  try {
@@ -197,13 +197,13 @@ export const CompactSize: P.CoderType<bigint> = P.wrap({
197
197
  });
198
198
 
199
199
  // Same thing, but in number instead of bigint. Checks for safe integer inside
200
- export const CompactSizeLen = P.apply(CompactSize, P.coders.numberBigint);
200
+ export const CompactSizeLen: P.CoderType<number> = P.apply(CompactSize, P.coders.numberBigint);
201
201
 
202
202
  // ui8a of size <CompactSize>
203
- export const VarBytes = P.bytes(CompactSize);
203
+ export const VarBytes: P.CoderType<Uint8Array> = P.bytes(CompactSize);
204
204
 
205
205
  // SegWit v0 stack of witness buffers
206
- export const RawWitness = P.array(CompactSizeLen, VarBytes);
206
+ export const RawWitness: P.CoderType<Uint8Array[]> = P.array(CompactSizeLen, VarBytes);
207
207
 
208
208
  // Array of size <CompactSize>
209
209
  export const BTCArray = <T>(t: P.CoderType<T>): P.CoderType<T[]> => P.array(CompactSize, t);
@@ -235,7 +235,7 @@ function validateRawTx(tx: P.UnwrapCoder<typeof _RawTx>) {
235
235
  throw new Error('Segwit flag with empty witnesses array');
236
236
  return tx;
237
237
  }
238
- export const RawTx = P.validate(_RawTx, validateRawTx);
238
+ export const RawTx: typeof _RawTx = P.validate(_RawTx, validateRawTx);
239
239
  // Pre-SegWit serialization format (for PSBTv0)
240
240
  export const RawOldTx = P.struct({
241
241
  version: P.I32LE,
@@ -1,12 +1,19 @@
1
- import * as P from 'micro-packed';
2
1
  import { hex } from '@scure/base';
3
- import { Address, CustomScript, OutScript, checkScript, tapLeafHash } from './payment.js';
2
+ import * as P from 'micro-packed';
3
+ import { Address, type CustomScript, OutScript, checkScript, tapLeafHash } from './payment.js';
4
4
  import * as psbt from './psbt.js'; // circular
5
- import { CompactSizeLen, Script, VarBytes } from './script.js';
6
- import { RawOutput, RawTx, RawOldTx, RawWitness } from './script.js';
7
- import { NETWORK, Bytes, concatBytes, isBytes, equalBytes } from './utils.js';
5
+ import {
6
+ CompactSizeLen,
7
+ RawOldTx,
8
+ RawOutput,
9
+ RawTx,
10
+ RawWitness,
11
+ Script,
12
+ VarBytes,
13
+ } from './script.js';
8
14
  import * as u from './utils.js';
9
- import { getInputType, toVsize, normalizeInput, getPrevOut } from './utxo.js'; // circular
15
+ import { type Bytes, NETWORK, concatBytes, equalBytes, isBytes } from './utils.js';
16
+ import { getInputType, getPrevOut, normalizeInput, toVsize } from './utxo.js'; // circular
10
17
 
11
18
  const EMPTY32 = new Uint8Array(32);
12
19
  const EMPTY_OUTPUT: P.UnwrapCoder<typeof RawOutput> = {
@@ -30,10 +37,10 @@ export const PRECISION = 8;
30
37
  export const DEFAULT_VERSION = 2;
31
38
  export const DEFAULT_LOCKTIME = 0;
32
39
  export const DEFAULT_SEQUENCE = 4294967295;
33
- export const Decimal = P.coders.decimal(PRECISION);
40
+ export const Decimal: P.Coder<bigint, string> = P.coders.decimal(PRECISION);
34
41
 
35
42
  // Same as value || def, but doesn't overwrites zero ('0', 0, 0n, etc)
36
- export const def = <T>(value: T | undefined, def: T) => (value === undefined ? def : value);
43
+ export const def = <T>(value: T | undefined, def: T): T => (value === undefined ? def : value);
37
44
 
38
45
  export function cloneDeep<T>(obj: T): T {
39
46
  if (Array.isArray(obj)) return obj.map((i) => cloneDeep(i)) as unknown as T;
@@ -54,7 +61,7 @@ export function cloneDeep<T>(obj: T): T {
54
61
 
55
62
  // Mostly security features, hardened defaults;
56
63
  // but you still can parse other people tx with unspendable outputs and stuff if you want
57
- export type TxOpts = {
64
+ export interface TxOpts {
58
65
  version?: number;
59
66
  lockTime?: number;
60
67
  PSBTVersion?: number;
@@ -77,7 +84,9 @@ export type TxOpts = {
77
84
  allowLegacyWitnessUtxo?: boolean;
78
85
  lowR?: boolean; // Use lowR signatures
79
86
  customScripts?: CustomScript[]; // UNSAFE: Custom payment scripts
80
- };
87
+ // Allow to add additional unknown keys/values to the "unknown" array member
88
+ allowUnknown?: boolean;
89
+ }
81
90
 
82
91
  /**
83
92
  * Internal, exported only for backwards-compat. Use `SigHash` instead.
@@ -166,7 +175,7 @@ function unpackSighash(hashType: number) {
166
175
  };
167
176
  }
168
177
 
169
- function validateOpts(opts: TxOpts) {
178
+ function validateOpts(opts: TxOpts): Readonly<TxOpts> {
170
179
  if (opts !== undefined && {}.toString.call(opts) !== '[object Object]')
171
180
  throw new Error(`Wrong object type for transaction options: ${opts}`);
172
181
 
@@ -182,7 +191,8 @@ function validateOpts(opts: TxOpts) {
182
191
  if (typeof _opts.allowUnknowOutput !== 'undefined')
183
192
  opts.allowUnknownOutputs = _opts.allowUnknowOutput;
184
193
  // 0 and -1 happens in tests
185
- if (![-1, 0, 1, 2].includes(_opts.version)) throw new Error(`Unknown version: ${_opts.version}`);
194
+ if (![-1, 0, 1, 2, 3].includes(_opts.version))
195
+ throw new Error(`Unknown version: ${_opts.version}`);
186
196
  if (typeof _opts.lockTime !== 'number') throw new Error('Transaction lock time should be number');
187
197
  P.U32LE.encode(_opts.lockTime); // Additional range checks that lockTime
188
198
  // There is no PSBT v1, and any new version will probably have fields which we don't know how to parse, which
@@ -233,7 +243,7 @@ export class Transaction {
233
243
  }
234
244
 
235
245
  // Import
236
- static fromRaw(raw: Bytes, opts: TxOpts = {}) {
246
+ static fromRaw(raw: Bytes, opts: TxOpts = {}): Transaction {
237
247
  const parsed = RawTx.decode(raw);
238
248
  const tx = new Transaction({ ...opts, version: parsed.version, lockTime: parsed.lockTime });
239
249
  for (const o of parsed.outputs) tx.addOutput(o);
@@ -246,7 +256,7 @@ export class Transaction {
246
256
  return tx;
247
257
  }
248
258
  // PSBT
249
- static fromPSBT(psbt_: Bytes, opts: TxOpts = {}) {
259
+ static fromPSBT(psbt_: Bytes, opts: TxOpts = {}): Transaction {
250
260
  let parsed: P.UnwrapCoder<typeof psbt.RawPSBTV0>;
251
261
  try {
252
262
  parsed = psbt.RawPSBTV0.decode(psbt_);
@@ -281,7 +291,7 @@ export class Transaction {
281
291
  if (lockTime !== DEFAULT_LOCKTIME) tx.global.fallbackLocktime = lockTime;
282
292
  return tx;
283
293
  }
284
- toPSBT(PSBTVersion = this.opts.PSBTVersion) {
294
+ toPSBT(PSBTVersion: number | undefined = this.opts.PSBTVersion): Uint8Array {
285
295
  if (PSBTVersion !== 0 && PSBTVersion !== 2)
286
296
  throw new Error(`Wrong PSBT version=${PSBTVersion}`);
287
297
  // if (PSBTVersion === 0 && this.inputs.length === 0) {
@@ -338,7 +348,7 @@ export class Transaction {
338
348
  }
339
349
 
340
350
  // BIP370 lockTime (https://github.com/bitcoin/bips/blob/master/bip-0370.mediawiki#determining-lock-time)
341
- get lockTime() {
351
+ get lockTime(): number {
342
352
  let height = DEFAULT_LOCKTIME;
343
353
  let heightCnt = 0;
344
354
  let time = DEFAULT_LOCKTIME;
@@ -358,7 +368,7 @@ export class Transaction {
358
368
  return this.global.fallbackLocktime || DEFAULT_LOCKTIME;
359
369
  }
360
370
 
361
- get version() {
371
+ get version(): number {
362
372
  // Should be not possible
363
373
  if (this.global.txVersion === undefined) throw new Error('No global.txVersion');
364
374
  return this.global.txVersion;
@@ -419,7 +429,7 @@ export class Transaction {
419
429
  return { addInput, addOutput, inputs, outputs };
420
430
  }
421
431
 
422
- get isFinal() {
432
+ get isFinal(): boolean {
423
433
  for (let idx = 0; idx < this.inputs.length; idx++)
424
434
  if (this.inputStatus(idx) !== 'finalized') return false;
425
435
  return true;
@@ -453,7 +463,7 @@ export class Transaction {
453
463
  get vsize(): number {
454
464
  return toVsize(this.weight);
455
465
  }
456
- toBytes(withScriptSig = false, withWitness = false) {
466
+ toBytes(withScriptSig = false, withWitness = false): Uint8Array {
457
467
  return RawTx.encode({
458
468
  version: this.version,
459
469
  lockTime: this.lockTime,
@@ -469,15 +479,15 @@ export class Transaction {
469
479
  get unsignedTx(): Bytes {
470
480
  return this.toBytes(false, false);
471
481
  }
472
- get hex() {
482
+ get hex(): string {
473
483
  return hex.encode(this.toBytes(true, this.hasWitnesses));
474
484
  }
475
485
 
476
- get hash() {
486
+ get hash(): string {
477
487
  if (!this.isFinal) throw new Error('Transaction is not finalized');
478
488
  return hex.encode(u.sha256x2(this.toBytes(true)));
479
489
  }
480
- get id() {
490
+ get id(): string {
481
491
  if (!this.isFinal) throw new Error('Transaction is not finalized');
482
492
  return hex.encode(u.sha256x2(this.toBytes(true)).reverse());
483
493
  }
@@ -486,11 +496,11 @@ export class Transaction {
486
496
  if (!Number.isSafeInteger(idx) || 0 > idx || idx >= this.inputs.length)
487
497
  throw new Error(`Wrong input index=${idx}`);
488
498
  }
489
- getInput(idx: number) {
499
+ getInput(idx: number): psbt.TransactionInput {
490
500
  this.checkInputIdx(idx);
491
501
  return cloneDeep(this.inputs[idx]);
492
502
  }
493
- get inputsLength() {
503
+ get inputsLength(): number {
494
504
  return this.inputs.length;
495
505
  }
496
506
  // Modification
@@ -500,7 +510,7 @@ export class Transaction {
500
510
  this.inputs.push(normalizeInput(input, undefined, undefined, this.opts.disableScriptCheck));
501
511
  return this.inputs.length - 1;
502
512
  }
503
- updateInput(idx: number, input: psbt.TransactionInputUpdate, _ignoreSignStatus = false) {
513
+ updateInput(idx: number, input: psbt.TransactionInputUpdate, _ignoreSignStatus = false): void {
504
514
  this.checkInputIdx(idx);
505
515
  let allowedFields = undefined;
506
516
  if (!_ignoreSignStatus) {
@@ -512,7 +522,8 @@ export class Transaction {
512
522
  input,
513
523
  this.inputs[idx],
514
524
  allowedFields,
515
- this.opts.disableScriptCheck
525
+ this.opts.disableScriptCheck,
526
+ this.opts.allowUnknown
516
527
  );
517
528
  }
518
529
  // Output stuff
@@ -520,17 +531,17 @@ export class Transaction {
520
531
  if (!Number.isSafeInteger(idx) || 0 > idx || idx >= this.outputs.length)
521
532
  throw new Error(`Wrong output index=${idx}`);
522
533
  }
523
- getOutput(idx: number) {
534
+ getOutput(idx: number): psbt.TransactionOutput {
524
535
  this.checkOutputIdx(idx);
525
536
  return cloneDeep(this.outputs[idx]);
526
537
  }
527
- getOutputAddress(idx: number, network = NETWORK): string | undefined {
538
+ getOutputAddress(idx: number, network: u.BTC_NETWORK = NETWORK): string | undefined {
528
539
  const out = this.getOutput(idx);
529
540
  if (!out.script) return;
530
541
  return Address(network).encode(OutScript.decode(out.script));
531
542
  }
532
543
 
533
- get outputsLength() {
544
+ get outputsLength(): number {
534
545
  return this.outputs.length;
535
546
  }
536
547
  private normalizeOutput(
@@ -548,7 +559,7 @@ export class Transaction {
548
559
  if (script === undefined) script = cur?.script;
549
560
  let res: psbt.PSBTKeyMapKeys<typeof psbt.PSBTOutput> = { ...cur, ...o, amount, script };
550
561
  if (res.amount === undefined) delete res.amount;
551
- res = psbt.mergeKeyMap(psbt.PSBTOutput, res, cur, allowedFields);
562
+ res = psbt.mergeKeyMap(psbt.PSBTOutput, res, cur, allowedFields, this.opts.allowUnknown);
552
563
  psbt.PSBTOutputCoder.encode(res);
553
564
  if (
554
565
  res.script &&
@@ -568,7 +579,7 @@ export class Transaction {
568
579
  this.outputs.push(this.normalizeOutput(o));
569
580
  return this.outputs.length - 1;
570
581
  }
571
- updateOutput(idx: number, output: psbt.TransactionOutputUpdate, _ignoreSignStatus = false) {
582
+ updateOutput(idx: number, output: psbt.TransactionOutputUpdate, _ignoreSignStatus = false): void {
572
583
  this.checkOutputIdx(idx);
573
584
  let allowedFields = undefined;
574
585
  if (!_ignoreSignStatus) {
@@ -578,7 +589,7 @@ export class Transaction {
578
589
  }
579
590
  this.outputs[idx] = this.normalizeOutput(output, this.outputs[idx], allowedFields);
580
591
  }
581
- addOutputAddress(address: string, amount: bigint, network = NETWORK): number {
592
+ addOutputAddress(address: string, amount: bigint, network: u.BTC_NETWORK = NETWORK): number {
582
593
  return this.addOutput({ script: OutScript.encode(Address(network).decode(address)), amount });
583
594
  }
584
595
  // Utils
@@ -633,7 +644,12 @@ export class Transaction {
633
644
  });
634
645
  return u.sha256x2(tmpTx, P.I32LE.encode(hashType));
635
646
  }
636
- preimageWitnessV0(idx: number, prevOutScript: Bytes, hashType: number, amount: bigint) {
647
+ preimageWitnessV0(
648
+ idx: number,
649
+ prevOutScript: Bytes,
650
+ hashType: number,
651
+ amount: bigint
652
+ ): Uint8Array {
637
653
  const { isAny, isNone, isSingle } = unpackSighash(hashType);
638
654
  let inputHash = EMPTY32;
639
655
  let sequenceHash = EMPTY32;
@@ -671,7 +687,7 @@ export class Transaction {
671
687
  leafScript?: Bytes,
672
688
  leafVer = 0xc0,
673
689
  annex?: Bytes
674
- ) {
690
+ ): Uint8Array {
675
691
  if (!Array.isArray(amount) || this.inputs.length !== amount.length)
676
692
  throw new Error(`Invalid amounts array=${amount}`);
677
693
  if (!Array.isArray(prevOutScript) || this.inputs.length !== prevOutScript.length)
@@ -878,7 +894,7 @@ export class Transaction {
878
894
  return num;
879
895
  }
880
896
 
881
- finalizeIdx(idx: number) {
897
+ finalizeIdx(idx: number): void {
882
898
  this.checkInputIdx(idx);
883
899
  if (this.fee < 0n) throw new Error('Outputs spends more than inputs amount');
884
900
  const input = this.inputs[idx];
@@ -1027,10 +1043,10 @@ export class Transaction {
1027
1043
  if (finalScriptWitness) input.finalScriptWitness = finalScriptWitness;
1028
1044
  cleanFinalInput(input);
1029
1045
  }
1030
- finalize() {
1046
+ finalize(): void {
1031
1047
  for (let i = 0; i < this.inputs.length; i++) this.finalizeIdx(i);
1032
1048
  }
1033
- extract() {
1049
+ extract(): Uint8Array {
1034
1050
  if (!this.isFinal) throw new Error('Transaction has unfinalized inputs');
1035
1051
  if (!this.outputs.length) throw new Error('Transaction has no outputs');
1036
1052
  if (this.fee < 0n) throw new Error('Outputs spends more than inputs amount');
@@ -1057,12 +1073,18 @@ export class Transaction {
1057
1073
  : P.EMPTY;
1058
1074
  if (!equalBytes(thisUnsigned, otherUnsigned))
1059
1075
  throw new Error(`Transaction/combine: different unsigned tx`);
1060
- this.global = psbt.mergeKeyMap(psbt.PSBTGlobal, this.global, other.global);
1076
+ this.global = psbt.mergeKeyMap(
1077
+ psbt.PSBTGlobal,
1078
+ this.global,
1079
+ other.global,
1080
+ undefined,
1081
+ this.opts.allowUnknown
1082
+ );
1061
1083
  for (let i = 0; i < this.inputs.length; i++) this.updateInput(i, other.inputs[i], true);
1062
1084
  for (let i = 0; i < this.outputs.length; i++) this.updateOutput(i, other.outputs[i], true);
1063
1085
  return this;
1064
1086
  }
1065
- clone() {
1087
+ clone(): Transaction {
1066
1088
  // deepClone probably faster, but this enforces that encoding is valid
1067
1089
  return Transaction.fromPSBT(this.toPSBT(this.opts.PSBTVersion), this.opts);
1068
1090
  }