@btc-vision/bitcoin 6.4.0 → 6.4.1
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/build/address.d.ts +1 -0
- package/build/address.js +56 -8
- package/build/networks.js +6 -6
- package/package.json +1 -1
- package/src/address.ts +71 -9
- package/src/block.ts +233 -233
- package/src/bufferutils.ts +188 -188
- package/src/networks.ts +6 -6
- package/src/psbt/psbtutils.ts +320 -320
- package/src/psbt.ts +2187 -2187
- package/test/address.spec.ts +155 -155
- package/test/bitcoin.core.spec.ts +212 -212
- package/test/block.spec.ts +171 -171
- package/test/bufferutils.spec.ts +450 -450
- package/test/crypto.spec.ts +49 -49
- package/test/integration/addresses.spec.ts +142 -142
- package/test/integration/bip32.spec.ts +130 -130
- package/test/integration/blocks.spec.ts +28 -28
- package/test/integration/cltv.spec.ts +241 -241
- package/test/integration/csv.spec.ts +452 -452
- package/test/integration/payments.spec.ts +110 -110
- package/test/integration/taproot.spec.ts +663 -663
- package/test/integration/transactions.spec.ts +668 -668
- package/test/payments.spec.ts +114 -114
- package/test/payments.utils.ts +165 -165
- package/test/psbt.spec.ts +1285 -1285
- package/test/script.spec.ts +186 -186
- package/test/script_number.spec.ts +26 -26
- package/test/script_signature.spec.ts +66 -66
- package/test/transaction.spec.ts +337 -337
- package/test/ts-node-register.js +7 -7
- package/test/types.spec.ts +53 -53
package/src/psbt.ts
CHANGED
|
@@ -1,2187 +1,2187 @@
|
|
|
1
|
-
import { Psbt as PsbtBase } from 'bip174';
|
|
2
|
-
import * as varuint from 'bip174/src/lib/converter/varint.js';
|
|
3
|
-
import {
|
|
4
|
-
Bip32Derivation,
|
|
5
|
-
KeyValue,
|
|
6
|
-
PartialSig,
|
|
7
|
-
PsbtGlobal,
|
|
8
|
-
PsbtGlobalUpdate,
|
|
9
|
-
PsbtInput,
|
|
10
|
-
PsbtInputUpdate,
|
|
11
|
-
PsbtOutput,
|
|
12
|
-
PsbtOutputUpdate,
|
|
13
|
-
TapKeySig,
|
|
14
|
-
TapScriptSig,
|
|
15
|
-
Transaction as ITransaction,
|
|
16
|
-
TransactionFromBuffer,
|
|
17
|
-
} from 'bip174/src/lib/interfaces.js';
|
|
18
|
-
import { checkForInput, checkForOutput } from 'bip174/src/lib/utils.js';
|
|
19
|
-
import { BIP32Interface } from 'bip32';
|
|
20
|
-
import { ECPairInterface } from 'ecpair';
|
|
21
|
-
import { fromOutputScript, toOutputScript } from './address.js';
|
|
22
|
-
import { cloneBuffer, reverseBuffer } from './bufferutils.js';
|
|
23
|
-
import { payments } from './index.js';
|
|
24
|
-
import { bitcoin as btcNetwork, Network } from './networks.js';
|
|
25
|
-
import { tapleafHash } from './payments/bip341.js';
|
|
26
|
-
import { Payment, PaymentOpts } from './payments/index.js';
|
|
27
|
-
import {
|
|
28
|
-
checkTaprootInputFields,
|
|
29
|
-
checkTaprootInputForSigs,
|
|
30
|
-
checkTaprootOutputFields,
|
|
31
|
-
isTaprootInput,
|
|
32
|
-
serializeTaprootSignature,
|
|
33
|
-
tapScriptFinalizer,
|
|
34
|
-
toXOnly,
|
|
35
|
-
} from './psbt/bip371.js';
|
|
36
|
-
import {
|
|
37
|
-
checkInputForSig,
|
|
38
|
-
isP2MS,
|
|
39
|
-
isP2PK,
|
|
40
|
-
isP2PKH,
|
|
41
|
-
isP2SHScript,
|
|
42
|
-
isP2TR,
|
|
43
|
-
isP2WPKH,
|
|
44
|
-
isP2WSHScript,
|
|
45
|
-
pubkeyInScript,
|
|
46
|
-
witnessStackToScriptWitness,
|
|
47
|
-
} from './psbt/psbtutils.js';
|
|
48
|
-
import * as bscript from './script.js';
|
|
49
|
-
import { Output, Transaction } from './transaction.js';
|
|
50
|
-
|
|
51
|
-
export interface TransactionInput {
|
|
52
|
-
hash: string | Buffer;
|
|
53
|
-
index: number;
|
|
54
|
-
sequence?: number;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export interface PsbtTxInput extends TransactionInput {
|
|
58
|
-
hash: Buffer;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export interface TransactionOutput {
|
|
62
|
-
script: Buffer;
|
|
63
|
-
value: number;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export interface PsbtTxOutput extends TransactionOutput {
|
|
67
|
-
address: string | undefined;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// msghash is 32 byte hash of preimage, signature is 64 byte compact signature (r,s 32 bytes each)
|
|
71
|
-
export type ValidateSigFunction = (pubkey: Buffer, msghash: Buffer, signature: Buffer) => boolean;
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* These are the default arguments for a Psbt instance.
|
|
75
|
-
*/
|
|
76
|
-
const DEFAULT_OPTS: PsbtOpts = {
|
|
77
|
-
/**
|
|
78
|
-
* A bitcoinjs Network object. This is only used if you pass an `address`
|
|
79
|
-
* parameter to addOutput. Otherwise it is not needed and can be left default.
|
|
80
|
-
*/
|
|
81
|
-
network: btcNetwork,
|
|
82
|
-
/**
|
|
83
|
-
* When extractTransaction is called, the fee rate is checked.
|
|
84
|
-
* THIS IS NOT TO BE RELIED ON.
|
|
85
|
-
* It is only here as a last ditch effort to prevent sending a 500 BTC fee etc.
|
|
86
|
-
*/
|
|
87
|
-
maximumFeeRate: 5000, // satoshi per byte
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
// Not a breaking change.
|
|
91
|
-
export interface PsbtBaseExtended extends Omit<PsbtBase, 'inputs'> {
|
|
92
|
-
inputs: PsbtInput[];
|
|
93
|
-
globalMap: PsbtGlobal;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Psbt class can parse and generate a PSBT binary based off of the BIP174.
|
|
98
|
-
* There are 6 roles that this class fulfills. (Explained in BIP174)
|
|
99
|
-
*
|
|
100
|
-
* Creator: This can be done with `new Psbt()`
|
|
101
|
-
*
|
|
102
|
-
* Updater: This can be done with `psbt.addInput(input)`, `psbt.addInputs(inputs)`,
|
|
103
|
-
* `psbt.addOutput(output)`, `psbt.addOutputs(outputs)` when you are looking to
|
|
104
|
-
* add new inputs and outputs to the PSBT, and `psbt.updateGlobal(itemObject)`,
|
|
105
|
-
* `psbt.updateInput(itemObject)`, `psbt.updateOutput(itemObject)`
|
|
106
|
-
* addInput requires hash: Buffer | string; and index: number; as attributes
|
|
107
|
-
* and can also include any attributes that are used in updateInput method.
|
|
108
|
-
* addOutput requires script: Buffer; and value: number; and likewise can include
|
|
109
|
-
* data for updateOutput.
|
|
110
|
-
* For a list of what attributes should be what types. Check the bip174 library.
|
|
111
|
-
* Also, check the integration tests for some examples of usage.
|
|
112
|
-
*
|
|
113
|
-
* Signer: There are a few methods. signAllInputs and signAllInputsAsync, which will search all input
|
|
114
|
-
* information for your pubkey or pubkeyhash, and only sign inputs where it finds
|
|
115
|
-
* your info. Or you can explicitly sign a specific input with signInput and
|
|
116
|
-
* signInputAsync. For the async methods you can create a SignerAsync object
|
|
117
|
-
* and use something like a hardware wallet to sign with. (You must implement this)
|
|
118
|
-
*
|
|
119
|
-
* Combiner: psbts can be combined easily with `psbt.combine(psbt2, psbt3, psbt4 ...)`
|
|
120
|
-
* the psbt calling combine will always have precedence when a conflict occurs.
|
|
121
|
-
* Combine checks if the internal bitcoin transaction is the same, so be sure that
|
|
122
|
-
* all sequences, version, locktime, etc. are the same before combining.
|
|
123
|
-
*
|
|
124
|
-
* Input Finalizer: This role is fairly important. Not only does it need to construct
|
|
125
|
-
* the input scriptSigs and witnesses, but it SHOULD verify the signatures etc.
|
|
126
|
-
* Before running `psbt.finalizeAllInputs()` please run `psbt.validateSignaturesOfAllInputs()`
|
|
127
|
-
* Running any finalize method will delete any data in the input(s) that are no longer
|
|
128
|
-
* needed due to the finalized scripts containing the information.
|
|
129
|
-
*
|
|
130
|
-
* Transaction Extractor: This role will perform some checks before returning a
|
|
131
|
-
* Transaction object. Such as fee rate not being larger than maximumFeeRate etc.
|
|
132
|
-
*/
|
|
133
|
-
/**
|
|
134
|
-
* Psbt class can parse and generate a PSBT binary based off of the BIP174.
|
|
135
|
-
*/
|
|
136
|
-
export class Psbt {
|
|
137
|
-
private readonly __CACHE: PsbtCache;
|
|
138
|
-
private readonly opts: PsbtOpts;
|
|
139
|
-
|
|
140
|
-
constructor(
|
|
141
|
-
opts: PsbtOptsOptional = {},
|
|
142
|
-
public data: PsbtBaseExtended = new PsbtBase(new PsbtTransaction()),
|
|
143
|
-
) {
|
|
144
|
-
this.opts = Object.assign({}, DEFAULT_OPTS, opts);
|
|
145
|
-
this.__CACHE = {
|
|
146
|
-
__NON_WITNESS_UTXO_TX_CACHE: [],
|
|
147
|
-
__NON_WITNESS_UTXO_BUF_CACHE: [],
|
|
148
|
-
__TX_IN_CACHE: {},
|
|
149
|
-
// @ts-expect-error no.
|
|
150
|
-
__TX: this.data.globalMap.unsignedTx.tx, // Now TypeScript knows unsignedTx is a PsbtTransaction
|
|
151
|
-
__UNSAFE_SIGN_NONSEGWIT: false,
|
|
152
|
-
};
|
|
153
|
-
if (this.data.inputs.length === 0) this.setVersion(2);
|
|
154
|
-
|
|
155
|
-
const dpew = <T>(obj: T, attr: string, enumerable: boolean, writable: boolean): void => {
|
|
156
|
-
Object.defineProperty(obj, attr, {
|
|
157
|
-
enumerable,
|
|
158
|
-
writable,
|
|
159
|
-
});
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
dpew(this, '__CACHE', false, true);
|
|
163
|
-
dpew(this, 'opts', false, true);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
get inputCount(): number {
|
|
167
|
-
return this.data.inputs.length;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
get version(): number {
|
|
171
|
-
return this.__CACHE.__TX.version;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
set version(version: number) {
|
|
175
|
-
this.setVersion(version);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
get locktime(): number {
|
|
179
|
-
return this.__CACHE.__TX.locktime;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
set locktime(locktime: number) {
|
|
183
|
-
this.setLocktime(locktime);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
get txInputs(): PsbtTxInput[] {
|
|
187
|
-
return this.__CACHE.__TX.ins.map((input) => ({
|
|
188
|
-
hash: cloneBuffer(input.hash),
|
|
189
|
-
index: input.index,
|
|
190
|
-
sequence: input.sequence,
|
|
191
|
-
}));
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
get txOutputs(): PsbtTxOutput[] {
|
|
195
|
-
return this.__CACHE.__TX.outs.map((output) => {
|
|
196
|
-
let address;
|
|
197
|
-
try {
|
|
198
|
-
address = fromOutputScript(output.script, this.opts.network);
|
|
199
|
-
} catch (_) {}
|
|
200
|
-
return {
|
|
201
|
-
script: cloneBuffer(output.script),
|
|
202
|
-
value: output.value,
|
|
203
|
-
address,
|
|
204
|
-
};
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
static fromBase64(data: string, opts: PsbtOptsOptional = {}): Psbt {
|
|
209
|
-
const buffer = Buffer.from(data, 'base64');
|
|
210
|
-
return this.fromBuffer(buffer, opts);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
static fromHex(data: string, opts: PsbtOptsOptional = {}): Psbt {
|
|
214
|
-
const buffer = Buffer.from(data, 'hex');
|
|
215
|
-
return this.fromBuffer(buffer, opts);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
static fromBuffer(buffer: Buffer, opts: PsbtOptsOptional = {}): Psbt {
|
|
219
|
-
const psbtBase = PsbtBase.fromBuffer(buffer, transactionFromBuffer);
|
|
220
|
-
const psbt = new Psbt(opts, psbtBase);
|
|
221
|
-
checkTxForDupeIns(psbt.__CACHE.__TX, psbt.__CACHE);
|
|
222
|
-
return psbt;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
combine(...those: Psbt[]): this {
|
|
226
|
-
this.data.combine(...those.map((o) => o.data));
|
|
227
|
-
return this;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
clone(): Psbt {
|
|
231
|
-
// TODO: more efficient cloning
|
|
232
|
-
return Psbt.fromBuffer(this.data.toBuffer(), JSON.parse(JSON.stringify(this.opts)));
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
setMaximumFeeRate(satoshiPerByte: number): void {
|
|
236
|
-
check32Bit(satoshiPerByte); // 42.9 BTC per byte IS excessive... so throw
|
|
237
|
-
this.opts.maximumFeeRate = satoshiPerByte;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
setVersion(version: number): this {
|
|
241
|
-
check32Bit(version);
|
|
242
|
-
checkInputsForPartialSig(this.data.inputs, 'setVersion');
|
|
243
|
-
const c = this.__CACHE;
|
|
244
|
-
c.__TX.version = version;
|
|
245
|
-
c.__EXTRACTED_TX = undefined;
|
|
246
|
-
return this;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
setLocktime(locktime: number): this {
|
|
250
|
-
check32Bit(locktime);
|
|
251
|
-
checkInputsForPartialSig(this.data.inputs, 'setLocktime');
|
|
252
|
-
const c = this.__CACHE;
|
|
253
|
-
c.__TX.locktime = locktime;
|
|
254
|
-
c.__EXTRACTED_TX = undefined;
|
|
255
|
-
return this;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
setInputSequence(inputIndex: number, sequence: number): this {
|
|
259
|
-
check32Bit(sequence);
|
|
260
|
-
checkInputsForPartialSig(this.data.inputs, 'setInputSequence');
|
|
261
|
-
const c = this.__CACHE;
|
|
262
|
-
if (c.__TX.ins.length <= inputIndex) {
|
|
263
|
-
throw new Error('Input index too high');
|
|
264
|
-
}
|
|
265
|
-
c.__TX.ins[inputIndex].sequence = sequence;
|
|
266
|
-
c.__EXTRACTED_TX = undefined;
|
|
267
|
-
return this;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
addInputs(inputDatas: PsbtInputExtended[], checkPartialSigs: boolean = true): this {
|
|
271
|
-
inputDatas.forEach((inputData) => this.addInput(inputData, checkPartialSigs));
|
|
272
|
-
return this;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
addInput(inputData: PsbtInputExtended, checkPartialSigs: boolean = true): this {
|
|
276
|
-
if (!inputData || inputData.hash === undefined || inputData.index === undefined) {
|
|
277
|
-
throw new Error(
|
|
278
|
-
`Invalid arguments for Psbt.addInput. ` +
|
|
279
|
-
`Requires single object with at least [hash] and [index]`,
|
|
280
|
-
);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
checkTaprootInputFields(inputData, inputData, 'addInput');
|
|
284
|
-
|
|
285
|
-
if (checkPartialSigs) {
|
|
286
|
-
checkInputsForPartialSig(this.data.inputs, 'addInput');
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
if (inputData.witnessScript) checkInvalidP2WSH(inputData.witnessScript);
|
|
290
|
-
const c = this.__CACHE;
|
|
291
|
-
this.data.addInput(inputData);
|
|
292
|
-
const txIn = c.__TX.ins[c.__TX.ins.length - 1];
|
|
293
|
-
checkTxInputCache(c, txIn);
|
|
294
|
-
|
|
295
|
-
const inputIndex = this.data.inputs.length - 1;
|
|
296
|
-
const input = this.data.inputs[inputIndex];
|
|
297
|
-
if (input.nonWitnessUtxo) {
|
|
298
|
-
addNonWitnessTxCache(this.__CACHE, input, inputIndex);
|
|
299
|
-
}
|
|
300
|
-
c.__FEE = undefined;
|
|
301
|
-
c.__FEE_RATE = undefined;
|
|
302
|
-
c.__EXTRACTED_TX = undefined;
|
|
303
|
-
return this;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
addOutputs(outputDatas: PsbtOutputExtended[]): this {
|
|
307
|
-
outputDatas.forEach((outputData) => this.addOutput(outputData));
|
|
308
|
-
return this;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
addOutput(outputData: PsbtOutputExtended): this {
|
|
312
|
-
if (
|
|
313
|
-
arguments.length > 1 ||
|
|
314
|
-
!outputData ||
|
|
315
|
-
outputData.value === undefined ||
|
|
316
|
-
((outputData as any).address === undefined && (outputData as any).script === undefined)
|
|
317
|
-
) {
|
|
318
|
-
throw new Error(
|
|
319
|
-
`Invalid arguments for Psbt.addOutput. ` +
|
|
320
|
-
`Requires single object with at least [script or address] and [value]`,
|
|
321
|
-
);
|
|
322
|
-
}
|
|
323
|
-
checkInputsForPartialSig(this.data.inputs, 'addOutput');
|
|
324
|
-
const { address } = outputData as any;
|
|
325
|
-
if (typeof address === 'string') {
|
|
326
|
-
const { network } = this.opts;
|
|
327
|
-
const script = toOutputScript(address, network);
|
|
328
|
-
outputData = Object.assign({}, outputData, { script });
|
|
329
|
-
}
|
|
330
|
-
checkTaprootOutputFields(outputData, outputData, 'addOutput');
|
|
331
|
-
|
|
332
|
-
const c = this.__CACHE;
|
|
333
|
-
this.data.addOutput(outputData);
|
|
334
|
-
c.__FEE = undefined;
|
|
335
|
-
c.__FEE_RATE = undefined;
|
|
336
|
-
c.__EXTRACTED_TX = undefined;
|
|
337
|
-
return this;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
extractTransaction(disableFeeCheck?: boolean, disableOutputChecks?: boolean): Transaction {
|
|
341
|
-
if (disableOutputChecks) {
|
|
342
|
-
this.data.inputs = this.data.inputs.filter((i) => !i.partialSig);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
if (!this.data.inputs.every(isFinalized)) throw new Error('Not finalized');
|
|
346
|
-
const c = this.__CACHE;
|
|
347
|
-
if (!disableFeeCheck) {
|
|
348
|
-
checkFees(this, c, this.opts);
|
|
349
|
-
}
|
|
350
|
-
if (c.__EXTRACTED_TX) return c.__EXTRACTED_TX;
|
|
351
|
-
const tx = c.__TX.clone();
|
|
352
|
-
inputFinalizeGetAmts(this.data.inputs, tx, c, true, disableOutputChecks);
|
|
353
|
-
return tx;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
getFeeRate(disableOutputChecks: boolean = false): number {
|
|
357
|
-
return getTxCacheValue(
|
|
358
|
-
'__FEE_RATE',
|
|
359
|
-
'fee rate',
|
|
360
|
-
this.data.inputs,
|
|
361
|
-
this.__CACHE,
|
|
362
|
-
disableOutputChecks,
|
|
363
|
-
)!;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
getFee(disableOutputChecks: boolean = false): number {
|
|
367
|
-
return getTxCacheValue(
|
|
368
|
-
'__FEE',
|
|
369
|
-
'fee',
|
|
370
|
-
this.data.inputs,
|
|
371
|
-
this.__CACHE,
|
|
372
|
-
disableOutputChecks,
|
|
373
|
-
)!;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
finalizeAllInputs(): this {
|
|
377
|
-
checkForInput(this.data.inputs, 0); // making sure we have at least one
|
|
378
|
-
range(this.data.inputs.length).forEach((idx) => this.finalizeInput(idx));
|
|
379
|
-
return this;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
finalizeInput(
|
|
383
|
-
inputIndex: number,
|
|
384
|
-
finalScriptsFunc?: FinalScriptsFunc | FinalTaprootScriptsFunc,
|
|
385
|
-
canRunChecks?: boolean,
|
|
386
|
-
): this {
|
|
387
|
-
const input = checkForInput(this.data.inputs, inputIndex);
|
|
388
|
-
if (isTaprootInput(input)) {
|
|
389
|
-
return this._finalizeTaprootInput(
|
|
390
|
-
inputIndex,
|
|
391
|
-
input,
|
|
392
|
-
undefined,
|
|
393
|
-
finalScriptsFunc as FinalTaprootScriptsFunc,
|
|
394
|
-
);
|
|
395
|
-
}
|
|
396
|
-
return this._finalizeInput(
|
|
397
|
-
inputIndex,
|
|
398
|
-
input,
|
|
399
|
-
finalScriptsFunc as FinalScriptsFunc,
|
|
400
|
-
canRunChecks ?? true,
|
|
401
|
-
);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
finalizeTaprootInput(
|
|
405
|
-
inputIndex: number,
|
|
406
|
-
tapLeafHashToFinalize?: Buffer,
|
|
407
|
-
finalScriptsFunc: FinalTaprootScriptsFunc = tapScriptFinalizer,
|
|
408
|
-
): this {
|
|
409
|
-
const input = checkForInput(this.data.inputs, inputIndex);
|
|
410
|
-
if (isTaprootInput(input))
|
|
411
|
-
return this._finalizeTaprootInput(
|
|
412
|
-
inputIndex,
|
|
413
|
-
input,
|
|
414
|
-
tapLeafHashToFinalize,
|
|
415
|
-
finalScriptsFunc,
|
|
416
|
-
);
|
|
417
|
-
throw new Error(`Cannot finalize input #${inputIndex}. Not Taproot.`);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
getInputType(inputIndex: number): AllScriptType {
|
|
421
|
-
const input = checkForInput(this.data.inputs, inputIndex);
|
|
422
|
-
const script = getScriptFromUtxo(inputIndex, input, this.__CACHE);
|
|
423
|
-
const result = getMeaningfulScript(
|
|
424
|
-
script,
|
|
425
|
-
inputIndex,
|
|
426
|
-
'input',
|
|
427
|
-
input.redeemScript || redeemFromFinalScriptSig(input.finalScriptSig),
|
|
428
|
-
input.witnessScript || redeemFromFinalWitnessScript(input.finalScriptWitness),
|
|
429
|
-
);
|
|
430
|
-
const type = result.type === 'raw' ? '' : result.type + '-';
|
|
431
|
-
const mainType = classifyScript(result.meaningfulScript);
|
|
432
|
-
return (type + mainType) as AllScriptType;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
inputHasPubkey(inputIndex: number, pubkey: Buffer): boolean {
|
|
436
|
-
const input = checkForInput(this.data.inputs, inputIndex);
|
|
437
|
-
return pubkeyInInput(pubkey, input, inputIndex, this.__CACHE);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
inputHasHDKey(inputIndex: number, root: HDSigner): boolean {
|
|
441
|
-
const input = checkForInput(this.data.inputs, inputIndex);
|
|
442
|
-
const derivationIsMine = bip32DerivationIsMine(root);
|
|
443
|
-
return !!input.bip32Derivation && input.bip32Derivation.some(derivationIsMine);
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
outputHasPubkey(outputIndex: number, pubkey: Buffer): boolean {
|
|
447
|
-
const output = checkForOutput(this.data.outputs, outputIndex);
|
|
448
|
-
return pubkeyInOutput(pubkey, output, outputIndex, this.__CACHE);
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
outputHasHDKey(outputIndex: number, root: HDSigner): boolean {
|
|
452
|
-
const output = checkForOutput(this.data.outputs, outputIndex);
|
|
453
|
-
const derivationIsMine = bip32DerivationIsMine(root);
|
|
454
|
-
return !!output.bip32Derivation && output.bip32Derivation.some(derivationIsMine);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
validateSignaturesOfAllInputs(validator: ValidateSigFunction): boolean {
|
|
458
|
-
checkForInput(this.data.inputs, 0); // making sure we have at least one
|
|
459
|
-
const results = range(this.data.inputs.length).map((idx) =>
|
|
460
|
-
this.validateSignaturesOfInput(idx, validator),
|
|
461
|
-
);
|
|
462
|
-
return results.reduce((final, res) => res === true && final, true);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
validateSignaturesOfInput(
|
|
466
|
-
inputIndex: number,
|
|
467
|
-
validator: ValidateSigFunction,
|
|
468
|
-
pubkey?: Buffer,
|
|
469
|
-
): boolean {
|
|
470
|
-
const input = this.data.inputs[inputIndex];
|
|
471
|
-
if (isTaprootInput(input))
|
|
472
|
-
return this.validateSignaturesOfTaprootInput(inputIndex, validator, pubkey);
|
|
473
|
-
|
|
474
|
-
return this._validateSignaturesOfInput(inputIndex, validator, pubkey);
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
signAllInputsHD(hdKeyPair: HDSigner, sighashTypes: number[] = [Transaction.SIGHASH_ALL]): this {
|
|
478
|
-
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
|
|
479
|
-
throw new Error('Need HDSigner to sign input');
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
const results: boolean[] = [];
|
|
483
|
-
for (const i of range(this.data.inputs.length)) {
|
|
484
|
-
try {
|
|
485
|
-
this.signInputHD(i, hdKeyPair, sighashTypes);
|
|
486
|
-
results.push(true);
|
|
487
|
-
} catch (err) {
|
|
488
|
-
results.push(false);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
if (results.every((v) => v === false)) {
|
|
492
|
-
throw new Error('No inputs were signed');
|
|
493
|
-
}
|
|
494
|
-
return this;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
signAllInputsHDAsync(
|
|
498
|
-
hdKeyPair: HDSigner | HDSignerAsync,
|
|
499
|
-
sighashTypes: number[] = [Transaction.SIGHASH_ALL],
|
|
500
|
-
): Promise<void> {
|
|
501
|
-
return new Promise((resolve, reject): any => {
|
|
502
|
-
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
|
|
503
|
-
return reject(new Error('Need HDSigner to sign input'));
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
const results: boolean[] = [];
|
|
507
|
-
const promises: Array<Promise<void>> = [];
|
|
508
|
-
for (const i of range(this.data.inputs.length)) {
|
|
509
|
-
promises.push(
|
|
510
|
-
this.signInputHDAsync(i, hdKeyPair, sighashTypes).then(
|
|
511
|
-
() => {
|
|
512
|
-
results.push(true);
|
|
513
|
-
},
|
|
514
|
-
() => {
|
|
515
|
-
results.push(false);
|
|
516
|
-
},
|
|
517
|
-
),
|
|
518
|
-
);
|
|
519
|
-
}
|
|
520
|
-
return Promise.all(promises).then(() => {
|
|
521
|
-
if (results.every((v) => v === false)) {
|
|
522
|
-
return reject(new Error('No inputs were signed'));
|
|
523
|
-
}
|
|
524
|
-
resolve();
|
|
525
|
-
});
|
|
526
|
-
});
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
signInputHD(
|
|
530
|
-
inputIndex: number,
|
|
531
|
-
hdKeyPair: HDSigner,
|
|
532
|
-
sighashTypes: number[] = [Transaction.SIGHASH_ALL],
|
|
533
|
-
): this {
|
|
534
|
-
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
|
|
535
|
-
throw new Error('Need HDSigner to sign input');
|
|
536
|
-
}
|
|
537
|
-
const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair) as Signer[];
|
|
538
|
-
signers.forEach((signer) => this.signInput(inputIndex, signer, sighashTypes));
|
|
539
|
-
return this;
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
signInputHDAsync(
|
|
543
|
-
inputIndex: number,
|
|
544
|
-
hdKeyPair: HDSigner | HDSignerAsync,
|
|
545
|
-
sighashTypes: number[] = [Transaction.SIGHASH_ALL],
|
|
546
|
-
): Promise<void> {
|
|
547
|
-
return new Promise((resolve, reject): any => {
|
|
548
|
-
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
|
|
549
|
-
return reject(new Error('Need HDSigner to sign input'));
|
|
550
|
-
}
|
|
551
|
-
const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair);
|
|
552
|
-
const promises = signers.map((signer) =>
|
|
553
|
-
this.signInputAsync(inputIndex, signer as unknown as Signer, sighashTypes),
|
|
554
|
-
);
|
|
555
|
-
return Promise.all(promises)
|
|
556
|
-
.then(() => {
|
|
557
|
-
resolve();
|
|
558
|
-
})
|
|
559
|
-
.catch(reject);
|
|
560
|
-
});
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
signAllInputs(
|
|
564
|
-
keyPair: Signer | SignerAlternative | BIP32Interface | ECPairInterface,
|
|
565
|
-
sighashTypes?: number[],
|
|
566
|
-
): this {
|
|
567
|
-
if (!keyPair || !keyPair.publicKey) throw new Error('Need Signer to sign input');
|
|
568
|
-
|
|
569
|
-
// TODO: Add a pubkey/pubkeyhash cache to each input
|
|
570
|
-
// as input information is added, then eventually
|
|
571
|
-
// optimize this method.
|
|
572
|
-
const results: boolean[] = [];
|
|
573
|
-
for (const i of range(this.data.inputs.length)) {
|
|
574
|
-
try {
|
|
575
|
-
this.signInput(i, keyPair, sighashTypes);
|
|
576
|
-
results.push(true);
|
|
577
|
-
} catch (err) {
|
|
578
|
-
results.push(false);
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
if (results.every((v) => v === false)) {
|
|
582
|
-
throw new Error('No inputs were signed');
|
|
583
|
-
}
|
|
584
|
-
return this;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
signAllInputsAsync(
|
|
588
|
-
keyPair: Signer | SignerAlternative | SignerAsync | BIP32Interface | ECPairInterface,
|
|
589
|
-
sighashTypes?: number[],
|
|
590
|
-
): Promise<void> {
|
|
591
|
-
return new Promise((resolve, reject): any => {
|
|
592
|
-
if (!keyPair || !keyPair.publicKey)
|
|
593
|
-
return reject(new Error('Need Signer to sign input'));
|
|
594
|
-
|
|
595
|
-
// TODO: Add a pubkey/pubkeyhash cache to each input
|
|
596
|
-
// as input information is added, then eventually
|
|
597
|
-
// optimize this method.
|
|
598
|
-
const results: boolean[] = [];
|
|
599
|
-
const promises: Array<Promise<void>> = [];
|
|
600
|
-
for (const [i] of this.data.inputs.entries()) {
|
|
601
|
-
promises.push(
|
|
602
|
-
this.signInputAsync(i, keyPair, sighashTypes).then(
|
|
603
|
-
() => {
|
|
604
|
-
results.push(true);
|
|
605
|
-
},
|
|
606
|
-
() => {
|
|
607
|
-
results.push(false);
|
|
608
|
-
},
|
|
609
|
-
),
|
|
610
|
-
);
|
|
611
|
-
}
|
|
612
|
-
return Promise.all(promises).then(() => {
|
|
613
|
-
if (results.every((v) => v === false)) {
|
|
614
|
-
return reject(new Error('No inputs were signed'));
|
|
615
|
-
}
|
|
616
|
-
resolve();
|
|
617
|
-
});
|
|
618
|
-
});
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
signInput(
|
|
622
|
-
inputIndex: number,
|
|
623
|
-
keyPair: Signer | SignerAlternative | BIP32Interface | ECPairInterface,
|
|
624
|
-
sighashTypes?: number[],
|
|
625
|
-
): this {
|
|
626
|
-
if (!keyPair || !keyPair.publicKey) {
|
|
627
|
-
throw new Error('Need Signer to sign input');
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
const input = checkForInput(this.data.inputs, inputIndex);
|
|
631
|
-
if (isTaprootInput(input)) {
|
|
632
|
-
return this._signTaprootInput(inputIndex, input, keyPair, undefined, sighashTypes);
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
return this._signInput(inputIndex, keyPair, sighashTypes);
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
signTaprootInput(
|
|
639
|
-
inputIndex: number,
|
|
640
|
-
keyPair: Signer | SignerAlternative | BIP32Interface | ECPairInterface,
|
|
641
|
-
tapLeafHashToSign?: Buffer,
|
|
642
|
-
sighashTypes?: number[],
|
|
643
|
-
): this {
|
|
644
|
-
if (!keyPair || !keyPair.publicKey) {
|
|
645
|
-
throw new Error('Need Signer to sign input');
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
const input = checkForInput(this.data.inputs, inputIndex);
|
|
649
|
-
if (isTaprootInput(input)) {
|
|
650
|
-
return this._signTaprootInput(
|
|
651
|
-
inputIndex,
|
|
652
|
-
input,
|
|
653
|
-
keyPair,
|
|
654
|
-
tapLeafHashToSign,
|
|
655
|
-
sighashTypes,
|
|
656
|
-
);
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
throw new Error(`Input #${inputIndex} is not of type Taproot.`);
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
signInputAsync(
|
|
663
|
-
inputIndex: number,
|
|
664
|
-
keyPair: Signer | SignerAlternative | SignerAsync | BIP32Interface | ECPairInterface,
|
|
665
|
-
sighashTypes?: number[],
|
|
666
|
-
): Promise<void> {
|
|
667
|
-
return Promise.resolve().then(() => {
|
|
668
|
-
if (!keyPair || !keyPair.publicKey) throw new Error('Need Signer to sign input');
|
|
669
|
-
|
|
670
|
-
const input = checkForInput(this.data.inputs, inputIndex);
|
|
671
|
-
if (isTaprootInput(input))
|
|
672
|
-
return this._signTaprootInputAsync(
|
|
673
|
-
inputIndex,
|
|
674
|
-
input,
|
|
675
|
-
keyPair,
|
|
676
|
-
undefined,
|
|
677
|
-
sighashTypes,
|
|
678
|
-
);
|
|
679
|
-
|
|
680
|
-
return this._signInputAsync(inputIndex, keyPair, sighashTypes);
|
|
681
|
-
});
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
signTaprootInputAsync(
|
|
685
|
-
inputIndex: number,
|
|
686
|
-
keyPair: Signer | SignerAlternative | SignerAsync | BIP32Interface | ECPairInterface,
|
|
687
|
-
tapLeafHash?: Buffer,
|
|
688
|
-
sighashTypes?: number[],
|
|
689
|
-
): Promise<void> {
|
|
690
|
-
return Promise.resolve().then(() => {
|
|
691
|
-
if (!keyPair || !keyPair.publicKey) throw new Error('Need Signer to sign input');
|
|
692
|
-
|
|
693
|
-
const input = checkForInput(this.data.inputs, inputIndex);
|
|
694
|
-
if (isTaprootInput(input))
|
|
695
|
-
return this._signTaprootInputAsync(
|
|
696
|
-
inputIndex,
|
|
697
|
-
input,
|
|
698
|
-
keyPair,
|
|
699
|
-
tapLeafHash,
|
|
700
|
-
sighashTypes,
|
|
701
|
-
);
|
|
702
|
-
|
|
703
|
-
throw new Error(`Input #${inputIndex} is not of type Taproot.`);
|
|
704
|
-
});
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
toBuffer(): Buffer {
|
|
708
|
-
checkCache(this.__CACHE);
|
|
709
|
-
return this.data.toBuffer();
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
toHex(): string {
|
|
713
|
-
checkCache(this.__CACHE);
|
|
714
|
-
return this.data.toHex();
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
toBase64(): string {
|
|
718
|
-
checkCache(this.__CACHE);
|
|
719
|
-
return this.data.toBase64();
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
updateGlobal(updateData: PsbtGlobalUpdate): this {
|
|
723
|
-
this.data.updateGlobal(updateData);
|
|
724
|
-
return this;
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
updateInput(inputIndex: number, updateData: PsbtInputUpdate): this {
|
|
728
|
-
if (updateData.witnessScript) checkInvalidP2WSH(updateData.witnessScript);
|
|
729
|
-
checkTaprootInputFields(this.data.inputs[inputIndex], updateData, 'updateInput');
|
|
730
|
-
this.data.updateInput(inputIndex, updateData);
|
|
731
|
-
if (updateData.nonWitnessUtxo) {
|
|
732
|
-
addNonWitnessTxCache(this.__CACHE, this.data.inputs[inputIndex], inputIndex);
|
|
733
|
-
}
|
|
734
|
-
return this;
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
updateOutput(outputIndex: number, updateData: PsbtOutputUpdate): this {
|
|
738
|
-
const outputData = this.data.outputs[outputIndex];
|
|
739
|
-
checkTaprootOutputFields(outputData, updateData, 'updateOutput');
|
|
740
|
-
|
|
741
|
-
this.data.updateOutput(outputIndex, updateData);
|
|
742
|
-
return this;
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
addUnknownKeyValToGlobal(keyVal: KeyValue): this {
|
|
746
|
-
this.data.addUnknownKeyValToGlobal(keyVal);
|
|
747
|
-
return this;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
addUnknownKeyValToInput(inputIndex: number, keyVal: KeyValue): this {
|
|
751
|
-
this.data.addUnknownKeyValToInput(inputIndex, keyVal);
|
|
752
|
-
return this;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
addUnknownKeyValToOutput(outputIndex: number, keyVal: KeyValue): this {
|
|
756
|
-
this.data.addUnknownKeyValToOutput(outputIndex, keyVal);
|
|
757
|
-
return this;
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
clearFinalizedInput(inputIndex: number): this {
|
|
761
|
-
this.data.clearFinalizedInput(inputIndex);
|
|
762
|
-
return this;
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
public checkTaprootHashesForSig(
|
|
766
|
-
inputIndex: number,
|
|
767
|
-
input: PsbtInput,
|
|
768
|
-
keyPair: Signer | SignerAlternative | SignerAsync | BIP32Interface | ECPairInterface,
|
|
769
|
-
tapLeafHashToSign?: Buffer,
|
|
770
|
-
allowedSighashTypes?: number[],
|
|
771
|
-
): { hash: Buffer; leafHash?: Buffer }[] {
|
|
772
|
-
if (typeof keyPair.signSchnorr !== 'function')
|
|
773
|
-
throw new Error(`Need Schnorr Signer to sign taproot input #${inputIndex}.`);
|
|
774
|
-
|
|
775
|
-
const hashesForSig = getTaprootHashesForSig(
|
|
776
|
-
inputIndex,
|
|
777
|
-
input,
|
|
778
|
-
this.data.inputs,
|
|
779
|
-
keyPair.publicKey,
|
|
780
|
-
this.__CACHE,
|
|
781
|
-
tapLeafHashToSign,
|
|
782
|
-
allowedSighashTypes,
|
|
783
|
-
);
|
|
784
|
-
|
|
785
|
-
if (!hashesForSig || !hashesForSig.length)
|
|
786
|
-
throw new Error(
|
|
787
|
-
`Can not sign for input #${inputIndex} with the key ${keyPair.publicKey.toString(
|
|
788
|
-
'hex',
|
|
789
|
-
)}`,
|
|
790
|
-
);
|
|
791
|
-
|
|
792
|
-
return hashesForSig;
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
private _finalizeInput(
|
|
796
|
-
inputIndex: number,
|
|
797
|
-
input: PsbtInput,
|
|
798
|
-
finalScriptsFunc: FinalScriptsFunc = getFinalScripts,
|
|
799
|
-
canRunChecks: boolean = true,
|
|
800
|
-
): this {
|
|
801
|
-
const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput(
|
|
802
|
-
inputIndex,
|
|
803
|
-
input,
|
|
804
|
-
this.__CACHE,
|
|
805
|
-
);
|
|
806
|
-
if (!script) throw new Error(`No script found for input #${inputIndex}`);
|
|
807
|
-
|
|
808
|
-
checkPartialSigSighashes(input);
|
|
809
|
-
|
|
810
|
-
const { finalScriptSig, finalScriptWitness } = finalScriptsFunc(
|
|
811
|
-
inputIndex,
|
|
812
|
-
input,
|
|
813
|
-
script,
|
|
814
|
-
isSegwit,
|
|
815
|
-
isP2SH,
|
|
816
|
-
isP2WSH,
|
|
817
|
-
canRunChecks,
|
|
818
|
-
);
|
|
819
|
-
|
|
820
|
-
if (finalScriptSig) this.data.updateInput(inputIndex, { finalScriptSig });
|
|
821
|
-
if (finalScriptWitness) this.data.updateInput(inputIndex, { finalScriptWitness });
|
|
822
|
-
if (!finalScriptSig && !finalScriptWitness)
|
|
823
|
-
throw new Error(`Unknown error finalizing input #${inputIndex}`);
|
|
824
|
-
|
|
825
|
-
this.data.clearFinalizedInput(inputIndex);
|
|
826
|
-
return this;
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
private _finalizeTaprootInput(
|
|
830
|
-
inputIndex: number,
|
|
831
|
-
input: PsbtInput,
|
|
832
|
-
tapLeafHashToFinalize?: Buffer,
|
|
833
|
-
finalScriptsFunc = tapScriptFinalizer,
|
|
834
|
-
): this {
|
|
835
|
-
if (!input.witnessUtxo)
|
|
836
|
-
throw new Error(`Cannot finalize input #${inputIndex}. Missing witness utxo.`);
|
|
837
|
-
|
|
838
|
-
// Check key spend first. Increased privacy and reduced block space.
|
|
839
|
-
if (input.tapKeySig) {
|
|
840
|
-
const payment = payments.p2tr({
|
|
841
|
-
output: input.witnessUtxo.script,
|
|
842
|
-
signature: input.tapKeySig,
|
|
843
|
-
});
|
|
844
|
-
const finalScriptWitness = witnessStackToScriptWitness(payment.witness!);
|
|
845
|
-
this.data.updateInput(inputIndex, { finalScriptWitness });
|
|
846
|
-
} else {
|
|
847
|
-
const { finalScriptWitness } = finalScriptsFunc(
|
|
848
|
-
inputIndex,
|
|
849
|
-
input,
|
|
850
|
-
tapLeafHashToFinalize,
|
|
851
|
-
);
|
|
852
|
-
this.data.updateInput(inputIndex, { finalScriptWitness });
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
this.data.clearFinalizedInput(inputIndex);
|
|
856
|
-
|
|
857
|
-
return this;
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
private _validateSignaturesOfInput(
|
|
861
|
-
inputIndex: number,
|
|
862
|
-
validator: ValidateSigFunction,
|
|
863
|
-
pubkey?: Buffer,
|
|
864
|
-
): boolean {
|
|
865
|
-
const input = this.data.inputs[inputIndex];
|
|
866
|
-
const partialSig = (input || {}).partialSig;
|
|
867
|
-
if (!input || !partialSig || partialSig.length < 1)
|
|
868
|
-
throw new Error('No signatures to validate');
|
|
869
|
-
if (typeof validator !== 'function')
|
|
870
|
-
throw new Error('Need validator function to validate signatures');
|
|
871
|
-
const mySigs = pubkey ? partialSig.filter((sig) => sig.pubkey.equals(pubkey)) : partialSig;
|
|
872
|
-
if (mySigs.length < 1) throw new Error('No signatures for this pubkey');
|
|
873
|
-
const results: boolean[] = [];
|
|
874
|
-
let hashCache: Buffer;
|
|
875
|
-
let scriptCache: Buffer;
|
|
876
|
-
let sighashCache: number;
|
|
877
|
-
for (const pSig of mySigs) {
|
|
878
|
-
const sig = bscript.signature.decode(pSig.signature);
|
|
879
|
-
const { hash, script } =
|
|
880
|
-
sighashCache! !== sig.hashType
|
|
881
|
-
? getHashForSig(
|
|
882
|
-
inputIndex,
|
|
883
|
-
Object.assign({}, input, {
|
|
884
|
-
sighashType: sig.hashType,
|
|
885
|
-
}),
|
|
886
|
-
this.__CACHE,
|
|
887
|
-
true,
|
|
888
|
-
)
|
|
889
|
-
: { hash: hashCache!, script: scriptCache! };
|
|
890
|
-
sighashCache = sig.hashType;
|
|
891
|
-
hashCache = hash;
|
|
892
|
-
scriptCache = script;
|
|
893
|
-
checkScriptForPubkey(pSig.pubkey, script, 'verify');
|
|
894
|
-
results.push(validator(pSig.pubkey, hash, sig.signature));
|
|
895
|
-
}
|
|
896
|
-
return results.every((res) => res === true);
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
private validateSignaturesOfTaprootInput(
|
|
900
|
-
inputIndex: number,
|
|
901
|
-
validator: ValidateSigFunction,
|
|
902
|
-
pubkey?: Buffer,
|
|
903
|
-
): boolean {
|
|
904
|
-
const input = this.data.inputs[inputIndex];
|
|
905
|
-
const tapKeySig = (input || {}).tapKeySig;
|
|
906
|
-
const tapScriptSig = (input || {}).tapScriptSig;
|
|
907
|
-
if (!input && !tapKeySig && !(tapScriptSig && !tapScriptSig.length))
|
|
908
|
-
throw new Error('No signatures to validate');
|
|
909
|
-
if (typeof validator !== 'function')
|
|
910
|
-
throw new Error('Need validator function to validate signatures');
|
|
911
|
-
|
|
912
|
-
pubkey = pubkey && toXOnly(pubkey);
|
|
913
|
-
const allHashses = pubkey
|
|
914
|
-
? getTaprootHashesForSig(inputIndex, input, this.data.inputs, pubkey, this.__CACHE)
|
|
915
|
-
: getAllTaprootHashesForSig(inputIndex, input, this.data.inputs, this.__CACHE);
|
|
916
|
-
|
|
917
|
-
if (!allHashses.length) throw new Error('No signatures for this pubkey');
|
|
918
|
-
|
|
919
|
-
const tapKeyHash = allHashses.find((h) => !h.leafHash);
|
|
920
|
-
let validationResultCount = 0;
|
|
921
|
-
if (tapKeySig && tapKeyHash) {
|
|
922
|
-
const isValidTapkeySig = validator(
|
|
923
|
-
tapKeyHash.pubkey,
|
|
924
|
-
tapKeyHash.hash,
|
|
925
|
-
trimTaprootSig(tapKeySig),
|
|
926
|
-
);
|
|
927
|
-
if (!isValidTapkeySig) return false;
|
|
928
|
-
validationResultCount++;
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
if (tapScriptSig) {
|
|
932
|
-
for (const tapSig of tapScriptSig) {
|
|
933
|
-
const tapSigHash = allHashses.find((h) => tapSig.pubkey.equals(h.pubkey));
|
|
934
|
-
if (tapSigHash) {
|
|
935
|
-
const isValidTapScriptSig = validator(
|
|
936
|
-
tapSig.pubkey,
|
|
937
|
-
tapSigHash.hash,
|
|
938
|
-
trimTaprootSig(tapSig.signature),
|
|
939
|
-
);
|
|
940
|
-
if (!isValidTapScriptSig) return false;
|
|
941
|
-
validationResultCount++;
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
return validationResultCount > 0;
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
private _signInput(
|
|
950
|
-
inputIndex: number,
|
|
951
|
-
keyPair: Signer | SignerAlternative | BIP32Interface | ECPairInterface,
|
|
952
|
-
sighashTypes: number[] = [Transaction.SIGHASH_ALL],
|
|
953
|
-
): this {
|
|
954
|
-
//hookSigner(keyPair);
|
|
955
|
-
|
|
956
|
-
const { hash, sighashType } = getHashAndSighashType(
|
|
957
|
-
this.data.inputs,
|
|
958
|
-
inputIndex,
|
|
959
|
-
keyPair.publicKey,
|
|
960
|
-
this.__CACHE,
|
|
961
|
-
sighashTypes,
|
|
962
|
-
);
|
|
963
|
-
|
|
964
|
-
const partialSig = [
|
|
965
|
-
{
|
|
966
|
-
pubkey: keyPair.publicKey,
|
|
967
|
-
signature: bscript.signature.encode(keyPair.sign(hash), sighashType),
|
|
968
|
-
},
|
|
969
|
-
];
|
|
970
|
-
|
|
971
|
-
this.data.updateInput(inputIndex, { partialSig });
|
|
972
|
-
return this;
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
private _signTaprootInput(
|
|
976
|
-
inputIndex: number,
|
|
977
|
-
input: PsbtInput,
|
|
978
|
-
keyPair: Signer | SignerAlternative | BIP32Interface | ECPairInterface,
|
|
979
|
-
tapLeafHashToSign?: Buffer,
|
|
980
|
-
allowedSighashTypes: number[] = [Transaction.SIGHASH_DEFAULT],
|
|
981
|
-
): this {
|
|
982
|
-
//hookSigner(keyPair);
|
|
983
|
-
|
|
984
|
-
const hashesForSig = this.checkTaprootHashesForSig(
|
|
985
|
-
inputIndex,
|
|
986
|
-
input,
|
|
987
|
-
keyPair,
|
|
988
|
-
tapLeafHashToSign,
|
|
989
|
-
allowedSighashTypes,
|
|
990
|
-
);
|
|
991
|
-
|
|
992
|
-
const tapKeySig: TapKeySig = hashesForSig
|
|
993
|
-
.filter((h) => !h.leafHash)
|
|
994
|
-
.map((h) =>
|
|
995
|
-
serializeTaprootSignature(keyPair.signSchnorr!(h.hash), input.sighashType),
|
|
996
|
-
)[0];
|
|
997
|
-
|
|
998
|
-
const tapScriptSig: TapScriptSig[] = hashesForSig
|
|
999
|
-
.filter((h) => !!h.leafHash)
|
|
1000
|
-
.map(
|
|
1001
|
-
(h) =>
|
|
1002
|
-
({
|
|
1003
|
-
pubkey: toXOnly(keyPair.publicKey),
|
|
1004
|
-
signature: serializeTaprootSignature(
|
|
1005
|
-
keyPair.signSchnorr!(h.hash),
|
|
1006
|
-
input.sighashType,
|
|
1007
|
-
),
|
|
1008
|
-
leafHash: h.leafHash,
|
|
1009
|
-
}) as TapScriptSig,
|
|
1010
|
-
);
|
|
1011
|
-
|
|
1012
|
-
if (tapKeySig) {
|
|
1013
|
-
this.data.updateInput(inputIndex, { tapKeySig });
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
if (tapScriptSig.length) {
|
|
1017
|
-
this.data.updateInput(inputIndex, { tapScriptSig });
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
return this;
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
private _signInputAsync(
|
|
1024
|
-
inputIndex: number,
|
|
1025
|
-
keyPair: Signer | SignerAlternative | SignerAsync | BIP32Interface | ECPairInterface,
|
|
1026
|
-
sighashTypes: number[] = [Transaction.SIGHASH_ALL],
|
|
1027
|
-
): Promise<void> {
|
|
1028
|
-
//hookSigner(keyPair);
|
|
1029
|
-
|
|
1030
|
-
const { hash, sighashType } = getHashAndSighashType(
|
|
1031
|
-
this.data.inputs,
|
|
1032
|
-
inputIndex,
|
|
1033
|
-
keyPair.publicKey,
|
|
1034
|
-
this.__CACHE,
|
|
1035
|
-
sighashTypes,
|
|
1036
|
-
);
|
|
1037
|
-
|
|
1038
|
-
return Promise.resolve(keyPair.sign(hash)).then((signature) => {
|
|
1039
|
-
const partialSig = [
|
|
1040
|
-
{
|
|
1041
|
-
pubkey: keyPair.publicKey,
|
|
1042
|
-
signature: bscript.signature.encode(signature, sighashType),
|
|
1043
|
-
},
|
|
1044
|
-
];
|
|
1045
|
-
|
|
1046
|
-
this.data.updateInput(inputIndex, { partialSig });
|
|
1047
|
-
});
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
private async _signTaprootInputAsync(
|
|
1051
|
-
inputIndex: number,
|
|
1052
|
-
input: PsbtInput,
|
|
1053
|
-
keyPair: Signer | SignerAlternative | SignerAsync | BIP32Interface | ECPairInterface,
|
|
1054
|
-
tapLeafHash?: Buffer,
|
|
1055
|
-
sighashTypes: number[] = [Transaction.SIGHASH_DEFAULT],
|
|
1056
|
-
): Promise<void> {
|
|
1057
|
-
//hookSigner(keyPair);
|
|
1058
|
-
|
|
1059
|
-
const hashesForSig = this.checkTaprootHashesForSig(
|
|
1060
|
-
inputIndex,
|
|
1061
|
-
input,
|
|
1062
|
-
keyPair,
|
|
1063
|
-
tapLeafHash,
|
|
1064
|
-
sighashTypes,
|
|
1065
|
-
);
|
|
1066
|
-
|
|
1067
|
-
const signaturePromises: Promise<
|
|
1068
|
-
{ tapKeySig: Buffer } | { tapScriptSig: TapScriptSig[] }
|
|
1069
|
-
>[] = [];
|
|
1070
|
-
|
|
1071
|
-
const tapKeyHash = hashesForSig.filter((h) => !h.leafHash)[0];
|
|
1072
|
-
if (tapKeyHash) {
|
|
1073
|
-
const tapKeySigPromise = Promise.resolve(keyPair.signSchnorr!(tapKeyHash.hash)).then(
|
|
1074
|
-
(sig) => {
|
|
1075
|
-
return {
|
|
1076
|
-
tapKeySig: serializeTaprootSignature(sig, input.sighashType),
|
|
1077
|
-
};
|
|
1078
|
-
},
|
|
1079
|
-
);
|
|
1080
|
-
signaturePromises.push(tapKeySigPromise);
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
const tapScriptHashes = hashesForSig.filter((h) => !!h.leafHash);
|
|
1084
|
-
if (tapScriptHashes.length) {
|
|
1085
|
-
const tapScriptSigPromises = tapScriptHashes.map(async (tsh) => {
|
|
1086
|
-
const signature = await keyPair.signSchnorr!(tsh.hash);
|
|
1087
|
-
|
|
1088
|
-
const tapScriptSig = [
|
|
1089
|
-
{
|
|
1090
|
-
pubkey: toXOnly(keyPair.publicKey),
|
|
1091
|
-
signature: serializeTaprootSignature(signature, input.sighashType),
|
|
1092
|
-
leafHash: tsh.leafHash,
|
|
1093
|
-
} as TapScriptSig,
|
|
1094
|
-
];
|
|
1095
|
-
|
|
1096
|
-
return { tapScriptSig };
|
|
1097
|
-
});
|
|
1098
|
-
signaturePromises.push(...tapScriptSigPromises);
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
const results = await Promise.all(signaturePromises);
|
|
1102
|
-
for (const v of results) {
|
|
1103
|
-
this.data.updateInput(inputIndex, v);
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
interface PsbtCache {
|
|
1109
|
-
__NON_WITNESS_UTXO_TX_CACHE: Transaction[];
|
|
1110
|
-
__NON_WITNESS_UTXO_BUF_CACHE: Buffer[];
|
|
1111
|
-
__TX_IN_CACHE: { [index: string]: number };
|
|
1112
|
-
__TX: Transaction;
|
|
1113
|
-
__FEE_RATE?: number;
|
|
1114
|
-
__FEE?: number;
|
|
1115
|
-
__EXTRACTED_TX?: Transaction;
|
|
1116
|
-
__UNSAFE_SIGN_NONSEGWIT: boolean;
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
export interface PsbtOptsOptional {
|
|
1120
|
-
network?: Network;
|
|
1121
|
-
maximumFeeRate?: number;
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
export interface PsbtOpts {
|
|
1125
|
-
network: Network;
|
|
1126
|
-
maximumFeeRate: number;
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
export interface PsbtInputExtended extends PsbtInput, TransactionInput {}
|
|
1130
|
-
|
|
1131
|
-
export type PsbtOutputExtended = PsbtOutputExtendedAddress | PsbtOutputExtendedScript;
|
|
1132
|
-
|
|
1133
|
-
export interface PsbtOutputExtendedAddress extends PsbtOutput {
|
|
1134
|
-
address: string;
|
|
1135
|
-
value: number;
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
export interface PsbtOutputExtendedScript extends PsbtOutput {
|
|
1139
|
-
script: Buffer;
|
|
1140
|
-
value: number;
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
interface HDSignerBase {
|
|
1144
|
-
/**
|
|
1145
|
-
* DER format compressed publicKey buffer
|
|
1146
|
-
*/
|
|
1147
|
-
publicKey: Buffer;
|
|
1148
|
-
/**
|
|
1149
|
-
* The first 4 bytes of the sha256-ripemd160 of the publicKey
|
|
1150
|
-
*/
|
|
1151
|
-
fingerprint: Buffer;
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
export interface HDSigner extends HDSignerBase {
|
|
1155
|
-
/**
|
|
1156
|
-
* The path string must match /^m(\/\d+'?)+$/
|
|
1157
|
-
* ex. m/44'/0'/0'/1/23 levels with ' must be hard derivations
|
|
1158
|
-
*/
|
|
1159
|
-
derivePath(path: string): HDSigner;
|
|
1160
|
-
|
|
1161
|
-
/**
|
|
1162
|
-
* Input hash (the "message digest") for the signature algorithm
|
|
1163
|
-
* Return a 64 byte signature (32 byte r and 32 byte s in that order)
|
|
1164
|
-
*/
|
|
1165
|
-
sign(hash: Buffer): Buffer;
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
/**
|
|
1169
|
-
* Same as above but with async sign method
|
|
1170
|
-
*/
|
|
1171
|
-
export interface HDSignerAsync extends HDSignerBase {
|
|
1172
|
-
derivePath(path: string): HDSignerAsync;
|
|
1173
|
-
|
|
1174
|
-
sign(hash: Buffer): Promise<Buffer>;
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
export interface SignerAlternative {
|
|
1178
|
-
publicKey: Buffer;
|
|
1179
|
-
lowR: boolean;
|
|
1180
|
-
|
|
1181
|
-
sign(hash: Buffer, lowR?: boolean): Buffer;
|
|
1182
|
-
|
|
1183
|
-
verify(hash: Buffer, signature: Buffer): boolean;
|
|
1184
|
-
|
|
1185
|
-
signSchnorr(hash: Buffer): Buffer;
|
|
1186
|
-
|
|
1187
|
-
verifySchnorr(hash: Buffer, signature: Buffer): boolean;
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
|
-
export interface Signer {
|
|
1191
|
-
publicKey: Buffer;
|
|
1192
|
-
network?: Network;
|
|
1193
|
-
|
|
1194
|
-
sign(hash: Buffer, lowR?: boolean): Buffer;
|
|
1195
|
-
|
|
1196
|
-
signSchnorr?(hash: Buffer): Buffer;
|
|
1197
|
-
|
|
1198
|
-
getPublicKey?(): Buffer;
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
export interface SignerAsync {
|
|
1202
|
-
publicKey: Buffer;
|
|
1203
|
-
network?: Network;
|
|
1204
|
-
|
|
1205
|
-
sign(hash: Buffer, lowR?: boolean): Promise<Buffer>;
|
|
1206
|
-
|
|
1207
|
-
signSchnorr?(hash: Buffer): Promise<Buffer>;
|
|
1208
|
-
|
|
1209
|
-
getPublicKey?(): Buffer;
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
/**
|
|
1213
|
-
* This function is needed to pass to the bip174 base class's fromBuffer.
|
|
1214
|
-
* It takes the "transaction buffer" portion of the psbt buffer and returns a
|
|
1215
|
-
* Transaction (From the bip174 library) interface.
|
|
1216
|
-
*/
|
|
1217
|
-
const transactionFromBuffer: TransactionFromBuffer = (buffer: Buffer): ITransaction =>
|
|
1218
|
-
new PsbtTransaction(buffer);
|
|
1219
|
-
|
|
1220
|
-
/**
|
|
1221
|
-
* This class implements the Transaction interface from bip174 library.
|
|
1222
|
-
* It contains a bitcoinjs-lib Transaction object.
|
|
1223
|
-
*/
|
|
1224
|
-
class PsbtTransaction implements ITransaction {
|
|
1225
|
-
tx: Transaction;
|
|
1226
|
-
|
|
1227
|
-
constructor(buffer: Buffer = Buffer.from([2, 0, 0, 0, 0, 0, 0, 0, 0, 0])) {
|
|
1228
|
-
this.tx = Transaction.fromBuffer(buffer);
|
|
1229
|
-
checkTxEmpty(this.tx);
|
|
1230
|
-
Object.defineProperty(this, 'tx', {
|
|
1231
|
-
enumerable: false,
|
|
1232
|
-
writable: true,
|
|
1233
|
-
});
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
getInputOutputCounts(): {
|
|
1237
|
-
inputCount: number;
|
|
1238
|
-
outputCount: number;
|
|
1239
|
-
} {
|
|
1240
|
-
return {
|
|
1241
|
-
inputCount: this.tx.ins.length,
|
|
1242
|
-
outputCount: this.tx.outs.length,
|
|
1243
|
-
};
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
addInput(input: TransactionInput): void {
|
|
1247
|
-
if (
|
|
1248
|
-
input.hash === undefined ||
|
|
1249
|
-
input.index === undefined ||
|
|
1250
|
-
(!Buffer.isBuffer(input.hash) && typeof input.hash !== 'string') ||
|
|
1251
|
-
typeof input.index !== 'number'
|
|
1252
|
-
) {
|
|
1253
|
-
throw new Error('Error adding input.');
|
|
1254
|
-
}
|
|
1255
|
-
const hash =
|
|
1256
|
-
typeof input.hash === 'string'
|
|
1257
|
-
? reverseBuffer(Buffer.from(input.hash, 'hex'))
|
|
1258
|
-
: input.hash;
|
|
1259
|
-
|
|
1260
|
-
this.tx.addInput(hash, input.index, input.sequence);
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
addOutput(output: TransactionOutput): void {
|
|
1264
|
-
if (
|
|
1265
|
-
output.script === undefined ||
|
|
1266
|
-
output.value === undefined ||
|
|
1267
|
-
!Buffer.isBuffer(output.script) ||
|
|
1268
|
-
typeof output.value !== 'number'
|
|
1269
|
-
) {
|
|
1270
|
-
throw new Error('Error adding output.');
|
|
1271
|
-
}
|
|
1272
|
-
this.tx.addOutput(output.script, output.value);
|
|
1273
|
-
}
|
|
1274
|
-
|
|
1275
|
-
toBuffer(): Buffer {
|
|
1276
|
-
return this.tx.toBuffer();
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
|
-
function canFinalize(input: PsbtInput, script: Buffer, scriptType: string): boolean {
|
|
1281
|
-
switch (scriptType) {
|
|
1282
|
-
case 'pubkey':
|
|
1283
|
-
case 'pubkeyhash':
|
|
1284
|
-
case 'witnesspubkeyhash':
|
|
1285
|
-
return hasSigs(1, input.partialSig);
|
|
1286
|
-
case 'multisig':
|
|
1287
|
-
const p2ms = payments.p2ms({ output: script });
|
|
1288
|
-
return hasSigs(p2ms.m!, input.partialSig, p2ms.pubkeys);
|
|
1289
|
-
case 'nonstandard':
|
|
1290
|
-
return true;
|
|
1291
|
-
default:
|
|
1292
|
-
return false;
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
|
|
1296
|
-
function checkCache(cache: PsbtCache): void {
|
|
1297
|
-
if (cache.__UNSAFE_SIGN_NONSEGWIT !== false) {
|
|
1298
|
-
throw new Error('Not BIP174 compliant, can not export');
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
|
|
1302
|
-
function hasSigs(neededSigs: number, partialSig?: any[], pubkeys?: Buffer[]): boolean {
|
|
1303
|
-
if (!partialSig) return false;
|
|
1304
|
-
let sigs: any;
|
|
1305
|
-
if (pubkeys) {
|
|
1306
|
-
sigs = pubkeys
|
|
1307
|
-
.map((pkey) => {
|
|
1308
|
-
const pubkey = compressPubkey(pkey);
|
|
1309
|
-
return partialSig.find((pSig) => pSig.pubkey.equals(pubkey));
|
|
1310
|
-
})
|
|
1311
|
-
.filter((v) => !!v);
|
|
1312
|
-
} else {
|
|
1313
|
-
sigs = partialSig;
|
|
1314
|
-
}
|
|
1315
|
-
if (sigs.length > neededSigs) throw new Error('Too many signatures');
|
|
1316
|
-
return sigs.length === neededSigs;
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
function isFinalized(input: PsbtInput): boolean {
|
|
1320
|
-
return !!input.finalScriptSig || !!input.finalScriptWitness;
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
|
-
function bip32DerivationIsMine(root: HDSigner): (d: Bip32Derivation) => boolean {
|
|
1324
|
-
return (d: Bip32Derivation): boolean => {
|
|
1325
|
-
if (!d.masterFingerprint.equals(root.fingerprint)) return false;
|
|
1326
|
-
if (!root.derivePath(d.path).publicKey.equals(d.pubkey)) return false;
|
|
1327
|
-
return true;
|
|
1328
|
-
};
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
function check32Bit(num: number): void {
|
|
1332
|
-
if (typeof num !== 'number' || num !== Math.floor(num) || num > 0xffffffff || num < 0) {
|
|
1333
|
-
throw new Error('Invalid 32 bit integer');
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
function checkFees(psbt: Psbt, cache: PsbtCache, opts: PsbtOpts): void {
|
|
1338
|
-
const feeRate = cache.__FEE_RATE || psbt.getFeeRate();
|
|
1339
|
-
const vsize = cache.__EXTRACTED_TX!.virtualSize();
|
|
1340
|
-
const satoshis = feeRate * vsize;
|
|
1341
|
-
if (feeRate >= opts.maximumFeeRate) {
|
|
1342
|
-
throw new Error(
|
|
1343
|
-
`Warning: You are paying around ${(satoshis / 1e8).toFixed(8)} in ` +
|
|
1344
|
-
`fees, which is ${feeRate} satoshi per byte for a transaction ` +
|
|
1345
|
-
`with a VSize of ${vsize} bytes (segwit counted as 0.25 byte per ` +
|
|
1346
|
-
`byte). Use setMaximumFeeRate method to raise your threshold, or ` +
|
|
1347
|
-
`pass true to the first arg of extractTransaction.`,
|
|
1348
|
-
);
|
|
1349
|
-
}
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
|
-
function checkInputsForPartialSig(inputs: PsbtInput[], action: string): void {
|
|
1353
|
-
inputs.forEach((input) => {
|
|
1354
|
-
const throws = isTaprootInput(input)
|
|
1355
|
-
? checkTaprootInputForSigs(input, action)
|
|
1356
|
-
: checkInputForSig(input, action);
|
|
1357
|
-
if (throws) throw new Error('Can not modify transaction, signatures exist.');
|
|
1358
|
-
});
|
|
1359
|
-
}
|
|
1360
|
-
|
|
1361
|
-
function checkPartialSigSighashes(input: PsbtInput): void {
|
|
1362
|
-
if (!input.sighashType || !input.partialSig) return;
|
|
1363
|
-
const { partialSig, sighashType } = input;
|
|
1364
|
-
partialSig.forEach((pSig) => {
|
|
1365
|
-
const { hashType } = bscript.signature.decode(pSig.signature);
|
|
1366
|
-
if (sighashType !== hashType) {
|
|
1367
|
-
throw new Error('Signature sighash does not match input sighash type');
|
|
1368
|
-
}
|
|
1369
|
-
});
|
|
1370
|
-
}
|
|
1371
|
-
|
|
1372
|
-
function checkScriptForPubkey(pubkey: Buffer, script: Buffer, action: string): void {
|
|
1373
|
-
if (!pubkeyInScript(pubkey, script)) {
|
|
1374
|
-
throw new Error(`Can not ${action} for this input with the key ${pubkey.toString('hex')}`);
|
|
1375
|
-
}
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
|
-
function checkTxEmpty(tx: Transaction): void {
|
|
1379
|
-
const isEmpty = tx.ins.every(
|
|
1380
|
-
(input) =>
|
|
1381
|
-
input.script &&
|
|
1382
|
-
input.script.length === 0 &&
|
|
1383
|
-
input.witness &&
|
|
1384
|
-
input.witness.length === 0,
|
|
1385
|
-
);
|
|
1386
|
-
if (!isEmpty) {
|
|
1387
|
-
throw new Error('Format Error: Transaction ScriptSigs are not empty');
|
|
1388
|
-
}
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
function checkTxForDupeIns(tx: Transaction, cache: PsbtCache): void {
|
|
1392
|
-
tx.ins.forEach((input) => {
|
|
1393
|
-
checkTxInputCache(cache, input);
|
|
1394
|
-
});
|
|
1395
|
-
}
|
|
1396
|
-
|
|
1397
|
-
function checkTxInputCache(cache: PsbtCache, input: { hash: Buffer; index: number }): void {
|
|
1398
|
-
const key = reverseBuffer(Buffer.from(input.hash)).toString('hex') + ':' + input.index;
|
|
1399
|
-
if (cache.__TX_IN_CACHE[key]) throw new Error('Duplicate input detected.');
|
|
1400
|
-
cache.__TX_IN_CACHE[key] = 1;
|
|
1401
|
-
}
|
|
1402
|
-
|
|
1403
|
-
function scriptCheckerFactory(
|
|
1404
|
-
payment: (a: Payment, opts?: PaymentOpts) => Payment,
|
|
1405
|
-
paymentScriptName: string,
|
|
1406
|
-
): (idx: number, scriptPubKey: Buffer, redeemScript: Buffer, ioType: 'input' | 'output') => void {
|
|
1407
|
-
return (
|
|
1408
|
-
inputIndex: number,
|
|
1409
|
-
scriptPubKey: Buffer,
|
|
1410
|
-
redeemScript: Buffer,
|
|
1411
|
-
ioType: 'input' | 'output',
|
|
1412
|
-
): void => {
|
|
1413
|
-
const redeemScriptOutput = payment({
|
|
1414
|
-
redeem: { output: redeemScript },
|
|
1415
|
-
}).output as Buffer;
|
|
1416
|
-
|
|
1417
|
-
if (!scriptPubKey.equals(redeemScriptOutput)) {
|
|
1418
|
-
throw new Error(
|
|
1419
|
-
`${paymentScriptName} for ${ioType} #${inputIndex} doesn't match the scriptPubKey in the prevout`,
|
|
1420
|
-
);
|
|
1421
|
-
}
|
|
1422
|
-
};
|
|
1423
|
-
}
|
|
1424
|
-
|
|
1425
|
-
const checkRedeemScript = scriptCheckerFactory(payments.p2sh, 'Redeem script');
|
|
1426
|
-
const checkWitnessScript = scriptCheckerFactory(payments.p2wsh, 'Witness script');
|
|
1427
|
-
|
|
1428
|
-
type TxCacheNumberKey = '__FEE_RATE' | '__FEE';
|
|
1429
|
-
|
|
1430
|
-
function getTxCacheValue(
|
|
1431
|
-
key: TxCacheNumberKey,
|
|
1432
|
-
name: string,
|
|
1433
|
-
inputs: PsbtInput[],
|
|
1434
|
-
c: PsbtCache,
|
|
1435
|
-
disableOutputChecks: boolean = false,
|
|
1436
|
-
): number | undefined {
|
|
1437
|
-
if (!inputs.every(isFinalized)) throw new Error(`PSBT must be finalized to calculate ${name}`);
|
|
1438
|
-
if (key === '__FEE_RATE' && c.__FEE_RATE) return c.__FEE_RATE;
|
|
1439
|
-
if (key === '__FEE' && c.__FEE) return c.__FEE;
|
|
1440
|
-
let tx: Transaction;
|
|
1441
|
-
let mustFinalize = true;
|
|
1442
|
-
if (c.__EXTRACTED_TX) {
|
|
1443
|
-
tx = c.__EXTRACTED_TX;
|
|
1444
|
-
mustFinalize = false;
|
|
1445
|
-
} else {
|
|
1446
|
-
tx = c.__TX.clone();
|
|
1447
|
-
}
|
|
1448
|
-
inputFinalizeGetAmts(inputs, tx, c, mustFinalize, disableOutputChecks);
|
|
1449
|
-
if (key === '__FEE_RATE') return c.__FEE_RATE!;
|
|
1450
|
-
else if (key === '__FEE') return c.__FEE!;
|
|
1451
|
-
}
|
|
1452
|
-
|
|
1453
|
-
/**
|
|
1454
|
-
* This function must do two things:
|
|
1455
|
-
* 1. Check if the `input` can be finalized. If it can not be finalized, throw.
|
|
1456
|
-
* ie. `Can not finalize input #${inputIndex}`
|
|
1457
|
-
* 2. Create the finalScriptSig and finalScriptWitness Buffers.
|
|
1458
|
-
*/
|
|
1459
|
-
type FinalScriptsFunc = (
|
|
1460
|
-
inputIndex: number, // Which input is it?
|
|
1461
|
-
input: PsbtInput, // The PSBT input contents
|
|
1462
|
-
script: Buffer, // The "meaningful" locking script Buffer (redeemScript for P2SH etc.)
|
|
1463
|
-
isSegwit: boolean, // Is it segwit?
|
|
1464
|
-
isP2SH: boolean, // Is it P2SH?
|
|
1465
|
-
isP2WSH: boolean, // Is it P2WSH?
|
|
1466
|
-
canRunChecks: boolean,
|
|
1467
|
-
) => {
|
|
1468
|
-
finalScriptSig: Buffer | undefined;
|
|
1469
|
-
finalScriptWitness: Buffer | undefined;
|
|
1470
|
-
};
|
|
1471
|
-
type FinalTaprootScriptsFunc = (
|
|
1472
|
-
inputIndex: number, // Which input is it?
|
|
1473
|
-
input: PsbtInput, // The PSBT input contents
|
|
1474
|
-
tapLeafHashToFinalize?: Buffer, // Only finalize this specific leaf
|
|
1475
|
-
) => {
|
|
1476
|
-
finalScriptWitness: Buffer | undefined;
|
|
1477
|
-
};
|
|
1478
|
-
|
|
1479
|
-
export function getFinalScripts(
|
|
1480
|
-
inputIndex: number,
|
|
1481
|
-
input: PsbtInput,
|
|
1482
|
-
script: Buffer,
|
|
1483
|
-
isSegwit: boolean,
|
|
1484
|
-
isP2SH: boolean,
|
|
1485
|
-
isP2WSH: boolean,
|
|
1486
|
-
canRunChecks: boolean = true,
|
|
1487
|
-
): {
|
|
1488
|
-
finalScriptSig: Buffer | undefined;
|
|
1489
|
-
finalScriptWitness: Buffer | undefined;
|
|
1490
|
-
} {
|
|
1491
|
-
const scriptType = classifyScript(script);
|
|
1492
|
-
if (!canFinalize(input, script, scriptType) && canRunChecks) {
|
|
1493
|
-
throw new Error(`Can not finalize input #${inputIndex}`);
|
|
1494
|
-
}
|
|
1495
|
-
|
|
1496
|
-
return prepareFinalScripts(script, scriptType, input.partialSig!, isSegwit, isP2SH, isP2WSH);
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
export function prepareFinalScripts(
|
|
1500
|
-
script: Buffer,
|
|
1501
|
-
scriptType: string,
|
|
1502
|
-
partialSig: PartialSig[],
|
|
1503
|
-
isSegwit: boolean,
|
|
1504
|
-
isP2SH: boolean,
|
|
1505
|
-
isP2WSH: boolean,
|
|
1506
|
-
): {
|
|
1507
|
-
finalScriptSig: Buffer | undefined;
|
|
1508
|
-
finalScriptWitness: Buffer | undefined;
|
|
1509
|
-
} {
|
|
1510
|
-
let finalScriptSig: Buffer | undefined;
|
|
1511
|
-
let finalScriptWitness: Buffer | undefined;
|
|
1512
|
-
|
|
1513
|
-
// Wow, the payments API is very handy
|
|
1514
|
-
const payment: payments.Payment = getPayment(script, scriptType, partialSig);
|
|
1515
|
-
const p2wsh = !isP2WSH ? null : payments.p2wsh({ redeem: payment });
|
|
1516
|
-
const p2sh = !isP2SH ? null : payments.p2sh({ redeem: p2wsh || payment });
|
|
1517
|
-
|
|
1518
|
-
if (isSegwit) {
|
|
1519
|
-
if (p2wsh) {
|
|
1520
|
-
finalScriptWitness = witnessStackToScriptWitness(p2wsh.witness!);
|
|
1521
|
-
} else {
|
|
1522
|
-
finalScriptWitness = witnessStackToScriptWitness(payment.witness!);
|
|
1523
|
-
}
|
|
1524
|
-
if (p2sh) {
|
|
1525
|
-
finalScriptSig = p2sh.input;
|
|
1526
|
-
}
|
|
1527
|
-
} else {
|
|
1528
|
-
if (p2sh) {
|
|
1529
|
-
finalScriptSig = p2sh.input;
|
|
1530
|
-
} else {
|
|
1531
|
-
finalScriptSig = payment.input;
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
return {
|
|
1535
|
-
finalScriptSig,
|
|
1536
|
-
finalScriptWitness,
|
|
1537
|
-
};
|
|
1538
|
-
}
|
|
1539
|
-
|
|
1540
|
-
function getHashAndSighashType(
|
|
1541
|
-
inputs: PsbtInput[],
|
|
1542
|
-
inputIndex: number,
|
|
1543
|
-
pubkey: Buffer,
|
|
1544
|
-
cache: PsbtCache,
|
|
1545
|
-
sighashTypes: number[],
|
|
1546
|
-
): {
|
|
1547
|
-
hash: Buffer;
|
|
1548
|
-
sighashType: number;
|
|
1549
|
-
} {
|
|
1550
|
-
const input = checkForInput(inputs, inputIndex);
|
|
1551
|
-
const { hash, sighashType, script } = getHashForSig(
|
|
1552
|
-
inputIndex,
|
|
1553
|
-
input,
|
|
1554
|
-
cache,
|
|
1555
|
-
false,
|
|
1556
|
-
sighashTypes,
|
|
1557
|
-
);
|
|
1558
|
-
|
|
1559
|
-
checkScriptForPubkey(pubkey, script, 'sign');
|
|
1560
|
-
return {
|
|
1561
|
-
hash,
|
|
1562
|
-
sighashType,
|
|
1563
|
-
};
|
|
1564
|
-
}
|
|
1565
|
-
|
|
1566
|
-
function getHashForSig(
|
|
1567
|
-
inputIndex: number,
|
|
1568
|
-
input: PsbtInput,
|
|
1569
|
-
cache: PsbtCache,
|
|
1570
|
-
forValidate: boolean,
|
|
1571
|
-
sighashTypes?: number[],
|
|
1572
|
-
): {
|
|
1573
|
-
script: Buffer;
|
|
1574
|
-
hash: Buffer;
|
|
1575
|
-
sighashType: number;
|
|
1576
|
-
} {
|
|
1577
|
-
const unsignedTx = cache.__TX;
|
|
1578
|
-
const sighashType = input.sighashType || Transaction.SIGHASH_ALL;
|
|
1579
|
-
checkSighashTypeAllowed(sighashType, sighashTypes);
|
|
1580
|
-
|
|
1581
|
-
let hash: Buffer;
|
|
1582
|
-
let prevout: Output;
|
|
1583
|
-
|
|
1584
|
-
if (input.nonWitnessUtxo) {
|
|
1585
|
-
const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(cache, input, inputIndex);
|
|
1586
|
-
|
|
1587
|
-
const prevoutHash = unsignedTx.ins[inputIndex].hash;
|
|
1588
|
-
const utxoHash = nonWitnessUtxoTx.getHash();
|
|
1589
|
-
|
|
1590
|
-
// If a non-witness UTXO is provided, its hash must match the hash specified in the prevout
|
|
1591
|
-
if (!prevoutHash.equals(utxoHash)) {
|
|
1592
|
-
throw new Error(
|
|
1593
|
-
`Non-witness UTXO hash for input #${inputIndex} doesn't match the hash specified in the prevout`,
|
|
1594
|
-
);
|
|
1595
|
-
}
|
|
1596
|
-
|
|
1597
|
-
const prevoutIndex = unsignedTx.ins[inputIndex].index;
|
|
1598
|
-
prevout = nonWitnessUtxoTx.outs[prevoutIndex] as Output;
|
|
1599
|
-
} else if (input.witnessUtxo) {
|
|
1600
|
-
prevout = input.witnessUtxo;
|
|
1601
|
-
} else {
|
|
1602
|
-
throw new Error('Need a Utxo input item for signing');
|
|
1603
|
-
}
|
|
1604
|
-
|
|
1605
|
-
const { meaningfulScript, type } = getMeaningfulScript(
|
|
1606
|
-
prevout.script,
|
|
1607
|
-
inputIndex,
|
|
1608
|
-
'input',
|
|
1609
|
-
input.redeemScript,
|
|
1610
|
-
input.witnessScript,
|
|
1611
|
-
);
|
|
1612
|
-
|
|
1613
|
-
if (['p2sh-p2wsh', 'p2wsh'].indexOf(type) >= 0) {
|
|
1614
|
-
hash = unsignedTx.hashForWitnessV0(
|
|
1615
|
-
inputIndex,
|
|
1616
|
-
meaningfulScript,
|
|
1617
|
-
prevout.value,
|
|
1618
|
-
sighashType,
|
|
1619
|
-
);
|
|
1620
|
-
} else if (isP2WPKH(meaningfulScript)) {
|
|
1621
|
-
// P2WPKH uses the P2PKH template for prevoutScript when signing
|
|
1622
|
-
const signingScript = payments.p2pkh({
|
|
1623
|
-
hash: meaningfulScript.slice(2),
|
|
1624
|
-
}).output!;
|
|
1625
|
-
hash = unsignedTx.hashForWitnessV0(inputIndex, signingScript, prevout.value, sighashType);
|
|
1626
|
-
} else {
|
|
1627
|
-
// non-segwit
|
|
1628
|
-
if (input.nonWitnessUtxo === undefined && cache.__UNSAFE_SIGN_NONSEGWIT === false)
|
|
1629
|
-
throw new Error(
|
|
1630
|
-
`Input #${inputIndex} has witnessUtxo but non-segwit script: ` +
|
|
1631
|
-
`${meaningfulScript.toString('hex')}`,
|
|
1632
|
-
);
|
|
1633
|
-
if (!forValidate && cache.__UNSAFE_SIGN_NONSEGWIT !== false)
|
|
1634
|
-
console.warn(
|
|
1635
|
-
'Warning: Signing non-segwit inputs without the full parent transaction ' +
|
|
1636
|
-
'means there is a chance that a miner could feed you incorrect information ' +
|
|
1637
|
-
"to trick you into paying large fees. This behavior is the same as Psbt's predecessor " +
|
|
1638
|
-
'(TransactionBuilder - now removed) when signing non-segwit scripts. You are not ' +
|
|
1639
|
-
'able to export this Psbt with toBuffer|toBase64|toHex since it is not ' +
|
|
1640
|
-
'BIP174 compliant.\n*********************\nPROCEED WITH CAUTION!\n' +
|
|
1641
|
-
'*********************',
|
|
1642
|
-
);
|
|
1643
|
-
hash = unsignedTx.hashForSignature(inputIndex, meaningfulScript, sighashType);
|
|
1644
|
-
}
|
|
1645
|
-
|
|
1646
|
-
return {
|
|
1647
|
-
script: meaningfulScript,
|
|
1648
|
-
sighashType,
|
|
1649
|
-
hash,
|
|
1650
|
-
};
|
|
1651
|
-
}
|
|
1652
|
-
|
|
1653
|
-
function getAllTaprootHashesForSig(
|
|
1654
|
-
inputIndex: number,
|
|
1655
|
-
input: PsbtInput,
|
|
1656
|
-
inputs: PsbtInput[],
|
|
1657
|
-
cache: PsbtCache,
|
|
1658
|
-
): { pubkey: Buffer; hash: Buffer; leafHash?: Buffer }[] {
|
|
1659
|
-
const allPublicKeys: Buffer[] = [];
|
|
1660
|
-
if (input.tapInternalKey) {
|
|
1661
|
-
const key = getPrevoutTaprootKey(inputIndex, input, cache);
|
|
1662
|
-
if (key) {
|
|
1663
|
-
allPublicKeys.push(key);
|
|
1664
|
-
}
|
|
1665
|
-
}
|
|
1666
|
-
|
|
1667
|
-
if (input.tapScriptSig) {
|
|
1668
|
-
const tapScriptPubkeys = input.tapScriptSig.map((tss) => tss.pubkey);
|
|
1669
|
-
allPublicKeys.push(...tapScriptPubkeys);
|
|
1670
|
-
}
|
|
1671
|
-
|
|
1672
|
-
const allHashes = allPublicKeys.map((pubicKey) =>
|
|
1673
|
-
getTaprootHashesForSig(inputIndex, input, inputs, pubicKey, cache),
|
|
1674
|
-
);
|
|
1675
|
-
|
|
1676
|
-
return allHashes.flat();
|
|
1677
|
-
}
|
|
1678
|
-
|
|
1679
|
-
function getPrevoutTaprootKey(
|
|
1680
|
-
inputIndex: number,
|
|
1681
|
-
input: PsbtInput,
|
|
1682
|
-
cache: PsbtCache,
|
|
1683
|
-
): Buffer | null {
|
|
1684
|
-
const { script } = getScriptAndAmountFromUtxo(inputIndex, input, cache);
|
|
1685
|
-
return isP2TR(script) ? script.subarray(2, 34) : null;
|
|
1686
|
-
}
|
|
1687
|
-
|
|
1688
|
-
function trimTaprootSig(signature: Buffer): Buffer {
|
|
1689
|
-
return signature.length === 64 ? signature : signature.subarray(0, 64);
|
|
1690
|
-
}
|
|
1691
|
-
|
|
1692
|
-
function getTaprootHashesForSig(
|
|
1693
|
-
inputIndex: number,
|
|
1694
|
-
input: PsbtInput,
|
|
1695
|
-
inputs: PsbtInput[],
|
|
1696
|
-
pubkey: Buffer,
|
|
1697
|
-
cache: PsbtCache,
|
|
1698
|
-
tapLeafHashToSign?: Buffer,
|
|
1699
|
-
allowedSighashTypes?: number[],
|
|
1700
|
-
): { pubkey: Buffer; hash: Buffer; leafHash?: Buffer }[] {
|
|
1701
|
-
const unsignedTx = cache.__TX;
|
|
1702
|
-
|
|
1703
|
-
const sighashType = input.sighashType || Transaction.SIGHASH_DEFAULT;
|
|
1704
|
-
checkSighashTypeAllowed(sighashType, allowedSighashTypes);
|
|
1705
|
-
|
|
1706
|
-
const prevOuts: Output[] = inputs.map((i, index) =>
|
|
1707
|
-
getScriptAndAmountFromUtxo(index, i, cache),
|
|
1708
|
-
);
|
|
1709
|
-
const signingScripts = prevOuts.map((o) => o.script);
|
|
1710
|
-
const values = prevOuts.map((o) => o.value);
|
|
1711
|
-
|
|
1712
|
-
const hashes: { pubkey: Buffer; hash: Buffer; leafHash?: Buffer }[] = [];
|
|
1713
|
-
if (input.tapInternalKey && !tapLeafHashToSign) {
|
|
1714
|
-
const outputKey = getPrevoutTaprootKey(inputIndex, input, cache) || Buffer.from([]);
|
|
1715
|
-
if (toXOnly(pubkey).equals(outputKey)) {
|
|
1716
|
-
const tapKeyHash = unsignedTx.hashForWitnessV1(
|
|
1717
|
-
inputIndex,
|
|
1718
|
-
signingScripts,
|
|
1719
|
-
values,
|
|
1720
|
-
sighashType,
|
|
1721
|
-
);
|
|
1722
|
-
hashes.push({ pubkey, hash: tapKeyHash });
|
|
1723
|
-
}
|
|
1724
|
-
}
|
|
1725
|
-
|
|
1726
|
-
const tapLeafHashes = (input.tapLeafScript || [])
|
|
1727
|
-
.filter((tapLeaf) => pubkeyInScript(pubkey, tapLeaf.script))
|
|
1728
|
-
.map((tapLeaf) => {
|
|
1729
|
-
const hash = tapleafHash({
|
|
1730
|
-
output: tapLeaf.script,
|
|
1731
|
-
version: tapLeaf.leafVersion,
|
|
1732
|
-
});
|
|
1733
|
-
return Object.assign({ hash }, tapLeaf);
|
|
1734
|
-
})
|
|
1735
|
-
.filter((tapLeaf) => !tapLeafHashToSign || tapLeafHashToSign.equals(tapLeaf.hash))
|
|
1736
|
-
.map((tapLeaf) => {
|
|
1737
|
-
const tapScriptHash = unsignedTx.hashForWitnessV1(
|
|
1738
|
-
inputIndex,
|
|
1739
|
-
signingScripts,
|
|
1740
|
-
values,
|
|
1741
|
-
sighashType,
|
|
1742
|
-
tapLeaf.hash,
|
|
1743
|
-
);
|
|
1744
|
-
|
|
1745
|
-
return {
|
|
1746
|
-
pubkey,
|
|
1747
|
-
hash: tapScriptHash,
|
|
1748
|
-
leafHash: tapLeaf.hash,
|
|
1749
|
-
};
|
|
1750
|
-
});
|
|
1751
|
-
|
|
1752
|
-
return hashes.concat(tapLeafHashes);
|
|
1753
|
-
}
|
|
1754
|
-
|
|
1755
|
-
function checkSighashTypeAllowed(sighashType: number, sighashTypes?: number[]): void {
|
|
1756
|
-
if (sighashTypes && sighashTypes.indexOf(sighashType) < 0) {
|
|
1757
|
-
const str = sighashTypeToString(sighashType);
|
|
1758
|
-
throw new Error(
|
|
1759
|
-
`Sighash type is not allowed. Retry the sign method passing the ` +
|
|
1760
|
-
`sighashTypes array of whitelisted types. Sighash type: ${str}`,
|
|
1761
|
-
);
|
|
1762
|
-
}
|
|
1763
|
-
}
|
|
1764
|
-
|
|
1765
|
-
function getPayment(
|
|
1766
|
-
script: Buffer,
|
|
1767
|
-
scriptType: string,
|
|
1768
|
-
partialSig: PartialSig[],
|
|
1769
|
-
): payments.Payment {
|
|
1770
|
-
let payment: payments.Payment;
|
|
1771
|
-
switch (scriptType) {
|
|
1772
|
-
case 'multisig':
|
|
1773
|
-
const sigs = getSortedSigs(script, partialSig);
|
|
1774
|
-
payment = payments.p2ms({
|
|
1775
|
-
output: script,
|
|
1776
|
-
signatures: sigs,
|
|
1777
|
-
});
|
|
1778
|
-
break;
|
|
1779
|
-
case 'pubkey':
|
|
1780
|
-
payment = payments.p2pk({
|
|
1781
|
-
output: script,
|
|
1782
|
-
signature: partialSig[0].signature,
|
|
1783
|
-
});
|
|
1784
|
-
break;
|
|
1785
|
-
case 'pubkeyhash':
|
|
1786
|
-
payment = payments.p2pkh({
|
|
1787
|
-
output: script,
|
|
1788
|
-
pubkey: partialSig[0].pubkey,
|
|
1789
|
-
signature: partialSig[0].signature,
|
|
1790
|
-
});
|
|
1791
|
-
break;
|
|
1792
|
-
case 'witnesspubkeyhash':
|
|
1793
|
-
payment = payments.p2wpkh({
|
|
1794
|
-
output: script,
|
|
1795
|
-
pubkey: partialSig[0].pubkey,
|
|
1796
|
-
signature: partialSig[0].signature,
|
|
1797
|
-
});
|
|
1798
|
-
break;
|
|
1799
|
-
}
|
|
1800
|
-
return payment!;
|
|
1801
|
-
}
|
|
1802
|
-
|
|
1803
|
-
interface GetScriptReturn {
|
|
1804
|
-
script: Buffer | null;
|
|
1805
|
-
isSegwit: boolean;
|
|
1806
|
-
isP2SH: boolean;
|
|
1807
|
-
isP2WSH: boolean;
|
|
1808
|
-
}
|
|
1809
|
-
|
|
1810
|
-
function getScriptFromInput(
|
|
1811
|
-
inputIndex: number,
|
|
1812
|
-
input: PsbtInput,
|
|
1813
|
-
cache: PsbtCache,
|
|
1814
|
-
): GetScriptReturn {
|
|
1815
|
-
const unsignedTx = cache.__TX;
|
|
1816
|
-
const res: GetScriptReturn = {
|
|
1817
|
-
script: null,
|
|
1818
|
-
isSegwit: false,
|
|
1819
|
-
isP2SH: false,
|
|
1820
|
-
isP2WSH: false,
|
|
1821
|
-
};
|
|
1822
|
-
res.isP2SH = !!input.redeemScript;
|
|
1823
|
-
res.isP2WSH = !!input.witnessScript;
|
|
1824
|
-
if (input.witnessScript) {
|
|
1825
|
-
res.script = input.witnessScript;
|
|
1826
|
-
} else if (input.redeemScript) {
|
|
1827
|
-
res.script = input.redeemScript;
|
|
1828
|
-
} else {
|
|
1829
|
-
if (input.nonWitnessUtxo) {
|
|
1830
|
-
const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(cache, input, inputIndex);
|
|
1831
|
-
const prevoutIndex = unsignedTx.ins[inputIndex].index;
|
|
1832
|
-
res.script = nonWitnessUtxoTx.outs[prevoutIndex].script;
|
|
1833
|
-
} else if (input.witnessUtxo) {
|
|
1834
|
-
res.script = input.witnessUtxo.script;
|
|
1835
|
-
}
|
|
1836
|
-
}
|
|
1837
|
-
if (input.witnessScript || isP2WPKH(res.script!)) {
|
|
1838
|
-
res.isSegwit = true;
|
|
1839
|
-
}
|
|
1840
|
-
return res;
|
|
1841
|
-
}
|
|
1842
|
-
|
|
1843
|
-
function getSignersFromHD(
|
|
1844
|
-
inputIndex: number,
|
|
1845
|
-
inputs: PsbtInput[],
|
|
1846
|
-
hdKeyPair: HDSigner | HDSignerAsync,
|
|
1847
|
-
): (HDSigner | HDSignerAsync)[] {
|
|
1848
|
-
const input = checkForInput(inputs, inputIndex);
|
|
1849
|
-
if (!input.bip32Derivation || input.bip32Derivation.length === 0) {
|
|
1850
|
-
throw new Error('Need bip32Derivation to sign with HD');
|
|
1851
|
-
}
|
|
1852
|
-
const myDerivations = input.bip32Derivation
|
|
1853
|
-
.map((bipDv) => {
|
|
1854
|
-
if (bipDv.masterFingerprint.equals(hdKeyPair.fingerprint)) {
|
|
1855
|
-
return bipDv;
|
|
1856
|
-
} else {
|
|
1857
|
-
return;
|
|
1858
|
-
}
|
|
1859
|
-
})
|
|
1860
|
-
.filter((v) => !!v);
|
|
1861
|
-
if (myDerivations.length === 0) {
|
|
1862
|
-
throw new Error(
|
|
1863
|
-
'Need one bip32Derivation masterFingerprint to match the HDSigner fingerprint',
|
|
1864
|
-
);
|
|
1865
|
-
}
|
|
1866
|
-
|
|
1867
|
-
return myDerivations.map((bipDv) => {
|
|
1868
|
-
const node = hdKeyPair.derivePath(bipDv!.path);
|
|
1869
|
-
if (!bipDv!.pubkey.equals(node.publicKey)) {
|
|
1870
|
-
throw new Error('pubkey did not match bip32Derivation');
|
|
1871
|
-
}
|
|
1872
|
-
return node;
|
|
1873
|
-
});
|
|
1874
|
-
}
|
|
1875
|
-
|
|
1876
|
-
function getSortedSigs(script: Buffer, partialSig: PartialSig[]): Buffer[] {
|
|
1877
|
-
const p2ms = payments.p2ms({ output: script });
|
|
1878
|
-
// for each pubkey in order of p2ms script
|
|
1879
|
-
return p2ms
|
|
1880
|
-
.pubkeys!.map((pk) => {
|
|
1881
|
-
// filter partialSig array by pubkey being equal
|
|
1882
|
-
return (
|
|
1883
|
-
partialSig.filter((ps) => {
|
|
1884
|
-
return ps.pubkey.equals(pk);
|
|
1885
|
-
})[0] || {}
|
|
1886
|
-
).signature;
|
|
1887
|
-
// Any pubkey without a match will return undefined
|
|
1888
|
-
// this last filter removes all the undefined items in the array.
|
|
1889
|
-
})
|
|
1890
|
-
.filter((v) => !!v);
|
|
1891
|
-
}
|
|
1892
|
-
|
|
1893
|
-
function scriptWitnessToWitnessStack(buffer: Buffer): Buffer[] {
|
|
1894
|
-
let offset = 0;
|
|
1895
|
-
|
|
1896
|
-
function readSlice(n: number): Buffer {
|
|
1897
|
-
offset += n;
|
|
1898
|
-
return buffer.slice(offset - n, offset);
|
|
1899
|
-
}
|
|
1900
|
-
|
|
1901
|
-
function readVarInt(): number {
|
|
1902
|
-
const vi = varuint.decode(buffer, offset);
|
|
1903
|
-
offset += (varuint.decode as any).bytes;
|
|
1904
|
-
return vi;
|
|
1905
|
-
}
|
|
1906
|
-
|
|
1907
|
-
function readVarSlice(): Buffer {
|
|
1908
|
-
return readSlice(readVarInt());
|
|
1909
|
-
}
|
|
1910
|
-
|
|
1911
|
-
function readVector(): Buffer[] {
|
|
1912
|
-
const count = readVarInt();
|
|
1913
|
-
const vector: Buffer[] = [];
|
|
1914
|
-
for (let i = 0; i < count; i++) vector.push(readVarSlice());
|
|
1915
|
-
return vector;
|
|
1916
|
-
}
|
|
1917
|
-
|
|
1918
|
-
return readVector();
|
|
1919
|
-
}
|
|
1920
|
-
|
|
1921
|
-
function sighashTypeToString(sighashType: number): string {
|
|
1922
|
-
let text = sighashType & Transaction.SIGHASH_ANYONECANPAY ? 'SIGHASH_ANYONECANPAY | ' : '';
|
|
1923
|
-
const sigMod = sighashType & 0x1f;
|
|
1924
|
-
switch (sigMod) {
|
|
1925
|
-
case Transaction.SIGHASH_ALL:
|
|
1926
|
-
text += 'SIGHASH_ALL';
|
|
1927
|
-
break;
|
|
1928
|
-
case Transaction.SIGHASH_SINGLE:
|
|
1929
|
-
text += 'SIGHASH_SINGLE';
|
|
1930
|
-
break;
|
|
1931
|
-
case Transaction.SIGHASH_NONE:
|
|
1932
|
-
text += 'SIGHASH_NONE';
|
|
1933
|
-
break;
|
|
1934
|
-
}
|
|
1935
|
-
return text;
|
|
1936
|
-
}
|
|
1937
|
-
|
|
1938
|
-
function addNonWitnessTxCache(cache: PsbtCache, input: PsbtInput, inputIndex: number): void {
|
|
1939
|
-
cache.__NON_WITNESS_UTXO_BUF_CACHE[inputIndex] = input.nonWitnessUtxo!;
|
|
1940
|
-
cache.__NON_WITNESS_UTXO_TX_CACHE[inputIndex] = Transaction.fromBuffer(input.nonWitnessUtxo!);
|
|
1941
|
-
|
|
1942
|
-
const self = cache;
|
|
1943
|
-
const selfIndex = inputIndex;
|
|
1944
|
-
delete input.nonWitnessUtxo;
|
|
1945
|
-
Object.defineProperty(input, 'nonWitnessUtxo', {
|
|
1946
|
-
enumerable: true,
|
|
1947
|
-
get(): Buffer {
|
|
1948
|
-
const buf = self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex];
|
|
1949
|
-
const txCache = self.__NON_WITNESS_UTXO_TX_CACHE[selfIndex];
|
|
1950
|
-
if (buf !== undefined) {
|
|
1951
|
-
return buf;
|
|
1952
|
-
} else {
|
|
1953
|
-
const newBuf = txCache.toBuffer();
|
|
1954
|
-
self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] = newBuf;
|
|
1955
|
-
return newBuf;
|
|
1956
|
-
}
|
|
1957
|
-
},
|
|
1958
|
-
set(data: Buffer): void {
|
|
1959
|
-
self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] = data;
|
|
1960
|
-
},
|
|
1961
|
-
});
|
|
1962
|
-
}
|
|
1963
|
-
|
|
1964
|
-
function inputFinalizeGetAmts(
|
|
1965
|
-
inputs: PsbtInput[],
|
|
1966
|
-
tx: Transaction,
|
|
1967
|
-
cache: PsbtCache,
|
|
1968
|
-
mustFinalize: boolean,
|
|
1969
|
-
disableOutputChecks?: boolean,
|
|
1970
|
-
): void {
|
|
1971
|
-
let inputAmount = 0;
|
|
1972
|
-
inputs.forEach((input, idx) => {
|
|
1973
|
-
if (mustFinalize && input.finalScriptSig) tx.ins[idx].script = input.finalScriptSig;
|
|
1974
|
-
if (mustFinalize && input.finalScriptWitness) {
|
|
1975
|
-
tx.ins[idx].witness = scriptWitnessToWitnessStack(input.finalScriptWitness);
|
|
1976
|
-
}
|
|
1977
|
-
if (input.witnessUtxo) {
|
|
1978
|
-
inputAmount += input.witnessUtxo.value;
|
|
1979
|
-
} else if (input.nonWitnessUtxo) {
|
|
1980
|
-
const nwTx = nonWitnessUtxoTxFromCache(cache, input, idx);
|
|
1981
|
-
const vout = tx.ins[idx].index;
|
|
1982
|
-
const out = nwTx.outs[vout] as Output;
|
|
1983
|
-
inputAmount += out.value;
|
|
1984
|
-
}
|
|
1985
|
-
});
|
|
1986
|
-
const outputAmount = (tx.outs as Output[]).reduce((total, o) => total + o.value, 0);
|
|
1987
|
-
const fee = inputAmount - outputAmount;
|
|
1988
|
-
if (!disableOutputChecks) {
|
|
1989
|
-
if (fee < 0) {
|
|
1990
|
-
throw new Error('Outputs are spending more than Inputs');
|
|
1991
|
-
}
|
|
1992
|
-
}
|
|
1993
|
-
const bytes = tx.virtualSize();
|
|
1994
|
-
cache.__FEE = fee;
|
|
1995
|
-
cache.__EXTRACTED_TX = tx;
|
|
1996
|
-
cache.__FEE_RATE = Math.floor(fee / bytes);
|
|
1997
|
-
}
|
|
1998
|
-
|
|
1999
|
-
function nonWitnessUtxoTxFromCache(
|
|
2000
|
-
cache: PsbtCache,
|
|
2001
|
-
input: PsbtInput,
|
|
2002
|
-
inputIndex: number,
|
|
2003
|
-
): Transaction {
|
|
2004
|
-
const c = cache.__NON_WITNESS_UTXO_TX_CACHE;
|
|
2005
|
-
if (!c[inputIndex]) {
|
|
2006
|
-
addNonWitnessTxCache(cache, input, inputIndex);
|
|
2007
|
-
}
|
|
2008
|
-
return c[inputIndex];
|
|
2009
|
-
}
|
|
2010
|
-
|
|
2011
|
-
function getScriptFromUtxo(inputIndex: number, input: PsbtInput, cache: PsbtCache): Buffer {
|
|
2012
|
-
const { script } = getScriptAndAmountFromUtxo(inputIndex, input, cache);
|
|
2013
|
-
return script;
|
|
2014
|
-
}
|
|
2015
|
-
|
|
2016
|
-
function getScriptAndAmountFromUtxo(
|
|
2017
|
-
inputIndex: number,
|
|
2018
|
-
input: PsbtInput,
|
|
2019
|
-
cache: PsbtCache,
|
|
2020
|
-
): { script: Buffer; value: number } {
|
|
2021
|
-
if (input.witnessUtxo !== undefined) {
|
|
2022
|
-
return {
|
|
2023
|
-
script: input.witnessUtxo.script,
|
|
2024
|
-
value: input.witnessUtxo.value,
|
|
2025
|
-
};
|
|
2026
|
-
} else if (input.nonWitnessUtxo !== undefined) {
|
|
2027
|
-
const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(cache, input, inputIndex);
|
|
2028
|
-
const o = nonWitnessUtxoTx.outs[cache.__TX.ins[inputIndex].index];
|
|
2029
|
-
return { script: o.script, value: o.value };
|
|
2030
|
-
} else {
|
|
2031
|
-
throw new Error("Can't find pubkey in input without Utxo data");
|
|
2032
|
-
}
|
|
2033
|
-
}
|
|
2034
|
-
|
|
2035
|
-
function pubkeyInInput(
|
|
2036
|
-
pubkey: Buffer,
|
|
2037
|
-
input: PsbtInput,
|
|
2038
|
-
inputIndex: number,
|
|
2039
|
-
cache: PsbtCache,
|
|
2040
|
-
): boolean {
|
|
2041
|
-
const script = getScriptFromUtxo(inputIndex, input, cache);
|
|
2042
|
-
const { meaningfulScript } = getMeaningfulScript(
|
|
2043
|
-
script,
|
|
2044
|
-
inputIndex,
|
|
2045
|
-
'input',
|
|
2046
|
-
input.redeemScript,
|
|
2047
|
-
input.witnessScript,
|
|
2048
|
-
);
|
|
2049
|
-
return pubkeyInScript(pubkey, meaningfulScript);
|
|
2050
|
-
}
|
|
2051
|
-
|
|
2052
|
-
function pubkeyInOutput(
|
|
2053
|
-
pubkey: Buffer,
|
|
2054
|
-
output: PsbtOutput,
|
|
2055
|
-
outputIndex: number,
|
|
2056
|
-
cache: PsbtCache,
|
|
2057
|
-
): boolean {
|
|
2058
|
-
const script = cache.__TX.outs[outputIndex].script;
|
|
2059
|
-
const { meaningfulScript } = getMeaningfulScript(
|
|
2060
|
-
script,
|
|
2061
|
-
outputIndex,
|
|
2062
|
-
'output',
|
|
2063
|
-
output.redeemScript,
|
|
2064
|
-
output.witnessScript,
|
|
2065
|
-
);
|
|
2066
|
-
return pubkeyInScript(pubkey, meaningfulScript);
|
|
2067
|
-
}
|
|
2068
|
-
|
|
2069
|
-
function redeemFromFinalScriptSig(finalScript: Buffer | undefined): Buffer | undefined {
|
|
2070
|
-
if (!finalScript) return;
|
|
2071
|
-
const decomp = bscript.decompile(finalScript);
|
|
2072
|
-
if (!decomp) return;
|
|
2073
|
-
const lastItem = decomp[decomp.length - 1];
|
|
2074
|
-
if (!Buffer.isBuffer(lastItem) || isPubkeyLike(lastItem) || isSigLike(lastItem)) return;
|
|
2075
|
-
const sDecomp = bscript.decompile(lastItem);
|
|
2076
|
-
if (!sDecomp) return;
|
|
2077
|
-
return lastItem;
|
|
2078
|
-
}
|
|
2079
|
-
|
|
2080
|
-
function redeemFromFinalWitnessScript(finalScript: Buffer | undefined): Buffer | undefined {
|
|
2081
|
-
if (!finalScript) return;
|
|
2082
|
-
const decomp = scriptWitnessToWitnessStack(finalScript);
|
|
2083
|
-
const lastItem = decomp[decomp.length - 1];
|
|
2084
|
-
if (isPubkeyLike(lastItem)) return;
|
|
2085
|
-
const sDecomp = bscript.decompile(lastItem);
|
|
2086
|
-
if (!sDecomp) return;
|
|
2087
|
-
return lastItem;
|
|
2088
|
-
}
|
|
2089
|
-
|
|
2090
|
-
function compressPubkey(pubkey: Buffer): Buffer {
|
|
2091
|
-
if (pubkey.length === 65) {
|
|
2092
|
-
const parity = pubkey[64] & 1;
|
|
2093
|
-
const newKey = pubkey.slice(0, 33);
|
|
2094
|
-
newKey[0] = 2 | parity;
|
|
2095
|
-
return newKey;
|
|
2096
|
-
}
|
|
2097
|
-
return pubkey.slice();
|
|
2098
|
-
}
|
|
2099
|
-
|
|
2100
|
-
function isPubkeyLike(buf: Buffer): boolean {
|
|
2101
|
-
return buf.length === 33 && bscript.isCanonicalPubKey(buf);
|
|
2102
|
-
}
|
|
2103
|
-
|
|
2104
|
-
function isSigLike(buf: Buffer): boolean {
|
|
2105
|
-
return bscript.isCanonicalScriptSignature(buf);
|
|
2106
|
-
}
|
|
2107
|
-
|
|
2108
|
-
function getMeaningfulScript(
|
|
2109
|
-
script: Buffer,
|
|
2110
|
-
index: number,
|
|
2111
|
-
ioType: 'input' | 'output',
|
|
2112
|
-
redeemScript?: Buffer,
|
|
2113
|
-
witnessScript?: Buffer,
|
|
2114
|
-
): {
|
|
2115
|
-
meaningfulScript: Buffer;
|
|
2116
|
-
type: 'p2sh' | 'p2wsh' | 'p2sh-p2wsh' | 'raw';
|
|
2117
|
-
} {
|
|
2118
|
-
const isP2SH = isP2SHScript(script);
|
|
2119
|
-
const isP2SHP2WSH = isP2SH && redeemScript && isP2WSHScript(redeemScript);
|
|
2120
|
-
const isP2WSH = isP2WSHScript(script);
|
|
2121
|
-
|
|
2122
|
-
if (isP2SH && redeemScript === undefined)
|
|
2123
|
-
throw new Error('scriptPubkey is P2SH but redeemScript missing');
|
|
2124
|
-
if ((isP2WSH || isP2SHP2WSH) && witnessScript === undefined)
|
|
2125
|
-
throw new Error('scriptPubkey or redeemScript is P2WSH but witnessScript missing');
|
|
2126
|
-
|
|
2127
|
-
let meaningfulScript: Buffer;
|
|
2128
|
-
|
|
2129
|
-
if (isP2SHP2WSH) {
|
|
2130
|
-
meaningfulScript = witnessScript!;
|
|
2131
|
-
checkRedeemScript(index, script, redeemScript!, ioType);
|
|
2132
|
-
checkWitnessScript(index, redeemScript!, witnessScript!, ioType);
|
|
2133
|
-
checkInvalidP2WSH(meaningfulScript);
|
|
2134
|
-
} else if (isP2WSH) {
|
|
2135
|
-
meaningfulScript = witnessScript!;
|
|
2136
|
-
checkWitnessScript(index, script, witnessScript!, ioType);
|
|
2137
|
-
checkInvalidP2WSH(meaningfulScript);
|
|
2138
|
-
} else if (isP2SH) {
|
|
2139
|
-
meaningfulScript = redeemScript!;
|
|
2140
|
-
checkRedeemScript(index, script, redeemScript!, ioType);
|
|
2141
|
-
} else {
|
|
2142
|
-
meaningfulScript = script;
|
|
2143
|
-
}
|
|
2144
|
-
return {
|
|
2145
|
-
meaningfulScript,
|
|
2146
|
-
type: isP2SHP2WSH ? 'p2sh-p2wsh' : isP2SH ? 'p2sh' : isP2WSH ? 'p2wsh' : 'raw',
|
|
2147
|
-
};
|
|
2148
|
-
}
|
|
2149
|
-
|
|
2150
|
-
function checkInvalidP2WSH(script: Buffer): void {
|
|
2151
|
-
if (isP2WPKH(script) || isP2SHScript(script)) {
|
|
2152
|
-
throw new Error('P2WPKH or P2SH can not be contained within P2WSH');
|
|
2153
|
-
}
|
|
2154
|
-
}
|
|
2155
|
-
|
|
2156
|
-
type AllScriptType =
|
|
2157
|
-
| 'witnesspubkeyhash'
|
|
2158
|
-
| 'pubkeyhash'
|
|
2159
|
-
| 'multisig'
|
|
2160
|
-
| 'pubkey'
|
|
2161
|
-
| 'nonstandard'
|
|
2162
|
-
| 'p2sh-witnesspubkeyhash'
|
|
2163
|
-
| 'p2sh-pubkeyhash'
|
|
2164
|
-
| 'p2sh-multisig'
|
|
2165
|
-
| 'p2sh-pubkey'
|
|
2166
|
-
| 'p2sh-nonstandard'
|
|
2167
|
-
| 'p2wsh-pubkeyhash'
|
|
2168
|
-
| 'p2wsh-multisig'
|
|
2169
|
-
| 'p2wsh-pubkey'
|
|
2170
|
-
| 'p2wsh-nonstandard'
|
|
2171
|
-
| 'p2sh-p2wsh-pubkeyhash'
|
|
2172
|
-
| 'p2sh-p2wsh-multisig'
|
|
2173
|
-
| 'p2sh-p2wsh-pubkey'
|
|
2174
|
-
| 'p2sh-p2wsh-nonstandard';
|
|
2175
|
-
type ScriptType = 'witnesspubkeyhash' | 'pubkeyhash' | 'multisig' | 'pubkey' | 'nonstandard';
|
|
2176
|
-
|
|
2177
|
-
function classifyScript(script: Buffer): ScriptType {
|
|
2178
|
-
if (isP2WPKH(script)) return 'witnesspubkeyhash';
|
|
2179
|
-
if (isP2PKH(script)) return 'pubkeyhash';
|
|
2180
|
-
if (isP2MS(script)) return 'multisig';
|
|
2181
|
-
if (isP2PK(script)) return 'pubkey';
|
|
2182
|
-
return 'nonstandard';
|
|
2183
|
-
}
|
|
2184
|
-
|
|
2185
|
-
function range(n: number): number[] {
|
|
2186
|
-
return [...Array(n).keys()];
|
|
2187
|
-
}
|
|
1
|
+
import { Psbt as PsbtBase } from 'bip174';
|
|
2
|
+
import * as varuint from 'bip174/src/lib/converter/varint.js';
|
|
3
|
+
import {
|
|
4
|
+
Bip32Derivation,
|
|
5
|
+
KeyValue,
|
|
6
|
+
PartialSig,
|
|
7
|
+
PsbtGlobal,
|
|
8
|
+
PsbtGlobalUpdate,
|
|
9
|
+
PsbtInput,
|
|
10
|
+
PsbtInputUpdate,
|
|
11
|
+
PsbtOutput,
|
|
12
|
+
PsbtOutputUpdate,
|
|
13
|
+
TapKeySig,
|
|
14
|
+
TapScriptSig,
|
|
15
|
+
Transaction as ITransaction,
|
|
16
|
+
TransactionFromBuffer,
|
|
17
|
+
} from 'bip174/src/lib/interfaces.js';
|
|
18
|
+
import { checkForInput, checkForOutput } from 'bip174/src/lib/utils.js';
|
|
19
|
+
import { BIP32Interface } from 'bip32';
|
|
20
|
+
import { ECPairInterface } from 'ecpair';
|
|
21
|
+
import { fromOutputScript, toOutputScript } from './address.js';
|
|
22
|
+
import { cloneBuffer, reverseBuffer } from './bufferutils.js';
|
|
23
|
+
import { payments } from './index.js';
|
|
24
|
+
import { bitcoin as btcNetwork, Network } from './networks.js';
|
|
25
|
+
import { tapleafHash } from './payments/bip341.js';
|
|
26
|
+
import { Payment, PaymentOpts } from './payments/index.js';
|
|
27
|
+
import {
|
|
28
|
+
checkTaprootInputFields,
|
|
29
|
+
checkTaprootInputForSigs,
|
|
30
|
+
checkTaprootOutputFields,
|
|
31
|
+
isTaprootInput,
|
|
32
|
+
serializeTaprootSignature,
|
|
33
|
+
tapScriptFinalizer,
|
|
34
|
+
toXOnly,
|
|
35
|
+
} from './psbt/bip371.js';
|
|
36
|
+
import {
|
|
37
|
+
checkInputForSig,
|
|
38
|
+
isP2MS,
|
|
39
|
+
isP2PK,
|
|
40
|
+
isP2PKH,
|
|
41
|
+
isP2SHScript,
|
|
42
|
+
isP2TR,
|
|
43
|
+
isP2WPKH,
|
|
44
|
+
isP2WSHScript,
|
|
45
|
+
pubkeyInScript,
|
|
46
|
+
witnessStackToScriptWitness,
|
|
47
|
+
} from './psbt/psbtutils.js';
|
|
48
|
+
import * as bscript from './script.js';
|
|
49
|
+
import { Output, Transaction } from './transaction.js';
|
|
50
|
+
|
|
51
|
+
export interface TransactionInput {
|
|
52
|
+
hash: string | Buffer;
|
|
53
|
+
index: number;
|
|
54
|
+
sequence?: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface PsbtTxInput extends TransactionInput {
|
|
58
|
+
hash: Buffer;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface TransactionOutput {
|
|
62
|
+
script: Buffer;
|
|
63
|
+
value: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface PsbtTxOutput extends TransactionOutput {
|
|
67
|
+
address: string | undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// msghash is 32 byte hash of preimage, signature is 64 byte compact signature (r,s 32 bytes each)
|
|
71
|
+
export type ValidateSigFunction = (pubkey: Buffer, msghash: Buffer, signature: Buffer) => boolean;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* These are the default arguments for a Psbt instance.
|
|
75
|
+
*/
|
|
76
|
+
const DEFAULT_OPTS: PsbtOpts = {
|
|
77
|
+
/**
|
|
78
|
+
* A bitcoinjs Network object. This is only used if you pass an `address`
|
|
79
|
+
* parameter to addOutput. Otherwise it is not needed and can be left default.
|
|
80
|
+
*/
|
|
81
|
+
network: btcNetwork,
|
|
82
|
+
/**
|
|
83
|
+
* When extractTransaction is called, the fee rate is checked.
|
|
84
|
+
* THIS IS NOT TO BE RELIED ON.
|
|
85
|
+
* It is only here as a last ditch effort to prevent sending a 500 BTC fee etc.
|
|
86
|
+
*/
|
|
87
|
+
maximumFeeRate: 5000, // satoshi per byte
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Not a breaking change.
|
|
91
|
+
export interface PsbtBaseExtended extends Omit<PsbtBase, 'inputs'> {
|
|
92
|
+
inputs: PsbtInput[];
|
|
93
|
+
globalMap: PsbtGlobal;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Psbt class can parse and generate a PSBT binary based off of the BIP174.
|
|
98
|
+
* There are 6 roles that this class fulfills. (Explained in BIP174)
|
|
99
|
+
*
|
|
100
|
+
* Creator: This can be done with `new Psbt()`
|
|
101
|
+
*
|
|
102
|
+
* Updater: This can be done with `psbt.addInput(input)`, `psbt.addInputs(inputs)`,
|
|
103
|
+
* `psbt.addOutput(output)`, `psbt.addOutputs(outputs)` when you are looking to
|
|
104
|
+
* add new inputs and outputs to the PSBT, and `psbt.updateGlobal(itemObject)`,
|
|
105
|
+
* `psbt.updateInput(itemObject)`, `psbt.updateOutput(itemObject)`
|
|
106
|
+
* addInput requires hash: Buffer | string; and index: number; as attributes
|
|
107
|
+
* and can also include any attributes that are used in updateInput method.
|
|
108
|
+
* addOutput requires script: Buffer; and value: number; and likewise can include
|
|
109
|
+
* data for updateOutput.
|
|
110
|
+
* For a list of what attributes should be what types. Check the bip174 library.
|
|
111
|
+
* Also, check the integration tests for some examples of usage.
|
|
112
|
+
*
|
|
113
|
+
* Signer: There are a few methods. signAllInputs and signAllInputsAsync, which will search all input
|
|
114
|
+
* information for your pubkey or pubkeyhash, and only sign inputs where it finds
|
|
115
|
+
* your info. Or you can explicitly sign a specific input with signInput and
|
|
116
|
+
* signInputAsync. For the async methods you can create a SignerAsync object
|
|
117
|
+
* and use something like a hardware wallet to sign with. (You must implement this)
|
|
118
|
+
*
|
|
119
|
+
* Combiner: psbts can be combined easily with `psbt.combine(psbt2, psbt3, psbt4 ...)`
|
|
120
|
+
* the psbt calling combine will always have precedence when a conflict occurs.
|
|
121
|
+
* Combine checks if the internal bitcoin transaction is the same, so be sure that
|
|
122
|
+
* all sequences, version, locktime, etc. are the same before combining.
|
|
123
|
+
*
|
|
124
|
+
* Input Finalizer: This role is fairly important. Not only does it need to construct
|
|
125
|
+
* the input scriptSigs and witnesses, but it SHOULD verify the signatures etc.
|
|
126
|
+
* Before running `psbt.finalizeAllInputs()` please run `psbt.validateSignaturesOfAllInputs()`
|
|
127
|
+
* Running any finalize method will delete any data in the input(s) that are no longer
|
|
128
|
+
* needed due to the finalized scripts containing the information.
|
|
129
|
+
*
|
|
130
|
+
* Transaction Extractor: This role will perform some checks before returning a
|
|
131
|
+
* Transaction object. Such as fee rate not being larger than maximumFeeRate etc.
|
|
132
|
+
*/
|
|
133
|
+
/**
|
|
134
|
+
* Psbt class can parse and generate a PSBT binary based off of the BIP174.
|
|
135
|
+
*/
|
|
136
|
+
export class Psbt {
|
|
137
|
+
private readonly __CACHE: PsbtCache;
|
|
138
|
+
private readonly opts: PsbtOpts;
|
|
139
|
+
|
|
140
|
+
constructor(
|
|
141
|
+
opts: PsbtOptsOptional = {},
|
|
142
|
+
public data: PsbtBaseExtended = new PsbtBase(new PsbtTransaction()),
|
|
143
|
+
) {
|
|
144
|
+
this.opts = Object.assign({}, DEFAULT_OPTS, opts);
|
|
145
|
+
this.__CACHE = {
|
|
146
|
+
__NON_WITNESS_UTXO_TX_CACHE: [],
|
|
147
|
+
__NON_WITNESS_UTXO_BUF_CACHE: [],
|
|
148
|
+
__TX_IN_CACHE: {},
|
|
149
|
+
// @ts-expect-error no.
|
|
150
|
+
__TX: this.data.globalMap.unsignedTx.tx, // Now TypeScript knows unsignedTx is a PsbtTransaction
|
|
151
|
+
__UNSAFE_SIGN_NONSEGWIT: false,
|
|
152
|
+
};
|
|
153
|
+
if (this.data.inputs.length === 0) this.setVersion(2);
|
|
154
|
+
|
|
155
|
+
const dpew = <T>(obj: T, attr: string, enumerable: boolean, writable: boolean): void => {
|
|
156
|
+
Object.defineProperty(obj, attr, {
|
|
157
|
+
enumerable,
|
|
158
|
+
writable,
|
|
159
|
+
});
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
dpew(this, '__CACHE', false, true);
|
|
163
|
+
dpew(this, 'opts', false, true);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
get inputCount(): number {
|
|
167
|
+
return this.data.inputs.length;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
get version(): number {
|
|
171
|
+
return this.__CACHE.__TX.version;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
set version(version: number) {
|
|
175
|
+
this.setVersion(version);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
get locktime(): number {
|
|
179
|
+
return this.__CACHE.__TX.locktime;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
set locktime(locktime: number) {
|
|
183
|
+
this.setLocktime(locktime);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
get txInputs(): PsbtTxInput[] {
|
|
187
|
+
return this.__CACHE.__TX.ins.map((input) => ({
|
|
188
|
+
hash: cloneBuffer(input.hash),
|
|
189
|
+
index: input.index,
|
|
190
|
+
sequence: input.sequence,
|
|
191
|
+
}));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
get txOutputs(): PsbtTxOutput[] {
|
|
195
|
+
return this.__CACHE.__TX.outs.map((output) => {
|
|
196
|
+
let address;
|
|
197
|
+
try {
|
|
198
|
+
address = fromOutputScript(output.script, this.opts.network);
|
|
199
|
+
} catch (_) {}
|
|
200
|
+
return {
|
|
201
|
+
script: cloneBuffer(output.script),
|
|
202
|
+
value: output.value,
|
|
203
|
+
address,
|
|
204
|
+
};
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
static fromBase64(data: string, opts: PsbtOptsOptional = {}): Psbt {
|
|
209
|
+
const buffer = Buffer.from(data, 'base64');
|
|
210
|
+
return this.fromBuffer(buffer, opts);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
static fromHex(data: string, opts: PsbtOptsOptional = {}): Psbt {
|
|
214
|
+
const buffer = Buffer.from(data, 'hex');
|
|
215
|
+
return this.fromBuffer(buffer, opts);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
static fromBuffer(buffer: Buffer, opts: PsbtOptsOptional = {}): Psbt {
|
|
219
|
+
const psbtBase = PsbtBase.fromBuffer(buffer, transactionFromBuffer);
|
|
220
|
+
const psbt = new Psbt(opts, psbtBase);
|
|
221
|
+
checkTxForDupeIns(psbt.__CACHE.__TX, psbt.__CACHE);
|
|
222
|
+
return psbt;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
combine(...those: Psbt[]): this {
|
|
226
|
+
this.data.combine(...those.map((o) => o.data));
|
|
227
|
+
return this;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
clone(): Psbt {
|
|
231
|
+
// TODO: more efficient cloning
|
|
232
|
+
return Psbt.fromBuffer(this.data.toBuffer(), JSON.parse(JSON.stringify(this.opts)));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
setMaximumFeeRate(satoshiPerByte: number): void {
|
|
236
|
+
check32Bit(satoshiPerByte); // 42.9 BTC per byte IS excessive... so throw
|
|
237
|
+
this.opts.maximumFeeRate = satoshiPerByte;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
setVersion(version: number): this {
|
|
241
|
+
check32Bit(version);
|
|
242
|
+
checkInputsForPartialSig(this.data.inputs, 'setVersion');
|
|
243
|
+
const c = this.__CACHE;
|
|
244
|
+
c.__TX.version = version;
|
|
245
|
+
c.__EXTRACTED_TX = undefined;
|
|
246
|
+
return this;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
setLocktime(locktime: number): this {
|
|
250
|
+
check32Bit(locktime);
|
|
251
|
+
checkInputsForPartialSig(this.data.inputs, 'setLocktime');
|
|
252
|
+
const c = this.__CACHE;
|
|
253
|
+
c.__TX.locktime = locktime;
|
|
254
|
+
c.__EXTRACTED_TX = undefined;
|
|
255
|
+
return this;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
setInputSequence(inputIndex: number, sequence: number): this {
|
|
259
|
+
check32Bit(sequence);
|
|
260
|
+
checkInputsForPartialSig(this.data.inputs, 'setInputSequence');
|
|
261
|
+
const c = this.__CACHE;
|
|
262
|
+
if (c.__TX.ins.length <= inputIndex) {
|
|
263
|
+
throw new Error('Input index too high');
|
|
264
|
+
}
|
|
265
|
+
c.__TX.ins[inputIndex].sequence = sequence;
|
|
266
|
+
c.__EXTRACTED_TX = undefined;
|
|
267
|
+
return this;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
addInputs(inputDatas: PsbtInputExtended[], checkPartialSigs: boolean = true): this {
|
|
271
|
+
inputDatas.forEach((inputData) => this.addInput(inputData, checkPartialSigs));
|
|
272
|
+
return this;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
addInput(inputData: PsbtInputExtended, checkPartialSigs: boolean = true): this {
|
|
276
|
+
if (!inputData || inputData.hash === undefined || inputData.index === undefined) {
|
|
277
|
+
throw new Error(
|
|
278
|
+
`Invalid arguments for Psbt.addInput. ` +
|
|
279
|
+
`Requires single object with at least [hash] and [index]`,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
checkTaprootInputFields(inputData, inputData, 'addInput');
|
|
284
|
+
|
|
285
|
+
if (checkPartialSigs) {
|
|
286
|
+
checkInputsForPartialSig(this.data.inputs, 'addInput');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (inputData.witnessScript) checkInvalidP2WSH(inputData.witnessScript);
|
|
290
|
+
const c = this.__CACHE;
|
|
291
|
+
this.data.addInput(inputData);
|
|
292
|
+
const txIn = c.__TX.ins[c.__TX.ins.length - 1];
|
|
293
|
+
checkTxInputCache(c, txIn);
|
|
294
|
+
|
|
295
|
+
const inputIndex = this.data.inputs.length - 1;
|
|
296
|
+
const input = this.data.inputs[inputIndex];
|
|
297
|
+
if (input.nonWitnessUtxo) {
|
|
298
|
+
addNonWitnessTxCache(this.__CACHE, input, inputIndex);
|
|
299
|
+
}
|
|
300
|
+
c.__FEE = undefined;
|
|
301
|
+
c.__FEE_RATE = undefined;
|
|
302
|
+
c.__EXTRACTED_TX = undefined;
|
|
303
|
+
return this;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
addOutputs(outputDatas: PsbtOutputExtended[]): this {
|
|
307
|
+
outputDatas.forEach((outputData) => this.addOutput(outputData));
|
|
308
|
+
return this;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
addOutput(outputData: PsbtOutputExtended): this {
|
|
312
|
+
if (
|
|
313
|
+
arguments.length > 1 ||
|
|
314
|
+
!outputData ||
|
|
315
|
+
outputData.value === undefined ||
|
|
316
|
+
((outputData as any).address === undefined && (outputData as any).script === undefined)
|
|
317
|
+
) {
|
|
318
|
+
throw new Error(
|
|
319
|
+
`Invalid arguments for Psbt.addOutput. ` +
|
|
320
|
+
`Requires single object with at least [script or address] and [value]`,
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
checkInputsForPartialSig(this.data.inputs, 'addOutput');
|
|
324
|
+
const { address } = outputData as any;
|
|
325
|
+
if (typeof address === 'string') {
|
|
326
|
+
const { network } = this.opts;
|
|
327
|
+
const script = toOutputScript(address, network);
|
|
328
|
+
outputData = Object.assign({}, outputData, { script });
|
|
329
|
+
}
|
|
330
|
+
checkTaprootOutputFields(outputData, outputData, 'addOutput');
|
|
331
|
+
|
|
332
|
+
const c = this.__CACHE;
|
|
333
|
+
this.data.addOutput(outputData);
|
|
334
|
+
c.__FEE = undefined;
|
|
335
|
+
c.__FEE_RATE = undefined;
|
|
336
|
+
c.__EXTRACTED_TX = undefined;
|
|
337
|
+
return this;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
extractTransaction(disableFeeCheck?: boolean, disableOutputChecks?: boolean): Transaction {
|
|
341
|
+
if (disableOutputChecks) {
|
|
342
|
+
this.data.inputs = this.data.inputs.filter((i) => !i.partialSig);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (!this.data.inputs.every(isFinalized)) throw new Error('Not finalized');
|
|
346
|
+
const c = this.__CACHE;
|
|
347
|
+
if (!disableFeeCheck) {
|
|
348
|
+
checkFees(this, c, this.opts);
|
|
349
|
+
}
|
|
350
|
+
if (c.__EXTRACTED_TX) return c.__EXTRACTED_TX;
|
|
351
|
+
const tx = c.__TX.clone();
|
|
352
|
+
inputFinalizeGetAmts(this.data.inputs, tx, c, true, disableOutputChecks);
|
|
353
|
+
return tx;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
getFeeRate(disableOutputChecks: boolean = false): number {
|
|
357
|
+
return getTxCacheValue(
|
|
358
|
+
'__FEE_RATE',
|
|
359
|
+
'fee rate',
|
|
360
|
+
this.data.inputs,
|
|
361
|
+
this.__CACHE,
|
|
362
|
+
disableOutputChecks,
|
|
363
|
+
)!;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
getFee(disableOutputChecks: boolean = false): number {
|
|
367
|
+
return getTxCacheValue(
|
|
368
|
+
'__FEE',
|
|
369
|
+
'fee',
|
|
370
|
+
this.data.inputs,
|
|
371
|
+
this.__CACHE,
|
|
372
|
+
disableOutputChecks,
|
|
373
|
+
)!;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
finalizeAllInputs(): this {
|
|
377
|
+
checkForInput(this.data.inputs, 0); // making sure we have at least one
|
|
378
|
+
range(this.data.inputs.length).forEach((idx) => this.finalizeInput(idx));
|
|
379
|
+
return this;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
finalizeInput(
|
|
383
|
+
inputIndex: number,
|
|
384
|
+
finalScriptsFunc?: FinalScriptsFunc | FinalTaprootScriptsFunc,
|
|
385
|
+
canRunChecks?: boolean,
|
|
386
|
+
): this {
|
|
387
|
+
const input = checkForInput(this.data.inputs, inputIndex);
|
|
388
|
+
if (isTaprootInput(input)) {
|
|
389
|
+
return this._finalizeTaprootInput(
|
|
390
|
+
inputIndex,
|
|
391
|
+
input,
|
|
392
|
+
undefined,
|
|
393
|
+
finalScriptsFunc as FinalTaprootScriptsFunc,
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
return this._finalizeInput(
|
|
397
|
+
inputIndex,
|
|
398
|
+
input,
|
|
399
|
+
finalScriptsFunc as FinalScriptsFunc,
|
|
400
|
+
canRunChecks ?? true,
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
finalizeTaprootInput(
|
|
405
|
+
inputIndex: number,
|
|
406
|
+
tapLeafHashToFinalize?: Buffer,
|
|
407
|
+
finalScriptsFunc: FinalTaprootScriptsFunc = tapScriptFinalizer,
|
|
408
|
+
): this {
|
|
409
|
+
const input = checkForInput(this.data.inputs, inputIndex);
|
|
410
|
+
if (isTaprootInput(input))
|
|
411
|
+
return this._finalizeTaprootInput(
|
|
412
|
+
inputIndex,
|
|
413
|
+
input,
|
|
414
|
+
tapLeafHashToFinalize,
|
|
415
|
+
finalScriptsFunc,
|
|
416
|
+
);
|
|
417
|
+
throw new Error(`Cannot finalize input #${inputIndex}. Not Taproot.`);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
getInputType(inputIndex: number): AllScriptType {
|
|
421
|
+
const input = checkForInput(this.data.inputs, inputIndex);
|
|
422
|
+
const script = getScriptFromUtxo(inputIndex, input, this.__CACHE);
|
|
423
|
+
const result = getMeaningfulScript(
|
|
424
|
+
script,
|
|
425
|
+
inputIndex,
|
|
426
|
+
'input',
|
|
427
|
+
input.redeemScript || redeemFromFinalScriptSig(input.finalScriptSig),
|
|
428
|
+
input.witnessScript || redeemFromFinalWitnessScript(input.finalScriptWitness),
|
|
429
|
+
);
|
|
430
|
+
const type = result.type === 'raw' ? '' : result.type + '-';
|
|
431
|
+
const mainType = classifyScript(result.meaningfulScript);
|
|
432
|
+
return (type + mainType) as AllScriptType;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
inputHasPubkey(inputIndex: number, pubkey: Buffer): boolean {
|
|
436
|
+
const input = checkForInput(this.data.inputs, inputIndex);
|
|
437
|
+
return pubkeyInInput(pubkey, input, inputIndex, this.__CACHE);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
inputHasHDKey(inputIndex: number, root: HDSigner): boolean {
|
|
441
|
+
const input = checkForInput(this.data.inputs, inputIndex);
|
|
442
|
+
const derivationIsMine = bip32DerivationIsMine(root);
|
|
443
|
+
return !!input.bip32Derivation && input.bip32Derivation.some(derivationIsMine);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
outputHasPubkey(outputIndex: number, pubkey: Buffer): boolean {
|
|
447
|
+
const output = checkForOutput(this.data.outputs, outputIndex);
|
|
448
|
+
return pubkeyInOutput(pubkey, output, outputIndex, this.__CACHE);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
outputHasHDKey(outputIndex: number, root: HDSigner): boolean {
|
|
452
|
+
const output = checkForOutput(this.data.outputs, outputIndex);
|
|
453
|
+
const derivationIsMine = bip32DerivationIsMine(root);
|
|
454
|
+
return !!output.bip32Derivation && output.bip32Derivation.some(derivationIsMine);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
validateSignaturesOfAllInputs(validator: ValidateSigFunction): boolean {
|
|
458
|
+
checkForInput(this.data.inputs, 0); // making sure we have at least one
|
|
459
|
+
const results = range(this.data.inputs.length).map((idx) =>
|
|
460
|
+
this.validateSignaturesOfInput(idx, validator),
|
|
461
|
+
);
|
|
462
|
+
return results.reduce((final, res) => res === true && final, true);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
validateSignaturesOfInput(
|
|
466
|
+
inputIndex: number,
|
|
467
|
+
validator: ValidateSigFunction,
|
|
468
|
+
pubkey?: Buffer,
|
|
469
|
+
): boolean {
|
|
470
|
+
const input = this.data.inputs[inputIndex];
|
|
471
|
+
if (isTaprootInput(input))
|
|
472
|
+
return this.validateSignaturesOfTaprootInput(inputIndex, validator, pubkey);
|
|
473
|
+
|
|
474
|
+
return this._validateSignaturesOfInput(inputIndex, validator, pubkey);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
signAllInputsHD(hdKeyPair: HDSigner, sighashTypes: number[] = [Transaction.SIGHASH_ALL]): this {
|
|
478
|
+
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
|
|
479
|
+
throw new Error('Need HDSigner to sign input');
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const results: boolean[] = [];
|
|
483
|
+
for (const i of range(this.data.inputs.length)) {
|
|
484
|
+
try {
|
|
485
|
+
this.signInputHD(i, hdKeyPair, sighashTypes);
|
|
486
|
+
results.push(true);
|
|
487
|
+
} catch (err) {
|
|
488
|
+
results.push(false);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (results.every((v) => v === false)) {
|
|
492
|
+
throw new Error('No inputs were signed');
|
|
493
|
+
}
|
|
494
|
+
return this;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
signAllInputsHDAsync(
|
|
498
|
+
hdKeyPair: HDSigner | HDSignerAsync,
|
|
499
|
+
sighashTypes: number[] = [Transaction.SIGHASH_ALL],
|
|
500
|
+
): Promise<void> {
|
|
501
|
+
return new Promise((resolve, reject): any => {
|
|
502
|
+
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
|
|
503
|
+
return reject(new Error('Need HDSigner to sign input'));
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const results: boolean[] = [];
|
|
507
|
+
const promises: Array<Promise<void>> = [];
|
|
508
|
+
for (const i of range(this.data.inputs.length)) {
|
|
509
|
+
promises.push(
|
|
510
|
+
this.signInputHDAsync(i, hdKeyPair, sighashTypes).then(
|
|
511
|
+
() => {
|
|
512
|
+
results.push(true);
|
|
513
|
+
},
|
|
514
|
+
() => {
|
|
515
|
+
results.push(false);
|
|
516
|
+
},
|
|
517
|
+
),
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
return Promise.all(promises).then(() => {
|
|
521
|
+
if (results.every((v) => v === false)) {
|
|
522
|
+
return reject(new Error('No inputs were signed'));
|
|
523
|
+
}
|
|
524
|
+
resolve();
|
|
525
|
+
});
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
signInputHD(
|
|
530
|
+
inputIndex: number,
|
|
531
|
+
hdKeyPair: HDSigner,
|
|
532
|
+
sighashTypes: number[] = [Transaction.SIGHASH_ALL],
|
|
533
|
+
): this {
|
|
534
|
+
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
|
|
535
|
+
throw new Error('Need HDSigner to sign input');
|
|
536
|
+
}
|
|
537
|
+
const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair) as Signer[];
|
|
538
|
+
signers.forEach((signer) => this.signInput(inputIndex, signer, sighashTypes));
|
|
539
|
+
return this;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
signInputHDAsync(
|
|
543
|
+
inputIndex: number,
|
|
544
|
+
hdKeyPair: HDSigner | HDSignerAsync,
|
|
545
|
+
sighashTypes: number[] = [Transaction.SIGHASH_ALL],
|
|
546
|
+
): Promise<void> {
|
|
547
|
+
return new Promise((resolve, reject): any => {
|
|
548
|
+
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
|
|
549
|
+
return reject(new Error('Need HDSigner to sign input'));
|
|
550
|
+
}
|
|
551
|
+
const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair);
|
|
552
|
+
const promises = signers.map((signer) =>
|
|
553
|
+
this.signInputAsync(inputIndex, signer as unknown as Signer, sighashTypes),
|
|
554
|
+
);
|
|
555
|
+
return Promise.all(promises)
|
|
556
|
+
.then(() => {
|
|
557
|
+
resolve();
|
|
558
|
+
})
|
|
559
|
+
.catch(reject);
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
signAllInputs(
|
|
564
|
+
keyPair: Signer | SignerAlternative | BIP32Interface | ECPairInterface,
|
|
565
|
+
sighashTypes?: number[],
|
|
566
|
+
): this {
|
|
567
|
+
if (!keyPair || !keyPair.publicKey) throw new Error('Need Signer to sign input');
|
|
568
|
+
|
|
569
|
+
// TODO: Add a pubkey/pubkeyhash cache to each input
|
|
570
|
+
// as input information is added, then eventually
|
|
571
|
+
// optimize this method.
|
|
572
|
+
const results: boolean[] = [];
|
|
573
|
+
for (const i of range(this.data.inputs.length)) {
|
|
574
|
+
try {
|
|
575
|
+
this.signInput(i, keyPair, sighashTypes);
|
|
576
|
+
results.push(true);
|
|
577
|
+
} catch (err) {
|
|
578
|
+
results.push(false);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
if (results.every((v) => v === false)) {
|
|
582
|
+
throw new Error('No inputs were signed');
|
|
583
|
+
}
|
|
584
|
+
return this;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
signAllInputsAsync(
|
|
588
|
+
keyPair: Signer | SignerAlternative | SignerAsync | BIP32Interface | ECPairInterface,
|
|
589
|
+
sighashTypes?: number[],
|
|
590
|
+
): Promise<void> {
|
|
591
|
+
return new Promise((resolve, reject): any => {
|
|
592
|
+
if (!keyPair || !keyPair.publicKey)
|
|
593
|
+
return reject(new Error('Need Signer to sign input'));
|
|
594
|
+
|
|
595
|
+
// TODO: Add a pubkey/pubkeyhash cache to each input
|
|
596
|
+
// as input information is added, then eventually
|
|
597
|
+
// optimize this method.
|
|
598
|
+
const results: boolean[] = [];
|
|
599
|
+
const promises: Array<Promise<void>> = [];
|
|
600
|
+
for (const [i] of this.data.inputs.entries()) {
|
|
601
|
+
promises.push(
|
|
602
|
+
this.signInputAsync(i, keyPair, sighashTypes).then(
|
|
603
|
+
() => {
|
|
604
|
+
results.push(true);
|
|
605
|
+
},
|
|
606
|
+
() => {
|
|
607
|
+
results.push(false);
|
|
608
|
+
},
|
|
609
|
+
),
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
return Promise.all(promises).then(() => {
|
|
613
|
+
if (results.every((v) => v === false)) {
|
|
614
|
+
return reject(new Error('No inputs were signed'));
|
|
615
|
+
}
|
|
616
|
+
resolve();
|
|
617
|
+
});
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
signInput(
|
|
622
|
+
inputIndex: number,
|
|
623
|
+
keyPair: Signer | SignerAlternative | BIP32Interface | ECPairInterface,
|
|
624
|
+
sighashTypes?: number[],
|
|
625
|
+
): this {
|
|
626
|
+
if (!keyPair || !keyPair.publicKey) {
|
|
627
|
+
throw new Error('Need Signer to sign input');
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const input = checkForInput(this.data.inputs, inputIndex);
|
|
631
|
+
if (isTaprootInput(input)) {
|
|
632
|
+
return this._signTaprootInput(inputIndex, input, keyPair, undefined, sighashTypes);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
return this._signInput(inputIndex, keyPair, sighashTypes);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
signTaprootInput(
|
|
639
|
+
inputIndex: number,
|
|
640
|
+
keyPair: Signer | SignerAlternative | BIP32Interface | ECPairInterface,
|
|
641
|
+
tapLeafHashToSign?: Buffer,
|
|
642
|
+
sighashTypes?: number[],
|
|
643
|
+
): this {
|
|
644
|
+
if (!keyPair || !keyPair.publicKey) {
|
|
645
|
+
throw new Error('Need Signer to sign input');
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const input = checkForInput(this.data.inputs, inputIndex);
|
|
649
|
+
if (isTaprootInput(input)) {
|
|
650
|
+
return this._signTaprootInput(
|
|
651
|
+
inputIndex,
|
|
652
|
+
input,
|
|
653
|
+
keyPair,
|
|
654
|
+
tapLeafHashToSign,
|
|
655
|
+
sighashTypes,
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
throw new Error(`Input #${inputIndex} is not of type Taproot.`);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
signInputAsync(
|
|
663
|
+
inputIndex: number,
|
|
664
|
+
keyPair: Signer | SignerAlternative | SignerAsync | BIP32Interface | ECPairInterface,
|
|
665
|
+
sighashTypes?: number[],
|
|
666
|
+
): Promise<void> {
|
|
667
|
+
return Promise.resolve().then(() => {
|
|
668
|
+
if (!keyPair || !keyPair.publicKey) throw new Error('Need Signer to sign input');
|
|
669
|
+
|
|
670
|
+
const input = checkForInput(this.data.inputs, inputIndex);
|
|
671
|
+
if (isTaprootInput(input))
|
|
672
|
+
return this._signTaprootInputAsync(
|
|
673
|
+
inputIndex,
|
|
674
|
+
input,
|
|
675
|
+
keyPair,
|
|
676
|
+
undefined,
|
|
677
|
+
sighashTypes,
|
|
678
|
+
);
|
|
679
|
+
|
|
680
|
+
return this._signInputAsync(inputIndex, keyPair, sighashTypes);
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
signTaprootInputAsync(
|
|
685
|
+
inputIndex: number,
|
|
686
|
+
keyPair: Signer | SignerAlternative | SignerAsync | BIP32Interface | ECPairInterface,
|
|
687
|
+
tapLeafHash?: Buffer,
|
|
688
|
+
sighashTypes?: number[],
|
|
689
|
+
): Promise<void> {
|
|
690
|
+
return Promise.resolve().then(() => {
|
|
691
|
+
if (!keyPair || !keyPair.publicKey) throw new Error('Need Signer to sign input');
|
|
692
|
+
|
|
693
|
+
const input = checkForInput(this.data.inputs, inputIndex);
|
|
694
|
+
if (isTaprootInput(input))
|
|
695
|
+
return this._signTaprootInputAsync(
|
|
696
|
+
inputIndex,
|
|
697
|
+
input,
|
|
698
|
+
keyPair,
|
|
699
|
+
tapLeafHash,
|
|
700
|
+
sighashTypes,
|
|
701
|
+
);
|
|
702
|
+
|
|
703
|
+
throw new Error(`Input #${inputIndex} is not of type Taproot.`);
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
toBuffer(): Buffer {
|
|
708
|
+
checkCache(this.__CACHE);
|
|
709
|
+
return this.data.toBuffer();
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
toHex(): string {
|
|
713
|
+
checkCache(this.__CACHE);
|
|
714
|
+
return this.data.toHex();
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
toBase64(): string {
|
|
718
|
+
checkCache(this.__CACHE);
|
|
719
|
+
return this.data.toBase64();
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
updateGlobal(updateData: PsbtGlobalUpdate): this {
|
|
723
|
+
this.data.updateGlobal(updateData);
|
|
724
|
+
return this;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
updateInput(inputIndex: number, updateData: PsbtInputUpdate): this {
|
|
728
|
+
if (updateData.witnessScript) checkInvalidP2WSH(updateData.witnessScript);
|
|
729
|
+
checkTaprootInputFields(this.data.inputs[inputIndex], updateData, 'updateInput');
|
|
730
|
+
this.data.updateInput(inputIndex, updateData);
|
|
731
|
+
if (updateData.nonWitnessUtxo) {
|
|
732
|
+
addNonWitnessTxCache(this.__CACHE, this.data.inputs[inputIndex], inputIndex);
|
|
733
|
+
}
|
|
734
|
+
return this;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
updateOutput(outputIndex: number, updateData: PsbtOutputUpdate): this {
|
|
738
|
+
const outputData = this.data.outputs[outputIndex];
|
|
739
|
+
checkTaprootOutputFields(outputData, updateData, 'updateOutput');
|
|
740
|
+
|
|
741
|
+
this.data.updateOutput(outputIndex, updateData);
|
|
742
|
+
return this;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
addUnknownKeyValToGlobal(keyVal: KeyValue): this {
|
|
746
|
+
this.data.addUnknownKeyValToGlobal(keyVal);
|
|
747
|
+
return this;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
addUnknownKeyValToInput(inputIndex: number, keyVal: KeyValue): this {
|
|
751
|
+
this.data.addUnknownKeyValToInput(inputIndex, keyVal);
|
|
752
|
+
return this;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
addUnknownKeyValToOutput(outputIndex: number, keyVal: KeyValue): this {
|
|
756
|
+
this.data.addUnknownKeyValToOutput(outputIndex, keyVal);
|
|
757
|
+
return this;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
clearFinalizedInput(inputIndex: number): this {
|
|
761
|
+
this.data.clearFinalizedInput(inputIndex);
|
|
762
|
+
return this;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
public checkTaprootHashesForSig(
|
|
766
|
+
inputIndex: number,
|
|
767
|
+
input: PsbtInput,
|
|
768
|
+
keyPair: Signer | SignerAlternative | SignerAsync | BIP32Interface | ECPairInterface,
|
|
769
|
+
tapLeafHashToSign?: Buffer,
|
|
770
|
+
allowedSighashTypes?: number[],
|
|
771
|
+
): { hash: Buffer; leafHash?: Buffer }[] {
|
|
772
|
+
if (typeof keyPair.signSchnorr !== 'function')
|
|
773
|
+
throw new Error(`Need Schnorr Signer to sign taproot input #${inputIndex}.`);
|
|
774
|
+
|
|
775
|
+
const hashesForSig = getTaprootHashesForSig(
|
|
776
|
+
inputIndex,
|
|
777
|
+
input,
|
|
778
|
+
this.data.inputs,
|
|
779
|
+
keyPair.publicKey,
|
|
780
|
+
this.__CACHE,
|
|
781
|
+
tapLeafHashToSign,
|
|
782
|
+
allowedSighashTypes,
|
|
783
|
+
);
|
|
784
|
+
|
|
785
|
+
if (!hashesForSig || !hashesForSig.length)
|
|
786
|
+
throw new Error(
|
|
787
|
+
`Can not sign for input #${inputIndex} with the key ${keyPair.publicKey.toString(
|
|
788
|
+
'hex',
|
|
789
|
+
)}`,
|
|
790
|
+
);
|
|
791
|
+
|
|
792
|
+
return hashesForSig;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
private _finalizeInput(
|
|
796
|
+
inputIndex: number,
|
|
797
|
+
input: PsbtInput,
|
|
798
|
+
finalScriptsFunc: FinalScriptsFunc = getFinalScripts,
|
|
799
|
+
canRunChecks: boolean = true,
|
|
800
|
+
): this {
|
|
801
|
+
const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput(
|
|
802
|
+
inputIndex,
|
|
803
|
+
input,
|
|
804
|
+
this.__CACHE,
|
|
805
|
+
);
|
|
806
|
+
if (!script) throw new Error(`No script found for input #${inputIndex}`);
|
|
807
|
+
|
|
808
|
+
checkPartialSigSighashes(input);
|
|
809
|
+
|
|
810
|
+
const { finalScriptSig, finalScriptWitness } = finalScriptsFunc(
|
|
811
|
+
inputIndex,
|
|
812
|
+
input,
|
|
813
|
+
script,
|
|
814
|
+
isSegwit,
|
|
815
|
+
isP2SH,
|
|
816
|
+
isP2WSH,
|
|
817
|
+
canRunChecks,
|
|
818
|
+
);
|
|
819
|
+
|
|
820
|
+
if (finalScriptSig) this.data.updateInput(inputIndex, { finalScriptSig });
|
|
821
|
+
if (finalScriptWitness) this.data.updateInput(inputIndex, { finalScriptWitness });
|
|
822
|
+
if (!finalScriptSig && !finalScriptWitness)
|
|
823
|
+
throw new Error(`Unknown error finalizing input #${inputIndex}`);
|
|
824
|
+
|
|
825
|
+
this.data.clearFinalizedInput(inputIndex);
|
|
826
|
+
return this;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
private _finalizeTaprootInput(
|
|
830
|
+
inputIndex: number,
|
|
831
|
+
input: PsbtInput,
|
|
832
|
+
tapLeafHashToFinalize?: Buffer,
|
|
833
|
+
finalScriptsFunc = tapScriptFinalizer,
|
|
834
|
+
): this {
|
|
835
|
+
if (!input.witnessUtxo)
|
|
836
|
+
throw new Error(`Cannot finalize input #${inputIndex}. Missing witness utxo.`);
|
|
837
|
+
|
|
838
|
+
// Check key spend first. Increased privacy and reduced block space.
|
|
839
|
+
if (input.tapKeySig) {
|
|
840
|
+
const payment = payments.p2tr({
|
|
841
|
+
output: input.witnessUtxo.script,
|
|
842
|
+
signature: input.tapKeySig,
|
|
843
|
+
});
|
|
844
|
+
const finalScriptWitness = witnessStackToScriptWitness(payment.witness!);
|
|
845
|
+
this.data.updateInput(inputIndex, { finalScriptWitness });
|
|
846
|
+
} else {
|
|
847
|
+
const { finalScriptWitness } = finalScriptsFunc(
|
|
848
|
+
inputIndex,
|
|
849
|
+
input,
|
|
850
|
+
tapLeafHashToFinalize,
|
|
851
|
+
);
|
|
852
|
+
this.data.updateInput(inputIndex, { finalScriptWitness });
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
this.data.clearFinalizedInput(inputIndex);
|
|
856
|
+
|
|
857
|
+
return this;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
private _validateSignaturesOfInput(
|
|
861
|
+
inputIndex: number,
|
|
862
|
+
validator: ValidateSigFunction,
|
|
863
|
+
pubkey?: Buffer,
|
|
864
|
+
): boolean {
|
|
865
|
+
const input = this.data.inputs[inputIndex];
|
|
866
|
+
const partialSig = (input || {}).partialSig;
|
|
867
|
+
if (!input || !partialSig || partialSig.length < 1)
|
|
868
|
+
throw new Error('No signatures to validate');
|
|
869
|
+
if (typeof validator !== 'function')
|
|
870
|
+
throw new Error('Need validator function to validate signatures');
|
|
871
|
+
const mySigs = pubkey ? partialSig.filter((sig) => sig.pubkey.equals(pubkey)) : partialSig;
|
|
872
|
+
if (mySigs.length < 1) throw new Error('No signatures for this pubkey');
|
|
873
|
+
const results: boolean[] = [];
|
|
874
|
+
let hashCache: Buffer;
|
|
875
|
+
let scriptCache: Buffer;
|
|
876
|
+
let sighashCache: number;
|
|
877
|
+
for (const pSig of mySigs) {
|
|
878
|
+
const sig = bscript.signature.decode(pSig.signature);
|
|
879
|
+
const { hash, script } =
|
|
880
|
+
sighashCache! !== sig.hashType
|
|
881
|
+
? getHashForSig(
|
|
882
|
+
inputIndex,
|
|
883
|
+
Object.assign({}, input, {
|
|
884
|
+
sighashType: sig.hashType,
|
|
885
|
+
}),
|
|
886
|
+
this.__CACHE,
|
|
887
|
+
true,
|
|
888
|
+
)
|
|
889
|
+
: { hash: hashCache!, script: scriptCache! };
|
|
890
|
+
sighashCache = sig.hashType;
|
|
891
|
+
hashCache = hash;
|
|
892
|
+
scriptCache = script;
|
|
893
|
+
checkScriptForPubkey(pSig.pubkey, script, 'verify');
|
|
894
|
+
results.push(validator(pSig.pubkey, hash, sig.signature));
|
|
895
|
+
}
|
|
896
|
+
return results.every((res) => res === true);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
private validateSignaturesOfTaprootInput(
|
|
900
|
+
inputIndex: number,
|
|
901
|
+
validator: ValidateSigFunction,
|
|
902
|
+
pubkey?: Buffer,
|
|
903
|
+
): boolean {
|
|
904
|
+
const input = this.data.inputs[inputIndex];
|
|
905
|
+
const tapKeySig = (input || {}).tapKeySig;
|
|
906
|
+
const tapScriptSig = (input || {}).tapScriptSig;
|
|
907
|
+
if (!input && !tapKeySig && !(tapScriptSig && !tapScriptSig.length))
|
|
908
|
+
throw new Error('No signatures to validate');
|
|
909
|
+
if (typeof validator !== 'function')
|
|
910
|
+
throw new Error('Need validator function to validate signatures');
|
|
911
|
+
|
|
912
|
+
pubkey = pubkey && toXOnly(pubkey);
|
|
913
|
+
const allHashses = pubkey
|
|
914
|
+
? getTaprootHashesForSig(inputIndex, input, this.data.inputs, pubkey, this.__CACHE)
|
|
915
|
+
: getAllTaprootHashesForSig(inputIndex, input, this.data.inputs, this.__CACHE);
|
|
916
|
+
|
|
917
|
+
if (!allHashses.length) throw new Error('No signatures for this pubkey');
|
|
918
|
+
|
|
919
|
+
const tapKeyHash = allHashses.find((h) => !h.leafHash);
|
|
920
|
+
let validationResultCount = 0;
|
|
921
|
+
if (tapKeySig && tapKeyHash) {
|
|
922
|
+
const isValidTapkeySig = validator(
|
|
923
|
+
tapKeyHash.pubkey,
|
|
924
|
+
tapKeyHash.hash,
|
|
925
|
+
trimTaprootSig(tapKeySig),
|
|
926
|
+
);
|
|
927
|
+
if (!isValidTapkeySig) return false;
|
|
928
|
+
validationResultCount++;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
if (tapScriptSig) {
|
|
932
|
+
for (const tapSig of tapScriptSig) {
|
|
933
|
+
const tapSigHash = allHashses.find((h) => tapSig.pubkey.equals(h.pubkey));
|
|
934
|
+
if (tapSigHash) {
|
|
935
|
+
const isValidTapScriptSig = validator(
|
|
936
|
+
tapSig.pubkey,
|
|
937
|
+
tapSigHash.hash,
|
|
938
|
+
trimTaprootSig(tapSig.signature),
|
|
939
|
+
);
|
|
940
|
+
if (!isValidTapScriptSig) return false;
|
|
941
|
+
validationResultCount++;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
return validationResultCount > 0;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
private _signInput(
|
|
950
|
+
inputIndex: number,
|
|
951
|
+
keyPair: Signer | SignerAlternative | BIP32Interface | ECPairInterface,
|
|
952
|
+
sighashTypes: number[] = [Transaction.SIGHASH_ALL],
|
|
953
|
+
): this {
|
|
954
|
+
//hookSigner(keyPair);
|
|
955
|
+
|
|
956
|
+
const { hash, sighashType } = getHashAndSighashType(
|
|
957
|
+
this.data.inputs,
|
|
958
|
+
inputIndex,
|
|
959
|
+
keyPair.publicKey,
|
|
960
|
+
this.__CACHE,
|
|
961
|
+
sighashTypes,
|
|
962
|
+
);
|
|
963
|
+
|
|
964
|
+
const partialSig = [
|
|
965
|
+
{
|
|
966
|
+
pubkey: keyPair.publicKey,
|
|
967
|
+
signature: bscript.signature.encode(keyPair.sign(hash), sighashType),
|
|
968
|
+
},
|
|
969
|
+
];
|
|
970
|
+
|
|
971
|
+
this.data.updateInput(inputIndex, { partialSig });
|
|
972
|
+
return this;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
private _signTaprootInput(
|
|
976
|
+
inputIndex: number,
|
|
977
|
+
input: PsbtInput,
|
|
978
|
+
keyPair: Signer | SignerAlternative | BIP32Interface | ECPairInterface,
|
|
979
|
+
tapLeafHashToSign?: Buffer,
|
|
980
|
+
allowedSighashTypes: number[] = [Transaction.SIGHASH_DEFAULT],
|
|
981
|
+
): this {
|
|
982
|
+
//hookSigner(keyPair);
|
|
983
|
+
|
|
984
|
+
const hashesForSig = this.checkTaprootHashesForSig(
|
|
985
|
+
inputIndex,
|
|
986
|
+
input,
|
|
987
|
+
keyPair,
|
|
988
|
+
tapLeafHashToSign,
|
|
989
|
+
allowedSighashTypes,
|
|
990
|
+
);
|
|
991
|
+
|
|
992
|
+
const tapKeySig: TapKeySig = hashesForSig
|
|
993
|
+
.filter((h) => !h.leafHash)
|
|
994
|
+
.map((h) =>
|
|
995
|
+
serializeTaprootSignature(keyPair.signSchnorr!(h.hash), input.sighashType),
|
|
996
|
+
)[0];
|
|
997
|
+
|
|
998
|
+
const tapScriptSig: TapScriptSig[] = hashesForSig
|
|
999
|
+
.filter((h) => !!h.leafHash)
|
|
1000
|
+
.map(
|
|
1001
|
+
(h) =>
|
|
1002
|
+
({
|
|
1003
|
+
pubkey: toXOnly(keyPair.publicKey),
|
|
1004
|
+
signature: serializeTaprootSignature(
|
|
1005
|
+
keyPair.signSchnorr!(h.hash),
|
|
1006
|
+
input.sighashType,
|
|
1007
|
+
),
|
|
1008
|
+
leafHash: h.leafHash,
|
|
1009
|
+
}) as TapScriptSig,
|
|
1010
|
+
);
|
|
1011
|
+
|
|
1012
|
+
if (tapKeySig) {
|
|
1013
|
+
this.data.updateInput(inputIndex, { tapKeySig });
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
if (tapScriptSig.length) {
|
|
1017
|
+
this.data.updateInput(inputIndex, { tapScriptSig });
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
return this;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
private _signInputAsync(
|
|
1024
|
+
inputIndex: number,
|
|
1025
|
+
keyPair: Signer | SignerAlternative | SignerAsync | BIP32Interface | ECPairInterface,
|
|
1026
|
+
sighashTypes: number[] = [Transaction.SIGHASH_ALL],
|
|
1027
|
+
): Promise<void> {
|
|
1028
|
+
//hookSigner(keyPair);
|
|
1029
|
+
|
|
1030
|
+
const { hash, sighashType } = getHashAndSighashType(
|
|
1031
|
+
this.data.inputs,
|
|
1032
|
+
inputIndex,
|
|
1033
|
+
keyPair.publicKey,
|
|
1034
|
+
this.__CACHE,
|
|
1035
|
+
sighashTypes,
|
|
1036
|
+
);
|
|
1037
|
+
|
|
1038
|
+
return Promise.resolve(keyPair.sign(hash)).then((signature) => {
|
|
1039
|
+
const partialSig = [
|
|
1040
|
+
{
|
|
1041
|
+
pubkey: keyPair.publicKey,
|
|
1042
|
+
signature: bscript.signature.encode(signature, sighashType),
|
|
1043
|
+
},
|
|
1044
|
+
];
|
|
1045
|
+
|
|
1046
|
+
this.data.updateInput(inputIndex, { partialSig });
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
private async _signTaprootInputAsync(
|
|
1051
|
+
inputIndex: number,
|
|
1052
|
+
input: PsbtInput,
|
|
1053
|
+
keyPair: Signer | SignerAlternative | SignerAsync | BIP32Interface | ECPairInterface,
|
|
1054
|
+
tapLeafHash?: Buffer,
|
|
1055
|
+
sighashTypes: number[] = [Transaction.SIGHASH_DEFAULT],
|
|
1056
|
+
): Promise<void> {
|
|
1057
|
+
//hookSigner(keyPair);
|
|
1058
|
+
|
|
1059
|
+
const hashesForSig = this.checkTaprootHashesForSig(
|
|
1060
|
+
inputIndex,
|
|
1061
|
+
input,
|
|
1062
|
+
keyPair,
|
|
1063
|
+
tapLeafHash,
|
|
1064
|
+
sighashTypes,
|
|
1065
|
+
);
|
|
1066
|
+
|
|
1067
|
+
const signaturePromises: Promise<
|
|
1068
|
+
{ tapKeySig: Buffer } | { tapScriptSig: TapScriptSig[] }
|
|
1069
|
+
>[] = [];
|
|
1070
|
+
|
|
1071
|
+
const tapKeyHash = hashesForSig.filter((h) => !h.leafHash)[0];
|
|
1072
|
+
if (tapKeyHash) {
|
|
1073
|
+
const tapKeySigPromise = Promise.resolve(keyPair.signSchnorr!(tapKeyHash.hash)).then(
|
|
1074
|
+
(sig) => {
|
|
1075
|
+
return {
|
|
1076
|
+
tapKeySig: serializeTaprootSignature(sig, input.sighashType),
|
|
1077
|
+
};
|
|
1078
|
+
},
|
|
1079
|
+
);
|
|
1080
|
+
signaturePromises.push(tapKeySigPromise);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
const tapScriptHashes = hashesForSig.filter((h) => !!h.leafHash);
|
|
1084
|
+
if (tapScriptHashes.length) {
|
|
1085
|
+
const tapScriptSigPromises = tapScriptHashes.map(async (tsh) => {
|
|
1086
|
+
const signature = await keyPair.signSchnorr!(tsh.hash);
|
|
1087
|
+
|
|
1088
|
+
const tapScriptSig = [
|
|
1089
|
+
{
|
|
1090
|
+
pubkey: toXOnly(keyPair.publicKey),
|
|
1091
|
+
signature: serializeTaprootSignature(signature, input.sighashType),
|
|
1092
|
+
leafHash: tsh.leafHash,
|
|
1093
|
+
} as TapScriptSig,
|
|
1094
|
+
];
|
|
1095
|
+
|
|
1096
|
+
return { tapScriptSig };
|
|
1097
|
+
});
|
|
1098
|
+
signaturePromises.push(...tapScriptSigPromises);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
const results = await Promise.all(signaturePromises);
|
|
1102
|
+
for (const v of results) {
|
|
1103
|
+
this.data.updateInput(inputIndex, v);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
interface PsbtCache {
|
|
1109
|
+
__NON_WITNESS_UTXO_TX_CACHE: Transaction[];
|
|
1110
|
+
__NON_WITNESS_UTXO_BUF_CACHE: Buffer[];
|
|
1111
|
+
__TX_IN_CACHE: { [index: string]: number };
|
|
1112
|
+
__TX: Transaction;
|
|
1113
|
+
__FEE_RATE?: number;
|
|
1114
|
+
__FEE?: number;
|
|
1115
|
+
__EXTRACTED_TX?: Transaction;
|
|
1116
|
+
__UNSAFE_SIGN_NONSEGWIT: boolean;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
export interface PsbtOptsOptional {
|
|
1120
|
+
network?: Network;
|
|
1121
|
+
maximumFeeRate?: number;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
export interface PsbtOpts {
|
|
1125
|
+
network: Network;
|
|
1126
|
+
maximumFeeRate: number;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
export interface PsbtInputExtended extends PsbtInput, TransactionInput {}
|
|
1130
|
+
|
|
1131
|
+
export type PsbtOutputExtended = PsbtOutputExtendedAddress | PsbtOutputExtendedScript;
|
|
1132
|
+
|
|
1133
|
+
export interface PsbtOutputExtendedAddress extends PsbtOutput {
|
|
1134
|
+
address: string;
|
|
1135
|
+
value: number;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
export interface PsbtOutputExtendedScript extends PsbtOutput {
|
|
1139
|
+
script: Buffer;
|
|
1140
|
+
value: number;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
interface HDSignerBase {
|
|
1144
|
+
/**
|
|
1145
|
+
* DER format compressed publicKey buffer
|
|
1146
|
+
*/
|
|
1147
|
+
publicKey: Buffer;
|
|
1148
|
+
/**
|
|
1149
|
+
* The first 4 bytes of the sha256-ripemd160 of the publicKey
|
|
1150
|
+
*/
|
|
1151
|
+
fingerprint: Buffer;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
export interface HDSigner extends HDSignerBase {
|
|
1155
|
+
/**
|
|
1156
|
+
* The path string must match /^m(\/\d+'?)+$/
|
|
1157
|
+
* ex. m/44'/0'/0'/1/23 levels with ' must be hard derivations
|
|
1158
|
+
*/
|
|
1159
|
+
derivePath(path: string): HDSigner;
|
|
1160
|
+
|
|
1161
|
+
/**
|
|
1162
|
+
* Input hash (the "message digest") for the signature algorithm
|
|
1163
|
+
* Return a 64 byte signature (32 byte r and 32 byte s in that order)
|
|
1164
|
+
*/
|
|
1165
|
+
sign(hash: Buffer): Buffer;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
/**
|
|
1169
|
+
* Same as above but with async sign method
|
|
1170
|
+
*/
|
|
1171
|
+
export interface HDSignerAsync extends HDSignerBase {
|
|
1172
|
+
derivePath(path: string): HDSignerAsync;
|
|
1173
|
+
|
|
1174
|
+
sign(hash: Buffer): Promise<Buffer>;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
export interface SignerAlternative {
|
|
1178
|
+
publicKey: Buffer;
|
|
1179
|
+
lowR: boolean;
|
|
1180
|
+
|
|
1181
|
+
sign(hash: Buffer, lowR?: boolean): Buffer;
|
|
1182
|
+
|
|
1183
|
+
verify(hash: Buffer, signature: Buffer): boolean;
|
|
1184
|
+
|
|
1185
|
+
signSchnorr(hash: Buffer): Buffer;
|
|
1186
|
+
|
|
1187
|
+
verifySchnorr(hash: Buffer, signature: Buffer): boolean;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
export interface Signer {
|
|
1191
|
+
publicKey: Buffer;
|
|
1192
|
+
network?: Network;
|
|
1193
|
+
|
|
1194
|
+
sign(hash: Buffer, lowR?: boolean): Buffer;
|
|
1195
|
+
|
|
1196
|
+
signSchnorr?(hash: Buffer): Buffer;
|
|
1197
|
+
|
|
1198
|
+
getPublicKey?(): Buffer;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
export interface SignerAsync {
|
|
1202
|
+
publicKey: Buffer;
|
|
1203
|
+
network?: Network;
|
|
1204
|
+
|
|
1205
|
+
sign(hash: Buffer, lowR?: boolean): Promise<Buffer>;
|
|
1206
|
+
|
|
1207
|
+
signSchnorr?(hash: Buffer): Promise<Buffer>;
|
|
1208
|
+
|
|
1209
|
+
getPublicKey?(): Buffer;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
/**
|
|
1213
|
+
* This function is needed to pass to the bip174 base class's fromBuffer.
|
|
1214
|
+
* It takes the "transaction buffer" portion of the psbt buffer and returns a
|
|
1215
|
+
* Transaction (From the bip174 library) interface.
|
|
1216
|
+
*/
|
|
1217
|
+
const transactionFromBuffer: TransactionFromBuffer = (buffer: Buffer): ITransaction =>
|
|
1218
|
+
new PsbtTransaction(buffer);
|
|
1219
|
+
|
|
1220
|
+
/**
|
|
1221
|
+
* This class implements the Transaction interface from bip174 library.
|
|
1222
|
+
* It contains a bitcoinjs-lib Transaction object.
|
|
1223
|
+
*/
|
|
1224
|
+
class PsbtTransaction implements ITransaction {
|
|
1225
|
+
tx: Transaction;
|
|
1226
|
+
|
|
1227
|
+
constructor(buffer: Buffer = Buffer.from([2, 0, 0, 0, 0, 0, 0, 0, 0, 0])) {
|
|
1228
|
+
this.tx = Transaction.fromBuffer(buffer);
|
|
1229
|
+
checkTxEmpty(this.tx);
|
|
1230
|
+
Object.defineProperty(this, 'tx', {
|
|
1231
|
+
enumerable: false,
|
|
1232
|
+
writable: true,
|
|
1233
|
+
});
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
getInputOutputCounts(): {
|
|
1237
|
+
inputCount: number;
|
|
1238
|
+
outputCount: number;
|
|
1239
|
+
} {
|
|
1240
|
+
return {
|
|
1241
|
+
inputCount: this.tx.ins.length,
|
|
1242
|
+
outputCount: this.tx.outs.length,
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
addInput(input: TransactionInput): void {
|
|
1247
|
+
if (
|
|
1248
|
+
input.hash === undefined ||
|
|
1249
|
+
input.index === undefined ||
|
|
1250
|
+
(!Buffer.isBuffer(input.hash) && typeof input.hash !== 'string') ||
|
|
1251
|
+
typeof input.index !== 'number'
|
|
1252
|
+
) {
|
|
1253
|
+
throw new Error('Error adding input.');
|
|
1254
|
+
}
|
|
1255
|
+
const hash =
|
|
1256
|
+
typeof input.hash === 'string'
|
|
1257
|
+
? reverseBuffer(Buffer.from(input.hash, 'hex'))
|
|
1258
|
+
: input.hash;
|
|
1259
|
+
|
|
1260
|
+
this.tx.addInput(hash, input.index, input.sequence);
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
addOutput(output: TransactionOutput): void {
|
|
1264
|
+
if (
|
|
1265
|
+
output.script === undefined ||
|
|
1266
|
+
output.value === undefined ||
|
|
1267
|
+
!Buffer.isBuffer(output.script) ||
|
|
1268
|
+
typeof output.value !== 'number'
|
|
1269
|
+
) {
|
|
1270
|
+
throw new Error('Error adding output.');
|
|
1271
|
+
}
|
|
1272
|
+
this.tx.addOutput(output.script, output.value);
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
toBuffer(): Buffer {
|
|
1276
|
+
return this.tx.toBuffer();
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
function canFinalize(input: PsbtInput, script: Buffer, scriptType: string): boolean {
|
|
1281
|
+
switch (scriptType) {
|
|
1282
|
+
case 'pubkey':
|
|
1283
|
+
case 'pubkeyhash':
|
|
1284
|
+
case 'witnesspubkeyhash':
|
|
1285
|
+
return hasSigs(1, input.partialSig);
|
|
1286
|
+
case 'multisig':
|
|
1287
|
+
const p2ms = payments.p2ms({ output: script });
|
|
1288
|
+
return hasSigs(p2ms.m!, input.partialSig, p2ms.pubkeys);
|
|
1289
|
+
case 'nonstandard':
|
|
1290
|
+
return true;
|
|
1291
|
+
default:
|
|
1292
|
+
return false;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
function checkCache(cache: PsbtCache): void {
|
|
1297
|
+
if (cache.__UNSAFE_SIGN_NONSEGWIT !== false) {
|
|
1298
|
+
throw new Error('Not BIP174 compliant, can not export');
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
function hasSigs(neededSigs: number, partialSig?: any[], pubkeys?: Buffer[]): boolean {
|
|
1303
|
+
if (!partialSig) return false;
|
|
1304
|
+
let sigs: any;
|
|
1305
|
+
if (pubkeys) {
|
|
1306
|
+
sigs = pubkeys
|
|
1307
|
+
.map((pkey) => {
|
|
1308
|
+
const pubkey = compressPubkey(pkey);
|
|
1309
|
+
return partialSig.find((pSig) => pSig.pubkey.equals(pubkey));
|
|
1310
|
+
})
|
|
1311
|
+
.filter((v) => !!v);
|
|
1312
|
+
} else {
|
|
1313
|
+
sigs = partialSig;
|
|
1314
|
+
}
|
|
1315
|
+
if (sigs.length > neededSigs) throw new Error('Too many signatures');
|
|
1316
|
+
return sigs.length === neededSigs;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
function isFinalized(input: PsbtInput): boolean {
|
|
1320
|
+
return !!input.finalScriptSig || !!input.finalScriptWitness;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
function bip32DerivationIsMine(root: HDSigner): (d: Bip32Derivation) => boolean {
|
|
1324
|
+
return (d: Bip32Derivation): boolean => {
|
|
1325
|
+
if (!d.masterFingerprint.equals(root.fingerprint)) return false;
|
|
1326
|
+
if (!root.derivePath(d.path).publicKey.equals(d.pubkey)) return false;
|
|
1327
|
+
return true;
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
function check32Bit(num: number): void {
|
|
1332
|
+
if (typeof num !== 'number' || num !== Math.floor(num) || num > 0xffffffff || num < 0) {
|
|
1333
|
+
throw new Error('Invalid 32 bit integer');
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
function checkFees(psbt: Psbt, cache: PsbtCache, opts: PsbtOpts): void {
|
|
1338
|
+
const feeRate = cache.__FEE_RATE || psbt.getFeeRate();
|
|
1339
|
+
const vsize = cache.__EXTRACTED_TX!.virtualSize();
|
|
1340
|
+
const satoshis = feeRate * vsize;
|
|
1341
|
+
if (feeRate >= opts.maximumFeeRate) {
|
|
1342
|
+
throw new Error(
|
|
1343
|
+
`Warning: You are paying around ${(satoshis / 1e8).toFixed(8)} in ` +
|
|
1344
|
+
`fees, which is ${feeRate} satoshi per byte for a transaction ` +
|
|
1345
|
+
`with a VSize of ${vsize} bytes (segwit counted as 0.25 byte per ` +
|
|
1346
|
+
`byte). Use setMaximumFeeRate method to raise your threshold, or ` +
|
|
1347
|
+
`pass true to the first arg of extractTransaction.`,
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
function checkInputsForPartialSig(inputs: PsbtInput[], action: string): void {
|
|
1353
|
+
inputs.forEach((input) => {
|
|
1354
|
+
const throws = isTaprootInput(input)
|
|
1355
|
+
? checkTaprootInputForSigs(input, action)
|
|
1356
|
+
: checkInputForSig(input, action);
|
|
1357
|
+
if (throws) throw new Error('Can not modify transaction, signatures exist.');
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
function checkPartialSigSighashes(input: PsbtInput): void {
|
|
1362
|
+
if (!input.sighashType || !input.partialSig) return;
|
|
1363
|
+
const { partialSig, sighashType } = input;
|
|
1364
|
+
partialSig.forEach((pSig) => {
|
|
1365
|
+
const { hashType } = bscript.signature.decode(pSig.signature);
|
|
1366
|
+
if (sighashType !== hashType) {
|
|
1367
|
+
throw new Error('Signature sighash does not match input sighash type');
|
|
1368
|
+
}
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
function checkScriptForPubkey(pubkey: Buffer, script: Buffer, action: string): void {
|
|
1373
|
+
if (!pubkeyInScript(pubkey, script)) {
|
|
1374
|
+
throw new Error(`Can not ${action} for this input with the key ${pubkey.toString('hex')}`);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
function checkTxEmpty(tx: Transaction): void {
|
|
1379
|
+
const isEmpty = tx.ins.every(
|
|
1380
|
+
(input) =>
|
|
1381
|
+
input.script &&
|
|
1382
|
+
input.script.length === 0 &&
|
|
1383
|
+
input.witness &&
|
|
1384
|
+
input.witness.length === 0,
|
|
1385
|
+
);
|
|
1386
|
+
if (!isEmpty) {
|
|
1387
|
+
throw new Error('Format Error: Transaction ScriptSigs are not empty');
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
function checkTxForDupeIns(tx: Transaction, cache: PsbtCache): void {
|
|
1392
|
+
tx.ins.forEach((input) => {
|
|
1393
|
+
checkTxInputCache(cache, input);
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
function checkTxInputCache(cache: PsbtCache, input: { hash: Buffer; index: number }): void {
|
|
1398
|
+
const key = reverseBuffer(Buffer.from(input.hash)).toString('hex') + ':' + input.index;
|
|
1399
|
+
if (cache.__TX_IN_CACHE[key]) throw new Error('Duplicate input detected.');
|
|
1400
|
+
cache.__TX_IN_CACHE[key] = 1;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
function scriptCheckerFactory(
|
|
1404
|
+
payment: (a: Payment, opts?: PaymentOpts) => Payment,
|
|
1405
|
+
paymentScriptName: string,
|
|
1406
|
+
): (idx: number, scriptPubKey: Buffer, redeemScript: Buffer, ioType: 'input' | 'output') => void {
|
|
1407
|
+
return (
|
|
1408
|
+
inputIndex: number,
|
|
1409
|
+
scriptPubKey: Buffer,
|
|
1410
|
+
redeemScript: Buffer,
|
|
1411
|
+
ioType: 'input' | 'output',
|
|
1412
|
+
): void => {
|
|
1413
|
+
const redeemScriptOutput = payment({
|
|
1414
|
+
redeem: { output: redeemScript },
|
|
1415
|
+
}).output as Buffer;
|
|
1416
|
+
|
|
1417
|
+
if (!scriptPubKey.equals(redeemScriptOutput)) {
|
|
1418
|
+
throw new Error(
|
|
1419
|
+
`${paymentScriptName} for ${ioType} #${inputIndex} doesn't match the scriptPubKey in the prevout`,
|
|
1420
|
+
);
|
|
1421
|
+
}
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
const checkRedeemScript = scriptCheckerFactory(payments.p2sh, 'Redeem script');
|
|
1426
|
+
const checkWitnessScript = scriptCheckerFactory(payments.p2wsh, 'Witness script');
|
|
1427
|
+
|
|
1428
|
+
type TxCacheNumberKey = '__FEE_RATE' | '__FEE';
|
|
1429
|
+
|
|
1430
|
+
function getTxCacheValue(
|
|
1431
|
+
key: TxCacheNumberKey,
|
|
1432
|
+
name: string,
|
|
1433
|
+
inputs: PsbtInput[],
|
|
1434
|
+
c: PsbtCache,
|
|
1435
|
+
disableOutputChecks: boolean = false,
|
|
1436
|
+
): number | undefined {
|
|
1437
|
+
if (!inputs.every(isFinalized)) throw new Error(`PSBT must be finalized to calculate ${name}`);
|
|
1438
|
+
if (key === '__FEE_RATE' && c.__FEE_RATE) return c.__FEE_RATE;
|
|
1439
|
+
if (key === '__FEE' && c.__FEE) return c.__FEE;
|
|
1440
|
+
let tx: Transaction;
|
|
1441
|
+
let mustFinalize = true;
|
|
1442
|
+
if (c.__EXTRACTED_TX) {
|
|
1443
|
+
tx = c.__EXTRACTED_TX;
|
|
1444
|
+
mustFinalize = false;
|
|
1445
|
+
} else {
|
|
1446
|
+
tx = c.__TX.clone();
|
|
1447
|
+
}
|
|
1448
|
+
inputFinalizeGetAmts(inputs, tx, c, mustFinalize, disableOutputChecks);
|
|
1449
|
+
if (key === '__FEE_RATE') return c.__FEE_RATE!;
|
|
1450
|
+
else if (key === '__FEE') return c.__FEE!;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
/**
|
|
1454
|
+
* This function must do two things:
|
|
1455
|
+
* 1. Check if the `input` can be finalized. If it can not be finalized, throw.
|
|
1456
|
+
* ie. `Can not finalize input #${inputIndex}`
|
|
1457
|
+
* 2. Create the finalScriptSig and finalScriptWitness Buffers.
|
|
1458
|
+
*/
|
|
1459
|
+
type FinalScriptsFunc = (
|
|
1460
|
+
inputIndex: number, // Which input is it?
|
|
1461
|
+
input: PsbtInput, // The PSBT input contents
|
|
1462
|
+
script: Buffer, // The "meaningful" locking script Buffer (redeemScript for P2SH etc.)
|
|
1463
|
+
isSegwit: boolean, // Is it segwit?
|
|
1464
|
+
isP2SH: boolean, // Is it P2SH?
|
|
1465
|
+
isP2WSH: boolean, // Is it P2WSH?
|
|
1466
|
+
canRunChecks: boolean,
|
|
1467
|
+
) => {
|
|
1468
|
+
finalScriptSig: Buffer | undefined;
|
|
1469
|
+
finalScriptWitness: Buffer | undefined;
|
|
1470
|
+
};
|
|
1471
|
+
type FinalTaprootScriptsFunc = (
|
|
1472
|
+
inputIndex: number, // Which input is it?
|
|
1473
|
+
input: PsbtInput, // The PSBT input contents
|
|
1474
|
+
tapLeafHashToFinalize?: Buffer, // Only finalize this specific leaf
|
|
1475
|
+
) => {
|
|
1476
|
+
finalScriptWitness: Buffer | undefined;
|
|
1477
|
+
};
|
|
1478
|
+
|
|
1479
|
+
export function getFinalScripts(
|
|
1480
|
+
inputIndex: number,
|
|
1481
|
+
input: PsbtInput,
|
|
1482
|
+
script: Buffer,
|
|
1483
|
+
isSegwit: boolean,
|
|
1484
|
+
isP2SH: boolean,
|
|
1485
|
+
isP2WSH: boolean,
|
|
1486
|
+
canRunChecks: boolean = true,
|
|
1487
|
+
): {
|
|
1488
|
+
finalScriptSig: Buffer | undefined;
|
|
1489
|
+
finalScriptWitness: Buffer | undefined;
|
|
1490
|
+
} {
|
|
1491
|
+
const scriptType = classifyScript(script);
|
|
1492
|
+
if (!canFinalize(input, script, scriptType) && canRunChecks) {
|
|
1493
|
+
throw new Error(`Can not finalize input #${inputIndex}`);
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
return prepareFinalScripts(script, scriptType, input.partialSig!, isSegwit, isP2SH, isP2WSH);
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
export function prepareFinalScripts(
|
|
1500
|
+
script: Buffer,
|
|
1501
|
+
scriptType: string,
|
|
1502
|
+
partialSig: PartialSig[],
|
|
1503
|
+
isSegwit: boolean,
|
|
1504
|
+
isP2SH: boolean,
|
|
1505
|
+
isP2WSH: boolean,
|
|
1506
|
+
): {
|
|
1507
|
+
finalScriptSig: Buffer | undefined;
|
|
1508
|
+
finalScriptWitness: Buffer | undefined;
|
|
1509
|
+
} {
|
|
1510
|
+
let finalScriptSig: Buffer | undefined;
|
|
1511
|
+
let finalScriptWitness: Buffer | undefined;
|
|
1512
|
+
|
|
1513
|
+
// Wow, the payments API is very handy
|
|
1514
|
+
const payment: payments.Payment = getPayment(script, scriptType, partialSig);
|
|
1515
|
+
const p2wsh = !isP2WSH ? null : payments.p2wsh({ redeem: payment });
|
|
1516
|
+
const p2sh = !isP2SH ? null : payments.p2sh({ redeem: p2wsh || payment });
|
|
1517
|
+
|
|
1518
|
+
if (isSegwit) {
|
|
1519
|
+
if (p2wsh) {
|
|
1520
|
+
finalScriptWitness = witnessStackToScriptWitness(p2wsh.witness!);
|
|
1521
|
+
} else {
|
|
1522
|
+
finalScriptWitness = witnessStackToScriptWitness(payment.witness!);
|
|
1523
|
+
}
|
|
1524
|
+
if (p2sh) {
|
|
1525
|
+
finalScriptSig = p2sh.input;
|
|
1526
|
+
}
|
|
1527
|
+
} else {
|
|
1528
|
+
if (p2sh) {
|
|
1529
|
+
finalScriptSig = p2sh.input;
|
|
1530
|
+
} else {
|
|
1531
|
+
finalScriptSig = payment.input;
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
return {
|
|
1535
|
+
finalScriptSig,
|
|
1536
|
+
finalScriptWitness,
|
|
1537
|
+
};
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
function getHashAndSighashType(
|
|
1541
|
+
inputs: PsbtInput[],
|
|
1542
|
+
inputIndex: number,
|
|
1543
|
+
pubkey: Buffer,
|
|
1544
|
+
cache: PsbtCache,
|
|
1545
|
+
sighashTypes: number[],
|
|
1546
|
+
): {
|
|
1547
|
+
hash: Buffer;
|
|
1548
|
+
sighashType: number;
|
|
1549
|
+
} {
|
|
1550
|
+
const input = checkForInput(inputs, inputIndex);
|
|
1551
|
+
const { hash, sighashType, script } = getHashForSig(
|
|
1552
|
+
inputIndex,
|
|
1553
|
+
input,
|
|
1554
|
+
cache,
|
|
1555
|
+
false,
|
|
1556
|
+
sighashTypes,
|
|
1557
|
+
);
|
|
1558
|
+
|
|
1559
|
+
checkScriptForPubkey(pubkey, script, 'sign');
|
|
1560
|
+
return {
|
|
1561
|
+
hash,
|
|
1562
|
+
sighashType,
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
function getHashForSig(
|
|
1567
|
+
inputIndex: number,
|
|
1568
|
+
input: PsbtInput,
|
|
1569
|
+
cache: PsbtCache,
|
|
1570
|
+
forValidate: boolean,
|
|
1571
|
+
sighashTypes?: number[],
|
|
1572
|
+
): {
|
|
1573
|
+
script: Buffer;
|
|
1574
|
+
hash: Buffer;
|
|
1575
|
+
sighashType: number;
|
|
1576
|
+
} {
|
|
1577
|
+
const unsignedTx = cache.__TX;
|
|
1578
|
+
const sighashType = input.sighashType || Transaction.SIGHASH_ALL;
|
|
1579
|
+
checkSighashTypeAllowed(sighashType, sighashTypes);
|
|
1580
|
+
|
|
1581
|
+
let hash: Buffer;
|
|
1582
|
+
let prevout: Output;
|
|
1583
|
+
|
|
1584
|
+
if (input.nonWitnessUtxo) {
|
|
1585
|
+
const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(cache, input, inputIndex);
|
|
1586
|
+
|
|
1587
|
+
const prevoutHash = unsignedTx.ins[inputIndex].hash;
|
|
1588
|
+
const utxoHash = nonWitnessUtxoTx.getHash();
|
|
1589
|
+
|
|
1590
|
+
// If a non-witness UTXO is provided, its hash must match the hash specified in the prevout
|
|
1591
|
+
if (!prevoutHash.equals(utxoHash)) {
|
|
1592
|
+
throw new Error(
|
|
1593
|
+
`Non-witness UTXO hash for input #${inputIndex} doesn't match the hash specified in the prevout`,
|
|
1594
|
+
);
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
const prevoutIndex = unsignedTx.ins[inputIndex].index;
|
|
1598
|
+
prevout = nonWitnessUtxoTx.outs[prevoutIndex] as Output;
|
|
1599
|
+
} else if (input.witnessUtxo) {
|
|
1600
|
+
prevout = input.witnessUtxo;
|
|
1601
|
+
} else {
|
|
1602
|
+
throw new Error('Need a Utxo input item for signing');
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
const { meaningfulScript, type } = getMeaningfulScript(
|
|
1606
|
+
prevout.script,
|
|
1607
|
+
inputIndex,
|
|
1608
|
+
'input',
|
|
1609
|
+
input.redeemScript,
|
|
1610
|
+
input.witnessScript,
|
|
1611
|
+
);
|
|
1612
|
+
|
|
1613
|
+
if (['p2sh-p2wsh', 'p2wsh'].indexOf(type) >= 0) {
|
|
1614
|
+
hash = unsignedTx.hashForWitnessV0(
|
|
1615
|
+
inputIndex,
|
|
1616
|
+
meaningfulScript,
|
|
1617
|
+
prevout.value,
|
|
1618
|
+
sighashType,
|
|
1619
|
+
);
|
|
1620
|
+
} else if (isP2WPKH(meaningfulScript)) {
|
|
1621
|
+
// P2WPKH uses the P2PKH template for prevoutScript when signing
|
|
1622
|
+
const signingScript = payments.p2pkh({
|
|
1623
|
+
hash: meaningfulScript.slice(2),
|
|
1624
|
+
}).output!;
|
|
1625
|
+
hash = unsignedTx.hashForWitnessV0(inputIndex, signingScript, prevout.value, sighashType);
|
|
1626
|
+
} else {
|
|
1627
|
+
// non-segwit
|
|
1628
|
+
if (input.nonWitnessUtxo === undefined && cache.__UNSAFE_SIGN_NONSEGWIT === false)
|
|
1629
|
+
throw new Error(
|
|
1630
|
+
`Input #${inputIndex} has witnessUtxo but non-segwit script: ` +
|
|
1631
|
+
`${meaningfulScript.toString('hex')}`,
|
|
1632
|
+
);
|
|
1633
|
+
if (!forValidate && cache.__UNSAFE_SIGN_NONSEGWIT !== false)
|
|
1634
|
+
console.warn(
|
|
1635
|
+
'Warning: Signing non-segwit inputs without the full parent transaction ' +
|
|
1636
|
+
'means there is a chance that a miner could feed you incorrect information ' +
|
|
1637
|
+
"to trick you into paying large fees. This behavior is the same as Psbt's predecessor " +
|
|
1638
|
+
'(TransactionBuilder - now removed) when signing non-segwit scripts. You are not ' +
|
|
1639
|
+
'able to export this Psbt with toBuffer|toBase64|toHex since it is not ' +
|
|
1640
|
+
'BIP174 compliant.\n*********************\nPROCEED WITH CAUTION!\n' +
|
|
1641
|
+
'*********************',
|
|
1642
|
+
);
|
|
1643
|
+
hash = unsignedTx.hashForSignature(inputIndex, meaningfulScript, sighashType);
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
return {
|
|
1647
|
+
script: meaningfulScript,
|
|
1648
|
+
sighashType,
|
|
1649
|
+
hash,
|
|
1650
|
+
};
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
function getAllTaprootHashesForSig(
|
|
1654
|
+
inputIndex: number,
|
|
1655
|
+
input: PsbtInput,
|
|
1656
|
+
inputs: PsbtInput[],
|
|
1657
|
+
cache: PsbtCache,
|
|
1658
|
+
): { pubkey: Buffer; hash: Buffer; leafHash?: Buffer }[] {
|
|
1659
|
+
const allPublicKeys: Buffer[] = [];
|
|
1660
|
+
if (input.tapInternalKey) {
|
|
1661
|
+
const key = getPrevoutTaprootKey(inputIndex, input, cache);
|
|
1662
|
+
if (key) {
|
|
1663
|
+
allPublicKeys.push(key);
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
if (input.tapScriptSig) {
|
|
1668
|
+
const tapScriptPubkeys = input.tapScriptSig.map((tss) => tss.pubkey);
|
|
1669
|
+
allPublicKeys.push(...tapScriptPubkeys);
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
const allHashes = allPublicKeys.map((pubicKey) =>
|
|
1673
|
+
getTaprootHashesForSig(inputIndex, input, inputs, pubicKey, cache),
|
|
1674
|
+
);
|
|
1675
|
+
|
|
1676
|
+
return allHashes.flat();
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
function getPrevoutTaprootKey(
|
|
1680
|
+
inputIndex: number,
|
|
1681
|
+
input: PsbtInput,
|
|
1682
|
+
cache: PsbtCache,
|
|
1683
|
+
): Buffer | null {
|
|
1684
|
+
const { script } = getScriptAndAmountFromUtxo(inputIndex, input, cache);
|
|
1685
|
+
return isP2TR(script) ? script.subarray(2, 34) : null;
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
function trimTaprootSig(signature: Buffer): Buffer {
|
|
1689
|
+
return signature.length === 64 ? signature : signature.subarray(0, 64);
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
function getTaprootHashesForSig(
|
|
1693
|
+
inputIndex: number,
|
|
1694
|
+
input: PsbtInput,
|
|
1695
|
+
inputs: PsbtInput[],
|
|
1696
|
+
pubkey: Buffer,
|
|
1697
|
+
cache: PsbtCache,
|
|
1698
|
+
tapLeafHashToSign?: Buffer,
|
|
1699
|
+
allowedSighashTypes?: number[],
|
|
1700
|
+
): { pubkey: Buffer; hash: Buffer; leafHash?: Buffer }[] {
|
|
1701
|
+
const unsignedTx = cache.__TX;
|
|
1702
|
+
|
|
1703
|
+
const sighashType = input.sighashType || Transaction.SIGHASH_DEFAULT;
|
|
1704
|
+
checkSighashTypeAllowed(sighashType, allowedSighashTypes);
|
|
1705
|
+
|
|
1706
|
+
const prevOuts: Output[] = inputs.map((i, index) =>
|
|
1707
|
+
getScriptAndAmountFromUtxo(index, i, cache),
|
|
1708
|
+
);
|
|
1709
|
+
const signingScripts = prevOuts.map((o) => o.script);
|
|
1710
|
+
const values = prevOuts.map((o) => o.value);
|
|
1711
|
+
|
|
1712
|
+
const hashes: { pubkey: Buffer; hash: Buffer; leafHash?: Buffer }[] = [];
|
|
1713
|
+
if (input.tapInternalKey && !tapLeafHashToSign) {
|
|
1714
|
+
const outputKey = getPrevoutTaprootKey(inputIndex, input, cache) || Buffer.from([]);
|
|
1715
|
+
if (toXOnly(pubkey).equals(outputKey)) {
|
|
1716
|
+
const tapKeyHash = unsignedTx.hashForWitnessV1(
|
|
1717
|
+
inputIndex,
|
|
1718
|
+
signingScripts,
|
|
1719
|
+
values,
|
|
1720
|
+
sighashType,
|
|
1721
|
+
);
|
|
1722
|
+
hashes.push({ pubkey, hash: tapKeyHash });
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
const tapLeafHashes = (input.tapLeafScript || [])
|
|
1727
|
+
.filter((tapLeaf) => pubkeyInScript(pubkey, tapLeaf.script))
|
|
1728
|
+
.map((tapLeaf) => {
|
|
1729
|
+
const hash = tapleafHash({
|
|
1730
|
+
output: tapLeaf.script,
|
|
1731
|
+
version: tapLeaf.leafVersion,
|
|
1732
|
+
});
|
|
1733
|
+
return Object.assign({ hash }, tapLeaf);
|
|
1734
|
+
})
|
|
1735
|
+
.filter((tapLeaf) => !tapLeafHashToSign || tapLeafHashToSign.equals(tapLeaf.hash))
|
|
1736
|
+
.map((tapLeaf) => {
|
|
1737
|
+
const tapScriptHash = unsignedTx.hashForWitnessV1(
|
|
1738
|
+
inputIndex,
|
|
1739
|
+
signingScripts,
|
|
1740
|
+
values,
|
|
1741
|
+
sighashType,
|
|
1742
|
+
tapLeaf.hash,
|
|
1743
|
+
);
|
|
1744
|
+
|
|
1745
|
+
return {
|
|
1746
|
+
pubkey,
|
|
1747
|
+
hash: tapScriptHash,
|
|
1748
|
+
leafHash: tapLeaf.hash,
|
|
1749
|
+
};
|
|
1750
|
+
});
|
|
1751
|
+
|
|
1752
|
+
return hashes.concat(tapLeafHashes);
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
function checkSighashTypeAllowed(sighashType: number, sighashTypes?: number[]): void {
|
|
1756
|
+
if (sighashTypes && sighashTypes.indexOf(sighashType) < 0) {
|
|
1757
|
+
const str = sighashTypeToString(sighashType);
|
|
1758
|
+
throw new Error(
|
|
1759
|
+
`Sighash type is not allowed. Retry the sign method passing the ` +
|
|
1760
|
+
`sighashTypes array of whitelisted types. Sighash type: ${str}`,
|
|
1761
|
+
);
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
function getPayment(
|
|
1766
|
+
script: Buffer,
|
|
1767
|
+
scriptType: string,
|
|
1768
|
+
partialSig: PartialSig[],
|
|
1769
|
+
): payments.Payment {
|
|
1770
|
+
let payment: payments.Payment;
|
|
1771
|
+
switch (scriptType) {
|
|
1772
|
+
case 'multisig':
|
|
1773
|
+
const sigs = getSortedSigs(script, partialSig);
|
|
1774
|
+
payment = payments.p2ms({
|
|
1775
|
+
output: script,
|
|
1776
|
+
signatures: sigs,
|
|
1777
|
+
});
|
|
1778
|
+
break;
|
|
1779
|
+
case 'pubkey':
|
|
1780
|
+
payment = payments.p2pk({
|
|
1781
|
+
output: script,
|
|
1782
|
+
signature: partialSig[0].signature,
|
|
1783
|
+
});
|
|
1784
|
+
break;
|
|
1785
|
+
case 'pubkeyhash':
|
|
1786
|
+
payment = payments.p2pkh({
|
|
1787
|
+
output: script,
|
|
1788
|
+
pubkey: partialSig[0].pubkey,
|
|
1789
|
+
signature: partialSig[0].signature,
|
|
1790
|
+
});
|
|
1791
|
+
break;
|
|
1792
|
+
case 'witnesspubkeyhash':
|
|
1793
|
+
payment = payments.p2wpkh({
|
|
1794
|
+
output: script,
|
|
1795
|
+
pubkey: partialSig[0].pubkey,
|
|
1796
|
+
signature: partialSig[0].signature,
|
|
1797
|
+
});
|
|
1798
|
+
break;
|
|
1799
|
+
}
|
|
1800
|
+
return payment!;
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
interface GetScriptReturn {
|
|
1804
|
+
script: Buffer | null;
|
|
1805
|
+
isSegwit: boolean;
|
|
1806
|
+
isP2SH: boolean;
|
|
1807
|
+
isP2WSH: boolean;
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
function getScriptFromInput(
|
|
1811
|
+
inputIndex: number,
|
|
1812
|
+
input: PsbtInput,
|
|
1813
|
+
cache: PsbtCache,
|
|
1814
|
+
): GetScriptReturn {
|
|
1815
|
+
const unsignedTx = cache.__TX;
|
|
1816
|
+
const res: GetScriptReturn = {
|
|
1817
|
+
script: null,
|
|
1818
|
+
isSegwit: false,
|
|
1819
|
+
isP2SH: false,
|
|
1820
|
+
isP2WSH: false,
|
|
1821
|
+
};
|
|
1822
|
+
res.isP2SH = !!input.redeemScript;
|
|
1823
|
+
res.isP2WSH = !!input.witnessScript;
|
|
1824
|
+
if (input.witnessScript) {
|
|
1825
|
+
res.script = input.witnessScript;
|
|
1826
|
+
} else if (input.redeemScript) {
|
|
1827
|
+
res.script = input.redeemScript;
|
|
1828
|
+
} else {
|
|
1829
|
+
if (input.nonWitnessUtxo) {
|
|
1830
|
+
const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(cache, input, inputIndex);
|
|
1831
|
+
const prevoutIndex = unsignedTx.ins[inputIndex].index;
|
|
1832
|
+
res.script = nonWitnessUtxoTx.outs[prevoutIndex].script;
|
|
1833
|
+
} else if (input.witnessUtxo) {
|
|
1834
|
+
res.script = input.witnessUtxo.script;
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
if (input.witnessScript || isP2WPKH(res.script!)) {
|
|
1838
|
+
res.isSegwit = true;
|
|
1839
|
+
}
|
|
1840
|
+
return res;
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
function getSignersFromHD(
|
|
1844
|
+
inputIndex: number,
|
|
1845
|
+
inputs: PsbtInput[],
|
|
1846
|
+
hdKeyPair: HDSigner | HDSignerAsync,
|
|
1847
|
+
): (HDSigner | HDSignerAsync)[] {
|
|
1848
|
+
const input = checkForInput(inputs, inputIndex);
|
|
1849
|
+
if (!input.bip32Derivation || input.bip32Derivation.length === 0) {
|
|
1850
|
+
throw new Error('Need bip32Derivation to sign with HD');
|
|
1851
|
+
}
|
|
1852
|
+
const myDerivations = input.bip32Derivation
|
|
1853
|
+
.map((bipDv) => {
|
|
1854
|
+
if (bipDv.masterFingerprint.equals(hdKeyPair.fingerprint)) {
|
|
1855
|
+
return bipDv;
|
|
1856
|
+
} else {
|
|
1857
|
+
return;
|
|
1858
|
+
}
|
|
1859
|
+
})
|
|
1860
|
+
.filter((v) => !!v);
|
|
1861
|
+
if (myDerivations.length === 0) {
|
|
1862
|
+
throw new Error(
|
|
1863
|
+
'Need one bip32Derivation masterFingerprint to match the HDSigner fingerprint',
|
|
1864
|
+
);
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
return myDerivations.map((bipDv) => {
|
|
1868
|
+
const node = hdKeyPair.derivePath(bipDv!.path);
|
|
1869
|
+
if (!bipDv!.pubkey.equals(node.publicKey)) {
|
|
1870
|
+
throw new Error('pubkey did not match bip32Derivation');
|
|
1871
|
+
}
|
|
1872
|
+
return node;
|
|
1873
|
+
});
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
function getSortedSigs(script: Buffer, partialSig: PartialSig[]): Buffer[] {
|
|
1877
|
+
const p2ms = payments.p2ms({ output: script });
|
|
1878
|
+
// for each pubkey in order of p2ms script
|
|
1879
|
+
return p2ms
|
|
1880
|
+
.pubkeys!.map((pk) => {
|
|
1881
|
+
// filter partialSig array by pubkey being equal
|
|
1882
|
+
return (
|
|
1883
|
+
partialSig.filter((ps) => {
|
|
1884
|
+
return ps.pubkey.equals(pk);
|
|
1885
|
+
})[0] || {}
|
|
1886
|
+
).signature;
|
|
1887
|
+
// Any pubkey without a match will return undefined
|
|
1888
|
+
// this last filter removes all the undefined items in the array.
|
|
1889
|
+
})
|
|
1890
|
+
.filter((v) => !!v);
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
function scriptWitnessToWitnessStack(buffer: Buffer): Buffer[] {
|
|
1894
|
+
let offset = 0;
|
|
1895
|
+
|
|
1896
|
+
function readSlice(n: number): Buffer {
|
|
1897
|
+
offset += n;
|
|
1898
|
+
return buffer.slice(offset - n, offset);
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
function readVarInt(): number {
|
|
1902
|
+
const vi = varuint.decode(buffer, offset);
|
|
1903
|
+
offset += (varuint.decode as any).bytes;
|
|
1904
|
+
return vi;
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
function readVarSlice(): Buffer {
|
|
1908
|
+
return readSlice(readVarInt());
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
function readVector(): Buffer[] {
|
|
1912
|
+
const count = readVarInt();
|
|
1913
|
+
const vector: Buffer[] = [];
|
|
1914
|
+
for (let i = 0; i < count; i++) vector.push(readVarSlice());
|
|
1915
|
+
return vector;
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
return readVector();
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
function sighashTypeToString(sighashType: number): string {
|
|
1922
|
+
let text = sighashType & Transaction.SIGHASH_ANYONECANPAY ? 'SIGHASH_ANYONECANPAY | ' : '';
|
|
1923
|
+
const sigMod = sighashType & 0x1f;
|
|
1924
|
+
switch (sigMod) {
|
|
1925
|
+
case Transaction.SIGHASH_ALL:
|
|
1926
|
+
text += 'SIGHASH_ALL';
|
|
1927
|
+
break;
|
|
1928
|
+
case Transaction.SIGHASH_SINGLE:
|
|
1929
|
+
text += 'SIGHASH_SINGLE';
|
|
1930
|
+
break;
|
|
1931
|
+
case Transaction.SIGHASH_NONE:
|
|
1932
|
+
text += 'SIGHASH_NONE';
|
|
1933
|
+
break;
|
|
1934
|
+
}
|
|
1935
|
+
return text;
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
function addNonWitnessTxCache(cache: PsbtCache, input: PsbtInput, inputIndex: number): void {
|
|
1939
|
+
cache.__NON_WITNESS_UTXO_BUF_CACHE[inputIndex] = input.nonWitnessUtxo!;
|
|
1940
|
+
cache.__NON_WITNESS_UTXO_TX_CACHE[inputIndex] = Transaction.fromBuffer(input.nonWitnessUtxo!);
|
|
1941
|
+
|
|
1942
|
+
const self = cache;
|
|
1943
|
+
const selfIndex = inputIndex;
|
|
1944
|
+
delete input.nonWitnessUtxo;
|
|
1945
|
+
Object.defineProperty(input, 'nonWitnessUtxo', {
|
|
1946
|
+
enumerable: true,
|
|
1947
|
+
get(): Buffer {
|
|
1948
|
+
const buf = self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex];
|
|
1949
|
+
const txCache = self.__NON_WITNESS_UTXO_TX_CACHE[selfIndex];
|
|
1950
|
+
if (buf !== undefined) {
|
|
1951
|
+
return buf;
|
|
1952
|
+
} else {
|
|
1953
|
+
const newBuf = txCache.toBuffer();
|
|
1954
|
+
self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] = newBuf;
|
|
1955
|
+
return newBuf;
|
|
1956
|
+
}
|
|
1957
|
+
},
|
|
1958
|
+
set(data: Buffer): void {
|
|
1959
|
+
self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] = data;
|
|
1960
|
+
},
|
|
1961
|
+
});
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
function inputFinalizeGetAmts(
|
|
1965
|
+
inputs: PsbtInput[],
|
|
1966
|
+
tx: Transaction,
|
|
1967
|
+
cache: PsbtCache,
|
|
1968
|
+
mustFinalize: boolean,
|
|
1969
|
+
disableOutputChecks?: boolean,
|
|
1970
|
+
): void {
|
|
1971
|
+
let inputAmount = 0;
|
|
1972
|
+
inputs.forEach((input, idx) => {
|
|
1973
|
+
if (mustFinalize && input.finalScriptSig) tx.ins[idx].script = input.finalScriptSig;
|
|
1974
|
+
if (mustFinalize && input.finalScriptWitness) {
|
|
1975
|
+
tx.ins[idx].witness = scriptWitnessToWitnessStack(input.finalScriptWitness);
|
|
1976
|
+
}
|
|
1977
|
+
if (input.witnessUtxo) {
|
|
1978
|
+
inputAmount += input.witnessUtxo.value;
|
|
1979
|
+
} else if (input.nonWitnessUtxo) {
|
|
1980
|
+
const nwTx = nonWitnessUtxoTxFromCache(cache, input, idx);
|
|
1981
|
+
const vout = tx.ins[idx].index;
|
|
1982
|
+
const out = nwTx.outs[vout] as Output;
|
|
1983
|
+
inputAmount += out.value;
|
|
1984
|
+
}
|
|
1985
|
+
});
|
|
1986
|
+
const outputAmount = (tx.outs as Output[]).reduce((total, o) => total + o.value, 0);
|
|
1987
|
+
const fee = inputAmount - outputAmount;
|
|
1988
|
+
if (!disableOutputChecks) {
|
|
1989
|
+
if (fee < 0) {
|
|
1990
|
+
throw new Error('Outputs are spending more than Inputs');
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
const bytes = tx.virtualSize();
|
|
1994
|
+
cache.__FEE = fee;
|
|
1995
|
+
cache.__EXTRACTED_TX = tx;
|
|
1996
|
+
cache.__FEE_RATE = Math.floor(fee / bytes);
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
function nonWitnessUtxoTxFromCache(
|
|
2000
|
+
cache: PsbtCache,
|
|
2001
|
+
input: PsbtInput,
|
|
2002
|
+
inputIndex: number,
|
|
2003
|
+
): Transaction {
|
|
2004
|
+
const c = cache.__NON_WITNESS_UTXO_TX_CACHE;
|
|
2005
|
+
if (!c[inputIndex]) {
|
|
2006
|
+
addNonWitnessTxCache(cache, input, inputIndex);
|
|
2007
|
+
}
|
|
2008
|
+
return c[inputIndex];
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
function getScriptFromUtxo(inputIndex: number, input: PsbtInput, cache: PsbtCache): Buffer {
|
|
2012
|
+
const { script } = getScriptAndAmountFromUtxo(inputIndex, input, cache);
|
|
2013
|
+
return script;
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
function getScriptAndAmountFromUtxo(
|
|
2017
|
+
inputIndex: number,
|
|
2018
|
+
input: PsbtInput,
|
|
2019
|
+
cache: PsbtCache,
|
|
2020
|
+
): { script: Buffer; value: number } {
|
|
2021
|
+
if (input.witnessUtxo !== undefined) {
|
|
2022
|
+
return {
|
|
2023
|
+
script: input.witnessUtxo.script,
|
|
2024
|
+
value: input.witnessUtxo.value,
|
|
2025
|
+
};
|
|
2026
|
+
} else if (input.nonWitnessUtxo !== undefined) {
|
|
2027
|
+
const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(cache, input, inputIndex);
|
|
2028
|
+
const o = nonWitnessUtxoTx.outs[cache.__TX.ins[inputIndex].index];
|
|
2029
|
+
return { script: o.script, value: o.value };
|
|
2030
|
+
} else {
|
|
2031
|
+
throw new Error("Can't find pubkey in input without Utxo data");
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
function pubkeyInInput(
|
|
2036
|
+
pubkey: Buffer,
|
|
2037
|
+
input: PsbtInput,
|
|
2038
|
+
inputIndex: number,
|
|
2039
|
+
cache: PsbtCache,
|
|
2040
|
+
): boolean {
|
|
2041
|
+
const script = getScriptFromUtxo(inputIndex, input, cache);
|
|
2042
|
+
const { meaningfulScript } = getMeaningfulScript(
|
|
2043
|
+
script,
|
|
2044
|
+
inputIndex,
|
|
2045
|
+
'input',
|
|
2046
|
+
input.redeemScript,
|
|
2047
|
+
input.witnessScript,
|
|
2048
|
+
);
|
|
2049
|
+
return pubkeyInScript(pubkey, meaningfulScript);
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
function pubkeyInOutput(
|
|
2053
|
+
pubkey: Buffer,
|
|
2054
|
+
output: PsbtOutput,
|
|
2055
|
+
outputIndex: number,
|
|
2056
|
+
cache: PsbtCache,
|
|
2057
|
+
): boolean {
|
|
2058
|
+
const script = cache.__TX.outs[outputIndex].script;
|
|
2059
|
+
const { meaningfulScript } = getMeaningfulScript(
|
|
2060
|
+
script,
|
|
2061
|
+
outputIndex,
|
|
2062
|
+
'output',
|
|
2063
|
+
output.redeemScript,
|
|
2064
|
+
output.witnessScript,
|
|
2065
|
+
);
|
|
2066
|
+
return pubkeyInScript(pubkey, meaningfulScript);
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
function redeemFromFinalScriptSig(finalScript: Buffer | undefined): Buffer | undefined {
|
|
2070
|
+
if (!finalScript) return;
|
|
2071
|
+
const decomp = bscript.decompile(finalScript);
|
|
2072
|
+
if (!decomp) return;
|
|
2073
|
+
const lastItem = decomp[decomp.length - 1];
|
|
2074
|
+
if (!Buffer.isBuffer(lastItem) || isPubkeyLike(lastItem) || isSigLike(lastItem)) return;
|
|
2075
|
+
const sDecomp = bscript.decompile(lastItem);
|
|
2076
|
+
if (!sDecomp) return;
|
|
2077
|
+
return lastItem;
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
function redeemFromFinalWitnessScript(finalScript: Buffer | undefined): Buffer | undefined {
|
|
2081
|
+
if (!finalScript) return;
|
|
2082
|
+
const decomp = scriptWitnessToWitnessStack(finalScript);
|
|
2083
|
+
const lastItem = decomp[decomp.length - 1];
|
|
2084
|
+
if (isPubkeyLike(lastItem)) return;
|
|
2085
|
+
const sDecomp = bscript.decompile(lastItem);
|
|
2086
|
+
if (!sDecomp) return;
|
|
2087
|
+
return lastItem;
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
function compressPubkey(pubkey: Buffer): Buffer {
|
|
2091
|
+
if (pubkey.length === 65) {
|
|
2092
|
+
const parity = pubkey[64] & 1;
|
|
2093
|
+
const newKey = pubkey.slice(0, 33);
|
|
2094
|
+
newKey[0] = 2 | parity;
|
|
2095
|
+
return newKey;
|
|
2096
|
+
}
|
|
2097
|
+
return pubkey.slice();
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
function isPubkeyLike(buf: Buffer): boolean {
|
|
2101
|
+
return buf.length === 33 && bscript.isCanonicalPubKey(buf);
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
function isSigLike(buf: Buffer): boolean {
|
|
2105
|
+
return bscript.isCanonicalScriptSignature(buf);
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
function getMeaningfulScript(
|
|
2109
|
+
script: Buffer,
|
|
2110
|
+
index: number,
|
|
2111
|
+
ioType: 'input' | 'output',
|
|
2112
|
+
redeemScript?: Buffer,
|
|
2113
|
+
witnessScript?: Buffer,
|
|
2114
|
+
): {
|
|
2115
|
+
meaningfulScript: Buffer;
|
|
2116
|
+
type: 'p2sh' | 'p2wsh' | 'p2sh-p2wsh' | 'raw';
|
|
2117
|
+
} {
|
|
2118
|
+
const isP2SH = isP2SHScript(script);
|
|
2119
|
+
const isP2SHP2WSH = isP2SH && redeemScript && isP2WSHScript(redeemScript);
|
|
2120
|
+
const isP2WSH = isP2WSHScript(script);
|
|
2121
|
+
|
|
2122
|
+
if (isP2SH && redeemScript === undefined)
|
|
2123
|
+
throw new Error('scriptPubkey is P2SH but redeemScript missing');
|
|
2124
|
+
if ((isP2WSH || isP2SHP2WSH) && witnessScript === undefined)
|
|
2125
|
+
throw new Error('scriptPubkey or redeemScript is P2WSH but witnessScript missing');
|
|
2126
|
+
|
|
2127
|
+
let meaningfulScript: Buffer;
|
|
2128
|
+
|
|
2129
|
+
if (isP2SHP2WSH) {
|
|
2130
|
+
meaningfulScript = witnessScript!;
|
|
2131
|
+
checkRedeemScript(index, script, redeemScript!, ioType);
|
|
2132
|
+
checkWitnessScript(index, redeemScript!, witnessScript!, ioType);
|
|
2133
|
+
checkInvalidP2WSH(meaningfulScript);
|
|
2134
|
+
} else if (isP2WSH) {
|
|
2135
|
+
meaningfulScript = witnessScript!;
|
|
2136
|
+
checkWitnessScript(index, script, witnessScript!, ioType);
|
|
2137
|
+
checkInvalidP2WSH(meaningfulScript);
|
|
2138
|
+
} else if (isP2SH) {
|
|
2139
|
+
meaningfulScript = redeemScript!;
|
|
2140
|
+
checkRedeemScript(index, script, redeemScript!, ioType);
|
|
2141
|
+
} else {
|
|
2142
|
+
meaningfulScript = script;
|
|
2143
|
+
}
|
|
2144
|
+
return {
|
|
2145
|
+
meaningfulScript,
|
|
2146
|
+
type: isP2SHP2WSH ? 'p2sh-p2wsh' : isP2SH ? 'p2sh' : isP2WSH ? 'p2wsh' : 'raw',
|
|
2147
|
+
};
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
function checkInvalidP2WSH(script: Buffer): void {
|
|
2151
|
+
if (isP2WPKH(script) || isP2SHScript(script)) {
|
|
2152
|
+
throw new Error('P2WPKH or P2SH can not be contained within P2WSH');
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
type AllScriptType =
|
|
2157
|
+
| 'witnesspubkeyhash'
|
|
2158
|
+
| 'pubkeyhash'
|
|
2159
|
+
| 'multisig'
|
|
2160
|
+
| 'pubkey'
|
|
2161
|
+
| 'nonstandard'
|
|
2162
|
+
| 'p2sh-witnesspubkeyhash'
|
|
2163
|
+
| 'p2sh-pubkeyhash'
|
|
2164
|
+
| 'p2sh-multisig'
|
|
2165
|
+
| 'p2sh-pubkey'
|
|
2166
|
+
| 'p2sh-nonstandard'
|
|
2167
|
+
| 'p2wsh-pubkeyhash'
|
|
2168
|
+
| 'p2wsh-multisig'
|
|
2169
|
+
| 'p2wsh-pubkey'
|
|
2170
|
+
| 'p2wsh-nonstandard'
|
|
2171
|
+
| 'p2sh-p2wsh-pubkeyhash'
|
|
2172
|
+
| 'p2sh-p2wsh-multisig'
|
|
2173
|
+
| 'p2sh-p2wsh-pubkey'
|
|
2174
|
+
| 'p2sh-p2wsh-nonstandard';
|
|
2175
|
+
type ScriptType = 'witnesspubkeyhash' | 'pubkeyhash' | 'multisig' | 'pubkey' | 'nonstandard';
|
|
2176
|
+
|
|
2177
|
+
function classifyScript(script: Buffer): ScriptType {
|
|
2178
|
+
if (isP2WPKH(script)) return 'witnesspubkeyhash';
|
|
2179
|
+
if (isP2PKH(script)) return 'pubkeyhash';
|
|
2180
|
+
if (isP2MS(script)) return 'multisig';
|
|
2181
|
+
if (isP2PK(script)) return 'pubkey';
|
|
2182
|
+
return 'nonstandard';
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
function range(n: number): number[] {
|
|
2186
|
+
return [...Array(n).keys()];
|
|
2187
|
+
}
|