@btc-vision/bitcoin 7.0.0-alpha.1 → 7.0.0-alpha.3

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 (180) hide show
  1. package/README.md +334 -161
  2. package/browser/address.d.ts +5 -1
  3. package/browser/address.d.ts.map +1 -1
  4. package/browser/branded.d.ts +3 -14
  5. package/browser/branded.d.ts.map +1 -1
  6. package/browser/ecc/context.d.ts +22 -21
  7. package/browser/ecc/context.d.ts.map +1 -1
  8. package/browser/ecc/index.d.ts +1 -1
  9. package/browser/ecc/index.d.ts.map +1 -1
  10. package/browser/ecc/types.d.ts +10 -123
  11. package/browser/ecc/types.d.ts.map +1 -1
  12. package/browser/index.d.ts +3 -2
  13. package/browser/index.d.ts.map +1 -1
  14. package/browser/index.js +6465 -4692
  15. package/browser/opcodes.d.ts +11 -0
  16. package/browser/opcodes.d.ts.map +1 -1
  17. package/browser/payments/p2tr.d.ts.map +1 -1
  18. package/browser/psbt/PsbtCache.d.ts +54 -0
  19. package/browser/psbt/PsbtCache.d.ts.map +1 -0
  20. package/browser/psbt/PsbtFinalizer.d.ts +21 -0
  21. package/browser/psbt/PsbtFinalizer.d.ts.map +1 -0
  22. package/browser/psbt/PsbtSigner.d.ts +32 -0
  23. package/browser/psbt/PsbtSigner.d.ts.map +1 -0
  24. package/browser/psbt/PsbtTransaction.d.ts +25 -0
  25. package/browser/psbt/PsbtTransaction.d.ts.map +1 -0
  26. package/browser/psbt/types.d.ts +4 -70
  27. package/browser/psbt/types.d.ts.map +1 -1
  28. package/browser/psbt/validation.d.ts +1 -1
  29. package/browser/psbt/validation.d.ts.map +1 -1
  30. package/browser/psbt.d.ts +26 -40
  31. package/browser/psbt.d.ts.map +1 -1
  32. package/browser/script.d.ts.map +1 -1
  33. package/browser/transaction.d.ts +4 -4
  34. package/browser/transaction.d.ts.map +1 -1
  35. package/browser/types.d.ts +5 -3
  36. package/browser/types.d.ts.map +1 -1
  37. package/browser/workers/index.d.ts +3 -50
  38. package/browser/workers/index.d.ts.map +1 -1
  39. package/browser/workers/index.node.d.ts +24 -0
  40. package/browser/workers/index.node.d.ts.map +1 -0
  41. package/build/address.d.ts +5 -1
  42. package/build/address.d.ts.map +1 -1
  43. package/build/address.js +29 -17
  44. package/build/address.js.map +1 -1
  45. package/build/branded.d.ts +3 -14
  46. package/build/branded.d.ts.map +1 -1
  47. package/build/branded.js +0 -5
  48. package/build/branded.js.map +1 -1
  49. package/build/ecc/context.d.ts +22 -21
  50. package/build/ecc/context.d.ts.map +1 -1
  51. package/build/ecc/context.js +23 -95
  52. package/build/ecc/context.js.map +1 -1
  53. package/build/ecc/index.d.ts +1 -1
  54. package/build/ecc/index.d.ts.map +1 -1
  55. package/build/ecc/types.d.ts +7 -126
  56. package/build/ecc/types.d.ts.map +1 -1
  57. package/build/ecc/types.js +4 -1
  58. package/build/ecc/types.js.map +1 -1
  59. package/build/index.d.ts +3 -2
  60. package/build/index.d.ts.map +1 -1
  61. package/build/index.js +1 -1
  62. package/build/index.js.map +1 -1
  63. package/build/opcodes.d.ts +11 -0
  64. package/build/opcodes.d.ts.map +1 -1
  65. package/build/opcodes.js +19 -4
  66. package/build/opcodes.js.map +1 -1
  67. package/build/payments/p2tr.d.ts.map +1 -1
  68. package/build/payments/p2tr.js +2 -3
  69. package/build/payments/p2tr.js.map +1 -1
  70. package/build/psbt/PsbtCache.d.ts +54 -0
  71. package/build/psbt/PsbtCache.d.ts.map +1 -0
  72. package/build/psbt/PsbtCache.js +249 -0
  73. package/build/psbt/PsbtCache.js.map +1 -0
  74. package/build/psbt/PsbtFinalizer.d.ts +21 -0
  75. package/build/psbt/PsbtFinalizer.d.ts.map +1 -0
  76. package/build/psbt/PsbtFinalizer.js +157 -0
  77. package/build/psbt/PsbtFinalizer.js.map +1 -0
  78. package/build/psbt/PsbtSigner.d.ts +32 -0
  79. package/build/psbt/PsbtSigner.d.ts.map +1 -0
  80. package/build/psbt/PsbtSigner.js +192 -0
  81. package/build/psbt/PsbtSigner.js.map +1 -0
  82. package/build/psbt/PsbtTransaction.d.ts +25 -0
  83. package/build/psbt/PsbtTransaction.d.ts.map +1 -0
  84. package/build/psbt/PsbtTransaction.js +61 -0
  85. package/build/psbt/PsbtTransaction.js.map +1 -0
  86. package/build/psbt/types.d.ts +4 -70
  87. package/build/psbt/types.d.ts.map +1 -1
  88. package/build/psbt/validation.d.ts +1 -1
  89. package/build/psbt/validation.d.ts.map +1 -1
  90. package/build/psbt.d.ts +26 -40
  91. package/build/psbt.d.ts.map +1 -1
  92. package/build/psbt.js +177 -799
  93. package/build/psbt.js.map +1 -1
  94. package/build/script.d.ts.map +1 -1
  95. package/build/script.js +2 -2
  96. package/build/script.js.map +1 -1
  97. package/build/transaction.d.ts +4 -4
  98. package/build/transaction.d.ts.map +1 -1
  99. package/build/transaction.js +5 -4
  100. package/build/transaction.js.map +1 -1
  101. package/build/tsconfig.build.tsbuildinfo +1 -1
  102. package/build/types.d.ts +5 -3
  103. package/build/types.d.ts.map +1 -1
  104. package/build/types.js +9 -0
  105. package/build/types.js.map +1 -1
  106. package/build/workers/WorkerSigningPool.js +1 -1
  107. package/build/workers/WorkerSigningPool.js.map +1 -1
  108. package/build/workers/index.d.ts +3 -3
  109. package/build/workers/index.d.ts.map +1 -1
  110. package/build/workers/index.js +0 -3
  111. package/build/workers/index.js.map +1 -1
  112. package/build/workers/index.node.d.ts +24 -0
  113. package/build/workers/index.node.d.ts.map +1 -0
  114. package/build/workers/index.node.js +26 -0
  115. package/build/workers/index.node.js.map +1 -0
  116. package/package.json +27 -8
  117. package/src/address.ts +41 -18
  118. package/src/branded.ts +15 -13
  119. package/src/ecc/context.ts +30 -133
  120. package/src/ecc/index.ts +2 -2
  121. package/src/ecc/types.ts +7 -138
  122. package/src/index.ts +36 -2
  123. package/src/opcodes.ts +21 -4
  124. package/src/payments/p2tr.ts +2 -2
  125. package/src/psbt/PsbtCache.ts +325 -0
  126. package/src/psbt/PsbtFinalizer.ts +213 -0
  127. package/src/psbt/PsbtSigner.ts +302 -0
  128. package/src/psbt/PsbtTransaction.ts +82 -0
  129. package/src/psbt/types.ts +4 -86
  130. package/src/psbt/validation.ts +1 -1
  131. package/src/psbt.ts +348 -1197
  132. package/src/script.ts +2 -2
  133. package/src/transaction.ts +9 -8
  134. package/src/types.ts +14 -1
  135. package/src/workers/WorkerSigningPool.ts +1 -1
  136. package/src/workers/index.node.ts +27 -0
  137. package/src/workers/index.ts +7 -9
  138. package/test/address.spec.ts +2 -2
  139. package/test/bitcoin.core.spec.ts +5 -2
  140. package/test/browser/payments.spec.ts +151 -0
  141. package/test/browser/psbt.spec.ts +1510 -0
  142. package/test/browser/script.spec.ts +223 -0
  143. package/test/browser/setup.ts +13 -0
  144. package/test/browser/workers-signing.spec.ts +537 -0
  145. package/test/crypto.spec.ts +2 -2
  146. package/test/fixtures/core/base58_encode_decode.json +12 -48
  147. package/test/fixtures/core/base58_keys_invalid.json +50 -150
  148. package/test/fixtures/core/sighash.json +1 -3
  149. package/test/fixtures/core/tx_valid.json +133 -501
  150. package/test/fixtures/embed.json +3 -11
  151. package/test/fixtures/p2ms.json +21 -91
  152. package/test/fixtures/p2pk.json +5 -24
  153. package/test/fixtures/p2pkh.json +7 -36
  154. package/test/fixtures/p2sh.json +8 -54
  155. package/test/fixtures/p2tr.json +2 -6
  156. package/test/fixtures/p2wpkh.json +7 -36
  157. package/test/fixtures/p2wsh.json +14 -59
  158. package/test/fixtures/psbt.json +2 -6
  159. package/test/fixtures/script.json +12 -48
  160. package/test/integration/addresses.spec.ts +11 -5
  161. package/test/integration/bip32.spec.ts +1 -1
  162. package/test/integration/cltv.spec.ts +10 -6
  163. package/test/integration/csv.spec.ts +10 -9
  164. package/test/integration/payments.spec.ts +8 -4
  165. package/test/integration/taproot.spec.ts +26 -6
  166. package/test/integration/transactions.spec.ts +22 -8
  167. package/test/payments.spec.ts +1 -1
  168. package/test/payments.utils.ts +1 -1
  169. package/test/psbt.spec.ts +250 -64
  170. package/test/script_signature.spec.ts +1 -1
  171. package/test/transaction.spec.ts +18 -5
  172. package/test/tsconfig.json +6 -20
  173. package/test/workers-pool.spec.ts +22 -23
  174. package/test/workers-signing.spec.ts +7 -3
  175. package/test/workers.spec.ts +6 -7
  176. package/typedoc.json +11 -1
  177. package/vitest.config.browser.ts +68 -0
  178. package/browser/ecpair.d.ts +0 -99
  179. package/src/ecpair.d.ts +0 -99
  180. package/test/taproot-cache.spec.ts +0 -694
