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

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 (160) hide show
  1. package/browser/address.d.ts +5 -1
  2. package/browser/address.d.ts.map +1 -1
  3. package/browser/branded.d.ts +3 -14
  4. package/browser/branded.d.ts.map +1 -1
  5. package/browser/ecc/context.d.ts.map +1 -1
  6. package/browser/index.d.ts +2 -1
  7. package/browser/index.d.ts.map +1 -1
  8. package/browser/index.js +2964 -2919
  9. package/browser/opcodes.d.ts +11 -0
  10. package/browser/opcodes.d.ts.map +1 -1
  11. package/browser/psbt/PsbtCache.d.ts +54 -0
  12. package/browser/psbt/PsbtCache.d.ts.map +1 -0
  13. package/browser/psbt/PsbtFinalizer.d.ts +21 -0
  14. package/browser/psbt/PsbtFinalizer.d.ts.map +1 -0
  15. package/browser/psbt/PsbtSigner.d.ts +32 -0
  16. package/browser/psbt/PsbtSigner.d.ts.map +1 -0
  17. package/browser/psbt/PsbtTransaction.d.ts +25 -0
  18. package/browser/psbt/PsbtTransaction.d.ts.map +1 -0
  19. package/browser/psbt/types.d.ts +13 -13
  20. package/browser/psbt/types.d.ts.map +1 -1
  21. package/browser/psbt/validation.d.ts +1 -1
  22. package/browser/psbt/validation.d.ts.map +1 -1
  23. package/browser/psbt.d.ts +27 -39
  24. package/browser/psbt.d.ts.map +1 -1
  25. package/browser/script.d.ts.map +1 -1
  26. package/browser/transaction.d.ts +4 -4
  27. package/browser/transaction.d.ts.map +1 -1
  28. package/browser/types.d.ts +4 -2
  29. package/browser/types.d.ts.map +1 -1
  30. package/browser/workers/index.d.ts +3 -50
  31. package/browser/workers/index.d.ts.map +1 -1
  32. package/browser/workers/index.node.d.ts +24 -0
  33. package/browser/workers/index.node.d.ts.map +1 -0
  34. package/build/address.d.ts +5 -1
  35. package/build/address.d.ts.map +1 -1
  36. package/build/address.js +29 -17
  37. package/build/address.js.map +1 -1
  38. package/build/branded.d.ts +3 -14
  39. package/build/branded.d.ts.map +1 -1
  40. package/build/branded.js +0 -5
  41. package/build/branded.js.map +1 -1
  42. package/build/ecc/context.d.ts.map +1 -1
  43. package/build/ecc/context.js +68 -45
  44. package/build/ecc/context.js.map +1 -1
  45. package/build/index.d.ts +2 -1
  46. package/build/index.d.ts.map +1 -1
  47. package/build/index.js +1 -1
  48. package/build/index.js.map +1 -1
  49. package/build/opcodes.d.ts +11 -0
  50. package/build/opcodes.d.ts.map +1 -1
  51. package/build/opcodes.js +19 -4
  52. package/build/opcodes.js.map +1 -1
  53. package/build/psbt/PsbtCache.d.ts +54 -0
  54. package/build/psbt/PsbtCache.d.ts.map +1 -0
  55. package/build/psbt/PsbtCache.js +249 -0
  56. package/build/psbt/PsbtCache.js.map +1 -0
  57. package/build/psbt/PsbtFinalizer.d.ts +21 -0
  58. package/build/psbt/PsbtFinalizer.d.ts.map +1 -0
  59. package/build/psbt/PsbtFinalizer.js +157 -0
  60. package/build/psbt/PsbtFinalizer.js.map +1 -0
  61. package/build/psbt/PsbtSigner.d.ts +32 -0
  62. package/build/psbt/PsbtSigner.d.ts.map +1 -0
  63. package/build/psbt/PsbtSigner.js +192 -0
  64. package/build/psbt/PsbtSigner.js.map +1 -0
  65. package/build/psbt/PsbtTransaction.d.ts +25 -0
  66. package/build/psbt/PsbtTransaction.d.ts.map +1 -0
  67. package/build/psbt/PsbtTransaction.js +61 -0
  68. package/build/psbt/PsbtTransaction.js.map +1 -0
  69. package/build/psbt/types.d.ts +13 -13
  70. package/build/psbt/types.d.ts.map +1 -1
  71. package/build/psbt/validation.d.ts +1 -1
  72. package/build/psbt/validation.d.ts.map +1 -1
  73. package/build/psbt.d.ts +27 -39
  74. package/build/psbt.d.ts.map +1 -1
  75. package/build/psbt.js +139 -746
  76. package/build/psbt.js.map +1 -1
  77. package/build/script.d.ts.map +1 -1
  78. package/build/script.js +2 -2
  79. package/build/script.js.map +1 -1
  80. package/build/transaction.d.ts +4 -4
  81. package/build/transaction.d.ts.map +1 -1
  82. package/build/transaction.js +5 -4
  83. package/build/transaction.js.map +1 -1
  84. package/build/tsconfig.build.tsbuildinfo +1 -1
  85. package/build/types.d.ts +4 -2
  86. package/build/types.d.ts.map +1 -1
  87. package/build/types.js +9 -0
  88. package/build/types.js.map +1 -1
  89. package/build/workers/WorkerSigningPool.js +1 -1
  90. package/build/workers/WorkerSigningPool.js.map +1 -1
  91. package/build/workers/index.d.ts +3 -3
  92. package/build/workers/index.d.ts.map +1 -1
  93. package/build/workers/index.js +0 -3
  94. package/build/workers/index.js.map +1 -1
  95. package/build/workers/index.node.d.ts +24 -0
  96. package/build/workers/index.node.d.ts.map +1 -0
  97. package/build/workers/index.node.js +26 -0
  98. package/build/workers/index.node.js.map +1 -0
  99. package/package.json +28 -9
  100. package/src/address.ts +41 -18
  101. package/src/branded.ts +15 -13
  102. package/src/ecc/context.ts +75 -57
  103. package/src/index.ts +36 -1
  104. package/src/opcodes.ts +21 -4
  105. package/src/psbt/PsbtCache.ts +325 -0
  106. package/src/psbt/PsbtFinalizer.ts +213 -0
  107. package/src/psbt/PsbtSigner.ts +302 -0
  108. package/src/psbt/PsbtTransaction.ts +82 -0
  109. package/src/psbt/types.ts +13 -13
  110. package/src/psbt/validation.ts +1 -1
  111. package/src/psbt.ts +299 -1090
  112. package/src/script.ts +2 -2
  113. package/src/transaction.ts +9 -8
  114. package/src/types.ts +13 -0
  115. package/src/workers/WorkerSigningPool.ts +1 -1
  116. package/src/workers/index.node.ts +27 -0
  117. package/src/workers/index.ts +7 -9
  118. package/test/address.spec.ts +2 -2
  119. package/test/bitcoin.core.spec.ts +5 -2
  120. package/test/browser/payments.spec.ts +151 -0
  121. package/test/browser/psbt.spec.ts +1510 -0
  122. package/test/browser/script.spec.ts +223 -0
  123. package/test/browser/setup.ts +13 -0
  124. package/test/browser/workers-signing.spec.ts +537 -0
  125. package/test/crypto.spec.ts +2 -2
  126. package/test/fixtures/core/base58_encode_decode.json +12 -48
  127. package/test/fixtures/core/base58_keys_invalid.json +50 -150
  128. package/test/fixtures/core/sighash.json +1 -3
  129. package/test/fixtures/core/tx_valid.json +133 -501
  130. package/test/fixtures/embed.json +3 -11
  131. package/test/fixtures/p2ms.json +21 -91
  132. package/test/fixtures/p2pk.json +5 -24
  133. package/test/fixtures/p2pkh.json +7 -36
  134. package/test/fixtures/p2sh.json +8 -54
  135. package/test/fixtures/p2tr.json +2 -6
  136. package/test/fixtures/p2wpkh.json +7 -36
  137. package/test/fixtures/p2wsh.json +14 -59
  138. package/test/fixtures/psbt.json +2 -6
  139. package/test/fixtures/script.json +12 -48
  140. package/test/integration/addresses.spec.ts +11 -5
  141. package/test/integration/bip32.spec.ts +1 -1
  142. package/test/integration/cltv.spec.ts +10 -6
  143. package/test/integration/csv.spec.ts +10 -9
  144. package/test/integration/payments.spec.ts +8 -4
  145. package/test/integration/taproot.spec.ts +26 -6
  146. package/test/integration/transactions.spec.ts +22 -8
  147. package/test/payments.spec.ts +1 -1
  148. package/test/payments.utils.ts +1 -1
  149. package/test/psbt.spec.ts +250 -64
  150. package/test/script_signature.spec.ts +1 -1
  151. package/test/transaction.spec.ts +18 -5
  152. package/test/tsconfig.json +6 -20
  153. package/test/workers-pool.spec.ts +22 -23
  154. package/test/workers-signing.spec.ts +7 -3
  155. package/test/workers.spec.ts +6 -7
  156. package/typedoc.json +11 -1
  157. package/vitest.config.browser.ts +68 -0
  158. package/browser/ecpair.d.ts +0 -99
  159. package/src/ecpair.d.ts +0 -99
  160. package/test/taproot-cache.spec.ts +0 -694
package/src/psbt.ts CHANGED
@@ -1,27 +1,19 @@
1
1
  import type {
2
- Bip32Derivation,
3
2
  KeyValue,
4
- PartialSig,
5
3
  PsbtGlobalUpdate,
6
4
  PsbtInput,
7
5
  PsbtInputUpdate,
8
- PsbtOutput,
9
6
  PsbtOutputUpdate,
10
7
  TapKeySig,
11
8
  TapScriptSig,
12
- Transaction as ITransaction,
13
- TransactionFromBuffer,
14
9
  } from 'bip174';
15
10
  import { checkForInput, checkForOutput, Psbt as PsbtBase } from 'bip174';
16
- import { clone, equals, fromBase64, fromHex, reverse, toHex } from './io/index.js';
11
+ import { clone, equals, fromBase64, fromHex, toHex } from './io/index.js';
17
12
 
18
13
  import type { BIP32Interface } from '@btc-vision/bip32';
19
- import type { ECPairInterface } from 'ecpair';
20
- import { fromOutputScript, isUnknownSegwitVersion, toOutputScript } from './address.js';
14
+ import { fromOutputScript, toOutputScript } from './address.js';
21
15
  import { bitcoin as btcNetwork } from './networks.js';
22
- import type { P2SHPayment, P2WSHPayment } from './payments/index.js';
23
16
  import * as payments from './payments/index.js';
