@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.
- package/browser/address.d.ts +5 -1
- package/browser/address.d.ts.map +1 -1
- package/browser/branded.d.ts +3 -14
- package/browser/branded.d.ts.map +1 -1
- package/browser/ecc/context.d.ts.map +1 -1
- package/browser/index.d.ts +2 -1
- package/browser/index.d.ts.map +1 -1
- package/browser/index.js +2964 -2919
- package/browser/opcodes.d.ts +11 -0
- package/browser/opcodes.d.ts.map +1 -1
- package/browser/psbt/PsbtCache.d.ts +54 -0
- package/browser/psbt/PsbtCache.d.ts.map +1 -0
- package/browser/psbt/PsbtFinalizer.d.ts +21 -0
- package/browser/psbt/PsbtFinalizer.d.ts.map +1 -0
- package/browser/psbt/PsbtSigner.d.ts +32 -0
- package/browser/psbt/PsbtSigner.d.ts.map +1 -0
- package/browser/psbt/PsbtTransaction.d.ts +25 -0
- package/browser/psbt/PsbtTransaction.d.ts.map +1 -0
- package/browser/psbt/types.d.ts +13 -13
- package/browser/psbt/types.d.ts.map +1 -1
- package/browser/psbt/validation.d.ts +1 -1
- package/browser/psbt/validation.d.ts.map +1 -1
- package/browser/psbt.d.ts +27 -39
- package/browser/psbt.d.ts.map +1 -1
- package/browser/script.d.ts.map +1 -1
- package/browser/transaction.d.ts +4 -4
- package/browser/transaction.d.ts.map +1 -1
- package/browser/types.d.ts +4 -2
- package/browser/types.d.ts.map +1 -1
- package/browser/workers/index.d.ts +3 -50
- package/browser/workers/index.d.ts.map +1 -1
- package/browser/workers/index.node.d.ts +24 -0
- package/browser/workers/index.node.d.ts.map +1 -0
- package/build/address.d.ts +5 -1
- package/build/address.d.ts.map +1 -1
- package/build/address.js +29 -17
- package/build/address.js.map +1 -1
- package/build/branded.d.ts +3 -14
- package/build/branded.d.ts.map +1 -1
- package/build/branded.js +0 -5
- package/build/branded.js.map +1 -1
- package/build/ecc/context.d.ts.map +1 -1
- package/build/ecc/context.js +68 -45
- package/build/ecc/context.js.map +1 -1
- package/build/index.d.ts +2 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -1
- package/build/index.js.map +1 -1
- package/build/opcodes.d.ts +11 -0
- package/build/opcodes.d.ts.map +1 -1
- package/build/opcodes.js +19 -4
- package/build/opcodes.js.map +1 -1
- package/build/psbt/PsbtCache.d.ts +54 -0
- package/build/psbt/PsbtCache.d.ts.map +1 -0
- package/build/psbt/PsbtCache.js +249 -0
- package/build/psbt/PsbtCache.js.map +1 -0
- package/build/psbt/PsbtFinalizer.d.ts +21 -0
- package/build/psbt/PsbtFinalizer.d.ts.map +1 -0
- package/build/psbt/PsbtFinalizer.js +157 -0
- package/build/psbt/PsbtFinalizer.js.map +1 -0
- package/build/psbt/PsbtSigner.d.ts +32 -0
- package/build/psbt/PsbtSigner.d.ts.map +1 -0
- package/build/psbt/PsbtSigner.js +192 -0
- package/build/psbt/PsbtSigner.js.map +1 -0
- package/build/psbt/PsbtTransaction.d.ts +25 -0
- package/build/psbt/PsbtTransaction.d.ts.map +1 -0
- package/build/psbt/PsbtTransaction.js +61 -0
- package/build/psbt/PsbtTransaction.js.map +1 -0
- package/build/psbt/types.d.ts +13 -13
- package/build/psbt/types.d.ts.map +1 -1
- package/build/psbt/validation.d.ts +1 -1
- package/build/psbt/validation.d.ts.map +1 -1
- package/build/psbt.d.ts +27 -39
- package/build/psbt.d.ts.map +1 -1
- package/build/psbt.js +139 -746
- package/build/psbt.js.map +1 -1
- package/build/script.d.ts.map +1 -1
- package/build/script.js +2 -2
- package/build/script.js.map +1 -1
- package/build/transaction.d.ts +4 -4
- package/build/transaction.d.ts.map +1 -1
- package/build/transaction.js +5 -4
- package/build/transaction.js.map +1 -1
- package/build/tsconfig.build.tsbuildinfo +1 -1
- package/build/types.d.ts +4 -2
- package/build/types.d.ts.map +1 -1
- package/build/types.js +9 -0
- package/build/types.js.map +1 -1
- package/build/workers/WorkerSigningPool.js +1 -1
- package/build/workers/WorkerSigningPool.js.map +1 -1
- package/build/workers/index.d.ts +3 -3
- package/build/workers/index.d.ts.map +1 -1
- package/build/workers/index.js +0 -3
- package/build/workers/index.js.map +1 -1
- package/build/workers/index.node.d.ts +24 -0
- package/build/workers/index.node.d.ts.map +1 -0
- package/build/workers/index.node.js +26 -0
- package/build/workers/index.node.js.map +1 -0
- package/package.json +28 -9
- package/src/address.ts +41 -18
- package/src/branded.ts +15 -13
- package/src/ecc/context.ts +75 -57
- package/src/index.ts +36 -1
- package/src/opcodes.ts +21 -4
- package/src/psbt/PsbtCache.ts +325 -0
- package/src/psbt/PsbtFinalizer.ts +213 -0
- package/src/psbt/PsbtSigner.ts +302 -0
- package/src/psbt/PsbtTransaction.ts +82 -0
- package/src/psbt/types.ts +13 -13
- package/src/psbt/validation.ts +1 -1
- package/src/psbt.ts +299 -1090
- package/src/script.ts +2 -2
- package/src/transaction.ts +9 -8
- package/src/types.ts +13 -0
- package/src/workers/WorkerSigningPool.ts +1 -1
- package/src/workers/index.node.ts +27 -0
- package/src/workers/index.ts +7 -9
- package/test/address.spec.ts +2 -2
- package/test/bitcoin.core.spec.ts +5 -2
- package/test/browser/payments.spec.ts +151 -0
- package/test/browser/psbt.spec.ts +1510 -0
- package/test/browser/script.spec.ts +223 -0
- package/test/browser/setup.ts +13 -0
- package/test/browser/workers-signing.spec.ts +537 -0
- package/test/crypto.spec.ts +2 -2
- package/test/fixtures/core/base58_encode_decode.json +12 -48
- package/test/fixtures/core/base58_keys_invalid.json +50 -150
- package/test/fixtures/core/sighash.json +1 -3
- package/test/fixtures/core/tx_valid.json +133 -501
- package/test/fixtures/embed.json +3 -11
- package/test/fixtures/p2ms.json +21 -91
- package/test/fixtures/p2pk.json +5 -24
- package/test/fixtures/p2pkh.json +7 -36
- package/test/fixtures/p2sh.json +8 -54
- package/test/fixtures/p2tr.json +2 -6
- package/test/fixtures/p2wpkh.json +7 -36
- package/test/fixtures/p2wsh.json +14 -59
- package/test/fixtures/psbt.json +2 -6
- package/test/fixtures/script.json +12 -48
- package/test/integration/addresses.spec.ts +11 -5
- package/test/integration/bip32.spec.ts +1 -1
- package/test/integration/cltv.spec.ts +10 -6
- package/test/integration/csv.spec.ts +10 -9
- package/test/integration/payments.spec.ts +8 -4
- package/test/integration/taproot.spec.ts +26 -6
- package/test/integration/transactions.spec.ts +22 -8
- package/test/payments.spec.ts +1 -1
- package/test/payments.utils.ts +1 -1
- package/test/psbt.spec.ts +250 -64
- package/test/script_signature.spec.ts +1 -1
- package/test/transaction.spec.ts +18 -5
- package/test/tsconfig.json +6 -20
- package/test/workers-pool.spec.ts +22 -23
- package/test/workers-signing.spec.ts +7 -3
- package/test/workers.spec.ts +6 -7
- package/typedoc.json +11 -1
- package/vitest.config.browser.ts +68 -0
- package/browser/ecpair.d.ts +0 -99
- package/src/ecpair.d.ts +0 -99
- 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,
|
|
11
|
+
import { clone, equals, fromBase64, fromHex, toHex } from './io/index.js';
|
|
17
12
|
|
|
18
13
|
import type { BIP32Interface } from '@btc-vision/bip32';
|
|
19
|
-
import
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
192
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
296
|
-
|
|
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
|
-
|
|
304
|
-
|
|
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
|
-
|
|
317
|
-
|
|
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
|
-
|
|
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
|
-
|
|
330
|
-
|
|
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 =
|
|
370
|
-
checkTxInputCache(
|
|
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
|
-
|
|
399
|
+
this.#cache.addNonWitnessTxCache(input, inputIndex, txFromBuffer);
|
|
376
400
|
}
|
|
377
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
447
|
+
this.#cache.computeFeeRate(this.data.inputs, disableOutputChecks, txFromBuffer);
|
|
448
|
+
this.#cache.checkFees(this.#opts);
|
|
443
449
|
}
|
|
444
|
-
if (
|
|
445
|
-
const tx =
|
|
446
|
-
|
|
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
|
-
|
|
454
|
+
tx,
|
|
455
|
+
true,
|
|
456
456
|
disableOutputChecks,
|
|
457
|
+
txFromBuffer,
|
|
457
458
|
);
|
|
459
|
+
return tx;
|
|
458
460
|
}
|
|
459
461
|
|
|
460
|
-
|
|
461
|
-
return
|
|
462
|
+
public getFeeRate(disableOutputChecks: boolean = false): number {
|
|
463
|
+
return this.#cache.computeFeeRate(this.data.inputs, disableOutputChecks, txFromBuffer);
|
|
462
464
|
}
|
|
463
465
|
|
|
464
|
-
|
|
465
|
-
|
|
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,
|
|
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 ||
|
|
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,
|
|
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
|
|
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);
|
|
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.
|
|
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(
|
|
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
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
}
|
|
599
|
+
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
|
|
600
|
+
throw new Error('Need HDSigner to sign input');
|
|
601
|
+
}
|
|
593
602
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
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 |
|
|
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 |
|
|
672
|
+
public async signAllInputsAsync(
|
|
673
|
+
keyPair: Signer | SignerAlternative | SignerAsync | BIP32Interface | UniversalSigner,
|
|
677
674
|
sighashTypes?: number[],
|
|
678
675
|
): Promise<void> {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
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 |
|
|
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 |
|
|
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
|
-
|
|
|
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
|
-
|
|
|
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
|
-
|
|
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
|
-
|
|
|
883
|
+
| UniversalSigner,
|
|
892
884
|
tapLeafHashToSign?: Uint8Array,
|
|
893
885
|
allowedSighashTypes?: number[],
|
|
894
|
-
): { hash:
|
|
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 =
|
|
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:
|
|
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 =
|
|
1033
|
-
const 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
|
|
1042
|
-
: getAllTaprootHashesForSig(inputIndex, input, this.data.inputs
|
|
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 |
|
|
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 |
|
|
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:
|
|
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
|
-
|
|
|
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
|
-
|
|
|
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:
|
|
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
|
-
}
|