package/src/ecc/types.ts CHANGED
@@ -1,147 +1,16 @@
1
1
  /**
2
2
  * ECC (Elliptic Curve Cryptography) type definitions.
3
- * Defines interfaces for secp256k1 operations used in Taproot and signatures.
3
+ *
4
+ * Re-exports {@link CryptoBackend} from `@btc-vision/ecpair` as the canonical
5
+ * interface for secp256k1 operations. The legacy `EccLib` name is kept as a
6
+ * type alias for backward compatibility.
4
7
  *
5
8
  * @packageDocumentation
6
9
  */
7
10
 
8
- import type { Bytes32, PrivateKey, PublicKey, SchnorrSignature, Signature, XOnlyPublicKey, } from '../branded.js';
9
-
10
- /**
11
- * Parity of the y-coordinate for an x-only public key.
12
- * - 0: even y-coordinate
13
- * - 1: odd y-coordinate
14
- */
15
- export type Parity = 0 | 1;
16
-
17
- /**
18
- * Result of x-only point addition with tweak.
19
- */
20
- export interface XOnlyPointAddTweakResult {
21
- /** Parity of the resulting y-coordinate (0 = even, 1 = odd) */
22
- readonly parity: Parity;
23
- /** The resulting x-only public key */
24
- readonly xOnlyPubkey: XOnlyPublicKey;
25
- }
11
+ export type { CryptoBackend, XOnlyPointAddTweakResult, Parity } from '@btc-vision/ecpair';
26
12
 