24
- import { tapleafHash } from './payments/bip341.js';
25
17
  import {
26
18
  checkTaprootInputFields,
27
19
  checkTaprootOutputFields,
@@ -30,52 +22,17 @@ import {
30
22
  tapScriptFinalizer,
31
23
  } from './psbt/bip371.js';
32
24
  import { toXOnly } from './pubkey.js';
33
- import { isP2TR, isP2WPKH, pubkeyInScript, witnessStackToScriptWitness } from './psbt/psbtutils.js';
34
- import {
35
- check32Bit,
36
- checkCache,
37
- checkInputsForPartialSig,
38
- checkPartialSigSighashes,
39
- checkScriptForPubkey,
40
- checkTxEmpty,
41
- checkTxForDupeIns,
42
- checkTxInputCache,
43
- isFinalized,
44
- } from './psbt/validation.js';
45
- import {
46
- checkInvalidP2WSH,
47
- classifyScript,
48
- compressPubkey,
49
- getMeaningfulScript,
50
- isPubkeyLike,
51
- isSigLike,
52
- range,
53
- scriptWitnessToWitnessStack,
54
- sighashTypeToString,
55
- } from './psbt/utils.js';
56
25
  import * as bscript from './script.js';
57
- import type { Output } from './transaction.js';
58
26
  import { Transaction } from './transaction.js';
59
- import type {
60
- Bytes20,
61
- Bytes32,
62
- PublicKey,
63
- Satoshi,
64
- SchnorrSignature,
65
- Script,
66
- Signature,
67
- XOnlyPublicKey,
68
- } from './types.js';
69
- // Import types for internal use
27
+ import type { Bytes32, MessageHash, PublicKey, SchnorrSignature, Script } from './types.js';
28
+
70
29
  import type {
71
30
  AllScriptType,
72
31
  FinalScriptsFunc,
73
32
  FinalTaprootScriptsFunc,
74
- GetScriptReturn,
75
33
  HDSigner,
76
34
  HDSignerAsync,
77
35
  PsbtBaseExtended,
78
- PsbtCache,
79
36
  PsbtInputExtended,
80
37
  PsbtOpts,
81
38
  PsbtOptsOptional,
@@ -87,11 +44,30 @@ import type {
87
44
  SignerAlternative,
88
45
  SignerAsync,
89
46
  TaprootHashCheckSigner,
90
- TransactionInput,
91
- TransactionOutput,
92
- TxCacheNumberKey,
93
47
  ValidateSigFunction,
94
48
  } from './psbt/types.js';
49
+ // Import composition classes
50
+ import { PsbtCache } from './psbt/PsbtCache.js';
51
+ import { PsbtSigner } from './psbt/PsbtSigner.js';
52
+ import {
53
+ getFinalScripts as _getFinalScripts,
54
+ prepareFinalScripts as _prepareFinalScripts,
55
+ PsbtFinalizer,
56
+ } from './psbt/PsbtFinalizer.js';
57
+ import { PsbtTransaction, transactionFromBuffer } from './psbt/PsbtTransaction.js';
58
+ import {
59
+ check32Bit,
60
+ checkCache,
61
+ checkInputsForPartialSig,
62
+ checkPartialSigSighashes,
63
+ checkScriptForPubkey,
64
+ checkTxForDupeIns,
65
+ checkTxInputCache,
66
+ isFinalized,
67
+ } from './psbt/validation.js';
68
+ import { checkInvalidP2WSH, classifyScript, getMeaningfulScript, range } from './psbt/utils.js';
69
+ import { witnessStackToScriptWitness } from './psbt/psbtutils.js';
70
+ import type { UniversalSigner } from '@btc-vision/ecpair';
95
71
 
96
72
  // Re-export types from the types module
97
73
  export type {
@@ -105,7 +81,6 @@ export type {
105
81
  PsbtOpts,
106
82
  PsbtInputExtended,
107
83
  PsbtOutputExtended,
108
- PsbtOutputExtendedAddress,
109
84
  PsbtOutputExtendedScript,
110
85
  HDSigner,
111
86
  HDSignerAsync,
@@ -113,7 +88,7 @@ export type {
113
88
  Signer,
114
89
  SignerAsync,
115
90
  TaprootHashCheckSigner,
116
- PsbtCache,
91
+ PsbtCacheInterface,
117
92
  TxCacheNumberKey,
118
93
  ScriptType,
119
94
  AllScriptType,
@@ -122,23 +97,75 @@ export type {
122
97
  FinalTaprootScriptsFunc,
123
98
  } from './psbt/types.js';
124
99
 
100
+ // Re-export for backwards compatibility
101
+ export { getFinalScripts, prepareFinalScripts };
102
+ export { PsbtCache } from './psbt/PsbtCache.js';
103
+ export { PsbtSigner } from './psbt/PsbtSigner.js';
104
+ export { PsbtFinalizer } from './psbt/PsbtFinalizer.js';
105
+ export { PsbtTransaction, transactionFromBuffer } from './psbt/PsbtTransaction.js';
106
+
125
107
  /**
126
108
  * These are the default arguments for a Psbt instance.
127
109
  */
128
110
  const DEFAULT_OPTS: PsbtOpts = {
129
- /**
130
- * A bitcoinjs Network object. This is only used if you pass an `address`
131
- * parameter to addOutput. Otherwise it is not needed and can be left default.
132
- */
133
111
  network: btcNetwork,
134
- /**
135
- * When extractTransaction is called, the fee rate is checked.
136
- * THIS IS NOT TO BE RELIED ON.
137
- * It is only here as a last ditch effort to prevent sending a 500 BTC fee etc.
138
- */
139
- maximumFeeRate: 5000, // satoshi per byte
112
+ maximumFeeRate: 5000,
140
113
  };
141
114
 
115
+ /** Helper to create a Transaction from a buffer */
116
+ function txFromBuffer(buf: Uint8Array): Transaction {
117
+ return Transaction.fromBuffer(buf);
118
+ }
119
+
120
+ // Standalone exports that delegate to PsbtFinalizer
121
+ function getFinalScripts(
122
+ inputIndex: number,
123
+ input: PsbtInput,
124
+ script: Script,
125
+ isSegwit: boolean,
126
+ isP2SH: boolean,
127
+ isP2WSH: boolean,
128
+ canRunChecks: boolean = true,
129
+ solution?: Uint8Array[],
130
+ ): {
131
+ finalScriptSig: Script | undefined;
132
+ finalScriptWitness: Uint8Array | undefined;
133
+ } {
134
+ return _getFinalScripts(
135
+ inputIndex,
136
+ input,
137
+ script,
138
+ isSegwit,
139
+ isP2SH,
140
+ isP2WSH,
141
+ canRunChecks,
142
+ solution,
143
+ );
144
+ }
145
+
146
+ function prepareFinalScripts(
147
+ script: Uint8Array,
148
+ scriptType: string,
149
+ partialSig: import('bip174').PartialSig[],
150
+ isSegwit: boolean,
151
+ isP2SH: boolean,
152
+ isP2WSH: boolean,
153
+ solution?: Uint8Array[],
154
+ ): {
155
+ finalScriptSig: Script | undefined;
156
+ finalScriptWitness: Uint8Array | undefined;
157
+ } {
158
+ return _prepareFinalScripts(
159
+ script,
160
+ scriptType,
161
+ partialSig,
162
+ isSegwit,
163
+ isP2SH,
164
+ isP2WSH,
165
+ solution,
166
+ );
167
+ }
168
+
142
169
  /**
143
170
  * Psbt class can parse and generate a PSBT binary based off of the BIP174.
144
171
  * There are 6 roles that this class fulfills. (Explained in BIP174)
@@ -176,64 +203,46 @@ const DEFAULT_OPTS: PsbtOpts = {
176
203
  * Transaction Extractor: This role will perform some checks before returning a
177
204
  * Transaction object. Such as fee rate not being larger than maximumFeeRate etc.
178
205
  */
179
- /**
180
- * Psbt class can parse and generate a PSBT binary based off of the BIP174.
181
- */
182
206
  export class Psbt {
183
207
  readonly #cache: PsbtCache;
208
+ #signer: PsbtSigner | undefined;
209
+ #finalizer: PsbtFinalizer | undefined;
184
210
  readonly #opts: PsbtOpts;
185
211
 
186
- constructor(
212
+ public constructor(
187
213
  opts: PsbtOptsOptional = {},
188
214
  public data: PsbtBaseExtended = new PsbtBase(new PsbtTransaction()),
189
215
  ) {
190
216
  this.#opts = Object.assign({}, DEFAULT_OPTS, opts);
191
- this.#cache = {
192
- nonWitnessUtxoTxCache: [],
193
- nonWitnessUtxoBufCache: [],
194
- txInCache: {},
195
- // unsignedTx.tx property is dynamically added by PsbtBase
196
- tx: (this.data.globalMap.unsignedTx as PsbtTransaction).tx,
197
- unsafeSignNonSegwit: false,
198
- hasSignatures: false,
199
- };
217
+ const tx = (this.data.globalMap.unsignedTx as PsbtTransaction).tx;
218
+ this.#cache = new PsbtCache(tx);
200
219
 
201
220
  if (opts.version === 3) {
202
221
  this.setVersionTRUC();
203
222
  } else if (this.data.inputs.length === 0) this.setVersion(2);
204
223
  }
205
224
 
206
- /** @internal - Exposed for testing. Do not use in production code. */
207
- get __CACHE(): PsbtCache {
208
- return this.#cache;
209
- }
210
-
211
- /** @internal - Exposed for testing. Do not use in production code. */
212
- get opts(): PsbtOpts {
213
- return this.#opts;
214
- }
215
-
216
- get inputCount(): number {
225
+ public get inputCount(): number {
217
226
  return this.data.inputs.length;
218
227
  }
219
228
 
220
- get version(): number {
229
+ public get version(): number {
221
230
  return this.#cache.tx.version;
222
231
  }
223
232
 
224
- set version(version: number) {
233
+ public set version(version: number) {
225
234
  this.setVersion(version);
226
235
  }
227
236
 
228
- get locktime(): number {
237
+ public get locktime(): number {
229
238
  return this.#cache.tx.locktime;
230
239
  }
231
240
 
232
- set locktime(locktime: number) {
241
+ public set locktime(locktime: number) {
233
242
  this.setLocktime(locktime);
234
243
  }
235
244
 
236
- get txInputs(): PsbtTxInput[] {
245
+ public get txInputs(): PsbtTxInput[] {
237
246
  return this.#cache.tx.ins.map((input) => ({
238
247
  hash: clone(input.hash) as Bytes32,
239
248
  index: input.index,
@@ -241,12 +250,14 @@ export class Psbt {
241
250
  }));
242
251
  }
243
252
 
244
- get txOutputs(): PsbtTxOutput[] {
253
+ public get txOutputs(): PsbtTxOutput[] {
245
254
  return this.#cache.tx.outs.map((output) => {
246
- let address;
255
+ let address: string | undefined;
247
256
  try {
248
257
  address = fromOutputScript(output.script, this.#opts.network);
249
- } catch (_) {}
258
+ } catch (_) {
259
+ // Not all scripts can be converted to an address
260
+ }
250
261
  return {
251
262
  script: clone(output.script) as Script,
252
263
  value: output.value,
@@ -255,21 +266,36 @@ export class Psbt {
255
266
  });
256
267
  }
257
268
 
258
- static fromBase64(data: string, opts: PsbtOptsOptional = {}): Psbt {
269
+ /** Lazily initialized signer - created on first access */
270
+ get #lazySigner(): PsbtSigner {
271
+ if (!this.#signer) {
272
+ this.#signer = new PsbtSigner(this.#cache, txFromBuffer);
273
+ }
274
+ return this.#signer;
275
+ }
276
+
277
+ /** Lazily initialized finalizer - created on first access */
278
+ get #lazyFinalizer(): PsbtFinalizer {
279
+ if (!this.#finalizer) {
280
+ this.#finalizer = new PsbtFinalizer(this.#cache, txFromBuffer);
281
+ }
282
+ return this.#finalizer;
283
+ }
284
+
285
+ public static fromBase64(data: string, opts: PsbtOptsOptional = {}): Psbt {
259
286
  const buffer = fromBase64(data);
260
287
  return this.fromBuffer(buffer, opts);
261
288
  }
262
289
 
263
- static fromHex(data: string, opts: PsbtOptsOptional = {}): Psbt {
290
+ public static fromHex(data: string, opts: PsbtOptsOptional = {}): Psbt {
264
291
  const buffer = fromHex(data);
265
292
  return this.fromBuffer(buffer, opts);
266
293
  }
267
294
 
268
- static fromBuffer(buffer: Uint8Array, opts: PsbtOptsOptional = {}): Psbt {
295
+ public static fromBuffer(buffer: Uint8Array, opts: PsbtOptsOptional = {}): Psbt {
269
296
  const psbtBase = PsbtBase.fromBuffer(buffer, transactionFromBuffer);
270
297
  const psbt = new Psbt(opts, psbtBase);
271
298
  checkTxForDupeIns(psbt.#cache.tx, psbt.#cache);
272
- // Check if restored PSBT has any signatures (partial or finalized)
273
299
  psbt.#cache.hasSignatures = psbt.data.inputs.some(
274
300
  (input) =>
275
301
  input.partialSig?.length ||
@@ -281,62 +307,62 @@ export class Psbt {
281
307
  return psbt;
282
308
  }
283
309
 
284
- combine(...those: Psbt[]): this {
310
+ public combine(...those: Psbt[]): this {
285
311
  this.data.combine(...those.map((o) => o.data));
286
312
  return this;
287
313
  }
288
314
 
289
- clone(): Psbt {
290
- // TODO: more efficient cloning
291
- const clonedOpts = JSON.parse(JSON.stringify(this.#opts)) as PsbtOptsOptional;
315
+ public clone(): Psbt {
316
+ const clonedOpts = structuredClone(this.#opts) as PsbtOptsOptional;
292
317
  return Psbt.fromBuffer(new Uint8Array(this.data.toBuffer()), clonedOpts);
293
318
  }
294
319
 
295
- setMaximumFeeRate(satoshiPerByte: number): void {
296
- check32Bit(satoshiPerByte); // 42.9 BTC per byte IS excessive... so throw
320
+ public get maximumFeeRate(): number {
321
+ return this.#opts.maximumFeeRate;
322
+ }
323
+
324
+ public setMaximumFeeRate(satoshiPerByte: number): void {
325
+ check32Bit(satoshiPerByte);
297
326
  this.#opts.maximumFeeRate = satoshiPerByte;
298
327
  }
299
328
 
300
- setVersion(version: number): this {
329
+ public setVersion(version: number): this {
301
330
  check32Bit(version);
302
331
  checkInputsForPartialSig(this.data.inputs, 'setVersion', this.#cache.hasSignatures);
303
- const c = this.#cache;
304
- c.tx.version = version;
305
- c.extractedTx = undefined;
332
+ this.#cache.tx.version = version;
333
+ this.#cache.invalidate('outputs');
306
334
  return this;
307
335
  }
308
336
 
309
- setVersionTRUC(): this {
337
+ public setVersionTRUC(): this {
310
338
  return this.setVersion(Transaction.TRUC_VERSION);
311
339
  }
312
340
 
313
- setLocktime(locktime: number): this {
341
+ public setLocktime(locktime: number): this {
314
342
  check32Bit(locktime);
315
343
  checkInputsForPartialSig(this.data.inputs, 'setLocktime', this.#cache.hasSignatures);
316
- const c = this.#cache;
317
- c.tx.locktime = locktime;
318
- c.extractedTx = undefined;
344
+ this.#cache.tx.locktime = locktime;
345
+ this.#cache.invalidate('outputs');
319
346
  return this;
320
347
  }
321
348
 
322
- setInputSequence(inputIndex: number, sequence: number): this {
349
+ public setInputSequence(inputIndex: number, sequence: number): this {
323
350
  check32Bit(sequence);
324
351
  checkInputsForPartialSig(this.data.inputs, 'setInputSequence', this.#cache.hasSignatures);
325
- const c = this.#cache;
326
- if (c.tx.ins.length <= inputIndex) {
352
+ if (this.#cache.tx.ins.length <= inputIndex) {
327
353
  throw new Error('Input index too high');
328
354
  }
329
- c.tx.ins[inputIndex]!.sequence = sequence;
330
- c.extractedTx = undefined;
355
+ this.#cache.tx.ins[inputIndex]!.sequence = sequence;
356
+ this.#cache.invalidate('outputs');
331
357
  return this;
332
358
  }
333
359
 
334
- addInputs(inputDatas: PsbtInputExtended[], checkPartialSigs: boolean = true): this {
360
+ public addInputs(inputDatas: PsbtInputExtended[], checkPartialSigs: boolean = true): this {
335
361
  inputDatas.forEach((inputData) => this.addInput(inputData, checkPartialSigs));
336
362
  return this;
337
363
  }
338
364
 
339
- addInput(inputData: PsbtInputExtended, checkPartialSigs: boolean = true): this {
365
+ public addInput(inputData: PsbtInputExtended, checkPartialSigs: boolean = true): this {
340
366
  if (!inputData || inputData.hash === undefined || inputData.index === undefined) {
341
367
  throw new Error(
342
368
  `Invalid arguments for Psbt.addInput. ` +
@@ -351,7 +377,6 @@ export class Psbt {
351
377
  }
352
378
 
353
379
  if (inputData.witnessScript) checkInvalidP2WSH(inputData.witnessScript);
354
- // Convert witnessUtxo for bip174 v3 compatibility (value: bigint, script: Uint8Array)
355
380
  const normalizedInputData = inputData.witnessUtxo
356
381
  ? {
357
382
  ...inputData,
@@ -364,43 +389,25 @@ export class Psbt {
364
389
  },
365
390
  }
366
391
  : inputData;
367
- const c = this.#cache;
368
392
  this.data.addInput(normalizedInputData);
369
- const txIn = c.tx.ins[c.tx.ins.length - 1]!;
370
- checkTxInputCache(c, txIn);
393
+ const txIn = this.#cache.tx.ins[this.#cache.tx.ins.length - 1]!;
394
+ checkTxInputCache(this.#cache, txIn);
371
395
 
372
396
  const inputIndex = this.data.inputs.length - 1;
373
397
  const input = this.data.inputs[inputIndex]!;
374
398
  if (input.nonWitnessUtxo) {
375
- addNonWitnessTxCache(this.#cache, input, inputIndex);
399
+ this.#cache.addNonWitnessTxCache(input, inputIndex, txFromBuffer);
376
400
  }
377
- c.fee = undefined;
378
- c.feeRate = undefined;
379
- c.extractedTx = undefined;
380
- c.prevOuts = undefined;
381
- c.signingScripts = undefined;
382
- c.values = undefined;
383
- c.taprootHashCache = undefined;
401
+ this.#cache.invalidate('full');
384
402
  return this;
385
403
  }
386
404
 
387
- addOutputs(outputDatas: PsbtOutputExtended[], checkPartialSigs: boolean = true): this {
405
+ public addOutputs(outputDatas: PsbtOutputExtended[], checkPartialSigs: boolean = true): this {
388
406
  outputDatas.forEach((outputData) => this.addOutput(outputData, checkPartialSigs));
389
407
  return this;
390
408
  }
391
409
 
392
- /**
393
- * Add an output to the PSBT.
394
- *
395
- * **PERFORMANCE WARNING:** Passing an `address` string is ~10x slower than passing
396
- * a `script` directly due to address parsing overhead (bech32 decode, etc.).
397
- * For high-performance use cases with many outputs, pre-compute the script using
398
- * `toOutputScript(address, network)` and pass `{ script, value }` instead.
399
- *
400
- * @param outputData - Output data with either `address` or `script`, and `value`
401
- * @param checkPartialSigs - Whether to check for existing signatures (default: true)
402
- */
403
- addOutput(outputData: PsbtOutputExtended, checkPartialSigs: boolean = true): this {
410
+ public addOutput(outputData: PsbtOutputExtended, checkPartialSigs: boolean = true): this {
404
411
  const hasAddress = 'address' in outputData;
405
412
  const hasScript = 'script' in outputData;
406
413
  if (!outputData || outputData.value === undefined || (!hasAddress && !hasScript)) {
@@ -420,16 +427,15 @@ export class Psbt {
420
427
  }
421
428
  checkTaprootOutputFields(outputData, outputData, 'addOutput');
422
429
 
423
- const c = this.#cache;
424
430
  this.data.addOutput(outputData);
425
- c.fee = undefined;
426
- c.feeRate = undefined;
427
- c.extractedTx = undefined;
428
- c.taprootHashCache = undefined;
431
+ this.#cache.invalidate('outputs');
429
432
  return this;
430
433
  }
431
434
 
432
- extractTransaction(disableFeeCheck?: boolean, disableOutputChecks?: boolean): Transaction {
435
+ public extractTransaction(
436
+ disableFeeCheck?: boolean,
437
+ disableOutputChecks?: boolean,
438
+ ): Transaction {
433
439
  if (disableOutputChecks) {
434
440
  (this.data as unknown as { inputs: PsbtInput[] }).inputs = this.data.inputs.filter(
435
441
  (i) => !i.partialSig,
@@ -437,37 +443,37 @@ export class Psbt {
437
443
  }
438
444
 
439
445
  if (!this.data.inputs.every(isFinalized)) throw new Error('Not finalized');
440
- const c = this.#cache;
441
446
  if (!disableFeeCheck) {
442
- checkFees(this, c, this.#opts);
447
+ this.#cache.computeFeeRate(this.data.inputs, disableOutputChecks, txFromBuffer);
448
+ this.#cache.checkFees(this.#opts);
443
449
  }
444
- if (c.extractedTx) return c.extractedTx;
445
- const tx = c.tx.clone();
446
- inputFinalizeGetAmts(this.data.inputs, tx, c, true, disableOutputChecks);
447
- return tx;
448
- }
449
-
450
- getFeeRate(disableOutputChecks: boolean = false): number {
451
- return getTxCacheValue(
452
- 'feeRate',
453
- 'fee rate',
450
+ if (this.#cache.extractedTx) return this.#cache.extractedTx;
451
+ const tx = this.#cache.tx.clone();
452
+ this.#cache.finalizeAndComputeAmounts(
454
453
  this.data.inputs,
455
- this.#cache,
454
+ tx,
455
+ true,
456
456
  disableOutputChecks,
457
+ txFromBuffer,
457
458
  );
459
+ return tx;
458
460
  }
459
461
 
460
- getFee(disableOutputChecks: boolean = false): number {
461
- return getTxCacheValue('fee', 'fee', this.data.inputs, this.#cache, disableOutputChecks);
462
+ public getFeeRate(disableOutputChecks: boolean = false): number {
463
+ return this.#cache.computeFeeRate(this.data.inputs, disableOutputChecks, txFromBuffer);
462
464
  }
463
465
 
464
- finalizeAllInputs(): this {
465
- checkForInput(this.data.inputs, 0); // making sure we have at least one
466
+ public getFee(disableOutputChecks: boolean = false): number {
467
+ return this.#cache.computeFee(this.data.inputs, disableOutputChecks, txFromBuffer);
468
+ }
469
+
470
+ public finalizeAllInputs(): this {
471
+ checkForInput(this.data.inputs, 0);
466
472
  range(this.data.inputs.length).forEach((idx) => this.finalizeInput(idx));
467
473
  return this;
468
474
  }
469
475
 
470
- finalizeInput(
476
+ public finalizeInput(
471
477
  inputIndex: number,
472
478
  finalScriptsFunc?: FinalScriptsFunc | FinalTaprootScriptsFunc,
473
479
  canRunChecks?: boolean,
@@ -489,7 +495,7 @@ export class Psbt {
489
495
  );
490
496
  }
491
497
 
492
- finalizeTaprootInput(
498
+ public finalizeTaprootInput(
493
499
  inputIndex: number,
494
500
  tapLeafHashToFinalize?: Bytes32,
495
501
  finalScriptsFunc: FinalTaprootScriptsFunc = tapScriptFinalizer,
@@ -505,52 +511,53 @@ export class Psbt {
505
511
  throw new Error(`Cannot finalize input #${inputIndex}. Not Taproot.`);
506
512
  }
507
513
 
508
- getInputType(inputIndex: number): AllScriptType {
514
+ public getInputType(inputIndex: number): AllScriptType {
509
515
  const input = checkForInput(this.data.inputs, inputIndex);
510
- const script = getScriptFromUtxo(inputIndex, input, this.#cache);
516
+ const script = this.#cache.getScriptFromUtxo(inputIndex, input, txFromBuffer);
511
517
  const result = getMeaningfulScript(
512
518
  script,
513
519
  inputIndex,
514
520
  'input',
515
- input.redeemScript || redeemFromFinalScriptSig(input.finalScriptSig),
516
- input.witnessScript || redeemFromFinalWitnessScript(input.finalScriptWitness),
521
+ input.redeemScript || this.#cache.redeemFromFinalScriptSig(input.finalScriptSig),
522
+ input.witnessScript ||
523
+ this.#cache.redeemFromFinalWitnessScript(input.finalScriptWitness),
517
524
  );
518
525
  const type = result.type === 'raw' ? '' : result.type + '-';
519
526
  const mainType = classifyScript(result.meaningfulScript);
520
527
  return (type + mainType) as AllScriptType;
521
528
  }
522
529
 
523
- inputHasPubkey(inputIndex: number, pubkey: PublicKey): boolean {
530
+ public inputHasPubkey(inputIndex: number, pubkey: PublicKey): boolean {
524
531
  const input = checkForInput(this.data.inputs, inputIndex);
525
- return pubkeyInInput(pubkey, input, inputIndex, this.#cache);
532
+ return this.#cache.pubkeyInInput(pubkey, input, inputIndex, txFromBuffer);
526
533
  }
527
534
 
528
- inputHasHDKey(inputIndex: number, root: HDSigner): boolean {
535
+ public inputHasHDKey(inputIndex: number, root: HDSigner): boolean {
529
536
  const input = checkForInput(this.data.inputs, inputIndex);
530
- const derivationIsMine = bip32DerivationIsMine(root);
537
+ const derivationIsMine = this.#lazySigner.bip32DerivationIsMine(root);
531
538
  return !!input.bip32Derivation && input.bip32Derivation.some(derivationIsMine);
532
539
  }
533
540
 
534
- outputHasPubkey(outputIndex: number, pubkey: PublicKey): boolean {
541
+ public outputHasPubkey(outputIndex: number, pubkey: PublicKey): boolean {
535
542
  const output = checkForOutput(this.data.outputs, outputIndex);
536
- return pubkeyInOutput(pubkey, output, outputIndex, this.#cache);
543
+ return this.#cache.pubkeyInOutput(pubkey, output, outputIndex);
537
544
  }
538
545
 
539
- outputHasHDKey(outputIndex: number, root: HDSigner): boolean {
546
+ public outputHasHDKey(outputIndex: number, root: HDSigner): boolean {
540
547
  const output = checkForOutput(this.data.outputs, outputIndex);
541
- const derivationIsMine = bip32DerivationIsMine(root);
548
+ const derivationIsMine = this.#lazySigner.bip32DerivationIsMine(root);
542
549
  return !!output.bip32Derivation && output.bip32Derivation.some(derivationIsMine);
543
550
  }
544
551
 
545
- validateSignaturesOfAllInputs(validator: ValidateSigFunction): boolean {
546
- checkForInput(this.data.inputs, 0); // making sure we have at least one
552
+ public validateSignaturesOfAllInputs(validator: ValidateSigFunction): boolean {
553
+ checkForInput(this.data.inputs, 0);
547
554
  const results = range(this.data.inputs.length).map((idx) =>
548
555
  this.validateSignaturesOfInput(idx, validator),
549
556
  );
550
- return results.reduce((final, res) => res && final, true);
557
+ return results.every((res) => res);
551
558
  }
552
559
 
553
- validateSignaturesOfInput(
560
+ public validateSignaturesOfInput(
554
561
  inputIndex: number,
555
562
  validator: ValidateSigFunction,
556
563
  pubkey?: PublicKey,
@@ -562,7 +569,10 @@ export class Psbt {
562
569
  return this.#validateSignaturesOfInput(inputIndex, validator, pubkey);
563
570
  }
564
571
 
565
- signAllInputsHD(hdKeyPair: HDSigner, sighashTypes: number[] = [Transaction.SIGHASH_ALL]): this {
572
+ public signAllInputsHD(
573
+ hdKeyPair: HDSigner,
574
+ sighashTypes: number[] = [Transaction.SIGHASH_ALL],
575
+ ): this {
566
576
  if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
567
577
  throw new Error('Need HDSigner to sign input');
568
578
  }
@@ -582,39 +592,35 @@ export class Psbt {
582
592
  return this;
583
593
  }
584
594
 
585
- signAllInputsHDAsync(
595
+ public async signAllInputsHDAsync(
586
596
  hdKeyPair: HDSigner | HDSignerAsync,
587
597
  sighashTypes: number[] = [Transaction.SIGHASH_ALL],
588
598
  ): Promise<void> {
589
- return new Promise((resolve, reject) => {
590
- if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
591
- return reject(new Error('Need HDSigner to sign input'));
592
- }
599
+ if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
600
+ throw new Error('Need HDSigner to sign input');
601
+ }
593
602
 
594
- const results: boolean[] = [];
595
- const promises: Array<Promise<void>> = [];
596
- for (const i of range(this.data.inputs.length)) {
597
- promises.push(
598
- this.signInputHDAsync(i, hdKeyPair, sighashTypes).then(
599
- () => {
600
- results.push(true);
601
- },
602
- () => {
603
- results.push(false);
604
- },
605
- ),
606
- );
607
- }
608
- return Promise.all(promises).then(() => {
609
- if (results.every((v) => !v)) {
610
- return reject(new Error('No inputs were signed'));
611
- }
612
- resolve();
613
- });
614
- });
603
+ const results: boolean[] = [];
604
+ const promises: Array<Promise<void>> = [];
605
+ for (const i of range(this.data.inputs.length)) {
606
+ promises.push(
607
+ this.signInputHDAsync(i, hdKeyPair, sighashTypes).then(
608
+ () => {
609
+ results.push(true);
610
+ },
611
+ () => {
612
+ results.push(false);
613
+ },
614
+ ),
615
+ );
616
+ }
617
+ await Promise.all(promises);
618
+ if (results.every((v) => !v)) {
619
+ throw new Error('No inputs were signed');
620
+ }
615
621
  }
616
622
 
617
- signInputHD(
623
+ public signInputHD(
618
624
  inputIndex: number,
619
625
  hdKeyPair: HDSigner,
620
626
  sighashTypes: number[] = [Transaction.SIGHASH_ALL],
@@ -622,41 +628,32 @@ export class Psbt {
622
628
  if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
623
629
  throw new Error('Need HDSigner to sign input');
624
630
  }
625
- const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair);
631
+ const signers = this.#lazySigner.getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair);
626
632
  signers.forEach((signer) => this.signInput(inputIndex, signer, sighashTypes));
627
633
  return this;
628
634
  }
629
635
 
630
- signInputHDAsync(
636
+ public async signInputHDAsync(
631
637
  inputIndex: number,
632
638
  hdKeyPair: HDSigner | HDSignerAsync,
633
639
  sighashTypes: number[] = [Transaction.SIGHASH_ALL],
634
640
  ): Promise<void> {
635
- return new Promise((resolve, reject) => {
636
- if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
637
- return reject(new Error('Need HDSigner to sign input'));
638
- }
639
- const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair);
640
- const promises = signers.map((signer) =>
641
- this.signInputAsync(inputIndex, signer, sighashTypes),
642
- );
643
- return Promise.all(promises)
644
- .then(() => {
645
- resolve();
646
- })
647
- .catch(reject);
648
- });
641
+ if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
642
+ throw new Error('Need HDSigner to sign input');
643
+ }
644
+ const signers = this.#lazySigner.getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair);
645
+ const promises = signers.map((signer) =>
646
+ this.signInputAsync(inputIndex, signer, sighashTypes),
647
+ );
648
+ await Promise.all(promises);
649
649
  }
650
650
 
651
- signAllInputs(
652
- keyPair: Signer | SignerAlternative | BIP32Interface | ECPairInterface,
651
+ public signAllInputs(
652
+ keyPair: Signer | SignerAlternative | BIP32Interface | UniversalSigner,
653
653
  sighashTypes?: number[],
654
654
  ): this {
655
655
  if (!keyPair || !keyPair.publicKey) throw new Error('Need Signer to sign input');
656
656
 
657
- // TODO: Add a pubkey/pubkeyhash cache to each input
658
- // as input information is added, then eventually
659
- // optimize this method.
660
657
  const results: boolean[] = [];
661
658
  for (const i of range(this.data.inputs.length)) {
662
659
  try {
@@ -672,43 +669,35 @@ export class Psbt {
672
669
  return this;
673
670
  }
674
671
 
675
- signAllInputsAsync(
676
- keyPair: Signer | SignerAlternative | SignerAsync | BIP32Interface | ECPairInterface,
672
+ public async signAllInputsAsync(
673
+ keyPair: Signer | SignerAlternative | SignerAsync | BIP32Interface | UniversalSigner,
677
674
  sighashTypes?: number[],
678
675
  ): Promise<void> {
679
- return new Promise((resolve, reject) => {
680
- if (!keyPair || !keyPair.publicKey)
681
- return reject(new Error('Need Signer to sign input'));
682
-
683
- // TODO: Add a pubkey/pubkeyhash cache to each input
684
- // as input information is added, then eventually
685
- // optimize this method.
686
- const results: boolean[] = [];
687
- const promises: Array<Promise<void>> = [];
688
- for (const [i] of this.data.inputs.entries()) {
689
- promises.push(
690
- this.signInputAsync(i, keyPair, sighashTypes).then(
691
- () => {
692
- results.push(true);
693
- },
694
- () => {
695
- results.push(false);
696
- },
697
- ),
698
- );
699
- }
700
- return Promise.all(promises).then(() => {
701
- if (results.every((v) => !v)) {
702
- return reject(new Error('No inputs were signed'));
703
- }
704
- resolve();
705
- });
706
- });
676
+ if (!keyPair || !keyPair.publicKey) throw new Error('Need Signer to sign input');
677
+
678
+ const results: boolean[] = [];
679
+ const promises: Array<Promise<void>> = [];
680
+ for (const [i] of this.data.inputs.entries()) {
681
+ promises.push(
682
+ this.signInputAsync(i, keyPair, sighashTypes).then(
683
+ () => {
684
+ results.push(true);
685
+ },
686
+ () => {
687
+ results.push(false);
688
+ },
689
+ ),
690
+ );
691
+ }
692
+ await Promise.all(promises);
693
+ if (results.every((v) => !v)) {
694
+ throw new Error('No inputs were signed');
695
+ }
707
696
  }
708
697
 
709
- signInput(
698
+ public signInput(
710
699
  inputIndex: number,
711
- keyPair: Signer | SignerAlternative | HDSigner | BIP32Interface | ECPairInterface,
700
+ keyPair: Signer | SignerAlternative | HDSigner | BIP32Interface | UniversalSigner,
712
701
  sighashTypes?: number[],
713
702
  ): this {
714
703
  if (!keyPair || !keyPair.publicKey) {
@@ -723,9 +712,9 @@ export class Psbt {
723
712
  return this.#signInput(inputIndex, keyPair, sighashTypes);
724
713
  }
725
714
 
726
- signTaprootInput(
715
+ public signTaprootInput(
727
716
  inputIndex: number,
728
- keyPair: Signer | SignerAlternative | HDSigner | BIP32Interface | ECPairInterface,
717
+ keyPair: Signer | SignerAlternative | HDSigner | BIP32Interface | UniversalSigner,
729
718
  tapLeafHashToSign?: Uint8Array,
730
719
  sighashTypes?: number[],
731
720
  ): this {
@@ -747,7 +736,7 @@ export class Psbt {
747
736
  throw new Error(`Input #${inputIndex} is not of type Taproot.`);
748
737
  }
749
738
 
750
- signInputAsync(
739
+ public signInputAsync(
751
740
  inputIndex: number,
752
741
  keyPair:
753
742
  | Signer
@@ -756,7 +745,7 @@ export class Psbt {
756
745
  | HDSigner
757
746
  | HDSignerAsync
758
747
  | BIP32Interface
759
- | ECPairInterface,
748
+ | UniversalSigner,
760
749
  sighashTypes?: number[],
761
750
  ): Promise<void> {
762
751
  return Promise.resolve().then(() => {
@@ -776,7 +765,7 @@ export class Psbt {
776
765
  });
777
766
  }
778
767
 
779
- signTaprootInputAsync(
768
+ public signTaprootInputAsync(
780
769
  inputIndex: number,
781
770
  keyPair:
782
771
  | Signer
@@ -785,7 +774,7 @@ export class Psbt {
785
774
  | HDSigner
786
775
  | HDSignerAsync
787
776
  | BIP32Interface
788
- | ECPairInterface,
777
+ | UniversalSigner,
789
778
  tapLeafHash?: Uint8Array,
790
779
  sighashTypes?: number[],
791
780
  ): Promise<void> {
@@ -806,30 +795,29 @@ export class Psbt {
806
795
  });
807
796
  }
808
797
 
809
- toBuffer(): Uint8Array {
798
+ public toBuffer(): Uint8Array {
810
799
  checkCache(this.#cache);
811
800
  return new Uint8Array(this.data.toBuffer());
812
801
  }
813
802
 
814
- toHex(): string {
803
+ public toHex(): string {
815
804
  checkCache(this.#cache);
816
805
  return this.data.toHex();
817
806
  }
818
807
 
819
- toBase64(): string {
808
+ public toBase64(): string {
820
809
  checkCache(this.#cache);
821
810
  return this.data.toBase64();
822
811
  }
823
812
 
824
- updateGlobal(updateData: PsbtGlobalUpdate): this {
813
+ public updateGlobal(updateData: PsbtGlobalUpdate): this {
825
814
  this.data.updateGlobal(updateData);
826
815
  return this;
827
816
  }
828
817
 
829
- updateInput(inputIndex: number, updateData: PsbtInputUpdate): this {
818
+ public updateInput(inputIndex: number, updateData: PsbtInputUpdate): this {
830
819
  if (updateData.witnessScript) checkInvalidP2WSH(updateData.witnessScript);
831
820
  checkTaprootInputFields(this.data.inputs[inputIndex]!, updateData, 'updateInput');
832
- // Convert witnessUtxo for bip174 v3 compatibility (value: bigint, script: Uint8Array)
833
821
  const normalizedUpdate = updateData.witnessUtxo
834
822
  ? {
835
823
  ...updateData,
@@ -844,12 +832,16 @@ export class Psbt {
844
832
  : updateData;
845
833
  this.data.updateInput(inputIndex, normalizedUpdate);
846
834
  if (updateData.nonWitnessUtxo) {
847
- addNonWitnessTxCache(this.#cache, this.data.inputs[inputIndex]!, inputIndex);
835
+ this.#cache.addNonWitnessTxCache(
836
+ this.data.inputs[inputIndex]!,
837
+ inputIndex,
838
+ txFromBuffer,
839
+ );
848
840
  }
849
841
  return this;
850
842
  }
851
843
 
852
- updateOutput(outputIndex: number, updateData: PsbtOutputUpdate): this {
844
+ public updateOutput(outputIndex: number, updateData: PsbtOutputUpdate): this {
853
845
  const outputData = this.data.outputs[outputIndex]!;
854
846
  checkTaprootOutputFields(outputData, updateData, 'updateOutput');
855
847
 
@@ -857,27 +849,27 @@ export class Psbt {
857
849
  return this;
858
850
  }
859
851
 
860
- addUnknownKeyValToGlobal(keyVal: KeyValue): this {
852
+ public addUnknownKeyValToGlobal(keyVal: KeyValue): this {
861
853
  this.data.addUnknownKeyValToGlobal(keyVal);
862
854
  return this;
863
855
  }
864
856
 
865
- addUnknownKeyValToInput(inputIndex: number, keyVal: KeyValue): this {
857
+ public addUnknownKeyValToInput(inputIndex: number, keyVal: KeyValue): this {
866
858
  this.data.addUnknownKeyValToInput(inputIndex, keyVal);
867
859
  return this;
868
860
  }
869
861
 
870
- addUnknownKeyValToOutput(outputIndex: number, keyVal: KeyValue): this {
862
+ public addUnknownKeyValToOutput(outputIndex: number, keyVal: KeyValue): this {
871
863
  this.data.addUnknownKeyValToOutput(outputIndex, keyVal);
872
864
  return this;
873
865
  }
874
866
 
875
- clearFinalizedInput(inputIndex: number): this {
867
+ public clearFinalizedInput(inputIndex: number): this {
876
868
  this.data.clearFinalizedInput(inputIndex);
877
869
  return this;
878
870
  }
879
871
 
880
- checkTaprootHashesForSig(
872
+ public checkTaprootHashesForSig(
881
873
  inputIndex: number,
882
874
  input: PsbtInput,
883
875
  keyPair:
@@ -888,10 +880,10 @@ export class Psbt {
888
880
  | HDSignerAsync
889
881
  | TaprootHashCheckSigner
890
882
  | BIP32Interface
891
- | ECPairInterface,
883
+ | UniversalSigner,
892
884
  tapLeafHashToSign?: Uint8Array,
893
885
  allowedSighashTypes?: number[],
894
- ): { hash: Bytes32; leafHash?: Bytes32 }[] {
886
+ ): { hash: MessageHash; leafHash?: Bytes32 }[] {
895
887
  if (!('signSchnorr' in keyPair) || typeof keyPair.signSchnorr !== 'function')
896
888
  throw new Error(`Need Schnorr Signer to sign taproot input #${inputIndex}.`);
897
889
 
@@ -900,12 +892,11 @@ export class Psbt {
900
892
  ? keyPair.publicKey
901
893
  : new Uint8Array(keyPair.publicKey);
902
894
 
903
- const hashesForSig = getTaprootHashesForSig(
895
+ const hashesForSig = this.#lazySigner.getTaprootHashesForSig(
904
896
  inputIndex,
905
897
  input,
906
898
  this.data.inputs,
907
899
  pubkey,
908
- this.#cache,
909
900
  tapLeafHashToSign,
910
901
  allowedSighashTypes,
911
902
  );
@@ -922,10 +913,9 @@ export class Psbt {
922
913
  finalScriptsFunc: FinalScriptsFunc = getFinalScripts,
923
914
  canRunChecks: boolean = true,
924
915
  ): this {
925
- const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput(
916
+ const { script, isP2SH, isP2WSH, isSegwit } = this.#lazyFinalizer.getScriptFromInput(
926
917
  inputIndex,
927
918
  input,
928
- this.#cache,
929
919
  );
930
920
  if (!script) throw new Error(`No script found for input #${inputIndex}`);
931
921
 
@@ -959,7 +949,6 @@ export class Psbt {
959
949
  if (!input.witnessUtxo)
960
950
  throw new Error(`Cannot finalize input #${inputIndex}. Missing witness utxo.`);
961
951
 
962
- // Check key spend first. Increased privacy and reduced block space.
963
952
  if (input.tapKeySig) {
964
953
  const payment = payments.p2tr({
965
954
  output: input.witnessUtxo.script as Script,
@@ -988,7 +977,7 @@ export class Psbt {
988
977
  pubkey?: PublicKey,
989
978
  ): boolean {
990
979
  const input = this.data.inputs[inputIndex];
991
- const partialSig = (input || {}).partialSig;
980
+ const partialSig = input?.partialSig;
992
981
  if (!input || !partialSig || partialSig.length < 1)
993
982
  throw new Error('No signatures to validate');
994
983
  if (typeof validator !== 'function')
@@ -996,7 +985,7 @@ export class Psbt {
996
985
  const mySigs = pubkey ? partialSig.filter((sig) => equals(sig.pubkey, pubkey)) : partialSig;
997
986
  if (mySigs.length < 1) throw new Error('No signatures for this pubkey');
998
987
  const results: boolean[] = [];
999
- let hashCache: Bytes32 | undefined;
988
+ let hashCache: MessageHash | undefined;
1000
989
  let scriptCache: Script | undefined;
1001
990
  let sighashCache: number | undefined;
1002
991
  for (const pSig of mySigs) {
@@ -1005,12 +994,11 @@ export class Psbt {
1005
994
  const sig = bscript.signature.decode(pSigSignature);
1006
995
  const { hash, script } =
1007
996
  sighashCache !== sig.hashType || !hashCache || !scriptCache
1008
- ? getHashForSig(
997
+ ? this.#lazySigner.getHashForSig(
1009
998
  inputIndex,
1010
999
  Object.assign({}, input, {
1011
1000
  sighashType: sig.hashType,
1012
1001
  }),
1013
- this.#cache,
1014
1002
  true,
1015
1003
  )
1016
1004
  : { hash: hashCache, script: scriptCache };
@@ -1029,8 +1017,8 @@ export class Psbt {
1029
1017
  pubkey?: PublicKey,
1030
1018
  ): boolean {
1031
1019
  const input = this.data.inputs[inputIndex]!;
1032
- const tapKeySig = (input || {}).tapKeySig;
1033
- const tapScriptSig = (input || {}).tapScriptSig;
1020
+ const tapKeySig = input?.tapKeySig;
1021
+ const tapScriptSig = input?.tapScriptSig;
1034
1022
  if (!input && !tapKeySig && !(tapScriptSig && !tapScriptSig.length))
1035
1023
  throw new Error('No signatures to validate');
1036
1024
  if (typeof validator !== 'function')
@@ -1038,8 +1026,8 @@ export class Psbt {
1038
1026
 
1039
1027
  const xPubkey = pubkey ? toXOnly(pubkey) : undefined;
1040
1028
  const allHashses = xPubkey
1041
- ? getTaprootHashesForSig(inputIndex, input, this.data.inputs, xPubkey, this.#cache)
1042
- : getAllTaprootHashesForSig(inputIndex, input, this.data.inputs, this.#cache);
1029
+ ? this.#lazySigner.getTaprootHashesForSig(inputIndex, input, this.data.inputs, xPubkey)
1030
+ : this.#lazySigner.getAllTaprootHashesForSig(inputIndex, input, this.data.inputs);
1043
1031
 
1044
1032
  if (!allHashses.length) throw new Error('No signatures for this pubkey');
1045
1033
 
@@ -1049,7 +1037,7 @@ export class Psbt {
1049
1037
  const isValidTapkeySig = validator(
1050
1038
  tapKeyHash.pubkey,
1051
1039
  tapKeyHash.hash,
1052
- trimTaprootSig(tapKeySig),
1040
+ this.#lazySigner.trimTaprootSig(tapKeySig),
1053
1041
  );
1054
1042
  if (!isValidTapkeySig) return false;
1055
1043
  validationResultCount++;
@@ -1063,7 +1051,7 @@ export class Psbt {
1063
1051
  const isValidTapScriptSig = validator(
1064
1052
  tapSigPubkey,
1065
1053
  tapSigHash.hash,
1066
- trimTaprootSig(tapSig.signature),
1054
+ this.#lazySigner.trimTaprootSig(tapSig.signature),
1067
1055
  );
1068
1056
  if (!isValidTapScriptSig) return false;
1069
1057
  validationResultCount++;
@@ -1076,7 +1064,7 @@ export class Psbt {
1076
1064
 
1077
1065
  #signInput(
1078
1066
  inputIndex: number,
1079
- keyPair: Signer | SignerAlternative | HDSigner | BIP32Interface | ECPairInterface,
1067
+ keyPair: Signer | SignerAlternative | HDSigner | BIP32Interface | UniversalSigner,
1080
1068
  sighashTypes: number[] = [Transaction.SIGHASH_ALL],
1081
1069
  ): this {
1082
1070
  const pubkey =
@@ -1084,11 +1072,10 @@ export class Psbt {
1084
1072
  ? keyPair.publicKey
1085
1073
  : new Uint8Array(keyPair.publicKey);
1086
1074
 
1087
- const { hash, sighashType } = getHashAndSighashType(
1075
+ const { hash, sighashType } = this.#lazySigner.getHashAndSighashType(
1088
1076
  this.data.inputs,
1089
1077
  inputIndex,
1090
1078
  pubkey,
1091
- this.#cache,
1092
1079
  sighashTypes,
1093
1080
  );
1094
1081
 
@@ -1111,7 +1098,7 @@ export class Psbt {
1111
1098
  #signTaprootInput(
1112
1099
  inputIndex: number,
1113
1100
  input: PsbtInput,
1114
- keyPair: Signer | SignerAlternative | HDSigner | BIP32Interface | ECPairInterface,
1101
+ keyPair: Signer | SignerAlternative | HDSigner | BIP32Interface | UniversalSigner,
1115
1102
  tapLeafHashToSign?: Uint8Array,
1116
1103
  allowedSighashTypes: number[] = [Transaction.SIGHASH_DEFAULT],
1117
1104
  ): this {
@@ -1124,7 +1111,6 @@ export class Psbt {
1124
1111
  if (!('signSchnorr' in keyPair) || typeof keyPair.signSchnorr !== 'function')
1125
1112
  throw new Error(`Need Schnorr Signer to sign taproot input #${inputIndex}.`);
1126
1113
 
1127
- // checkTaprootHashesForSig validates signSchnorr exists
1128
1114
  const hashesForSig = this.checkTaprootHashesForSig(
1129
1115
  inputIndex,
1130
1116
  input,
@@ -1132,7 +1118,7 @@ export class Psbt {
1132
1118
  tapLeafHashToSign,
1133
1119
  allowedSighashTypes,
1134
1120
  );
1135
- const signSchnorr = (keyPair.signSchnorr as (h: Uint8Array) => Uint8Array).bind(keyPair);
1121
+ const signSchnorr = (keyPair.signSchnorr as (h: MessageHash) => SchnorrSignature).bind(keyPair);
1136
1122
 
1137
1123
  const tapKeySig = hashesForSig
1138
1124
  .filter((h) => !h.leafHash)
@@ -1176,7 +1162,7 @@ export class Psbt {
1176
1162
  | HDSigner
1177
1163
  | HDSignerAsync
1178
1164
  | BIP32Interface
1179
- | ECPairInterface,
1165
+ | UniversalSigner,
1180
1166
  sighashTypes: number[] = [Transaction.SIGHASH_ALL],
1181
1167
  ): Promise<void> {
1182
1168
  const pubkey =
@@ -1184,11 +1170,10 @@ export class Psbt {
1184
1170
  ? keyPair.publicKey
1185
1171
  : new Uint8Array(keyPair.publicKey);
1186
1172
 
1187
- const { hash, sighashType } = getHashAndSighashType(
1173
+ const { hash, sighashType } = this.#lazySigner.getHashAndSighashType(
1188
1174
  this.data.inputs,
1189
1175
  inputIndex,
1190
1176
  pubkey,
1191
- this.#cache,
1192
1177
  sighashTypes,
1193
1178
  );
1194
1179
 
@@ -1216,7 +1201,7 @@ export class Psbt {
1216
1201
  | HDSigner
1217
1202
  | HDSignerAsync
1218
1203
  | BIP32Interface
1219
- | ECPairInterface,
1204
+ | UniversalSigner,
1220
1205
  tapLeafHash?: Uint8Array,
1221
1206
  sighashTypes: number[] = [Transaction.SIGHASH_DEFAULT],
1222
1207
  ): Promise<void> {
@@ -1229,7 +1214,6 @@ export class Psbt {
1229
1214
  if (!('signSchnorr' in keyPair) || typeof keyPair.signSchnorr !== 'function')
1230
1215
  throw new Error(`Need Schnorr Signer to sign taproot input #${inputIndex}.`);
1231
1216
 
1232
- // checkTaprootHashesForSig validates signSchnorr exists
1233
1217
  const hashesForSig = this.checkTaprootHashesForSig(
1234
1218
  inputIndex,
1235
1219
  input,
@@ -1238,7 +1222,7 @@ export class Psbt {
1238
1222
  sighashTypes,
1239
1223
  );
1240
1224
  const signSchnorr = (
1241
- keyPair.signSchnorr as (hash: Uint8Array) => Uint8Array | Promise<Uint8Array>
1225
+ keyPair.signSchnorr as (hash: MessageHash) => SchnorrSignature | Promise<SchnorrSignature>
1242
1226
  ).bind(keyPair);
1243
1227
 
1244
1228
  type TapSignatureResult = { tapKeySig: Uint8Array } | { tapScriptSig: TapScriptSig[] };
@@ -1281,778 +1265,3 @@ export class Psbt {
1281
1265
  }
1282
1266
  }
1283
1267
  }
1284
-
1285
- /**
1286
- * This function is needed to pass to the bip174 base class's fromBuffer.
1287
- * It takes the "transaction buffer" portion of the psbt buffer and returns a
1288
- * Transaction (From the bip174 library) interface.
1289
- */
1290
- const transactionFromBuffer: TransactionFromBuffer = (buffer: Uint8Array): ITransaction =>
1291
- new PsbtTransaction(buffer);
1292
-
1293
- /**
1294
- * This class implements the Transaction interface from bip174 library.
1295
- * It contains a bitcoinjs-lib Transaction object.
1296
- */
1297
- class PsbtTransaction implements ITransaction {
1298
- tx: Transaction;
1299
-
1300
- constructor(buffer: Uint8Array = new Uint8Array([2, 0, 0, 0, 0, 0, 0, 0, 0, 0])) {
1301
- this.tx = Transaction.fromBuffer(buffer);
1302
- checkTxEmpty(this.tx);
1303
- Object.defineProperty(this, 'tx', {
1304
- enumerable: false,
1305
- writable: true,
1306
- });
1307
- }
1308
-
1309
- getInputOutputCounts(): {
1310
- inputCount: number;
1311
- outputCount: number;
1312
- } {
1313
- return {
1314
- inputCount: this.tx.ins.length,
1315
- outputCount: this.tx.outs.length,
1316
- };
1317
- }
1318
-
1319
- addInput(input: TransactionInput): void {
1320
- if (
1321
- input.hash === undefined ||
1322
- input.index === undefined ||
1323
- (!(input.hash instanceof Uint8Array) && typeof input.hash !== 'string') ||
1324
- typeof input.index !== 'number'
1325
- ) {
1326
- throw new Error('Error adding input.');
1327
- }
1328
- const hash = (
1329
- typeof input.hash === 'string' ? reverse(fromHex(input.hash)) : input.hash
1330
- ) as Bytes32;
1331
-
1332
- this.tx.addInput(hash, input.index, input.sequence);
1333
- }
1334
-
1335
- addOutput(output: TransactionOutput): void {
1336
- if (
1337
- output.script === undefined ||
1338
- output.value === undefined ||
1339
- !(output.script instanceof Uint8Array) ||
1340
- typeof output.value !== 'bigint'
1341
- ) {
1342
- throw new Error('Error adding output.');
1343
- }
1344
- this.tx.addOutput(output.script, output.value);
1345
- }
1346
-
1347
- toBuffer(): Uint8Array {
1348
- return this.tx.toBuffer();
1349
- }
1350
- }
1351
-
1352
- function canFinalize(input: PsbtInput, script: Uint8Array, scriptType: string): boolean {
1353
- switch (scriptType) {
1354
- case 'pubkey':
1355
- case 'pubkeyhash':
1356
- case 'witnesspubkeyhash':
1357
- return hasSigs(1, input.partialSig);
1358
- case 'multisig': {
1359
- const p2ms = payments.p2ms({
1360
- output: script as Script,
1361
- });
1362
- if (p2ms.m === undefined) throw new Error('Cannot determine m for multisig');
1363
- return hasSigs(p2ms.m, input.partialSig, p2ms.pubkeys);
1364
- }
1365
- case 'nonstandard':
1366
- return true;
1367
- default:
1368
- return false;
1369
- }
1370
- }
1371
-
1372
- function hasSigs(neededSigs: number, partialSig?: PartialSig[], pubkeys?: Uint8Array[]): boolean {
1373
- if (!partialSig) return false;
1374
- let sigs: PartialSig[];
1375
- if (pubkeys) {
1376
- sigs = pubkeys
1377
- .map((pkey) => {
1378
- const pubkey = compressPubkey(pkey);
1379
- return partialSig.find((pSig) => equals(pSig.pubkey, pubkey));
1380
- })
1381
- .filter((v): v is PartialSig => !!v);
1382
- } else {
1383
- sigs = partialSig;
1384
- }
1385
- if (sigs.length > neededSigs) throw new Error('Too many signatures');
1386
- return sigs.length === neededSigs;
1387
- }
1388
-
1389
- function bip32DerivationIsMine(root: HDSigner): (d: Bip32Derivation) => boolean {
1390
- return (d: Bip32Derivation): boolean => {
1391
- const fingerprint =
1392
- root.fingerprint instanceof Uint8Array
1393
- ? root.fingerprint
1394
- : new Uint8Array(root.fingerprint);
1395
- if (!equals(d.masterFingerprint, fingerprint)) return false;
1396
- const derivedPubkey = root.derivePath(d.path).publicKey;
1397
- const pubkey =
1398
- derivedPubkey instanceof Uint8Array ? derivedPubkey : new Uint8Array(derivedPubkey);
1399
- if (!equals(pubkey, d.pubkey)) return false;
1400
- return true;
1401
- };
1402
- }
1403
-
1404
- function checkFees(psbt: Psbt, cache: PsbtCache, opts: PsbtOpts): void {
1405
- const feeRate = cache.feeRate || psbt.getFeeRate();
1406
- if (!cache.extractedTx) throw new Error('Transaction not extracted');
1407
- const vsize = cache.extractedTx.virtualSize();
1408
- const satoshis = feeRate * vsize;
1409
- if (feeRate >= opts.maximumFeeRate) {
1410
- throw new Error(
1411
- `Warning: You are paying around ${(satoshis / 1e8).toFixed(8)} in ` +
1412
- `fees, which is ${feeRate} satoshi per byte for a transaction ` +
1413
- `with a VSize of ${vsize} bytes (segwit counted as 0.25 byte per ` +
1414
- `byte). Use setMaximumFeeRate method to raise your threshold, or ` +
1415
- `pass true to the first arg of extractTransaction.`,
1416
- );
1417
- }
1418
- }
1419
-
1420
- function getTxCacheValue(
1421
- key: TxCacheNumberKey,
1422
- name: string,
1423
- inputs: PsbtInput[],
1424
- c: PsbtCache,
1425
- disableOutputChecks: boolean = false,
1426
- ): number {
1427
- if (!inputs.every(isFinalized)) throw new Error(`PSBT must be finalized to calculate ${name}`);
1428
- if (key === 'feeRate' && c.feeRate) return c.feeRate;
1429
- if (key === 'fee' && c.fee) return c.fee;
1430
- let tx: Transaction;
1431
- let mustFinalize = true;
1432
- if (c.extractedTx) {
1433
- tx = c.extractedTx;
1434
- mustFinalize = false;
1435
- } else {
1436
- tx = c.tx.clone();
1437
- }
1438
- inputFinalizeGetAmts(inputs, tx, c, mustFinalize, disableOutputChecks);
1439
- const value = key === 'feeRate' ? c.feeRate : c.fee;
1440
- if (value === undefined) throw new Error(`Failed to calculate ${name}`);
1441
- return value;
1442
- }
1443
-
1444
- export function getFinalScripts(
1445
- inputIndex: number,
1446
- input: PsbtInput,
1447
- script: Script,
1448
- isSegwit: boolean,
1449
- isP2SH: boolean,
1450
- isP2WSH: boolean,
1451
- canRunChecks: boolean = true,
1452
- solution?: Uint8Array[],
1453
- ): {
1454
- finalScriptSig: Script | undefined;
1455
- finalScriptWitness: Uint8Array | undefined;
1456
- } {
1457
- const scriptType = classifyScript(script);
1458
- if (!canFinalize(input, script, scriptType) && canRunChecks) {
1459
- throw new Error(`Can not finalize input #${inputIndex}`);
1460
- }
1461
-
1462
- if (!input.partialSig) throw new Error('Input missing partial signatures');
1463
- return prepareFinalScripts(
1464
- script,
1465
- scriptType,
1466
- input.partialSig,
1467
- isSegwit,
1468
- isP2SH,
1469
- isP2WSH,
1470
- solution,
1471
- );
1472
- }
1473
-
1474
- export function prepareFinalScripts(
1475
- script: Uint8Array,
1476
- scriptType: string,
1477
- partialSig: PartialSig[],
1478
- isSegwit: boolean,
1479
- isP2SH: boolean,
1480
- isP2WSH: boolean,
1481
- solution?: Uint8Array[],
1482
- ): {
1483
- finalScriptSig: Script | undefined;
1484
- finalScriptWitness: Uint8Array | undefined;
1485
- } {
1486
- let finalScriptSig: Script | undefined;
1487
- let finalScriptWitness: Uint8Array | undefined;
1488
-
1489
- // Wow, the payments API is very handy
1490
- const payment: payments.Payment = getPayment(script, scriptType, partialSig);
1491
- const p2wsh = !isP2WSH ? null : payments.p2wsh({ redeem: payment } as P2WSHPayment);
1492
- const p2sh = !isP2SH ? null : payments.p2sh({ redeem: p2wsh || payment } as P2SHPayment);
1493
-
1494
- if (isSegwit) {
1495
- if (p2wsh && p2wsh.witness) {
1496
- finalScriptWitness = witnessStackToScriptWitness(p2wsh.witness);
1497
- } else if (payment && payment.witness) {
1498
- finalScriptWitness = witnessStackToScriptWitness(payment.witness);
1499
- } else {
1500
- // nonstandard segwit script
1501
- finalScriptWitness = witnessStackToScriptWitness(solution ?? [new Uint8Array([0x00])]);
1502
- }
1503
- if (p2sh) {
1504
- finalScriptSig = p2sh?.input as Script | undefined;
1505
- }
1506
- } else {
1507
- if (p2sh) {
1508
- finalScriptSig = p2sh?.input as Script | undefined;
1509
- } else {
1510
- if (!payment) {
1511
- finalScriptSig = (
1512
- Array.isArray(solution) && solution[0] ? solution[0] : new Uint8Array([0x01])
1513
- ) as Script;
1514
- } else {
1515
- finalScriptSig = payment.input as Script | undefined;
1516
- }
1517
- }
1518
- }
1519
- return {
1520
- finalScriptSig,
1521
- finalScriptWitness,
1522
- };
1523
- }
1524
-
1525
- function getHashAndSighashType(
1526
- inputs: PsbtInput[],
1527
- inputIndex: number,
1528
- pubkey: Uint8Array,
1529
- cache: PsbtCache,
1530
- sighashTypes: number[],
1531
- ): {
1532
- hash: Bytes32;
1533
- sighashType: number;
1534
- } {
1535
- const input = checkForInput(inputs, inputIndex);
1536
- const { hash, sighashType, script } = getHashForSig(
1537
- inputIndex,
1538
- input,
1539
- cache,
1540
- false,
1541
- sighashTypes,
1542
- );
1543
-
1544
- checkScriptForPubkey(pubkey as PublicKey, script, 'sign');
1545
- return {
1546
- hash,
1547
- sighashType,
1548
- };
1549
- }
1550
-
1551
- function getHashForSig(
1552
- inputIndex: number,
1553
- input: PsbtInput,
1554
- cache: PsbtCache,
1555
- forValidate: boolean,
1556
- sighashTypes?: number[],
1557
- ): {
1558
- script: Script;
1559
- hash: Bytes32;
1560
- sighashType: number;
1561
- } {
1562
- const unsignedTx = cache.tx;
1563
- const sighashType = input.sighashType || Transaction.SIGHASH_ALL;
1564
- checkSighashTypeAllowed(sighashType, sighashTypes);
1565
-
1566
- let hash: Bytes32;
1567
- let prevout: Output;
1568
-
1569
- if (input.nonWitnessUtxo) {
1570
- const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(cache, input, inputIndex);
1571
-
1572
- const prevoutHash = unsignedTx.ins[inputIndex]!.hash;
1573
- const utxoHash = nonWitnessUtxoTx.getHash();
1574
-
1575
- // If a non-witness UTXO is provided, its hash must match the hash specified in the prevout
1576
- if (!equals(prevoutHash, utxoHash)) {
1577
- throw new Error(
1578
- `Non-witness UTXO hash for input #${inputIndex} doesn't match the hash specified in the prevout`,
1579
- );
1580
- }
1581
-
1582
- const prevoutIndex = unsignedTx.ins[inputIndex]!.index;
1583
- prevout = nonWitnessUtxoTx.outs[prevoutIndex]!;
1584
- } else if (input.witnessUtxo) {
1585
- prevout = {
1586
- script: input.witnessUtxo.script as Script,
1587
- value: input.witnessUtxo.value as Satoshi,
1588
- };
1589
- } else {
1590
- throw new Error('Need a Utxo input item for signing');
1591
- }
1592
-
1593
- const { meaningfulScript, type } = getMeaningfulScript(
1594
- prevout.script,
1595
- inputIndex,
1596
- 'input',
1597
- input.redeemScript,
1598
- input.witnessScript,
1599
- );
1600
-
1601
- const script = meaningfulScript as Script;
1602
-
1603
- if (['p2sh-p2wsh', 'p2wsh'].indexOf(type) >= 0) {
1604
- hash = unsignedTx.hashForWitnessV0(inputIndex, script, prevout.value, sighashType);
1605
- } else if (isP2WPKH(meaningfulScript)) {
1606
- // P2WPKH uses the P2PKH template for prevoutScript when signing
1607
- const p2pkhPayment = payments.p2pkh({
1608
- hash: meaningfulScript.subarray(2) as Bytes20,
1609
- });
1610
- if (!p2pkhPayment.output) throw new Error('Unable to create signing script');
1611
- hash = unsignedTx.hashForWitnessV0(
1612
- inputIndex,
1613
- p2pkhPayment.output as Script,
1614
- prevout.value,
1615
- sighashType,
1616
- );
1617
- } else {
1618
- // non-segwit
1619
- if (input.nonWitnessUtxo === undefined && !cache.unsafeSignNonSegwit)
1620
- throw new Error(
1621
- `Input #${inputIndex} has witnessUtxo but non-segwit script: ` +
1622
- toHex(meaningfulScript),
1623
- );
1624
- if (!forValidate && cache.unsafeSignNonSegwit)
1625
- console.warn(
1626
- 'Warning: Signing non-segwit inputs without the full parent transaction ' +
1627
- 'means there is a chance that a miner could feed you incorrect information ' +
1628
- "to trick you into paying large fees. This behavior is the same as Psbt's predecessor " +
1629
- '(TransactionBuilder - now removed) when signing non-segwit scripts. You are not ' +
1630
- 'able to export this Psbt with toBuffer|toBase64|toHex since it is not ' +
1631
- 'BIP174 compliant.\n*********************\nPROCEED WITH CAUTION!\n' +
1632
- '*********************',
1633
- );
1634
- hash = unsignedTx.hashForSignature(inputIndex, script, sighashType);
1635
- }
1636
-
1637
- return {
1638
- script,
1639
- sighashType,
1640
- hash,
1641
- };
1642
- }
1643
-
1644
- function getAllTaprootHashesForSig(
1645
- inputIndex: number,
1646
- input: PsbtInput,
1647
- inputs: PsbtInput[],
1648
- cache: PsbtCache,
1649
- ): { pubkey: PublicKey; hash: Bytes32; leafHash?: Bytes32 }[] {
1650
- const allPublicKeys: Uint8Array[] = [];
1651
- if (input.tapInternalKey) {
1652
- const key = getPrevoutTaprootKey(inputIndex, input, cache);
1653
- if (key) {
1654
- allPublicKeys.push(key);
1655
- }
1656
- }
1657
-
1658
- if (input.tapScriptSig) {
1659
- const tapScriptPubkeys = input.tapScriptSig.map((tss) => tss.pubkey);
1660
- allPublicKeys.push(...tapScriptPubkeys);
1661
- }
1662
-
1663
- const allHashes = allPublicKeys.map((pubicKey) =>
1664
- getTaprootHashesForSig(inputIndex, input, inputs, pubicKey, cache),
1665
- );
1666
-
1667
- return allHashes.flat();
1668
- }
1669
-
1670
- function getPrevoutTaprootKey(
1671
- inputIndex: number,
1672
- input: PsbtInput,
1673
- cache: PsbtCache,
1674
- ): XOnlyPublicKey | null {
1675
- const { script } = getScriptAndAmountFromUtxo(inputIndex, input, cache);
1676
- return isP2TR(script) ? (script.subarray(2, 34) as XOnlyPublicKey) : null;
1677
- }
1678
-
1679
- function trimTaprootSig(signature: Uint8Array): Uint8Array {
1680
- return signature.length === 64 ? signature : signature.subarray(0, 64);
1681
- }
1682
-
1683
- function getTaprootHashesForSig(
1684
- inputIndex: number,
1685
- input: PsbtInput,
1686
- inputs: PsbtInput[],
1687
- pubkey: Uint8Array,
1688
- cache: PsbtCache,
1689
- tapLeafHashToSign?: Uint8Array,
1690
- allowedSighashTypes?: number[],
1691
- ): { pubkey: PublicKey; hash: Bytes32; leafHash?: Bytes32 }[] {
1692
- const unsignedTx = cache.tx;
1693
-
1694
- const sighashType = input.sighashType || Transaction.SIGHASH_DEFAULT;
1695
- checkSighashTypeAllowed(sighashType, allowedSighashTypes);
1696
-
1697
- if (!cache.prevOuts) {
1698
- const prevOuts = inputs.map((i, index) => getScriptAndAmountFromUtxo(index, i, cache));
1699
- cache.prevOuts = prevOuts;
1700
- cache.signingScripts = prevOuts.map((o) => o.script);
1701
- cache.values = prevOuts.map((o) => o.value);
1702
- }
1703
- const signingScripts = cache.signingScripts as readonly Script[];
1704
- const values = cache.values as readonly Satoshi[];
1705
-
1706
- // Compute taproot hash cache once for all inputs (O(n) -> O(1) per input)
1707
- if (!cache.taprootHashCache) {
1708
- cache.taprootHashCache = unsignedTx.getTaprootHashCache(signingScripts, values);
1709
- }
1710
- const taprootCache = cache.taprootHashCache;
1711
-
1712
- const hashes: { pubkey: PublicKey; hash: Bytes32; leafHash?: Bytes32 }[] = [];
1713
- if (input.tapInternalKey && !tapLeafHashToSign) {
1714
- const outputKey = getPrevoutTaprootKey(inputIndex, input, cache) || new Uint8Array(0);
1715
- if (equals(toXOnly(pubkey as PublicKey), outputKey)) {
1716
- const tapKeyHash = unsignedTx.hashForWitnessV1(
1717
- inputIndex,
1718
- signingScripts,
1719
- values,
1720
- sighashType,
1721
- undefined,
1722
- undefined,
1723
- taprootCache,
1724
- );
1725
- hashes.push({ pubkey: pubkey as PublicKey, hash: tapKeyHash });
1726
- }
1727
- }
1728
-
1729
- const tapLeafHashes = (input.tapLeafScript || [])
1730
- .filter((tapLeaf) => pubkeyInScript(pubkey, tapLeaf.script))
1731
- .map((tapLeaf) => {
1732
- const hash = tapleafHash({
1733
- output: tapLeaf.script,
1734
- version: tapLeaf.leafVersion,
1735
- });
1736
- return Object.assign({ hash }, tapLeaf);
1737
- })
1738
- .filter((tapLeaf) => !tapLeafHashToSign || equals(tapLeafHashToSign, tapLeaf.hash))
1739
- .map((tapLeaf) => {
1740
- const tapScriptHash = unsignedTx.hashForWitnessV1(
1741
- inputIndex,
1742
- signingScripts,
1743
- values,
1744
- sighashType,
1745
- tapLeaf.hash as Bytes32,
1746
- undefined,
1747
- taprootCache,
1748
- );
1749
-
1750
- return {
1751
- pubkey: pubkey as PublicKey,
1752
- hash: tapScriptHash,
1753
- leafHash: tapLeaf.hash as Bytes32,
1754
- };
1755
- });
1756
-
1757
- return hashes.concat(tapLeafHashes);
1758
- }
1759
-
1760
- function checkSighashTypeAllowed(sighashType: number, sighashTypes?: number[]): void {
1761
- if (sighashTypes && sighashTypes.indexOf(sighashType) < 0) {
1762
- const str = sighashTypeToString(sighashType);
1763
- throw new Error(
1764
- `Sighash type is not allowed. Retry the sign method passing the ` +
1765
- `sighashTypes array of whitelisted types. Sighash type: ${str}`,
1766
- );
1767
- }
1768
- }
1769
-
1770
- function getPayment(
1771
- script: Uint8Array,
1772
- scriptType: string,
1773
- partialSig: PartialSig[],
1774
- ): payments.Payment {
1775
- const scriptBranded = script as Script;
1776
- switch (scriptType) {
1777
- case 'multisig': {
1778
- const sigs = getSortedSigs(script, partialSig);
1779
- return payments.p2ms({
1780
- output: scriptBranded,
1781
- signatures: sigs as Signature[],
1782
- });
1783
- }
1784
- case 'pubkey':
1785
- return payments.p2pk({
1786
- output: scriptBranded,
1787
- signature: partialSig[0]!.signature as Signature,
1788
- });
1789
- case 'pubkeyhash':
1790
- return payments.p2pkh({
1791
- output: scriptBranded,
1792
- pubkey: partialSig[0]!.pubkey as PublicKey,
1793
- signature: partialSig[0]!.signature as Signature,
1794
- });
1795
- case 'witnesspubkeyhash':
1796
- return payments.p2wpkh({
1797
- output: scriptBranded,
1798
- pubkey: partialSig[0]!.pubkey as PublicKey,
1799
- signature: partialSig[0]!.signature as Signature,
1800
- });
1801
- default:
1802
- throw new Error(`Unknown script type: ${scriptType}`);
1803
- }
1804
- }
1805
-
1806
- function getScriptFromInput(
1807
- inputIndex: number,
1808
- input: PsbtInput,
1809
- cache: PsbtCache,
1810
- ): GetScriptReturn {
1811
- const unsignedTx = cache.tx;
1812
- const res: GetScriptReturn = {
1813
- script: null,
1814
- isSegwit: false,
1815
- isP2SH: false,
1816
- isP2WSH: false,
1817
- };
1818
- res.isP2SH = !!input.redeemScript;
1819
- res.isP2WSH = !!input.witnessScript;
1820
- if (input.witnessScript) {
1821
- res.script = input.witnessScript as Script;
1822
- } else if (input.redeemScript) {
1823
- res.script = input.redeemScript as Script;
1824
- } else {
1825
- if (input.nonWitnessUtxo) {
1826
- const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(cache, input, inputIndex);
1827
- const prevoutIndex = unsignedTx.ins[inputIndex]!.index;
1828
- res.script = nonWitnessUtxoTx.outs[prevoutIndex]!.script;
1829
- } else if (input.witnessUtxo) {
1830
- res.script = input.witnessUtxo.script as Script;
1831
- }
1832
- }
1833
-
1834
- if (input.witnessScript || (res.script && isP2WPKH(res.script))) {
1835
- res.isSegwit = true;
1836
- } else {
1837
- try {
1838
- const output = res.script;
1839
- if (!output) throw new TypeError('Invalid script for segwit address');
1840
-
1841
- res.isSegwit = isUnknownSegwitVersion(output);
1842
- } catch (e) {}
1843
- }
1844
-
1845
- return res;
1846
- }
1847
-
1848
- function getSignersFromHD<T extends HDSigner | HDSignerAsync>(
1849
- inputIndex: number,
1850
- inputs: PsbtInput[],
1851
- hdKeyPair: T,
1852
- ): T[] {
1853
- const input = checkForInput(inputs, inputIndex);
1854
- if (!input.bip32Derivation || input.bip32Derivation.length === 0) {
1855
- throw new Error('Need bip32Derivation to sign with HD');
1856
- }
1857
- const myDerivations = input.bip32Derivation
1858
- .map((bipDv) => {
1859
- if (equals(bipDv.masterFingerprint, hdKeyPair.fingerprint)) {
1860
- return bipDv;
1861
- } else {
1862
- return;
1863
- }
1864
- })
1865
- .filter((v) => !!v);
1866
- if (myDerivations.length === 0) {
1867
- throw new Error(
1868
- 'Need one bip32Derivation masterFingerprint to match the HDSigner fingerprint',
1869
- );
1870
- }
1871
-
1872
- return myDerivations.map((bipDv) => {
1873
- const node = hdKeyPair.derivePath(bipDv.path) as T;
1874
- if (!equals(bipDv.pubkey, node.publicKey)) {
1875
- throw new Error('pubkey did not match bip32Derivation');
1876
- }
1877
- return node;
1878
- });
1879
- }
1880
-
1881
- function getSortedSigs(script: Uint8Array, partialSig: PartialSig[]): Uint8Array[] {
1882
- const p2ms = payments.p2ms({ output: script as Script });
1883
- if (!p2ms.pubkeys) throw new Error('Cannot extract pubkeys from multisig script');
1884
- // for each pubkey in order of p2ms script
1885
- const result: Uint8Array[] = [];
1886
- for (const pk of p2ms.pubkeys) {
1887
- // filter partialSig array by pubkey being equal
1888
- const matched = partialSig.filter((ps) => {
1889
- return equals(ps.pubkey, pk);
1890
- })[0];
1891
- if (matched) {
1892
- result.push(new Uint8Array(matched.signature));
1893
- }
1894
- }
1895
- return result;
1896
- }
1897
-
1898
- function addNonWitnessTxCache(cache: PsbtCache, input: PsbtInput, inputIndex: number): void {
1899
- if (!input.nonWitnessUtxo) throw new Error('nonWitnessUtxo is required');
1900
- // Prevent prototype pollution - ensure input is a valid object
1901
- if (input === null || input === Object.prototype) {
1902
- throw new Error('Invalid input object');
1903
- }
1904
- const nonWitnessUtxoBuf = input.nonWitnessUtxo;
1905
- cache.nonWitnessUtxoBufCache[inputIndex] = nonWitnessUtxoBuf;
1906
- cache.nonWitnessUtxoTxCache[inputIndex] = Transaction.fromBuffer(nonWitnessUtxoBuf);
1907
-
1908
- const self = cache;
1909
- const selfIndex = inputIndex;
1910
- delete input.nonWitnessUtxo;
1911
- // Using Reflect.defineProperty to avoid prototype pollution concerns
1912
- Reflect.defineProperty(input, 'nonWitnessUtxo', {
1913
- enumerable: true,
1914
- get(): Uint8Array {
1915
- const buf = self.nonWitnessUtxoBufCache[selfIndex];
1916
- const txCache = self.nonWitnessUtxoTxCache[selfIndex];
1917
- if (buf !== undefined) {
1918
- return buf;
1919
- } else {
1920
- const newBuf = txCache!.toBuffer();
1921
- self.nonWitnessUtxoBufCache[selfIndex] = newBuf;
1922
- return newBuf;
1923
- }
1924
- },
1925
- set(data: Uint8Array): void {
1926
- self.nonWitnessUtxoBufCache[selfIndex] = data;
1927
- },
1928
- });
1929
- }
1930
-
1931
- function inputFinalizeGetAmts(
1932
- inputs: PsbtInput[],
1933
- tx: Transaction,
1934
- cache: PsbtCache,
1935
- mustFinalize: boolean,
1936
- disableOutputChecks?: boolean,
1937
- ): void {
1938
- let inputAmount = 0n;
1939
- inputs.forEach((input, idx) => {
1940
- if (mustFinalize && input.finalScriptSig)
1941
- tx.ins[idx]!.script = input.finalScriptSig as Script;
1942
- if (mustFinalize && input.finalScriptWitness) {
1943
- tx.ins[idx]!.witness = scriptWitnessToWitnessStack(input.finalScriptWitness);
1944
- }
1945
- if (input.witnessUtxo) {
1946
- inputAmount += input.witnessUtxo.value;
1947
- } else if (input.nonWitnessUtxo) {
1948
- const nwTx = nonWitnessUtxoTxFromCache(cache, input, idx);
1949
- const vout = tx.ins[idx]!.index;
1950
- const out = nwTx.outs[vout]!;
1951
- inputAmount += out.value;
1952
- }
1953
- });
1954
- const outputAmount = tx.outs.reduce((total, o) => total + o.value, 0n);
1955
- const fee = inputAmount - outputAmount;
1956
- if (!disableOutputChecks) {
1957
- if (fee < 0n) {
1958
- throw new Error(
1959
- `Outputs are spending more than Inputs ${inputAmount} < ${outputAmount}`,
1960
- );
1961
- }
1962
- }
1963
- const bytes = tx.virtualSize();
1964
- cache.fee = Number(fee);
1965
- cache.extractedTx = tx;
1966
- cache.feeRate = Math.floor(Number(fee) / bytes);
1967
- }
1968
-
1969
- function nonWitnessUtxoTxFromCache(
1970
- cache: PsbtCache,
1971
- input: PsbtInput,
1972
- inputIndex: number,
1973
- ): Transaction {
1974
- const c = cache.nonWitnessUtxoTxCache;
1975
- if (!c[inputIndex]) {
1976
- addNonWitnessTxCache(cache, input, inputIndex);
1977
- }
1978
- return c[inputIndex]!;
1979
- }
1980
-
1981
- function getScriptFromUtxo(inputIndex: number, input: PsbtInput, cache: PsbtCache): Script {
1982
- const { script } = getScriptAndAmountFromUtxo(inputIndex, input, cache);
1983
- return script;
1984
- }
1985
-
1986
- function getScriptAndAmountFromUtxo(
1987
- inputIndex: number,
1988
- input: PsbtInput,
1989
- cache: PsbtCache,
1990
- ): { script: Script; value: Satoshi } {
1991
- if (input.witnessUtxo !== undefined) {
1992
- return {
1993
- script: input.witnessUtxo.script as Script,
1994
- value: input.witnessUtxo.value as Satoshi,
1995
- };
1996
- } else if (input.nonWitnessUtxo !== undefined) {
1997
- const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(cache, input, inputIndex);
1998
- const o = nonWitnessUtxoTx.outs[cache.tx.ins[inputIndex]!.index]!;
1999
- return { script: o.script, value: o.value };
2000
- } else {
2001
- throw new Error("Can't find pubkey in input without Utxo data");
2002
- }
2003
- }
2004
-
2005
- function pubkeyInInput(
2006
- pubkey: PublicKey,
2007
- input: PsbtInput,
2008
- inputIndex: number,
2009
- cache: PsbtCache,
2010
- ): boolean {
2011
- const script = getScriptFromUtxo(inputIndex, input, cache);
2012
- const { meaningfulScript } = getMeaningfulScript(
2013
- script,
2014
- inputIndex,
2015
- 'input',
2016
- input.redeemScript,
2017
- input.witnessScript,
2018
- );
2019
- return pubkeyInScript(pubkey, meaningfulScript);
2020
- }
2021
-
2022
- function pubkeyInOutput(
2023
- pubkey: PublicKey,
2024
- output: PsbtOutput,
2025
- outputIndex: number,
2026
- cache: PsbtCache,
2027
- ): boolean {
2028
- const script = cache.tx.outs[outputIndex]!.script;
2029
- const { meaningfulScript } = getMeaningfulScript(
2030
- script,
2031
- outputIndex,
2032
- 'output',
2033
- output.redeemScript,
2034
- output.witnessScript,
2035
- );
2036
- return pubkeyInScript(pubkey, meaningfulScript);
2037
- }
2038
-
2039
- function redeemFromFinalScriptSig(finalScript: Uint8Array | undefined): Uint8Array | undefined {
2040
- if (!finalScript) return;
2041
- const decomp = bscript.decompile(finalScript);
2042
- if (!decomp) return;
2043
- const lastItem = decomp[decomp.length - 1]!;
2044
- if (!(lastItem instanceof Uint8Array) || isPubkeyLike(lastItem) || isSigLike(lastItem)) return;
2045
- const sDecomp = bscript.decompile(lastItem);
2046
- if (!sDecomp) return;
2047
- return lastItem;
2048
- }
2049
-
2050
- function redeemFromFinalWitnessScript(finalScript: Uint8Array | undefined): Uint8Array | undefined {
2051
- if (!finalScript) return;
2052
- const decomp = scriptWitnessToWitnessStack(finalScript);
2053
- const lastItem = decomp[decomp.length - 1]!;
2054
- if (isPubkeyLike(lastItem)) return;
2055
- const sDecomp = bscript.decompile(lastItem);
2056
- if (!sDecomp) return;
2057
- return lastItem;
2058
- }