@btc-vision/bitcoin 7.0.0-alpha.1 → 7.0.0-alpha.10
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/README.md +455 -155
- package/browser/address.d.ts +5 -1
- package/browser/address.d.ts.map +1 -1
- package/browser/branded.d.ts +3 -14
- package/browser/branded.d.ts.map +1 -1
- package/browser/chunks/psbt-parallel-BBFlkmiv.js +10717 -0
- package/browser/ecc/context.d.ts +22 -21
- package/browser/ecc/context.d.ts.map +1 -1
- package/browser/ecc/index.d.ts +1 -1
- package/browser/ecc/index.d.ts.map +1 -1
- package/browser/ecc/types.d.ts +10 -123
- package/browser/ecc/types.d.ts.map +1 -1
- package/browser/env.d.ts +13 -0
- package/browser/env.d.ts.map +1 -0
- package/browser/index.d.ts +6 -6
- package/browser/index.d.ts.map +1 -1
- package/browser/index.js +2602 -11786
- package/browser/io/hex.d.ts.map +1 -1
- package/browser/io/index.d.ts +0 -1
- package/browser/io/index.d.ts.map +1 -1
- package/browser/opcodes.d.ts +11 -0
- package/browser/opcodes.d.ts.map +1 -1
- package/browser/payments/p2tr.d.ts.map +1 -1
- package/browser/psbt/PsbtCache.d.ts +54 -0
- package/browser/psbt/PsbtCache.d.ts.map +1 -0
- package/browser/psbt/PsbtFinalizer.d.ts +21 -0
- package/browser/psbt/PsbtFinalizer.d.ts.map +1 -0
- package/browser/psbt/PsbtSigner.d.ts +32 -0
- package/browser/psbt/PsbtSigner.d.ts.map +1 -0
- package/browser/psbt/PsbtTransaction.d.ts +25 -0
- package/browser/psbt/PsbtTransaction.d.ts.map +1 -0
- package/browser/psbt/types.d.ts +4 -70
- package/browser/psbt/types.d.ts.map +1 -1
- package/browser/psbt/validation.d.ts +1 -1
- package/browser/psbt/validation.d.ts.map +1 -1
- package/browser/psbt.d.ts +26 -40
- package/browser/psbt.d.ts.map +1 -1
- package/browser/script.d.ts.map +1 -1
- package/browser/transaction.d.ts +4 -4
- package/browser/transaction.d.ts.map +1 -1
- package/browser/types.d.ts +5 -3
- package/browser/types.d.ts.map +1 -1
- package/browser/workers/WorkerSigningPool.d.ts +7 -0
- package/browser/workers/WorkerSigningPool.d.ts.map +1 -1
- package/browser/workers/WorkerSigningPool.node.d.ts +7 -0
- package/browser/workers/WorkerSigningPool.node.d.ts.map +1 -1
- package/browser/workers/WorkerSigningPool.sequential.d.ts +67 -0
- package/browser/workers/WorkerSigningPool.sequential.d.ts.map +1 -0
- package/browser/workers/WorkerSigningPool.worklet.d.ts +64 -0
- package/browser/workers/WorkerSigningPool.worklet.d.ts.map +1 -0
- package/browser/workers/index.browser.d.ts +16 -0
- package/browser/workers/index.browser.d.ts.map +1 -0
- package/browser/workers/index.d.ts +4 -64
- package/browser/workers/index.d.ts.map +1 -1
- package/browser/workers/index.js +28 -0
- package/browser/workers/index.node.d.ts +17 -0
- package/browser/workers/index.node.d.ts.map +1 -0
- package/browser/workers/index.react-native.d.ts +28 -0
- package/browser/workers/index.react-native.d.ts.map +1 -0
- package/browser/workers/index.shared.d.ts +15 -0
- package/browser/workers/index.shared.d.ts.map +1 -0
- package/browser/workers/psbt-parallel.d.ts +2 -3
- package/browser/workers/psbt-parallel.d.ts.map +1 -1
- package/browser/workers/types.d.ts +17 -0
- package/browser/workers/types.d.ts.map +1 -1
- package/build/address.d.ts +5 -1
- package/build/address.d.ts.map +1 -1
- package/build/address.js +29 -17
- package/build/address.js.map +1 -1
- package/build/bech32utils.js.map +1 -1
- package/build/block.js.map +1 -1
- package/build/branded.d.ts +3 -14
- package/build/branded.d.ts.map +1 -1
- package/build/branded.js +0 -5
- package/build/branded.js.map +1 -1
- package/build/ecc/context.d.ts +22 -21
- package/build/ecc/context.d.ts.map +1 -1
- package/build/ecc/context.js +23 -95
- package/build/ecc/context.js.map +1 -1
- package/build/ecc/index.d.ts +1 -1
- package/build/ecc/index.d.ts.map +1 -1
- package/build/ecc/types.d.ts +7 -126
- package/build/ecc/types.d.ts.map +1 -1
- package/build/ecc/types.js +4 -1
- package/build/ecc/types.js.map +1 -1
- package/build/env.d.ts +13 -0
- package/build/env.d.ts.map +1 -0
- package/build/env.js +198 -0
- package/build/env.js.map +1 -0
- package/build/index.d.ts +7 -6
- package/build/index.d.ts.map +1 -1
- package/build/index.js +7 -5
- package/build/index.js.map +1 -1
- package/build/io/hex.d.ts.map +1 -1
- package/build/io/hex.js +2 -1
- package/build/io/hex.js.map +1 -1
- package/build/io/index.d.ts +0 -1
- package/build/io/index.d.ts.map +1 -1
- package/build/io/index.js +0 -2
- package/build/io/index.js.map +1 -1
- package/build/opcodes.d.ts +11 -0
- package/build/opcodes.d.ts.map +1 -1
- package/build/opcodes.js +19 -4
- package/build/opcodes.js.map +1 -1
- package/build/payments/bip341.js.map +1 -1
- package/build/payments/embed.js.map +1 -1
- package/build/payments/p2ms.js.map +1 -1
- package/build/payments/p2pk.js.map +1 -1
- package/build/payments/p2pkh.js.map +1 -1
- package/build/payments/p2sh.js.map +1 -1
- package/build/payments/p2tr.d.ts.map +1 -1
- package/build/payments/p2tr.js +2 -3
- package/build/payments/p2tr.js.map +1 -1
- package/build/payments/p2wpkh.js.map +1 -1
- package/build/payments/p2wsh.js.map +1 -1
- package/build/psbt/PsbtCache.d.ts +54 -0
- package/build/psbt/PsbtCache.d.ts.map +1 -0
- package/build/psbt/PsbtCache.js +249 -0
- package/build/psbt/PsbtCache.js.map +1 -0
- package/build/psbt/PsbtFinalizer.d.ts +21 -0
- package/build/psbt/PsbtFinalizer.d.ts.map +1 -0
- package/build/psbt/PsbtFinalizer.js +157 -0
- package/build/psbt/PsbtFinalizer.js.map +1 -0
- package/build/psbt/PsbtSigner.d.ts +32 -0
- package/build/psbt/PsbtSigner.d.ts.map +1 -0
- package/build/psbt/PsbtSigner.js +192 -0
- package/build/psbt/PsbtSigner.js.map +1 -0
- package/build/psbt/PsbtTransaction.d.ts +25 -0
- package/build/psbt/PsbtTransaction.d.ts.map +1 -0
- package/build/psbt/PsbtTransaction.js +61 -0
- package/build/psbt/PsbtTransaction.js.map +1 -0
- package/build/psbt/types.d.ts +4 -70
- package/build/psbt/types.d.ts.map +1 -1
- package/build/psbt/validation.d.ts +1 -1
- package/build/psbt/validation.d.ts.map +1 -1
- package/build/psbt.d.ts +26 -40
- package/build/psbt.d.ts.map +1 -1
- package/build/psbt.js +177 -799
- package/build/psbt.js.map +1 -1
- package/build/script.d.ts.map +1 -1
- package/build/script.js +2 -2
- package/build/script.js.map +1 -1
- package/build/transaction.d.ts +4 -4
- package/build/transaction.d.ts.map +1 -1
- package/build/transaction.js +5 -4
- package/build/transaction.js.map +1 -1
- package/build/tsconfig.build.tsbuildinfo +1 -1
- package/build/types.d.ts +5 -3
- package/build/types.d.ts.map +1 -1
- package/build/types.js +11 -16
- package/build/types.js.map +1 -1
- package/build/workers/WorkerSigningPool.d.ts +7 -0
- package/build/workers/WorkerSigningPool.d.ts.map +1 -1
- package/build/workers/WorkerSigningPool.js +12 -1
- package/build/workers/WorkerSigningPool.js.map +1 -1
- package/build/workers/WorkerSigningPool.node.d.ts +7 -0
- package/build/workers/WorkerSigningPool.node.d.ts.map +1 -1
- package/build/workers/WorkerSigningPool.node.js +37 -5
- package/build/workers/WorkerSigningPool.node.js.map +1 -1
- package/build/workers/WorkerSigningPool.sequential.d.ts +76 -0
- package/build/workers/WorkerSigningPool.sequential.d.ts.map +1 -0
- package/build/workers/WorkerSigningPool.sequential.js +160 -0
- package/build/workers/WorkerSigningPool.sequential.js.map +1 -0
- package/build/workers/WorkerSigningPool.worklet.d.ts +79 -0
- package/build/workers/WorkerSigningPool.worklet.d.ts.map +1 -0
- package/build/workers/WorkerSigningPool.worklet.js +390 -0
- package/build/workers/WorkerSigningPool.worklet.js.map +1 -0
- package/build/workers/index.browser.d.ts +24 -0
- package/build/workers/index.browser.d.ts.map +1 -0
- package/build/workers/index.browser.js +30 -0
- package/build/workers/index.browser.js.map +1 -0
- package/build/workers/index.d.ts +6 -18
- package/build/workers/index.d.ts.map +1 -1
- package/build/workers/index.js +12 -14
- package/build/workers/index.js.map +1 -1
- package/build/workers/index.node.d.ts +38 -0
- package/build/workers/index.node.d.ts.map +1 -0
- package/build/workers/index.node.js +45 -0
- package/build/workers/index.node.js.map +1 -0
- package/build/workers/index.react-native.d.ts +28 -0
- package/build/workers/index.react-native.d.ts.map +1 -0
- package/build/workers/index.react-native.js +67 -0
- package/build/workers/index.react-native.js.map +1 -0
- package/build/workers/index.shared.d.ts +15 -0
- package/build/workers/index.shared.d.ts.map +1 -0
- package/build/workers/index.shared.js +20 -0
- package/build/workers/index.shared.js.map +1 -0
- package/build/workers/psbt-parallel.d.ts +2 -3
- package/build/workers/psbt-parallel.d.ts.map +1 -1
- package/build/workers/psbt-parallel.js +4 -4
- package/build/workers/psbt-parallel.js.map +1 -1
- package/build/workers/types.d.ts +17 -0
- package/build/workers/types.d.ts.map +1 -1
- package/package.json +46 -8
- package/src/address.ts +41 -18
- package/src/bech32utils.ts +3 -3
- package/src/block.ts +2 -2
- package/src/branded.ts +15 -13
- package/src/ecc/context.ts +30 -133
- package/src/ecc/index.ts +2 -2
- package/src/ecc/types.ts +7 -138
- package/src/env.ts +239 -0
- package/src/index.ts +45 -9
- package/src/io/hex.ts +2 -1
- package/src/io/index.ts +0 -3
- package/src/opcodes.ts +21 -4
- package/src/payments/bip341.ts +3 -3
- package/src/payments/embed.ts +1 -1
- package/src/payments/p2ms.ts +2 -2
- package/src/payments/p2pk.ts +2 -2
- package/src/payments/p2pkh.ts +3 -3
- package/src/payments/p2sh.ts +4 -4
- package/src/payments/p2tr.ts +9 -9
- package/src/payments/p2wpkh.ts +5 -5
- package/src/payments/p2wsh.ts +2 -2
- package/src/psbt/PsbtCache.ts +325 -0
- package/src/psbt/PsbtFinalizer.ts +213 -0
- package/src/psbt/PsbtSigner.ts +302 -0
- package/src/psbt/PsbtTransaction.ts +82 -0
- package/src/psbt/types.ts +4 -86
- package/src/psbt/validation.ts +1 -1
- package/src/psbt.ts +349 -1198
- package/src/script.ts +2 -2
- package/src/transaction.ts +10 -9
- package/src/types.ts +18 -28
- package/src/workers/WorkerSigningPool.node.ts +41 -5
- package/src/workers/WorkerSigningPool.sequential.ts +191 -0
- package/src/workers/WorkerSigningPool.ts +14 -1
- package/src/workers/WorkerSigningPool.worklet.ts +522 -0
- package/src/workers/index.browser.ts +34 -0
- package/src/workers/index.node.ts +50 -0
- package/src/workers/index.react-native.ts +110 -0
- package/src/workers/index.shared.ts +58 -0
- package/src/workers/index.ts +14 -65
- package/src/workers/psbt-parallel.ts +7 -7
- package/src/workers/types.ts +21 -0
- package/test/address.spec.ts +2 -2
- package/test/bitcoin.core.spec.ts +5 -2
- package/test/browser/payments.spec.ts +151 -0
- package/test/browser/psbt.spec.ts +1510 -0
- package/test/browser/script.spec.ts +223 -0
- package/test/browser/setup.ts +13 -0
- package/test/browser/workers-signing.spec.ts +537 -0
- package/test/crypto.spec.ts +2 -2
- package/test/env.spec.ts +418 -0
- package/test/fixtures/core/base58_encode_decode.json +12 -48
- package/test/fixtures/core/base58_keys_invalid.json +50 -150
- package/test/fixtures/core/sighash.json +1 -3
- package/test/fixtures/core/tx_valid.json +133 -501
- package/test/fixtures/embed.json +3 -11
- package/test/fixtures/p2ms.json +21 -91
- package/test/fixtures/p2pk.json +5 -24
- package/test/fixtures/p2pkh.json +7 -36
- package/test/fixtures/p2sh.json +8 -54
- package/test/fixtures/p2tr.json +2 -6
- package/test/fixtures/p2wpkh.json +7 -36
- package/test/fixtures/p2wsh.json +14 -59
- package/test/fixtures/psbt.json +2 -6
- package/test/fixtures/script.json +12 -48
- package/test/integration/addresses.spec.ts +11 -5
- package/test/integration/bip32.spec.ts +1 -1
- package/test/integration/cltv.spec.ts +10 -6
- package/test/integration/csv.spec.ts +10 -9
- package/test/integration/payments.spec.ts +8 -4
- package/test/integration/taproot.spec.ts +26 -6
- package/test/integration/transactions.spec.ts +22 -8
- package/test/payments.spec.ts +1 -1
- package/test/payments.utils.ts +1 -1
- package/test/psbt.spec.ts +250 -64
- package/test/script_signature.spec.ts +1 -1
- package/test/transaction.spec.ts +18 -5
- package/test/tsconfig.json +6 -20
- package/test/workers-pool.spec.ts +65 -23
- package/test/workers-sequential.spec.ts +669 -0
- package/test/workers-signing.spec.ts +7 -3
- package/test/workers-worklet.spec.ts +500 -0
- package/test/workers.spec.ts +6 -7
- package/typedoc.json +11 -1
- package/vite.config.browser.ts +31 -6
- package/vitest.config.browser.ts +68 -0
- package/browser/ecpair.d.ts +0 -99
- package/browser/io/MemoryPool.d.ts +0 -220
- package/browser/io/MemoryPool.d.ts.map +0 -1
- package/build/io/MemoryPool.d.ts +0 -220
- package/build/io/MemoryPool.d.ts.map +0 -1
- package/build/io/MemoryPool.js +0 -309
- package/build/io/MemoryPool.js.map +0 -1
- package/src/ecpair.d.ts +0 -99
- package/src/io/MemoryPool.ts +0 -343
- package/test/taproot-cache.spec.ts +0 -694
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import type { PsbtInput, PsbtOutput } from 'bip174';
|
|
2
|
+
import * as bscript from '../script.js';
|
|
3
|
+
import type { TaprootHashCache, Transaction } from '../transaction.js';
|
|
4
|
+
import type { PublicKey, Satoshi, Script, XOnlyPublicKey } from '../types.js';
|
|
5
|
+
import { isP2TR, isP2WPKH, pubkeyInScript } from './psbtutils.js';
|
|
6
|
+
import { getMeaningfulScript, isPubkeyLike, isSigLike, scriptWitnessToWitnessStack, } from './utils.js';
|
|
7
|
+
import type { GetScriptReturn, PrevOut, PsbtOpts } from './types.js';
|
|
8
|
+
import { isFinalized } from './validation.js';
|
|
9
|
+
import { isUnknownSegwitVersion } from '../address.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Internal PSBT cache for computed values.
|
|
13
|
+
* Wraps all cache management previously handled by a plain interface + scattered helper functions.
|
|
14
|
+
*/
|
|
15
|
+
export class PsbtCache {
|
|
16
|
+
public readonly nonWitnessUtxoTxCache: Transaction[];
|
|
17
|
+
public readonly nonWitnessUtxoBufCache: Uint8Array[];
|
|
18
|
+
public readonly txInCache: Record<string, number>;
|
|
19
|
+
public readonly tx: Transaction;
|
|
20
|
+
public unsafeSignNonSegwit: boolean;
|
|
21
|
+
public hasSignatures: boolean;
|
|
22
|
+
public fee: number | undefined;
|
|
23
|
+
public feeRate: number | undefined;
|
|
24
|
+
public extractedTx: Transaction | undefined;
|
|
25
|
+
public prevOuts: readonly PrevOut[] | undefined;
|
|
26
|
+
public signingScripts: readonly Script[] | undefined;
|
|
27
|
+
public values: readonly Satoshi[] | undefined;
|
|
28
|
+
public taprootHashCache: TaprootHashCache | undefined;
|
|
29
|
+
|
|
30
|
+
public constructor(tx: Transaction) {
|
|
31
|
+
this.nonWitnessUtxoTxCache = [];
|
|
32
|
+
this.nonWitnessUtxoBufCache = [];
|
|
33
|
+
this.txInCache = {};
|
|
34
|
+
this.tx = tx;
|
|
35
|
+
this.unsafeSignNonSegwit = false;
|
|
36
|
+
this.hasSignatures = false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Invalidates cached computed values.
|
|
41
|
+
* @param scope - 'full' clears everything (for input changes), 'outputs' clears fee/extract/taproot caches
|
|
42
|
+
*/
|
|
43
|
+
public invalidate(scope: 'full' | 'outputs'): void {
|
|
44
|
+
this.fee = undefined;
|
|
45
|
+
this.feeRate = undefined;
|
|
46
|
+
this.extractedTx = undefined;
|
|
47
|
+
this.taprootHashCache = undefined;
|
|
48
|
+
if (scope === 'full') {
|
|
49
|
+
this.prevOuts = undefined;
|
|
50
|
+
this.signingScripts = undefined;
|
|
51
|
+
this.values = undefined;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public addNonWitnessTxCache(
|
|
56
|
+
input: PsbtInput,
|
|
57
|
+
inputIndex: number,
|
|
58
|
+
txFromBuffer: (buf: Uint8Array) => Transaction,
|
|
59
|
+
): void {
|
|
60
|
+
if (!input.nonWitnessUtxo) throw new Error('nonWitnessUtxo is required');
|
|
61
|
+
if (input === null || input === Object.prototype) {
|
|
62
|
+
throw new Error('Invalid input object');
|
|
63
|
+
}
|
|
64
|
+
const nonWitnessUtxoBuf = input.nonWitnessUtxo;
|
|
65
|
+
this.nonWitnessUtxoBufCache[inputIndex] = nonWitnessUtxoBuf;
|
|
66
|
+
this.nonWitnessUtxoTxCache[inputIndex] = txFromBuffer(nonWitnessUtxoBuf);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public getNonWitnessUtxoTx(
|
|
70
|
+
input: PsbtInput,
|
|
71
|
+
inputIndex: number,
|
|
72
|
+
txFromBuffer: (buf: Uint8Array) => Transaction,
|
|
73
|
+
): Transaction {
|
|
74
|
+
const cached = this.nonWitnessUtxoTxCache[inputIndex];
|
|
75
|
+
if (!cached) {
|
|
76
|
+
this.addNonWitnessTxCache(input, inputIndex, txFromBuffer);
|
|
77
|
+
}
|
|
78
|
+
return this.nonWitnessUtxoTxCache[inputIndex]!;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public getScriptFromUtxo(
|
|
82
|
+
inputIndex: number,
|
|
83
|
+
input: PsbtInput,
|
|
84
|
+
txFromBuffer: (buf: Uint8Array) => Transaction,
|
|
85
|
+
): Script {
|
|
86
|
+
const { script } = this.getScriptAndAmountFromUtxo(inputIndex, input, txFromBuffer);
|
|
87
|
+
return script;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public getScriptAndAmountFromUtxo(
|
|
91
|
+
inputIndex: number,
|
|
92
|
+
input: PsbtInput,
|
|
93
|
+
txFromBuffer: (buf: Uint8Array) => Transaction,
|
|
94
|
+
): { script: Script; value: Satoshi } {
|
|
95
|
+
if (input.witnessUtxo !== undefined) {
|
|
96
|
+
return {
|
|
97
|
+
script: input.witnessUtxo.script as Script,
|
|
98
|
+
value: input.witnessUtxo.value as Satoshi,
|
|
99
|
+
};
|
|
100
|
+
} else if (input.nonWitnessUtxo !== undefined) {
|
|
101
|
+
const nonWitnessUtxoTx = this.getNonWitnessUtxoTx(input, inputIndex, txFromBuffer);
|
|
102
|
+
const o = nonWitnessUtxoTx.outs[this.tx.ins[inputIndex]!.index]!;
|
|
103
|
+
return { script: o.script, value: o.value };
|
|
104
|
+
} else {
|
|
105
|
+
throw new Error("Can't find pubkey in input without Utxo data");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
public computeFee(
|
|
110
|
+
inputs: PsbtInput[],
|
|
111
|
+
disableOutputChecks: boolean = false,
|
|
112
|
+
txFromBuffer?: (buf: Uint8Array) => Transaction,
|
|
113
|
+
): number {
|
|
114
|
+
if (!inputs.every(isFinalized)) throw new Error('PSBT must be finalized to calculate fee');
|
|
115
|
+
if (this.fee !== undefined) return this.fee;
|
|
116
|
+
let tx: Transaction;
|
|
117
|
+
let mustFinalize = true;
|
|
118
|
+
if (this.extractedTx) {
|
|
119
|
+
tx = this.extractedTx;
|
|
120
|
+
mustFinalize = false;
|
|
121
|
+
} else {
|
|
122
|
+
tx = this.tx.clone();
|
|
123
|
+
}
|
|
124
|
+
const { fee } = this.finalizeAndComputeAmounts(
|
|
125
|
+
inputs,
|
|
126
|
+
tx,
|
|
127
|
+
mustFinalize,
|
|
128
|
+
disableOutputChecks,
|
|
129
|
+
txFromBuffer,
|
|
130
|
+
);
|
|
131
|
+
return fee;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
public computeFeeRate(
|
|
135
|
+
inputs: PsbtInput[],
|
|
136
|
+
disableOutputChecks: boolean = false,
|
|
137
|
+
txFromBuffer?: (buf: Uint8Array) => Transaction,
|
|
138
|
+
): number {
|
|
139
|
+
if (!inputs.every(isFinalized))
|
|
140
|
+
throw new Error('PSBT must be finalized to calculate fee rate');
|
|
141
|
+
if (this.feeRate !== undefined) return this.feeRate;
|
|
142
|
+
let tx: Transaction;
|
|
143
|
+
let mustFinalize = true;
|
|
144
|
+
if (this.extractedTx) {
|
|
145
|
+
tx = this.extractedTx;
|
|
146
|
+
mustFinalize = false;
|
|
147
|
+
} else {
|
|
148
|
+
tx = this.tx.clone();
|
|
149
|
+
}
|
|
150
|
+
const { feeRate } = this.finalizeAndComputeAmounts(
|
|
151
|
+
inputs,
|
|
152
|
+
tx,
|
|
153
|
+
mustFinalize,
|
|
154
|
+
disableOutputChecks,
|
|
155
|
+
txFromBuffer,
|
|
156
|
+
);
|
|
157
|
+
return feeRate;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
public checkFees(opts: PsbtOpts): void {
|
|
161
|
+
const feeRate = this.feeRate;
|
|
162
|
+
if (!this.extractedTx) throw new Error('Transaction not extracted');
|
|
163
|
+
if (feeRate === undefined) throw new Error('Fee rate not computed');
|
|
164
|
+
const vsize = this.extractedTx.virtualSize();
|
|
165
|
+
const satoshis = feeRate * vsize;
|
|
166
|
+
if (feeRate >= opts.maximumFeeRate) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
`Warning: You are paying around ${(satoshis / 1e8).toFixed(8)} in ` +
|
|
169
|
+
`fees, which is ${feeRate} satoshi per byte for a transaction ` +
|
|
170
|
+
`with a VSize of ${vsize} bytes (segwit counted as 0.25 byte per ` +
|
|
171
|
+
`byte). Use setMaximumFeeRate method to raise your threshold, or ` +
|
|
172
|
+
`pass true to the first arg of extractTransaction.`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
public pubkeyInInput(
|
|
178
|
+
pubkey: PublicKey,
|
|
179
|
+
input: PsbtInput,
|
|
180
|
+
inputIndex: number,
|
|
181
|
+
txFromBuffer: (buf: Uint8Array) => Transaction,
|
|
182
|
+
): boolean {
|
|
183
|
+
const script = this.getScriptFromUtxo(inputIndex, input, txFromBuffer);
|
|
184
|
+
const { meaningfulScript } = getMeaningfulScript(
|
|
185
|
+
script,
|
|
186
|
+
inputIndex,
|
|
187
|
+
'input',
|
|
188
|
+
input.redeemScript,
|
|
189
|
+
input.witnessScript,
|
|
190
|
+
);
|
|
191
|
+
return pubkeyInScript(pubkey, meaningfulScript);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
public pubkeyInOutput(pubkey: PublicKey, output: PsbtOutput, outputIndex: number): boolean {
|
|
195
|
+
const script = this.tx.outs[outputIndex]!.script;
|
|
196
|
+
const { meaningfulScript } = getMeaningfulScript(
|
|
197
|
+
script,
|
|
198
|
+
outputIndex,
|
|
199
|
+
'output',
|
|
200
|
+
output.redeemScript,
|
|
201
|
+
output.witnessScript,
|
|
202
|
+
);
|
|
203
|
+
return pubkeyInScript(pubkey, meaningfulScript);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
public redeemFromFinalScriptSig(finalScript: Uint8Array | undefined): Uint8Array | undefined {
|
|
207
|
+
if (!finalScript) return;
|
|
208
|
+
const decomp = bscript.decompile(finalScript);
|
|
209
|
+
if (!decomp) return;
|
|
210
|
+
const lastItem = decomp[decomp.length - 1]!;
|
|
211
|
+
if (!(lastItem instanceof Uint8Array) || isPubkeyLike(lastItem) || isSigLike(lastItem))
|
|
212
|
+
return;
|
|
213
|
+
const sDecomp = bscript.decompile(lastItem);
|
|
214
|
+
if (!sDecomp) return;
|
|
215
|
+
return lastItem;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
public redeemFromFinalWitnessScript(
|
|
219
|
+
finalScript: Uint8Array | undefined,
|
|
220
|
+
): Uint8Array | undefined {
|
|
221
|
+
if (!finalScript) return;
|
|
222
|
+
const decomp = scriptWitnessToWitnessStack(finalScript);
|
|
223
|
+
const lastItem = decomp[decomp.length - 1]!;
|
|
224
|
+
if (isPubkeyLike(lastItem)) return;
|
|
225
|
+
const sDecomp = bscript.decompile(lastItem);
|
|
226
|
+
if (!sDecomp) return;
|
|
227
|
+
return lastItem;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Finalize transaction inputs and compute fee amounts.
|
|
232
|
+
* Returns computed values instead of mutating cache parameters directly.
|
|
233
|
+
*/
|
|
234
|
+
public finalizeAndComputeAmounts(
|
|
235
|
+
inputs: PsbtInput[],
|
|
236
|
+
tx: Transaction,
|
|
237
|
+
mustFinalize: boolean,
|
|
238
|
+
disableOutputChecks?: boolean,
|
|
239
|
+
txFromBuffer?: (buf: Uint8Array) => Transaction,
|
|
240
|
+
): { fee: number; feeRate: number } {
|
|
241
|
+
let inputAmount = 0n;
|
|
242
|
+
inputs.forEach((input, idx) => {
|
|
243
|
+
if (mustFinalize && input.finalScriptSig)
|
|
244
|
+
tx.ins[idx]!.script = input.finalScriptSig as Script;
|
|
245
|
+
if (mustFinalize && input.finalScriptWitness) {
|
|
246
|
+
tx.ins[idx]!.witness = scriptWitnessToWitnessStack(input.finalScriptWitness);
|
|
247
|
+
}
|
|
248
|
+
if (input.witnessUtxo) {
|
|
249
|
+
inputAmount += input.witnessUtxo.value;
|
|
250
|
+
} else if (input.nonWitnessUtxo) {
|
|
251
|
+
if (!txFromBuffer)
|
|
252
|
+
throw new Error('txFromBuffer is required for nonWitnessUtxo inputs');
|
|
253
|
+
const nwTx = this.getNonWitnessUtxoTx(input, idx, txFromBuffer);
|
|
254
|
+
const vout = tx.ins[idx]!.index;
|
|
255
|
+
const out = nwTx.outs[vout]!;
|
|
256
|
+
inputAmount += out.value;
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
const outputAmount = tx.outs.reduce((total, o) => total + o.value, 0n);
|
|
260
|
+
const feeValue = inputAmount - outputAmount;
|
|
261
|
+
if (!disableOutputChecks) {
|
|
262
|
+
if (feeValue < 0n) {
|
|
263
|
+
throw new Error(
|
|
264
|
+
`Outputs are spending more than Inputs ${inputAmount} < ${outputAmount}`,
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const bytes = tx.virtualSize();
|
|
269
|
+
const fee = Number(feeValue);
|
|
270
|
+
const feeRate = Math.floor(fee / bytes);
|
|
271
|
+
|
|
272
|
+
this.fee = fee;
|
|
273
|
+
this.extractedTx = tx;
|
|
274
|
+
this.feeRate = feeRate;
|
|
275
|
+
|
|
276
|
+
return { fee, feeRate };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
public getScriptFromInput(
|
|
280
|
+
inputIndex: number,
|
|
281
|
+
input: PsbtInput,
|
|
282
|
+
txFromBuffer: (buf: Uint8Array) => Transaction,
|
|
283
|
+
): GetScriptReturn {
|
|
284
|
+
const res: GetScriptReturn = {
|
|
285
|
+
script: null,
|
|
286
|
+
isSegwit: false,
|
|
287
|
+
isP2SH: false,
|
|
288
|
+
isP2WSH: false,
|
|
289
|
+
};
|
|
290
|
+
res.isP2SH = !!input.redeemScript;
|
|
291
|
+
res.isP2WSH = !!input.witnessScript;
|
|
292
|
+
if (input.witnessScript) {
|
|
293
|
+
res.script = input.witnessScript as Script;
|
|
294
|
+
} else if (input.redeemScript) {
|
|
295
|
+
res.script = input.redeemScript as Script;
|
|
296
|
+
} else {
|
|
297
|
+
if (input.nonWitnessUtxo) {
|
|
298
|
+
const nonWitnessUtxoTx = this.getNonWitnessUtxoTx(input, inputIndex, txFromBuffer);
|
|
299
|
+
const prevoutIndex = this.tx.ins[inputIndex]!.index;
|
|
300
|
+
res.script = nonWitnessUtxoTx.outs[prevoutIndex]!.script;
|
|
301
|
+
} else if (input.witnessUtxo) {
|
|
302
|
+
res.script = input.witnessUtxo.script as Script;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (input.witnessScript || (res.script && isP2WPKH(res.script))) {
|
|
307
|
+
res.isSegwit = true;
|
|
308
|
+
} else {
|
|
309
|
+
if (res.script && isUnknownSegwitVersion(res.script)) {
|
|
310
|
+
res.isSegwit = true;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return res;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
public getPrevoutTaprootKey(
|
|
318
|
+
inputIndex: number,
|
|
319
|
+
input: PsbtInput,
|
|
320
|
+
txFromBuffer: (buf: Uint8Array) => Transaction,
|
|
321
|
+
): XOnlyPublicKey | null {
|
|
322
|
+
const { script } = this.getScriptAndAmountFromUtxo(inputIndex, input, txFromBuffer);
|
|
323
|
+
return isP2TR(script) ? (script.subarray(2, 34) as XOnlyPublicKey) : null;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import type { PartialSig, PsbtInput } from 'bip174';
|
|
2
|
+
import { equals } from '../io/index.js';
|
|
3
|
+
import type { P2SHPayment, P2WSHPayment, Payment } from '../payments/index.js';
|
|
4
|
+
import * as payments from '../payments/index.js';
|
|
5
|
+
import { witnessStackToScriptWitness } from './psbtutils.js';
|
|
6
|
+
import { classifyScript, compressPubkey } from './utils.js';
|
|
7
|
+
import type { Transaction } from '../transaction.js';
|
|
8
|
+
import type { PublicKey, Script, Signature } from '../types.js';
|
|
9
|
+
import type { PsbtCache } from './PsbtCache.js';
|
|
10
|
+
import type { GetScriptReturn } from './types.js';
|
|
11
|
+
|
|
12
|
+
export interface FinalScriptsResult {
|
|
13
|
+
readonly finalScriptSig: Script | undefined;
|
|
14
|
+
readonly finalScriptWitness: Uint8Array | undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Class wrapping all finalization logic for PSBT.
|
|
19
|
+
*/
|
|
20
|
+
export class PsbtFinalizer {
|
|
21
|
+
readonly #cache: PsbtCache;
|
|
22
|
+
readonly #txFromBuffer: (buf: Uint8Array) => Transaction;
|
|
23
|
+
|
|
24
|
+
public constructor(cache: PsbtCache, txFromBuffer: (buf: Uint8Array) => Transaction) {
|
|
25
|
+
this.#cache = cache;
|
|
26
|
+
this.#txFromBuffer = txFromBuffer;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public getFinalScripts(
|
|
30
|
+
inputIndex: number,
|
|
31
|
+
input: PsbtInput,
|
|
32
|
+
script: Script,
|
|
33
|
+
isSegwit: boolean,
|
|
34
|
+
isP2SH: boolean,
|
|
35
|
+
isP2WSH: boolean,
|
|
36
|
+
canRunChecks: boolean = true,
|
|
37
|
+
solution?: Uint8Array[],
|
|
38
|
+
): FinalScriptsResult {
|
|
39
|
+
const scriptType = classifyScript(script);
|
|
40
|
+
if (!canFinalize(input, script, scriptType) && canRunChecks) {
|
|
41
|
+
throw new Error(`Can not finalize input #${inputIndex}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!input.partialSig) throw new Error('Input missing partial signatures');
|
|
45
|
+
return prepareFinalScripts(
|
|
46
|
+
script,
|
|
47
|
+
scriptType,
|
|
48
|
+
input.partialSig,
|
|
49
|
+
isSegwit,
|
|
50
|
+
isP2SH,
|
|
51
|
+
isP2WSH,
|
|
52
|
+
solution,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public getScriptFromInput(inputIndex: number, input: PsbtInput): GetScriptReturn {
|
|
57
|
+
return this.#cache.getScriptFromInput(inputIndex, input, this.#txFromBuffer);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function getFinalScripts(
|
|
62
|
+
inputIndex: number,
|
|
63
|
+
input: PsbtInput,
|
|
64
|
+
script: Script,
|
|
65
|
+
isSegwit: boolean,
|
|
66
|
+
isP2SH: boolean,
|
|
67
|
+
isP2WSH: boolean,
|
|
68
|
+
canRunChecks: boolean = true,
|
|
69
|
+
solution?: Uint8Array[],
|
|
70
|
+
): FinalScriptsResult {
|
|
71
|
+
const scriptType = classifyScript(script);
|
|
72
|
+
if (!canFinalize(input, script, scriptType) && canRunChecks) {
|
|
73
|
+
throw new Error(`Can not finalize input #${inputIndex}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!input.partialSig) throw new Error('Input missing partial signatures');
|
|
77
|
+
return prepareFinalScripts(
|
|
78
|
+
script,
|
|
79
|
+
scriptType,
|
|
80
|
+
input.partialSig,
|
|
81
|
+
isSegwit,
|
|
82
|
+
isP2SH,
|
|
83
|
+
isP2WSH,
|
|
84
|
+
solution,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function prepareFinalScripts(
|
|
89
|
+
script: Uint8Array,
|
|
90
|
+
scriptType: string,
|
|
91
|
+
partialSig: PartialSig[],
|
|
92
|
+
isSegwit: boolean,
|
|
93
|
+
isP2SH: boolean,
|
|
94
|
+
isP2WSH: boolean,
|
|
95
|
+
solution?: Uint8Array[],
|
|
96
|
+
): FinalScriptsResult {
|
|
97
|
+
let finalScriptSig: Script | undefined;
|
|
98
|
+
let finalScriptWitness: Uint8Array | undefined;
|
|
99
|
+
|
|
100
|
+
const payment: Payment = getPayment(script, scriptType, partialSig);
|
|
101
|
+
const p2wsh = !isP2WSH ? null : payments.p2wsh({ redeem: payment } as P2WSHPayment);
|
|
102
|
+
const p2sh = !isP2SH ? null : payments.p2sh({ redeem: p2wsh || payment } as P2SHPayment);
|
|
103
|
+
|
|
104
|
+
if (isSegwit) {
|
|
105
|
+
if (p2wsh && p2wsh.witness) {
|
|
106
|
+
finalScriptWitness = witnessStackToScriptWitness(p2wsh.witness);
|
|
107
|
+
} else if (payment && payment.witness) {
|
|
108
|
+
finalScriptWitness = witnessStackToScriptWitness(payment.witness);
|
|
109
|
+
} else {
|
|
110
|
+
finalScriptWitness = witnessStackToScriptWitness(solution ?? [new Uint8Array([0x00])]);
|
|
111
|
+
}
|
|
112
|
+
if (p2sh) {
|
|
113
|
+
finalScriptSig = p2sh?.input;
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
if (p2sh) {
|
|
117
|
+
finalScriptSig = p2sh?.input;
|
|
118
|
+
} else {
|
|
119
|
+
if (!payment) {
|
|
120
|
+
finalScriptSig = (
|
|
121
|
+
Array.isArray(solution) && solution[0] ? solution[0] : new Uint8Array([0x01])
|
|
122
|
+
) as Script;
|
|
123
|
+
} else {
|
|
124
|
+
finalScriptSig = payment.input;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return { finalScriptSig, finalScriptWitness };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function canFinalize(input: PsbtInput, script: Uint8Array, scriptType: string): boolean {
|
|
132
|
+
switch (scriptType) {
|
|
133
|
+
case 'pubkey':
|
|
134
|
+
case 'pubkeyhash':
|
|
135
|
+
case 'witnesspubkeyhash':
|
|
136
|
+
return hasSigs(1, input.partialSig);
|
|
137
|
+
case 'multisig': {
|
|
138
|
+
const p2ms = payments.p2ms({
|
|
139
|
+
output: script as Script,
|
|
140
|
+
});
|
|
141
|
+
if (p2ms.m === undefined) throw new Error('Cannot determine m for multisig');
|
|
142
|
+
return hasSigs(p2ms.m, input.partialSig, p2ms.pubkeys);
|
|
143
|
+
}
|
|
144
|
+
case 'nonstandard':
|
|
145
|
+
return true;
|
|
146
|
+
default:
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function hasSigs(neededSigs: number, partialSig?: PartialSig[], pubkeys?: Uint8Array[]): boolean {
|
|
152
|
+
if (!partialSig) return false;
|
|
153
|
+
let sigs: PartialSig[];
|
|
154
|
+
if (pubkeys) {
|
|
155
|
+
sigs = pubkeys
|
|
156
|
+
.map((pkey) => {
|
|
157
|
+
const pubkey = compressPubkey(pkey);
|
|
158
|
+
return partialSig.find((pSig) => equals(pSig.pubkey, pubkey));
|
|
159
|
+
})
|
|
160
|
+
.filter((v): v is PartialSig => !!v);
|
|
161
|
+
} else {
|
|
162
|
+
sigs = partialSig;
|
|
163
|
+
}
|
|
164
|
+
if (sigs.length > neededSigs) throw new Error('Too many signatures');
|
|
165
|
+
return sigs.length === neededSigs;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function getPayment(script: Uint8Array, scriptType: string, partialSig: PartialSig[]): Payment {
|
|
169
|
+
const scriptBranded = script as Script;
|
|
170
|
+
switch (scriptType) {
|
|
171
|
+
case 'multisig': {
|
|
172
|
+
const sigs = getSortedSigs(script, partialSig);
|
|
173
|
+
return payments.p2ms({
|
|
174
|
+
output: scriptBranded,
|
|
175
|
+
signatures: sigs as Signature[],
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
case 'pubkey':
|
|
179
|
+
return payments.p2pk({
|
|
180
|
+
output: scriptBranded,
|
|
181
|
+
signature: partialSig[0]!.signature as Signature,
|
|
182
|
+
});
|
|
183
|
+
case 'pubkeyhash':
|
|
184
|
+
return payments.p2pkh({
|
|
185
|
+
output: scriptBranded,
|
|
186
|
+
pubkey: partialSig[0]!.pubkey as PublicKey,
|
|
187
|
+
signature: partialSig[0]!.signature as Signature,
|
|
188
|
+
});
|
|
189
|
+
case 'witnesspubkeyhash':
|
|
190
|
+
return payments.p2wpkh({
|
|
191
|
+
output: scriptBranded,
|
|
192
|
+
pubkey: partialSig[0]!.pubkey as PublicKey,
|
|
193
|
+
signature: partialSig[0]!.signature as Signature,
|
|
194
|
+
});
|
|
195
|
+
default:
|
|
196
|
+
throw new Error(`Unknown script type: ${scriptType}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function getSortedSigs(script: Uint8Array, partialSig: PartialSig[]): Uint8Array[] {
|
|
201
|
+
const p2ms = payments.p2ms({ output: script as Script });
|
|
202
|
+
if (!p2ms.pubkeys) throw new Error('Cannot extract pubkeys from multisig script');
|
|
203
|
+
const result: Uint8Array[] = [];
|
|
204
|
+
for (const pk of p2ms.pubkeys) {
|
|
205
|
+
const matched = partialSig.filter((ps) => {
|
|
206
|
+
return equals(ps.pubkey, pk);
|
|
207
|
+
})[0];
|
|
208
|
+
if (matched) {
|
|
209
|
+
result.push(new Uint8Array(matched.signature));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return result;
|
|
213
|
+
}
|