27
13
  /**
28
- * Interface for the ECC library used by this library.
29
- * This is compatible with tiny-secp256k1 and @noble/secp256k1.
30
- *
31
- * @example
32
- * ```typescript
33
- * import { EccLib, initEccLib } from '@btc-vision/bitcoin';
34
- * import * as secp256k1 from 'tiny-secp256k1';
35
- *
36
- * // tiny-secp256k1 implements EccLib
37
- * const ecc: EccLib = secp256k1;
38
- * initEccLib(ecc);
39
- * ```
14
+ * @deprecated Use {@link CryptoBackend} from `@btc-vision/ecpair` instead.
40
15
  */
41
- export interface EccLib {
42
- /**
43
- * Checks if a 32-byte value is a valid x-only public key.
44
- *
45
- * @param p - 32-byte x-coordinate
46
- * @returns True if the point is valid on the secp256k1 curve
47
- */
48
- isXOnlyPoint(p: Uint8Array): boolean;
49
-
50
- /**
51
- * Adds a tweak to an x-only public key.
52
- *
53
- * @param p - 32-byte x-only public key
54
- * @param tweak - 32-byte scalar to add
55
- * @returns The tweaked public key with parity, or null if result is invalid
56
- */
57
- xOnlyPointAddTweak(p: XOnlyPublicKey, tweak: Bytes32): XOnlyPointAddTweakResult | null;
58
-
59
- /**
60
- * Signs a 32-byte message hash with a private key (ECDSA).
61
- * Optional - only needed for signing operations.
62
- *
63
- * @param hash - 32-byte message hash
64
- * @param privateKey - 32-byte private key
65
- * @returns DER-encoded signature
66
- */
67
- sign?(hash: Bytes32, privateKey: PrivateKey): Signature;
68
-
69
- /**
70
- * Signs a 32-byte message hash with a private key (Schnorr/BIP340).
71
- * Optional - only needed for Taproot key-path signing.
72
- *
73
- * @param hash - 32-byte message hash
74
- * @param privateKey - 32-byte private key
75
- * @returns 64-byte Schnorr signature
76
- */
77
- signSchnorr?(hash: Bytes32, privateKey: PrivateKey): SchnorrSignature;
78
-
79
- /**
80
- * Verifies an ECDSA signature.
81
- * Optional - only needed for signature verification.
82
- *
83
- * @param hash - 32-byte message hash
84
- * @param publicKey - 33 or 65-byte public key
85
- * @param signature - DER-encoded signature
86
- * @returns True if signature is valid
87
- */
88
- verify?(hash: Bytes32, publicKey: PublicKey, signature: Signature): boolean;
89
-
90
- /**
91
- * Verifies a Schnorr/BIP340 signature.
92
- * Optional - only needed for Taproot signature verification.
93
- *
94
- * @param hash - 32-byte message hash
95
- * @param publicKey - 32-byte x-only public key
96
- * @param signature - 64-byte Schnorr signature
97
- * @returns True if signature is valid
98
- */
99
- verifySchnorr?(hash: Bytes32, publicKey: XOnlyPublicKey, signature: SchnorrSignature): boolean;
100
-
101
- /**
102
- * Derives a public key from a private key.
103
- * Optional - only needed for key derivation.
104
- *
105
- * @param privateKey - 32-byte private key
106
- * @param compressed - Whether to return compressed (33-byte) or uncompressed (65-byte)
107
- * @returns The public key, or null if private key is invalid
108
- */
109
- pointFromScalar?(privateKey: PrivateKey, compressed?: boolean): PublicKey | null;
110
-
111
- /**
112
- * Computes the x-only public key from a private key.
113
- * Optional - only needed for Taproot key derivation.
114
- *
115
- * @param privateKey - 32-byte private key
116
- * @returns 32-byte x-only public key, or null if private key is invalid
117
- */
118
- xOnlyPointFromScalar?(privateKey: PrivateKey): XOnlyPublicKey | null;
119
-
120
- /**
121
- * Converts a full public key to x-only format.
122
- * Optional - only needed when working with x-only keys.
123
- *
124
- * @param pubkey - 33 or 65-byte public key
125
- * @returns 32-byte x-only public key
126
- */
127
- xOnlyPointFromPoint?(pubkey: PublicKey): XOnlyPublicKey;
128
-
129
- /**
130
- * Adds a scalar to a private key.
131
- * Optional - only needed for key tweaking.
132
- *
133
- * @param privateKey - 32-byte private key
134
- * @param tweak - 32-byte scalar to add
135
- * @returns The tweaked private key, or null if result is invalid
136
- */
137
- privateAdd?(privateKey: PrivateKey, tweak: Bytes32): PrivateKey | null;
138
-
139
- /**
140
- * Negates a private key.
141
- * Optional - only needed for Taproot parity handling.
142
- *
143
- * @param privateKey - 32-byte private key
144
- * @returns The negated private key
145
- */
146
- privateNegate?(privateKey: PrivateKey): PrivateKey;
147
- }
16
+ export type { CryptoBackend as EccLib } from '@btc-vision/ecpair';
package/src/index.ts CHANGED
@@ -39,7 +39,41 @@ export * as script from './script.js';
39
39
  export { Block } from './block.js';
