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