40
40
  /** @hidden */
41
41
  export * from './crypto.js';
42
- export * from './psbt.js';
42
+ export {
43
+ Psbt,
44
+ PsbtCache,
45
+ PsbtSigner,
46
+ PsbtFinalizer,
47
+ PsbtTransaction,
48
+ transactionFromBuffer,
49
+ getFinalScripts,
50
+ prepareFinalScripts,
51
+ } from './psbt.js';
52
+ export type {
53
+ TransactionInput,
54
+ PsbtTxInput,
55
+ TransactionOutput,
56
+ PsbtTxOutput,
57
+ ValidateSigFunction,
58
+ PsbtBaseExtended,
59
+ PsbtOptsOptional,
60
+ PsbtOpts,
61
+ PsbtInputExtended,
62
+ PsbtOutputExtended,
63
+ PsbtOutputExtendedScript,
64
+ HDSigner,
65
+ HDSignerAsync,
66
+ Signer,
67
+ SignerAsync,
68
+ TaprootHashCheckSigner,
69
+ PsbtCacheInterface,
70
+ TxCacheNumberKey,
71
+ ScriptType,
72
+ AllScriptType,
73
+ GetScriptReturn,
74
+ FinalScriptsFunc,
75
+ FinalTaprootScriptsFunc,
76
+ } from './psbt.js';
43
77
  /** @hidden */
44
78
  export { opcodes } from './opcodes.js';
45
79
  export { Transaction } from './transaction.js';
@@ -48,7 +82,7 @@ export type { TaprootHashCache } from './transaction.js';
48
82
  export type { Network } from './networks.js';
49
83
  /** @hidden */
50
84
  export { initEccLib, getEccLib, EccContext } from './ecc/context.js';
51
- export type { EccLib } from './ecc/types.js';
85
+ export type { CryptoBackend, EccLib } from './ecc/types.js';
52
86
  export { PaymentType } from './payments/index.js';
53
87
  export type {
54
88
  Payment,
package/src/opcodes.ts CHANGED
@@ -273,8 +273,25 @@ export const opcodes: Opcodes = {
273
273
  OP_INVALIDOPCODE: 255,
274
274
  };
275
275
 
276
- export const REVERSE_OPS: { [key: number]: string } = {};
277
- for (const op of Object.keys(opcodes)) {
278
- const code = opcodes[op as keyof Opcodes];
279
- REVERSE_OPS[code] = op;
276
+ let _reverseOps: { [key: number]: string } | undefined;
277
+
278
+ /**
279
+ * Returns the reverse mapping from opcode number to opcode name.
280
+ * Lazily computed on first call.
281
+ */
282
+ export function getReverseOps(): { [key: number]: string } {
283
+ if (!_reverseOps) {
284
+ _reverseOps = {};
285
+ for (const op of Object.keys(opcodes)) {
286
+ const code = opcodes[op as keyof Opcodes];
287
+ _reverseOps[code] = op;
288
+ }
289
+ }
290
+ return _reverseOps;
280
291
  }
292
+
293
+ /**
294
+ * @deprecated Use {@link getReverseOps}() for lazy initialization.
295
+ * This eagerly-initialized alias exists for backward compatibility.
296
+ */
297
+ export const REVERSE_OPS: { [key: number]: string } = getReverseOps();
@@ -9,11 +9,11 @@
9
9
 
10
10
  import { bech32m } from 'bech32';
11
11
  import { fromBech32 } from '../bech32utils.js';
12
- import { getEccLib } from '../ecc/context.js';
13
12
  import { bitcoin as BITCOIN_NETWORK, type Network } from '../networks.js';
14
13
  import * as bscript from '../script.js';
15
14
  import {
16
15
  type Bytes32,
16
+ isXOnlyPublicKey,
17
17
  type SchnorrSignature,
18
18
  type Script,
19
19
  stacksEqual,
@@ -716,7 +716,7 @@ export class P2TR {
716
716
  throw new TypeError('Internal pubkey mismatch');
717
717
  }
718
718
 
719
- if (!getEccLib().isXOnlyPoint(internalPk)) {
719
+ if (!isXOnlyPublicKey(internalPk)) {
720
720
  throw new TypeError('Invalid internalPubkey for p2tr witness');
721
721
  }
722
722
 
@@ -0,0 +1,325 @@
1
+ import type { PsbtInput, PsbtOutput } from 'bip174';
2
+ import * as bscript from '../script.js';
3
+ import type { TaprootHashCache, Transaction } from '../transaction.js';
4
+ import type { PublicKey, Satoshi, Script, XOnlyPublicKey } from '../types.js';
5
+ import { isP2TR, isP2WPKH, pubkeyInScript } from './psbtutils.js';
6
+ import { getMeaningfulScript, isPubkeyLike, isSigLike, scriptWitnessToWitnessStack, } from './utils.js';
7
+ import type { GetScriptReturn, PrevOut, PsbtOpts } from './types.js';
8
+ import { isFinalized } from './validation.js';
9
+ import { isUnknownSegwitVersion } from '../address.js';
10
+
11
+ /**
12
+ * Internal PSBT cache for computed values.
13
+ * Wraps all cache management previously handled by a plain interface + scattered helper functions.
14
+ */
15
+ export class PsbtCache {
16
+ public readonly nonWitnessUtxoTxCache: Transaction[];
17
+ public readonly nonWitnessUtxoBufCache: Uint8Array[];
18
+ public readonly txInCache: Record<string, number>;
19
+ public readonly tx: Transaction;
20
+ public unsafeSignNonSegwit: boolean;
21
+ public hasSignatures: boolean;
22
+ public fee: number | undefined;
23
+ public feeRate: number | undefined;
24
+ public extractedTx: Transaction | undefined;
25
+ public prevOuts: readonly PrevOut[] | undefined;
26
+ public signingScripts: readonly Script[] | undefined;
27
+ public values: readonly Satoshi[] | undefined;
28
+ public taprootHashCache: TaprootHashCache | undefined;
29
+
30
+ public constructor(tx: Transaction) {
31
+ this.nonWitnessUtxoTxCache = [];
32
+ this.nonWitnessUtxoBufCache = [];
33
+ this.txInCache = {};
34
+ this.tx = tx;
35
+ this.unsafeSignNonSegwit = false;
36
+ this.hasSignatures = false;
37
+ }
38
+
39
+ /**
40
+ * Invalidates cached computed values.
41
+ * @param scope - 'full' clears everything (for input changes), 'outputs' clears fee/extract/taproot caches
42
+ */
43
+ public invalidate(scope: 'full' | 'outputs'): void {
44
+ this.fee = undefined;
45
+ this.feeRate = undefined;
46
+ this.extractedTx = undefined;
47
+ this.taprootHashCache = undefined;
48
+ if (scope === 'full') {
49
+ this.prevOuts = undefined;
50
+ this.signingScripts = undefined;
51
+ this.values = undefined;
52
+ }
53
+ }
54
+
55
+ public addNonWitnessTxCache(
56
+ input: PsbtInput,
57
+ inputIndex: number,
58
+ txFromBuffer: (buf: Uint8Array) => Transaction,
59
+ ): void {
60
+ if (!input.nonWitnessUtxo) throw new Error('nonWitnessUtxo is required');
61
+ if (input === null || input === Object.prototype) {
62
+ throw new Error('Invalid input object');
63
+ }
64
+ const nonWitnessUtxoBuf = input.nonWitnessUtxo;
65
+ this.nonWitnessUtxoBufCache[inputIndex] = nonWitnessUtxoBuf;
66
+ this.nonWitnessUtxoTxCache[inputIndex] = txFromBuffer(nonWitnessUtxoBuf);
67
+ }
68
+
69
+ public getNonWitnessUtxoTx(
70
+ input: PsbtInput,
71
+ inputIndex: number,
72
+ txFromBuffer: (buf: Uint8Array) => Transaction,
73
+ ): Transaction {
74
+ const cached = this.nonWitnessUtxoTxCache[inputIndex];
75
+ if (!cached) {
76
+ this.addNonWitnessTxCache(input, inputIndex, txFromBuffer);
77
+ }
78
+ return this.nonWitnessUtxoTxCache[inputIndex]!;
79
+ }
80
+
81
+ public getScriptFromUtxo(
82
+ inputIndex: number,
83
+ input: PsbtInput,
84
+ txFromBuffer: (buf: Uint8Array) => Transaction,
85
+ ): Script {
86
+ const { script } = this.getScriptAndAmountFromUtxo(inputIndex, input, txFromBuffer);
87
+ return script;
88
+ }
89
+
90
+ public getScriptAndAmountFromUtxo(
91
+ inputIndex: number,
92
+ input: PsbtInput,
93
+ txFromBuffer: (buf: Uint8Array) => Transaction,
94
+ ): { script: Script; value: Satoshi } {
95
+ if (input.witnessUtxo !== undefined) {
96
+ return {
97
+ script: input.witnessUtxo.script as Script,
98
+ value: input.witnessUtxo.value as Satoshi,
99
+ };
100
+ } else if (input.nonWitnessUtxo !== undefined) {
101
+ const nonWitnessUtxoTx = this.getNonWitnessUtxoTx(input, inputIndex, txFromBuffer);
102
+ const o = nonWitnessUtxoTx.outs[this.tx.ins[inputIndex]!.index]!;
103
+ return { script: o.script, value: o.value };
104
+ } else {
105
+ throw new Error("Can't find pubkey in input without Utxo data");
106
+ }
107
+ }
108
+
109
+ public computeFee(
110
+ inputs: PsbtInput[],
111
+ disableOutputChecks: boolean = false,
112
+ txFromBuffer?: (buf: Uint8Array) => Transaction,
113
+ ): number {
114
+ if (!inputs.every(isFinalized)) throw new Error('PSBT must be finalized to calculate fee');
115
+ if (this.fee !== undefined) return this.fee;
116
+ let tx: Transaction;
117
+ let mustFinalize = true;
118
+ if (this.extractedTx) {
119
+ tx = this.extractedTx;
120
+ mustFinalize = false;
121
+ } else {
122
+ tx = this.tx.clone();
123
+ }
124
+ const { fee } = this.finalizeAndComputeAmounts(
125
+ inputs,
126
+ tx,
127
+ mustFinalize,
128
+ disableOutputChecks,
129
+ txFromBuffer,
130
+ );
131
+ return fee;
132
+ }
133
+
134
+ public computeFeeRate(
135
+ inputs: PsbtInput[],
136
+ disableOutputChecks: boolean = false,
137
+ txFromBuffer?: (buf: Uint8Array) => Transaction,
138
+ ): number {
139
+ if (!inputs.every(isFinalized))
140
+ throw new Error('PSBT must be finalized to calculate fee rate');
141
+ if (this.feeRate !== undefined) return this.feeRate;
142
+ let tx: Transaction;
143
+ let mustFinalize = true;
144
+ if (this.extractedTx) {
145
+ tx = this.extractedTx;
146
+ mustFinalize = false;
147
+ } else {
148
+ tx = this.tx.clone();
149
+ }
150
+ const { feeRate } = this.finalizeAndComputeAmounts(
151
+ inputs,
152
+ tx,
153
+ mustFinalize,
154
+ disableOutputChecks,
155
+ txFromBuffer,
156
+ );
157
+ return feeRate;
158
+ }
159
+
160
+ public checkFees(opts: PsbtOpts): void {
161
+ const feeRate = this.feeRate;
162
+ if (!this.extractedTx) throw new Error('Transaction not extracted');
163
+ if (feeRate === undefined) throw new Error('Fee rate not computed');
164
+ const vsize = this.extractedTx.virtualSize();
165
+ const satoshis = feeRate * vsize;
166
+ if (feeRate >= opts.maximumFeeRate) {
167
+ throw new Error(
168
+ `Warning: You are paying around ${(satoshis / 1e8).toFixed(8)} in ` +
169
+ `fees, which is ${feeRate} satoshi per byte for a transaction ` +
170
+ `with a VSize of ${vsize} bytes (segwit counted as 0.25 byte per ` +
171
+ `byte). Use setMaximumFeeRate method to raise your threshold, or ` +
172
+ `pass true to the first arg of extractTransaction.`,
173
+ );
174
+ }
175
+ }
176
+
177
+ public pubkeyInInput(
178
+ pubkey: PublicKey,
179
+ input: PsbtInput,
180
+ inputIndex: number,
181
+ txFromBuffer: (buf: Uint8Array) => Transaction,
182
+ ): boolean {
183
+ const script = this.getScriptFromUtxo(inputIndex, input, txFromBuffer);
184
+ const { meaningfulScript } = getMeaningfulScript(
185
+ script,
186
+ inputIndex,
187
+ 'input',
188
+ input.redeemScript,
189
+ input.witnessScript,
190
+ );
191
+ return pubkeyInScript(pubkey, meaningfulScript);
192
+ }
193
+
194
+ public pubkeyInOutput(pubkey: PublicKey, output: PsbtOutput, outputIndex: number): boolean {
195
+ const script = this.tx.outs[outputIndex]!.script;
196
+ const { meaningfulScript } = getMeaningfulScript(
197
+ script,
198
+ outputIndex,
199
+ 'output',
200
+ output.redeemScript,
201
+ output.witnessScript,
202
+ );
203
+ return pubkeyInScript(pubkey, meaningfulScript);
204
+ }
205
+
206
+ public redeemFromFinalScriptSig(finalScript: Uint8Array | undefined): Uint8Array | undefined {
207
+ if (!finalScript) return;
208
+ const decomp = bscript.decompile(finalScript);
209
+ if (!decomp) return;
210
+ const lastItem = decomp[decomp.length - 1]!;
211
+ if (!(lastItem instanceof Uint8Array) || isPubkeyLike(lastItem) || isSigLike(lastItem))
212
+ return;
213
+ const sDecomp = bscript.decompile(lastItem);
214
+ if (!sDecomp) return;
215
+ return lastItem;
216
+ }
217
+
218
+ public redeemFromFinalWitnessScript(
219
+ finalScript: Uint8Array | undefined,
220
+ ): Uint8Array | undefined {
221
+ if (!finalScript) return;
222
+ const decomp = scriptWitnessToWitnessStack(finalScript);
223
+ const lastItem = decomp[decomp.length - 1]!;
224
+ if (isPubkeyLike(lastItem)) return;
225
+ const sDecomp = bscript.decompile(lastItem);
226
+ if (!sDecomp) return;
227
+ return lastItem;
228
+ }
229
+
230
+ /**
231
+ * Finalize transaction inputs and compute fee amounts.
232
+ * Returns computed values instead of mutating cache parameters directly.
233
+ */
234
+ public finalizeAndComputeAmounts(
235
+ inputs: PsbtInput[],
236
+ tx: Transaction,
237
+ mustFinalize: boolean,
238
+ disableOutputChecks?: boolean,
239
+ txFromBuffer?: (buf: Uint8Array) => Transaction,
240
+ ): { fee: number; feeRate: number } {
241
+ let inputAmount = 0n;
242
+ inputs.forEach((input, idx) => {
243
+ if (mustFinalize && input.finalScriptSig)
244
+ tx.ins[idx]!.script = input.finalScriptSig as Script;
245
+ if (mustFinalize && input.finalScriptWitness) {
246
+ tx.ins[idx]!.witness = scriptWitnessToWitnessStack(input.finalScriptWitness);
247
+ }
248
+ if (input.witnessUtxo) {
249
+ inputAmount += input.witnessUtxo.value;
250
+ } else if (input.nonWitnessUtxo) {
251
+ if (!txFromBuffer)
252
+ throw new Error('txFromBuffer is required for nonWitnessUtxo inputs');
253
+ const nwTx = this.getNonWitnessUtxoTx(input, idx, txFromBuffer);
254
+ const vout = tx.ins[idx]!.index;
255
+ const out = nwTx.outs[vout]!;
256
+ inputAmount += out.value;
257
+ }
258
+ });
259
+ const outputAmount = tx.outs.reduce((total, o) => total + o.value, 0n);
260
+ const feeValue = inputAmount - outputAmount;
261
+ if (!disableOutputChecks) {
262
+ if (feeValue < 0n) {
263
+ throw new Error(
264
+ `Outputs are spending more than Inputs ${inputAmount} < ${outputAmount}`,
265
+ );
266
+ }
267
+ }
268
+ const bytes = tx.virtualSize();
269
+ const fee = Number(feeValue);
270
+ const feeRate = Math.floor(fee / bytes);
271
+
272
+ this.fee = fee;
273
+ this.extractedTx = tx;
274
+ this.feeRate = feeRate;
275
+
276
+ return { fee, feeRate };
277
+ }
278
+
279
+ public getScriptFromInput(
280
+ inputIndex: number,
281
+ input: PsbtInput,
282
+ txFromBuffer: (buf: Uint8Array) => Transaction,
283
+ ): GetScriptReturn {
284
+ const res: GetScriptReturn = {
285
+ script: null,
286
+ isSegwit: false,
287
+ isP2SH: false,
288
+ isP2WSH: false,
289
+ };
290
+ res.isP2SH = !!input.redeemScript;
291
+ res.isP2WSH = !!input.witnessScript;
292
+ if (input.witnessScript) {
293
+ res.script = input.witnessScript as Script;
294
+ } else if (input.redeemScript) {
295
+ res.script = input.redeemScript as Script;
296
+ } else {
297
+ if (input.nonWitnessUtxo) {
298
+ const nonWitnessUtxoTx = this.getNonWitnessUtxoTx(input, inputIndex, txFromBuffer);
299
+ const prevoutIndex = this.tx.ins[inputIndex]!.index;
300
+ res.script = nonWitnessUtxoTx.outs[prevoutIndex]!.script;
301
+ } else if (input.witnessUtxo) {
302
+ res.script = input.witnessUtxo.script as Script;
303
+ }
304
+ }
305
+
306
+ if (input.witnessScript || (res.script && isP2WPKH(res.script))) {
307
+ res.isSegwit = true;
308
+ } else {
309
+ if (res.script && isUnknownSegwitVersion(res.script)) {
310
+ res.isSegwit = true;
311
+ }
312
+ }
313
+
314
+ return res;
315
+ }
316
+
317
+ public getPrevoutTaprootKey(
318
+ inputIndex: number,
319
+ input: PsbtInput,
320
+ txFromBuffer: (buf: Uint8Array) => Transaction,
321
+ ): XOnlyPublicKey | null {
322
+ const { script } = this.getScriptAndAmountFromUtxo(inputIndex, input, txFromBuffer);
323
+ return isP2TR(script) ? (script.subarray(2, 34) as XOnlyPublicKey) : null;
324
+ }
325
+ }