@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,1510 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-adapted version of test/psbt.spec.ts
|
|
3
|
+
* Replaces Node.js crypto.randomBytes with browser-safe alternative.
|
|
4
|
+
*/
|
|
5
|
+
import assert from 'assert';
|
|
6
|
+
import { BIP32Factory } from '@btc-vision/bip32';
|
|
7
|
+
import * as ecc from 'tiny-secp256k1';
|
|
8
|
+
import { beforeEach, describe, it } from 'vitest';
|
|
9
|
+
|
|
10
|
+
import { randomBytes } from './setup.js';
|
|
11
|
+
import { convertScriptTree } from '../payments.utils.js';
|
|
12
|
+
import { LEAF_VERSION_TAPSCRIPT } from '../../src/payments/bip341.js';
|
|
13
|
+
import { tapTreeFromList, tapTreeToList } from '../../src/psbt/bip371.js';
|
|
14
|
+
import type { Bytes32, EccLib, MessageHash, PrivateKey, PublicKey, Satoshi, Script, Signature, Taptree, } from '../../src/types.js';
|
|
15
|
+
import type { HDSigner, Signer, SignerAsync, ValidateSigFunction } from '../../src/index.js';
|
|
16
|
+
import { initEccLib, networks, payments, Psbt } from '../../src/index.js';
|
|
17
|
+
import { equals } from '../../src/io/index.js';
|
|
18
|
+
|
|
19
|
+
import preFixtures from '../fixtures/psbt.json' with { type: 'json' };
|
|
20
|
+
import taprootFixtures from '../fixtures/p2tr.json' with { type: 'json' };
|
|
21
|
+
import { ECPairSigner, createNobleBackend } from '@btc-vision/ecpair';
|
|
22
|
+
import type { Network } from '../../src/networks.js';
|
|
23
|
+
|
|
24
|
+
const bip32 = BIP32Factory(ecc);
|
|
25
|
+
const backend = createNobleBackend();
|
|
26
|
+
const ECPair = {
|
|
27
|
+
makeRandom: (opts?: { network?: Network }) =>
|
|
28
|
+
ECPairSigner.makeRandom(backend, opts?.network ?? networks.bitcoin),
|
|
29
|
+
fromWIF: (wif: string, network?: Network | Network[]) =>
|
|
30
|
+
ECPairSigner.fromWIF(backend, wif, network ?? networks.bitcoin),
|
|
31
|
+
fromPublicKey: (pubkey: Uint8Array, opts?: { network?: Network }) =>
|
|
32
|
+
ECPairSigner.fromPublicKey(backend, pubkey as PublicKey, opts?.network ?? networks.bitcoin),
|
|
33
|
+
fromPrivateKey: (key: Uint8Array, opts?: { network?: Network }) =>
|
|
34
|
+
ECPairSigner.fromPrivateKey(backend, key as PrivateKey, opts?.network ?? networks.bitcoin),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const validator: ValidateSigFunction = (pubkey, msghash, signature): boolean =>
|
|
38
|
+
ECPair.fromPublicKey(pubkey).verify(msghash, signature as Signature);
|
|
39
|
+
|
|
40
|
+
const schnorrValidator: ValidateSigFunction = (pubkey, msghash, signature): boolean =>
|
|
41
|
+
ecc.verifySchnorr(msghash, pubkey, signature);
|
|
42
|
+
|
|
43
|
+
const initBuffers = (object: any): typeof preFixtures =>
|
|
44
|
+
JSON.parse(JSON.stringify(object), (_, value) => {
|
|
45
|
+
const regex = new RegExp(/^Buffer.from\(['"](.*)['"], ['"](.*)['"]\)$/);
|
|
46
|
+
const result = regex.exec(value);
|
|
47
|
+
if (!result) return value;
|
|
48
|
+
|
|
49
|
+
const data = result[1];
|
|
50
|
+
const encoding = result[2];
|
|
51
|
+
|
|
52
|
+
return Buffer.from(data, encoding as BufferEncoding);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const fixtures = initBuffers(preFixtures);
|
|
56
|
+
|
|
57
|
+
const upperCaseFirstLetter = (str: string): string => str.replace(/^./, (s) => s.toUpperCase());
|
|
58
|
+
|
|
59
|
+
const toAsyncSigner = (signer: Signer): SignerAsync => {
|
|
60
|
+
return {
|
|
61
|
+
publicKey: signer.publicKey,
|
|
62
|
+
sign: (hash: Bytes32, lowerR: boolean | undefined): Promise<Signature> => {
|
|
63
|
+
return new Promise((resolve, rejects): void => {
|
|
64
|
+
setTimeout(() => {
|
|
65
|
+
try {
|
|
66
|
+
const r = signer.sign(hash, lowerR);
|
|
67
|
+
resolve(r);
|
|
68
|
+
} catch (e) {
|
|
69
|
+
rejects(e as Error);
|
|
70
|
+
}
|
|
71
|
+
}, 10);
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
const failedAsyncSigner = (publicKey: Buffer): SignerAsync => {
|
|
77
|
+
return <SignerAsync>{
|
|
78
|
+
publicKey: publicKey as unknown as PublicKey,
|
|
79
|
+
sign: (__: Bytes32): Promise<Signature> => {
|
|
80
|
+
return new Promise((_, reject): void => {
|
|
81
|
+
setTimeout(() => {
|
|
82
|
+
reject(new Error('sign failed'));
|
|
83
|
+
}, 10);
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
describe(`Psbt`, () => {
|
|
90
|
+
beforeEach(() => {
|
|
91
|
+
// provide the ECC lib only when required
|
|
92
|
+
initEccLib(undefined);
|
|
93
|
+
});
|
|
94
|
+
describe('BIP174 Test Vectors', () => {
|
|
95
|
+
fixtures.bip174.invalid.forEach((f) => {
|
|
96
|
+
it(`Invalid: ${f.description}`, () => {
|
|
97
|
+
assert.throws(() => {
|
|
98
|
+
Psbt.fromBase64(f.psbt);
|
|
99
|
+
}, new RegExp(f.errorMessage));
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
fixtures.bip174.valid.forEach((f) => {
|
|
104
|
+
it(`Valid: ${f.description}`, () => {
|
|
105
|
+
assert.doesNotThrow(() => {
|
|
106
|
+
Psbt.fromBase64(f.psbt);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
fixtures.bip174.failSignChecks.forEach((f) => {
|
|
112
|
+
const keyPair = ECPair.makeRandom();
|
|
113
|
+
it(`Fails Signer checks: ${f.description}`, () => {
|
|
114
|
+
const psbt = Psbt.fromBase64(f.psbt);
|
|
115
|
+
assert.throws(() => {
|
|
116
|
+
psbt.signInput(f.inputToCheck, keyPair);
|
|
117
|
+
}, new RegExp(f.errorMessage));
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
fixtures.bip174.creator.forEach((f) => {
|
|
122
|
+
it('Creates expected PSBT', () => {
|
|
123
|
+
const psbt = new Psbt();
|
|
124
|
+
for (const input of f.inputs) {
|
|
125
|
+
psbt.addInput(input);
|
|
126
|
+
}
|
|
127
|
+
for (const output of f.outputs) {
|
|
128
|
+
const script = Buffer.from(output.script, 'hex') as unknown as Script;
|
|
129
|
+
const value = BigInt(output.value) as Satoshi;
|
|
130
|
+
psbt.addOutput({ ...output, script, value });
|
|
131
|
+
}
|
|
132
|
+
assert.strictEqual(psbt.toBase64(), f.result);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
fixtures.bip174.updater.forEach((f) => {
|
|
137
|
+
it('Updates PSBT to the expected result', () => {
|
|
138
|
+
if (f.isTaproot) initEccLib(ecc as unknown as EccLib);
|
|
139
|
+
const psbt = Psbt.fromBase64(f.psbt);
|
|
140
|
+
|
|
141
|
+
for (const inputOrOutput of ['input', 'output']) {
|
|
142
|
+
const fixtureData = (f as any)[`${inputOrOutput}Data`];
|
|
143
|
+
if (fixtureData) {
|
|
144
|
+
for (const [i, data] of fixtureData.entries()) {
|
|
145
|
+
const txt = upperCaseFirstLetter(inputOrOutput);
|
|
146
|
+
(psbt as any)[`update${txt}`](i, data);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
assert.strictEqual(psbt.toBase64(), f.result);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
fixtures.bip174.signer.forEach((f) => {
|
|
156
|
+
it('Signs PSBT to the expected result', () => {
|
|
157
|
+
if (f.isTaproot) initEccLib(ecc as unknown as EccLib);
|
|
158
|
+
const psbt = Psbt.fromBase64(f.psbt);
|
|
159
|
+
|
|
160
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
161
|
+
// @ts-ignore // cannot find tapLeafHashToSign
|
|
162
|
+
f.keys.forEach(({ inputToSign, tapLeafHashToSign, WIF }) => {
|
|
163
|
+
const keyPair = ECPair.fromWIF(WIF, networks.testnet);
|
|
164
|
+
if (tapLeafHashToSign)
|
|
165
|
+
psbt.signTaprootInput(
|
|
166
|
+
inputToSign,
|
|
167
|
+
keyPair,
|
|
168
|
+
Buffer.from(tapLeafHashToSign, 'hex'),
|
|
169
|
+
);
|
|
170
|
+
else psbt.signInput(inputToSign, keyPair);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Schnorr signatures are non-deterministic (BIP340 uses random aux bytes),
|
|
174
|
+
// so for taproot we just verify signing succeeded (output format differs)
|
|
175
|
+
if (!f.isTaproot) {
|
|
176
|
+
assert.strictEqual(psbt.toBase64(), f.result);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
fixtures.bip174.combiner.forEach((f) => {
|
|
182
|
+
it('Combines two PSBTs to the expected result', () => {
|
|
183
|
+
const psbts = f.psbts.map((psbt) => Psbt.fromBase64(psbt));
|
|
184
|
+
|
|
185
|
+
psbts[0].combine(psbts[1]);
|
|
186
|
+
|
|
187
|
+
assert.strictEqual(psbts[0].toHex(), Psbt.fromBase64(f.result).toHex());
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
fixtures.bip174.finalizer.forEach((f) => {
|
|
192
|
+
it('Finalizes inputs and gives the expected PSBT', () => {
|
|
193
|
+
if (f.isTaproot) initEccLib(ecc as unknown as EccLib);
|
|
194
|
+
const psbt = Psbt.fromBase64(f.psbt);
|
|
195
|
+
|
|
196
|
+
psbt.finalizeAllInputs();
|
|
197
|
+
|
|
198
|
+
assert.strictEqual(psbt.toBase64(), f.result);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
fixtures.bip174.extractor.forEach((f) => {
|
|
203
|
+
it('Extracts the expected transaction from a PSBT', () => {
|
|
204
|
+
const psbt1 = Psbt.fromBase64(f.psbt);
|
|
205
|
+
const transaction1 = psbt1.extractTransaction(true).toHex();
|
|
206
|
+
|
|
207
|
+
const psbt2 = Psbt.fromBase64(f.psbt);
|
|
208
|
+
const transaction2 = psbt2.extractTransaction().toHex();
|
|
209
|
+
|
|
210
|
+
assert.strictEqual(transaction1, transaction2);
|
|
211
|
+
assert.strictEqual(transaction1, f.transaction);
|
|
212
|
+
|
|
213
|
+
const psbt3 = Psbt.fromBase64(f.psbt);
|
|
214
|
+
delete psbt3.data.inputs[0].finalScriptSig;
|
|
215
|
+
delete psbt3.data.inputs[0].finalScriptWitness;
|
|
216
|
+
assert.throws(() => {
|
|
217
|
+
psbt3.extractTransaction();
|
|
218
|
+
}, new RegExp('Not finalized'));
|
|
219
|
+
|
|
220
|
+
const psbt4 = Psbt.fromBase64(f.psbt);
|
|
221
|
+
psbt4.setMaximumFeeRate(1);
|
|
222
|
+
assert.throws(() => {
|
|
223
|
+
psbt4.extractTransaction();
|
|
224
|
+
}, new RegExp('Warning: You are paying around [\\d.]+ in fees'));
|
|
225
|
+
|
|
226
|
+
const psbt5 = Psbt.fromBase64(f.psbt);
|
|
227
|
+
psbt5.extractTransaction(true);
|
|
228
|
+
const fr1 = psbt5.getFeeRate();
|
|
229
|
+
const fr2 = psbt5.getFeeRate();
|
|
230
|
+
assert.strictEqual(fr1, fr2);
|
|
231
|
+
|
|
232
|
+
const psbt6 = Psbt.fromBase64(f.psbt);
|
|
233
|
+
const f1 = psbt6.getFee();
|
|
234
|
+
const f2 = psbt6.getFee();
|
|
235
|
+
assert.strictEqual(f1, f2);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe('signInputAsync', () => {
|
|
241
|
+
fixtures.signInput.checks.forEach((f) => {
|
|
242
|
+
it(f.description, async () => {
|
|
243
|
+
if (f.isTaproot) initEccLib(ecc as unknown as EccLib);
|
|
244
|
+
if (f.shouldSign) {
|
|
245
|
+
const psbtThatShouldsign = Psbt.fromBase64(f.shouldSign.psbt);
|
|
246
|
+
await assert.doesNotReject(async () => {
|
|
247
|
+
await psbtThatShouldsign.signInputAsync(
|
|
248
|
+
f.shouldSign.inputToCheck,
|
|
249
|
+
ECPair.fromWIF(f.shouldSign.WIF),
|
|
250
|
+
f.shouldSign.sighashTypes || undefined,
|
|
251
|
+
);
|
|
252
|
+
if (f.shouldSign.result) {
|
|
253
|
+
if (!f.isTaproot) {
|
|
254
|
+
assert.strictEqual(
|
|
255
|
+
psbtThatShouldsign.toBase64(),
|
|
256
|
+
f.shouldSign.result,
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
const failMessage = f.isTaproot
|
|
262
|
+
? /Need Schnorr Signer to sign taproot input #0./
|
|
263
|
+
: /sign failed/;
|
|
264
|
+
await assert.rejects(async () => {
|
|
265
|
+
await psbtThatShouldsign.signInputAsync(
|
|
266
|
+
f.shouldSign.inputToCheck,
|
|
267
|
+
failedAsyncSigner(ECPair.fromWIF(f.shouldSign.WIF).publicKey),
|
|
268
|
+
f.shouldSign.sighashTypes || undefined,
|
|
269
|
+
);
|
|
270
|
+
}, failMessage);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (f.shouldThrow) {
|
|
274
|
+
const psbtThatShouldThrow = Psbt.fromBase64(f.shouldThrow.psbt);
|
|
275
|
+
await assert.rejects(async () => {
|
|
276
|
+
await psbtThatShouldThrow.signInputAsync(
|
|
277
|
+
f.shouldThrow.inputToCheck,
|
|
278
|
+
ECPair.fromWIF(f.shouldThrow.WIF),
|
|
279
|
+
(f.shouldThrow as any).sighashTypes || undefined,
|
|
280
|
+
);
|
|
281
|
+
}, new RegExp(f.shouldThrow.errorMessage));
|
|
282
|
+
await assert.rejects(async () => {
|
|
283
|
+
await psbtThatShouldThrow.signInputAsync(
|
|
284
|
+
f.shouldThrow.inputToCheck,
|
|
285
|
+
toAsyncSigner(ECPair.fromWIF(f.shouldThrow.WIF)),
|
|
286
|
+
(f.shouldThrow as any).sighashTypes || undefined,
|
|
287
|
+
);
|
|
288
|
+
}, new RegExp(f.shouldThrow.errorMessage));
|
|
289
|
+
await assert.rejects(async () => {
|
|
290
|
+
await (psbtThatShouldThrow.signInputAsync as any)(
|
|
291
|
+
f.shouldThrow.inputToCheck,
|
|
292
|
+
);
|
|
293
|
+
}, new RegExp('Need Signer to sign input'));
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
describe('signInput', () => {
|
|
300
|
+
fixtures.signInput.checks.forEach((f) => {
|
|
301
|
+
it(f.description, () => {
|
|
302
|
+
if (f.isTaproot) initEccLib(ecc as unknown as EccLib);
|
|
303
|
+
if (f.shouldSign) {
|
|
304
|
+
const psbtThatShouldsign = Psbt.fromBase64(f.shouldSign.psbt);
|
|
305
|
+
assert.doesNotThrow(() => {
|
|
306
|
+
psbtThatShouldsign.signInput(
|
|
307
|
+
f.shouldSign.inputToCheck,
|
|
308
|
+
ECPair.fromWIF(f.shouldSign.WIF),
|
|
309
|
+
f.shouldSign.sighashTypes || undefined,
|
|
310
|
+
);
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (f.shouldThrow) {
|
|
315
|
+
const psbtThatShouldThrow = Psbt.fromBase64(f.shouldThrow.psbt);
|
|
316
|
+
assert.throws(() => {
|
|
317
|
+
psbtThatShouldThrow.signInput(
|
|
318
|
+
f.shouldThrow.inputToCheck,
|
|
319
|
+
ECPair.fromWIF(f.shouldThrow.WIF),
|
|
320
|
+
(f.shouldThrow as any).sighashTypes || undefined,
|
|
321
|
+
);
|
|
322
|
+
}, new RegExp(f.shouldThrow.errorMessage));
|
|
323
|
+
assert.throws(() => {
|
|
324
|
+
(psbtThatShouldThrow.signInput as any)(f.shouldThrow.inputToCheck);
|
|
325
|
+
}, new RegExp('Need Signer to sign input'));
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
describe('signAllInputsAsync', () => {
|
|
332
|
+
fixtures.signInput.checks.forEach((f) => {
|
|
333
|
+
if (f.description === 'checks the input exists') return;
|
|
334
|
+
it(f.description, async () => {
|
|
335
|
+
if (f.isTaproot) initEccLib(ecc as unknown as EccLib);
|
|
336
|
+
if (f.shouldSign) {
|
|
337
|
+
const psbtThatShouldsign = Psbt.fromBase64(f.shouldSign.psbt);
|
|
338
|
+
await assert.doesNotReject(async () => {
|
|
339
|
+
await psbtThatShouldsign.signAllInputsAsync(
|
|
340
|
+
ECPair.fromWIF(f.shouldSign.WIF),
|
|
341
|
+
f.shouldSign.sighashTypes || undefined,
|
|
342
|
+
);
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (f.shouldThrow) {
|
|
347
|
+
const psbtThatShouldThrow = Psbt.fromBase64(f.shouldThrow.psbt);
|
|
348
|
+
await assert.rejects(async () => {
|
|
349
|
+
await psbtThatShouldThrow.signAllInputsAsync(
|
|
350
|
+
ECPair.fromWIF(f.shouldThrow.WIF),
|
|
351
|
+
(f.shouldThrow as any).sighashTypes || undefined,
|
|
352
|
+
);
|
|
353
|
+
}, new RegExp('No inputs were signed'));
|
|
354
|
+
await assert.rejects(async () => {
|
|
355
|
+
await (psbtThatShouldThrow.signAllInputsAsync as any)();
|
|
356
|
+
}, new RegExp('Need Signer to sign input'));
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
describe('signAllInputs', () => {
|
|
363
|
+
fixtures.signInput.checks.forEach((f) => {
|
|
364
|
+
if (f.description === 'checks the input exists') return;
|
|
365
|
+
it(f.description, () => {
|
|
366
|
+
if (f.isTaproot) initEccLib(ecc as unknown as EccLib);
|
|
367
|
+
if (f.shouldSign) {
|
|
368
|
+
const psbtThatShouldsign = Psbt.fromBase64(f.shouldSign.psbt);
|
|
369
|
+
assert.doesNotThrow(() => {
|
|
370
|
+
psbtThatShouldsign.signAllInputs(
|
|
371
|
+
ECPair.fromWIF(f.shouldSign.WIF),
|
|
372
|
+
f.shouldSign.sighashTypes || undefined,
|
|
373
|
+
);
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (f.shouldThrow) {
|
|
378
|
+
const psbtThatShouldThrow = Psbt.fromBase64(f.shouldThrow.psbt);
|
|
379
|
+
assert.throws(() => {
|
|
380
|
+
psbtThatShouldThrow.signAllInputs(
|
|
381
|
+
ECPair.fromWIF(f.shouldThrow.WIF),
|
|
382
|
+
(f.shouldThrow as any).sighashTypes || undefined,
|
|
383
|
+
);
|
|
384
|
+
}, new RegExp('No inputs were signed'));
|
|
385
|
+
assert.throws(() => {
|
|
386
|
+
(psbtThatShouldThrow.signAllInputs as any)();
|
|
387
|
+
}, new RegExp('Need Signer to sign input'));
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
describe('signInputHDAsync', () => {
|
|
394
|
+
fixtures.signInputHD.checks.forEach((f) => {
|
|
395
|
+
it(f.description, async () => {
|
|
396
|
+
if (f.shouldSign) {
|
|
397
|
+
const psbtThatShouldsign = Psbt.fromBase64(f.shouldSign.psbt);
|
|
398
|
+
await assert.doesNotReject(async () => {
|
|
399
|
+
await psbtThatShouldsign.signInputHDAsync(
|
|
400
|
+
f.shouldSign.inputToCheck,
|
|
401
|
+
bip32.fromBase58(f.shouldSign.xprv) as unknown as HDSigner,
|
|
402
|
+
(f.shouldSign as any).sighashTypes || undefined,
|
|
403
|
+
);
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (f.shouldThrow) {
|
|
408
|
+
const psbtThatShouldThrow = Psbt.fromBase64(f.shouldThrow.psbt);
|
|
409
|
+
await assert.rejects(async () => {
|
|
410
|
+
await psbtThatShouldThrow.signInputHDAsync(
|
|
411
|
+
f.shouldThrow.inputToCheck,
|
|
412
|
+
bip32.fromBase58(f.shouldThrow.xprv) as unknown as HDSigner,
|
|
413
|
+
(f.shouldThrow as any).sighashTypes || undefined,
|
|
414
|
+
);
|
|
415
|
+
}, new RegExp(f.shouldThrow.errorMessage));
|
|
416
|
+
await assert.rejects(async () => {
|
|
417
|
+
await (psbtThatShouldThrow.signInputHDAsync as any)(
|
|
418
|
+
f.shouldThrow.inputToCheck,
|
|
419
|
+
);
|
|
420
|
+
}, new RegExp('Need HDSigner to sign input'));
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
describe('signInputHD', () => {
|
|
427
|
+
fixtures.signInputHD.checks.forEach((f) => {
|
|
428
|
+
it(f.description, () => {
|
|
429
|
+
if (f.shouldSign) {
|
|
430
|
+
const psbtThatShouldsign = Psbt.fromBase64(f.shouldSign.psbt);
|
|
431
|
+
assert.doesNotThrow(() => {
|
|
432
|
+
psbtThatShouldsign.signInputHD(
|
|
433
|
+
f.shouldSign.inputToCheck,
|
|
434
|
+
bip32.fromBase58(f.shouldSign.xprv) as unknown as HDSigner,
|
|
435
|
+
(f.shouldSign as any).sighashTypes || undefined,
|
|
436
|
+
);
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (f.shouldThrow) {
|
|
441
|
+
const psbtThatShouldThrow = Psbt.fromBase64(f.shouldThrow.psbt);
|
|
442
|
+
assert.throws(() => {
|
|
443
|
+
psbtThatShouldThrow.signInputHD(
|
|
444
|
+
f.shouldThrow.inputToCheck,
|
|
445
|
+
bip32.fromBase58(f.shouldThrow.xprv) as unknown as HDSigner,
|
|
446
|
+
(f.shouldThrow as any).sighashTypes || undefined,
|
|
447
|
+
);
|
|
448
|
+
}, new RegExp(f.shouldThrow.errorMessage));
|
|
449
|
+
assert.throws(() => {
|
|
450
|
+
(psbtThatShouldThrow.signInputHD as any)(f.shouldThrow.inputToCheck);
|
|
451
|
+
}, new RegExp('Need HDSigner to sign input'));
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
describe('signAllInputsHDAsync', () => {
|
|
458
|
+
fixtures.signInputHD.checks.forEach((f) => {
|
|
459
|
+
it(f.description, async () => {
|
|
460
|
+
if (f.shouldSign) {
|
|
461
|
+
const psbtThatShouldsign = Psbt.fromBase64(f.shouldSign.psbt);
|
|
462
|
+
await assert.doesNotReject(async () => {
|
|
463
|
+
await psbtThatShouldsign.signAllInputsHDAsync(
|
|
464
|
+
bip32.fromBase58(f.shouldSign.xprv) as unknown as HDSigner,
|
|
465
|
+
(f.shouldSign as any).sighashTypes || undefined,
|
|
466
|
+
);
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (f.shouldThrow) {
|
|
471
|
+
const psbtThatShouldThrow = Psbt.fromBase64(f.shouldThrow.psbt);
|
|
472
|
+
await assert.rejects(async () => {
|
|
473
|
+
await psbtThatShouldThrow.signAllInputsHDAsync(
|
|
474
|
+
bip32.fromBase58(f.shouldThrow.xprv) as unknown as HDSigner,
|
|
475
|
+
(f.shouldThrow as any).sighashTypes || undefined,
|
|
476
|
+
);
|
|
477
|
+
}, new RegExp('No inputs were signed'));
|
|
478
|
+
await assert.rejects(async () => {
|
|
479
|
+
await (psbtThatShouldThrow.signAllInputsHDAsync as any)();
|
|
480
|
+
}, new RegExp('Need HDSigner to sign input'));
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
describe('signAllInputsHD', () => {
|
|
487
|
+
fixtures.signInputHD.checks.forEach((f) => {
|
|
488
|
+
it(f.description, () => {
|
|
489
|
+
if (f.shouldSign) {
|
|
490
|
+
const psbtThatShouldsign = Psbt.fromBase64(f.shouldSign.psbt);
|
|
491
|
+
assert.doesNotThrow(() => {
|
|
492
|
+
psbtThatShouldsign.signAllInputsHD(
|
|
493
|
+
bip32.fromBase58(f.shouldSign.xprv) as unknown as HDSigner,
|
|
494
|
+
(f.shouldSign as any).sighashTypes || undefined,
|
|
495
|
+
);
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (f.shouldThrow) {
|
|
500
|
+
const psbtThatShouldThrow = Psbt.fromBase64(f.shouldThrow.psbt);
|
|
501
|
+
assert.throws(() => {
|
|
502
|
+
psbtThatShouldThrow.signAllInputsHD(
|
|
503
|
+
bip32.fromBase58(f.shouldThrow.xprv) as unknown as HDSigner,
|
|
504
|
+
(f.shouldThrow as any).sighashTypes || undefined,
|
|
505
|
+
);
|
|
506
|
+
}, new RegExp('No inputs were signed'));
|
|
507
|
+
assert.throws(() => {
|
|
508
|
+
(psbtThatShouldThrow.signAllInputsHD as any)();
|
|
509
|
+
}, new RegExp('Need HDSigner to sign input'));
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
describe('finalizeInput', () => {
|
|
516
|
+
it(`Finalizes tapleaf by hash`, () => {
|
|
517
|
+
const f = fixtures.finalizeInput.finalizeTapleafByHash;
|
|
518
|
+
const psbt = Psbt.fromBase64(f.psbt);
|
|
519
|
+
|
|
520
|
+
psbt.finalizeTaprootInput(
|
|
521
|
+
f.index,
|
|
522
|
+
Buffer.from(f.leafHash, 'hex') as unknown as Bytes32,
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
assert.strictEqual(psbt.toBase64(), f.result);
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
it(`fails if tapleaf hash not found`, () => {
|
|
529
|
+
const f = fixtures.finalizeInput.finalizeTapleafByHash;
|
|
530
|
+
const psbt = Psbt.fromBase64(f.psbt);
|
|
531
|
+
|
|
532
|
+
assert.throws(() => {
|
|
533
|
+
psbt.finalizeTaprootInput(
|
|
534
|
+
f.index,
|
|
535
|
+
Buffer.from(f.leafHash, 'hex').reverse() as unknown as Bytes32,
|
|
536
|
+
);
|
|
537
|
+
}, new RegExp('Can not finalize taproot input #0. Signature for tapleaf script not found.'));
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it(`fails if trying to finalzie non-taproot input`, () => {
|
|
541
|
+
const psbt = new Psbt();
|
|
542
|
+
psbt.addInput({
|
|
543
|
+
hash: '0000000000000000000000000000000000000000000000000000000000000000',
|
|
544
|
+
index: 0,
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
assert.throws(() => {
|
|
548
|
+
psbt.finalizeTaprootInput(0);
|
|
549
|
+
}, new RegExp('Cannot finalize input #0. Not Taproot.'));
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
describe('finalizeAllInputs', () => {
|
|
554
|
+
fixtures.finalizeAllInputs.forEach((f) => {
|
|
555
|
+
it(`Finalizes inputs of type "${f.type}"`, () => {
|
|
556
|
+
const psbt = Psbt.fromBase64(f.psbt);
|
|
557
|
+
|
|
558
|
+
psbt.finalizeAllInputs();
|
|
559
|
+
|
|
560
|
+
assert.strictEqual(psbt.toBase64(), f.result);
|
|
561
|
+
});
|
|
562
|
+
});
|
|
563
|
+
it('fails if no script found', () => {
|
|
564
|
+
const psbt = new Psbt();
|
|
565
|
+
psbt.addInput({
|
|
566
|
+
hash: '0000000000000000000000000000000000000000000000000000000000000000',
|
|
567
|
+
index: 0,
|
|
568
|
+
});
|
|
569
|
+
assert.throws(() => {
|
|
570
|
+
psbt.finalizeAllInputs();
|
|
571
|
+
}, new RegExp('No script found for input #0'));
|
|
572
|
+
psbt.updateInput(0, {
|
|
573
|
+
witnessUtxo: {
|
|
574
|
+
script: Buffer.from(
|
|
575
|
+
'0014d85c2b71d0060b09c9886aeb815e50991dda124d',
|
|
576
|
+
'hex',
|
|
577
|
+
) as unknown as Script,
|
|
578
|
+
value: 200000n as Satoshi,
|
|
579
|
+
},
|
|
580
|
+
});
|
|
581
|
+
assert.throws(() => {
|
|
582
|
+
psbt.finalizeAllInputs();
|
|
583
|
+
}, new RegExp('Can not finalize input #0'));
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
describe('addInput', () => {
|
|
588
|
+
fixtures.addInput.checks.forEach((f) => {
|
|
589
|
+
it(f.description, () => {
|
|
590
|
+
const psbt = new Psbt();
|
|
591
|
+
|
|
592
|
+
if (f.exception) {
|
|
593
|
+
assert.throws(() => {
|
|
594
|
+
psbt.addInput(f.inputData as any);
|
|
595
|
+
}, new RegExp(f.exception));
|
|
596
|
+
assert.throws(() => {
|
|
597
|
+
psbt.addInputs([f.inputData as any]);
|
|
598
|
+
}, new RegExp(f.exception));
|
|
599
|
+
} else {
|
|
600
|
+
assert.doesNotThrow(() => {
|
|
601
|
+
psbt.addInputs([f.inputData as any]);
|
|
602
|
+
if (f.equals) {
|
|
603
|
+
assert.strictEqual(psbt.toBase64(), f.equals);
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
assert.throws(() => {
|
|
607
|
+
psbt.addInput(f.inputData as any);
|
|
608
|
+
}, new RegExp('Duplicate input detected.'));
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
});
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
describe('updateInput', () => {
|
|
615
|
+
fixtures.updateInput.checks.forEach((f) => {
|
|
616
|
+
it(f.description, () => {
|
|
617
|
+
const psbt = Psbt.fromBase64(f.psbt);
|
|
618
|
+
|
|
619
|
+
if (f.exception) {
|
|
620
|
+
assert.throws(() => {
|
|
621
|
+
psbt.updateInput(f.index, f.inputData as any);
|
|
622
|
+
}, new RegExp(f.exception));
|
|
623
|
+
}
|
|
624
|
+
});
|
|
625
|
+
});
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
describe('addOutput', () => {
|
|
629
|
+
fixtures.addOutput.checks.forEach((f) => {
|
|
630
|
+
it(f.description, () => {
|
|
631
|
+
if (f.isTaproot) initEccLib(ecc as unknown as EccLib);
|
|
632
|
+
const psbt = f.psbt ? Psbt.fromBase64(f.psbt) : new Psbt();
|
|
633
|
+
|
|
634
|
+
// Convert numeric value to bigint for valid outputs
|
|
635
|
+
const outputData =
|
|
636
|
+
f.outputData && typeof f.outputData.value === 'number'
|
|
637
|
+
? { ...f.outputData, value: BigInt(f.outputData.value) }
|
|
638
|
+
: f.outputData;
|
|
639
|
+
|
|
640
|
+
if (f.exception) {
|
|
641
|
+
assert.throws(() => {
|
|
642
|
+
psbt.addOutput(f.outputData as any);
|
|
643
|
+
}, new RegExp(f.exception));
|
|
644
|
+
assert.throws(() => {
|
|
645
|
+
psbt.addOutputs([f.outputData as any]);
|
|
646
|
+
}, new RegExp(f.exception));
|
|
647
|
+
} else {
|
|
648
|
+
assert.doesNotThrow(() => {
|
|
649
|
+
psbt.addOutput(outputData as any);
|
|
650
|
+
});
|
|
651
|
+
if (f.result) {
|
|
652
|
+
assert.strictEqual(psbt.toBase64(), f.result);
|
|
653
|
+
}
|
|
654
|
+
assert.doesNotThrow(() => {
|
|
655
|
+
psbt.addOutputs([outputData as any]);
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
});
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
describe('setVersion', () => {
|
|
663
|
+
it('Sets the version value of the unsigned transaction', () => {
|
|
664
|
+
const psbt = new Psbt();
|
|
665
|
+
|
|
666
|
+
assert.strictEqual(psbt.extractTransaction().version, 2);
|
|
667
|
+
psbt.setVersion(1);
|
|
668
|
+
assert.strictEqual(psbt.extractTransaction().version, 1);
|
|
669
|
+
});
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
describe('setLocktime', () => {
|
|
673
|
+
it('Sets the nLockTime value of the unsigned transaction', () => {
|
|
674
|
+
const psbt = new Psbt();
|
|
675
|
+
|
|
676
|
+
assert.strictEqual(psbt.extractTransaction().locktime, 0);
|
|
677
|
+
psbt.setLocktime(1);
|
|
678
|
+
assert.strictEqual(psbt.extractTransaction().locktime, 1);
|
|
679
|
+
});
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
describe('setInputSequence', () => {
|
|
683
|
+
it('Sets the sequence number for a given input', () => {
|
|
684
|
+
const psbt = new Psbt();
|
|
685
|
+
psbt.addInput({
|
|
686
|
+
hash: '0000000000000000000000000000000000000000000000000000000000000000',
|
|
687
|
+
index: 0,
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
assert.strictEqual(psbt.inputCount, 1);
|
|
691
|
+
assert.strictEqual(psbt.txInputs[0].sequence, 0xffffffff);
|
|
692
|
+
psbt.setInputSequence(0, 0);
|
|
693
|
+
assert.strictEqual(psbt.txInputs[0].sequence, 0);
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
it('throws if input index is too high', () => {
|
|
697
|
+
const psbt = new Psbt();
|
|
698
|
+
psbt.addInput({
|
|
699
|
+
hash: '0000000000000000000000000000000000000000000000000000000000000000',
|
|
700
|
+
index: 0,
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
assert.throws(() => {
|
|
704
|
+
psbt.setInputSequence(1, 0);
|
|
705
|
+
}, new RegExp('Input index too high'));
|
|
706
|
+
});
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
describe('getInputType', () => {
|
|
710
|
+
const key = ECPair.makeRandom();
|
|
711
|
+
const { publicKey } = key;
|
|
712
|
+
const p2wpkhPub = (pubkey: Buffer): Script =>
|
|
713
|
+
payments.p2wpkh({
|
|
714
|
+
pubkey: pubkey as unknown as PublicKey,
|
|
715
|
+
}).output!;
|
|
716
|
+
const p2pkhPub = (pubkey: Buffer): Script =>
|
|
717
|
+
payments.p2pkh({
|
|
718
|
+
pubkey: pubkey as unknown as PublicKey,
|
|
719
|
+
}).output!;
|
|
720
|
+
const p2shOut = (output: Uint8Array): Script =>
|
|
721
|
+
payments.p2sh({
|
|
722
|
+
redeem: { output: output as Script },
|
|
723
|
+
}).output!;
|
|
724
|
+
const p2wshOut = (output: Uint8Array): Script =>
|
|
725
|
+
payments.p2wsh({
|
|
726
|
+
redeem: { output: output as Script },
|
|
727
|
+
}).output!;
|
|
728
|
+
const p2shp2wshOut = (output: Uint8Array): Script => p2shOut(p2wshOut(output));
|
|
729
|
+
const noOuter = (output: Uint8Array): Script => output as Script;
|
|
730
|
+
|
|
731
|
+
function getInputTypeTest({
|
|
732
|
+
innerScript,
|
|
733
|
+
outerScript,
|
|
734
|
+
redeemGetter,
|
|
735
|
+
witnessGetter,
|
|
736
|
+
expectedType,
|
|
737
|
+
finalize,
|
|
738
|
+
}: any): void {
|
|
739
|
+
const psbt = new Psbt();
|
|
740
|
+
psbt.addInput({
|
|
741
|
+
hash: '0000000000000000000000000000000000000000000000000000000000000000',
|
|
742
|
+
index: 0,
|
|
743
|
+
witnessUtxo: {
|
|
744
|
+
script: outerScript(innerScript(publicKey)),
|
|
745
|
+
value: 2000n,
|
|
746
|
+
},
|
|
747
|
+
...(redeemGetter ? { redeemScript: redeemGetter(publicKey) } : {}),
|
|
748
|
+
...(witnessGetter ? { witnessScript: witnessGetter(publicKey) } : {}),
|
|
749
|
+
}).addOutput({
|
|
750
|
+
script: Buffer.from(
|
|
751
|
+
'0014d85c2b71d0060b09c9886aeb815e50991dda124d',
|
|
752
|
+
'hex',
|
|
753
|
+
) as unknown as Script,
|
|
754
|
+
value: 1800n as Satoshi,
|
|
755
|
+
});
|
|
756
|
+
if (finalize) psbt.signInput(0, key).finalizeInput(0);
|
|
757
|
+
const type = psbt.getInputType(0);
|
|
758
|
+
assert.strictEqual(type, expectedType, 'incorrect input type');
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
[
|
|
762
|
+
{
|
|
763
|
+
innerScript: p2pkhPub,
|
|
764
|
+
outerScript: noOuter,
|
|
765
|
+
redeemGetter: null,
|
|
766
|
+
witnessGetter: null,
|
|
767
|
+
expectedType: 'pubkeyhash',
|
|
768
|
+
},
|
|
769
|
+
{
|
|
770
|
+
innerScript: p2wpkhPub,
|
|
771
|
+
outerScript: noOuter,
|
|
772
|
+
redeemGetter: null,
|
|
773
|
+
witnessGetter: null,
|
|
774
|
+
expectedType: 'witnesspubkeyhash',
|
|
775
|
+
},
|
|
776
|
+
{
|
|
777
|
+
innerScript: p2pkhPub,
|
|
778
|
+
outerScript: p2shOut,
|
|
779
|
+
redeemGetter: p2pkhPub,
|
|
780
|
+
witnessGetter: null,
|
|
781
|
+
expectedType: 'p2sh-pubkeyhash',
|
|
782
|
+
},
|
|
783
|
+
{
|
|
784
|
+
innerScript: p2wpkhPub,
|
|
785
|
+
outerScript: p2shOut,
|
|
786
|
+
redeemGetter: p2wpkhPub,
|
|
787
|
+
witnessGetter: null,
|
|
788
|
+
expectedType: 'p2sh-witnesspubkeyhash',
|
|
789
|
+
finalize: true,
|
|
790
|
+
},
|
|
791
|
+
{
|
|
792
|
+
innerScript: p2pkhPub,
|
|
793
|
+
outerScript: p2wshOut,
|
|
794
|
+
redeemGetter: null,
|
|
795
|
+
witnessGetter: p2pkhPub,
|
|
796
|
+
expectedType: 'p2wsh-pubkeyhash',
|
|
797
|
+
finalize: true,
|
|
798
|
+
},
|
|
799
|
+
{
|
|
800
|
+
innerScript: p2pkhPub,
|
|
801
|
+
outerScript: p2shp2wshOut,
|
|
802
|
+
redeemGetter: (pk: Buffer): Script => p2wshOut(p2pkhPub(pk)),
|
|
803
|
+
witnessGetter: p2pkhPub,
|
|
804
|
+
expectedType: 'p2sh-p2wsh-pubkeyhash',
|
|
805
|
+
},
|
|
806
|
+
].forEach((testCase) => {
|
|
807
|
+
it(`detects ${testCase.expectedType} input type`, () => {
|
|
808
|
+
getInputTypeTest(testCase);
|
|
809
|
+
});
|
|
810
|
+
});
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
describe('inputHasHDKey', () => {
|
|
814
|
+
it('should return true if HD key is present', () => {
|
|
815
|
+
const root = bip32.fromSeed(Buffer.from(randomBytes(32)));
|
|
816
|
+
const root2 = bip32.fromSeed(Buffer.from(randomBytes(32)));
|
|
817
|
+
const path = "m/0'/0";
|
|
818
|
+
const derived = root.derivePath(path);
|
|
819
|
+
const psbt = new Psbt();
|
|
820
|
+
psbt.addInput({
|
|
821
|
+
hash: '0000000000000000000000000000000000000000000000000000000000000000',
|
|
822
|
+
index: 0,
|
|
823
|
+
bip32Derivation: [
|
|
824
|
+
{
|
|
825
|
+
masterFingerprint: Buffer.from(root.fingerprint),
|
|
826
|
+
path,
|
|
827
|
+
pubkey: Buffer.from(derived.publicKey),
|
|
828
|
+
},
|
|
829
|
+
],
|
|
830
|
+
});
|
|
831
|
+
assert.strictEqual(psbt.inputHasHDKey(0, root as unknown as HDSigner), true);
|
|
832
|
+
assert.strictEqual(psbt.inputHasHDKey(0, root2 as unknown as HDSigner), false);
|
|
833
|
+
});
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
describe('inputHasPubkey', () => {
|
|
837
|
+
it('should throw', () => {
|
|
838
|
+
// Use a valid 33-byte compressed pubkey for testing
|
|
839
|
+
const testPubkey = ECPair.makeRandom().publicKey;
|
|
840
|
+
|
|
841
|
+
const psbt = new Psbt();
|
|
842
|
+
psbt.addInput({
|
|
843
|
+
hash: '0000000000000000000000000000000000000000000000000000000000000000',
|
|
844
|
+
index: 0,
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
assert.throws(() => {
|
|
848
|
+
psbt.inputHasPubkey(0, testPubkey);
|
|
849
|
+
}, new RegExp("Can't find pubkey in input without Utxo data"));
|
|
850
|
+
|
|
851
|
+
psbt.updateInput(0, {
|
|
852
|
+
witnessUtxo: {
|
|
853
|
+
value: 1337n as Satoshi,
|
|
854
|
+
script: payments.p2sh({
|
|
855
|
+
redeem: { output: Buffer.from([0x51]) as unknown as Script },
|
|
856
|
+
}).output!,
|
|
857
|
+
},
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
assert.throws(() => {
|
|
861
|
+
psbt.inputHasPubkey(0, testPubkey);
|
|
862
|
+
}, new RegExp('scriptPubkey is P2SH but redeemScript missing'));
|
|
863
|
+
|
|
864
|
+
delete psbt.data.inputs[0].witnessUtxo;
|
|
865
|
+
|
|
866
|
+
psbt.updateInput(0, {
|
|
867
|
+
witnessUtxo: {
|
|
868
|
+
value: 1337n as Satoshi,
|
|
869
|
+
script: payments.p2wsh({
|
|
870
|
+
redeem: { output: Buffer.from([0x51]) as unknown as Script },
|
|
871
|
+
}).output!,
|
|
872
|
+
},
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
assert.throws(() => {
|
|
876
|
+
psbt.inputHasPubkey(0, testPubkey);
|
|
877
|
+
}, new RegExp('scriptPubkey or redeemScript is P2WSH but witnessScript missing'));
|
|
878
|
+
|
|
879
|
+
delete psbt.data.inputs[0].witnessUtxo;
|
|
880
|
+
|
|
881
|
+
// Create a script that contains the test pubkey
|
|
882
|
+
const scriptWithPubkey = Buffer.concat([
|
|
883
|
+
Buffer.from([0x21]),
|
|
884
|
+
testPubkey,
|
|
885
|
+
Buffer.from([0xac]),
|
|
886
|
+
]);
|
|
887
|
+
|
|
888
|
+
psbt.updateInput(0, {
|
|
889
|
+
witnessUtxo: {
|
|
890
|
+
value: 1337n as Satoshi,
|
|
891
|
+
script: payments.p2sh({
|
|
892
|
+
redeem: payments.p2wsh({
|
|
893
|
+
redeem: { output: scriptWithPubkey as unknown as Script },
|
|
894
|
+
}),
|
|
895
|
+
}).output!,
|
|
896
|
+
},
|
|
897
|
+
redeemScript: payments.p2wsh({
|
|
898
|
+
redeem: { output: scriptWithPubkey as unknown as Script },
|
|
899
|
+
}).output!,
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
assert.throws(() => {
|
|
903
|
+
psbt.inputHasPubkey(0, testPubkey);
|
|
904
|
+
}, new RegExp('scriptPubkey or redeemScript is P2WSH but witnessScript missing'));
|
|
905
|
+
|
|
906
|
+
psbt.updateInput(0, {
|
|
907
|
+
witnessScript: scriptWithPubkey,
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
assert.doesNotThrow(() => {
|
|
911
|
+
psbt.inputHasPubkey(0, testPubkey);
|
|
912
|
+
});
|
|
913
|
+
});
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
describe('outputHasHDKey', () => {
|
|
917
|
+
it('should return true if HD key is present', () => {
|
|
918
|
+
const root = bip32.fromSeed(Buffer.from(randomBytes(32)));
|
|
919
|
+
const root2 = bip32.fromSeed(Buffer.from(randomBytes(32)));
|
|
920
|
+
const path = "m/0'/0";
|
|
921
|
+
const derived = root.derivePath(path);
|
|
922
|
+
const psbt = new Psbt();
|
|
923
|
+
psbt.addInput({
|
|
924
|
+
hash: '0000000000000000000000000000000000000000000000000000000000000000',
|
|
925
|
+
index: 0,
|
|
926
|
+
}).addOutput({
|
|
927
|
+
script: Buffer.from(
|
|
928
|
+
'0014000102030405060708090a0b0c0d0e0f00010203',
|
|
929
|
+
'hex',
|
|
930
|
+
) as unknown as Script,
|
|
931
|
+
value: 2000n as Satoshi,
|
|
932
|
+
bip32Derivation: [
|
|
933
|
+
{
|
|
934
|
+
masterFingerprint: Buffer.from(root.fingerprint),
|
|
935
|
+
path,
|
|
936
|
+
pubkey: Buffer.from(derived.publicKey),
|
|
937
|
+
},
|
|
938
|
+
],
|
|
939
|
+
});
|
|
940
|
+
assert.strictEqual(psbt.outputHasHDKey(0, root as unknown as HDSigner), true);
|
|
941
|
+
assert.strictEqual(psbt.outputHasHDKey(0, root2 as unknown as HDSigner), false);
|
|
942
|
+
});
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
describe('outputHasPubkey', () => {
|
|
946
|
+
it('should throw', () => {
|
|
947
|
+
// Use a valid 33-byte compressed pubkey for testing
|
|
948
|
+
const testPubkey = ECPair.makeRandom().publicKey;
|
|
949
|
+
// Create a script that contains the test pubkey (P2PK format: <len> <pubkey> OP_CHECKSIG)
|
|
950
|
+
const scriptWithPubkey = Buffer.concat([
|
|
951
|
+
Buffer.from([0x21]),
|
|
952
|
+
testPubkey,
|
|
953
|
+
Buffer.from([0xac]),
|
|
954
|
+
]);
|
|
955
|
+
|
|
956
|
+
const psbt = new Psbt();
|
|
957
|
+
psbt.addInput({
|
|
958
|
+
hash: '0000000000000000000000000000000000000000000000000000000000000000',
|
|
959
|
+
index: 0,
|
|
960
|
+
}).addOutput({
|
|
961
|
+
script: payments.p2sh({
|
|
962
|
+
redeem: payments.p2wsh({
|
|
963
|
+
redeem: { output: scriptWithPubkey as unknown as Script },
|
|
964
|
+
}),
|
|
965
|
+
}).output!,
|
|
966
|
+
value: 1337n as Satoshi,
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
assert.throws(() => {
|
|
970
|
+
psbt.outputHasPubkey(0, testPubkey);
|
|
971
|
+
}, new RegExp('scriptPubkey is P2SH but redeemScript missing'));
|
|
972
|
+
|
|
973
|
+
const psbt2 = new Psbt();
|
|
974
|
+
psbt2.addInput({
|
|
975
|
+
hash: '0000000000000000000000000000000000000000000000000000000000000000',
|
|
976
|
+
index: 0,
|
|
977
|
+
}).addOutput({
|
|
978
|
+
script: payments.p2wsh({
|
|
979
|
+
redeem: { output: Buffer.from([0x51]) as unknown as Script },
|
|
980
|
+
}).output!,
|
|
981
|
+
value: 1337n as Satoshi,
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
assert.throws(() => {
|
|
985
|
+
psbt2.outputHasPubkey(0, testPubkey);
|
|
986
|
+
}, new RegExp('scriptPubkey or redeemScript is P2WSH but witnessScript missing'));
|
|
987
|
+
|
|
988
|
+
const psbt3 = new Psbt();
|
|
989
|
+
psbt3.addInput({
|
|
990
|
+
hash: '0000000000000000000000000000000000000000000000000000000000000000',
|
|
991
|
+
index: 0,
|
|
992
|
+
}).addOutput({
|
|
993
|
+
script: payments.p2sh({
|
|
994
|
+
redeem: payments.p2wsh({
|
|
995
|
+
redeem: { output: scriptWithPubkey as unknown as Script },
|
|
996
|
+
}),
|
|
997
|
+
}).output!,
|
|
998
|
+
value: 1337n as Satoshi,
|
|
999
|
+
});
|
|
1000
|
+
|
|
1001
|
+
psbt3.updateOutput(0, {
|
|
1002
|
+
redeemScript: payments.p2wsh({
|
|
1003
|
+
redeem: { output: scriptWithPubkey as unknown as Script },
|
|
1004
|
+
}).output!,
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
assert.throws(() => {
|
|
1008
|
+
psbt3.outputHasPubkey(0, testPubkey);
|
|
1009
|
+
}, new RegExp('scriptPubkey or redeemScript is P2WSH but witnessScript missing'));
|
|
1010
|
+
|
|
1011
|
+
delete psbt3.data.outputs[0].redeemScript;
|
|
1012
|
+
|
|
1013
|
+
psbt.updateOutput(0, {
|
|
1014
|
+
witnessScript: scriptWithPubkey,
|
|
1015
|
+
});
|
|
1016
|
+
|
|
1017
|
+
assert.throws(() => {
|
|
1018
|
+
psbt.outputHasPubkey(0, testPubkey);
|
|
1019
|
+
}, new RegExp('scriptPubkey is P2SH but redeemScript missing'));
|
|
1020
|
+
|
|
1021
|
+
psbt.updateOutput(0, {
|
|
1022
|
+
redeemScript: payments.p2wsh({
|
|
1023
|
+
redeem: { output: scriptWithPubkey as unknown as Script },
|
|
1024
|
+
}).output!,
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
assert.doesNotThrow(() => {
|
|
1028
|
+
psbt.outputHasPubkey(0, testPubkey);
|
|
1029
|
+
});
|
|
1030
|
+
});
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
describe('clone', () => {
|
|
1034
|
+
it('Should clone a psbt exactly with no reference', () => {
|
|
1035
|
+
const f = fixtures.clone;
|
|
1036
|
+
const psbt = Psbt.fromBase64(f.psbt);
|
|
1037
|
+
const notAClone = Object.assign(new Psbt(), psbt); // references still active
|
|
1038
|
+
const clone = psbt.clone();
|
|
1039
|
+
|
|
1040
|
+
assert.strictEqual(psbt.validateSignaturesOfAllInputs(validator), true);
|
|
1041
|
+
|
|
1042
|
+
assert.strictEqual(clone.toBase64(), psbt.toBase64());
|
|
1043
|
+
assert.strictEqual(clone.toBase64(), notAClone.toBase64());
|
|
1044
|
+
assert.strictEqual(psbt.toBase64(), notAClone.toBase64());
|
|
1045
|
+
// Mutate data layer to prove clone is independent
|
|
1046
|
+
psbt.data.inputs[0].partialSig = [];
|
|
1047
|
+
assert.notStrictEqual(clone.toBase64(), psbt.toBase64());
|
|
1048
|
+
assert.notStrictEqual(clone.toBase64(), notAClone.toBase64());
|
|
1049
|
+
assert.strictEqual(psbt.toBase64(), notAClone.toBase64());
|
|
1050
|
+
});
|
|
1051
|
+
});
|
|
1052
|
+
|
|
1053
|
+
describe('setMaximumFeeRate', () => {
|
|
1054
|
+
it('Sets the maximumFeeRate value', () => {
|
|
1055
|
+
const psbt = new Psbt();
|
|
1056
|
+
|
|
1057
|
+
assert.strictEqual(psbt.maximumFeeRate, 5000);
|
|
1058
|
+
psbt.setMaximumFeeRate(6000);
|
|
1059
|
+
assert.strictEqual(psbt.maximumFeeRate, 6000);
|
|
1060
|
+
});
|
|
1061
|
+
});
|
|
1062
|
+
|
|
1063
|
+
describe('validateSignaturesOfInput', () => {
|
|
1064
|
+
const f = fixtures.validateSignaturesOfInput;
|
|
1065
|
+
it('Correctly validates a signature', () => {
|
|
1066
|
+
const psbt = Psbt.fromBase64(f.psbt);
|
|
1067
|
+
|
|
1068
|
+
assert.strictEqual(psbt.validateSignaturesOfInput(f.index, validator), true);
|
|
1069
|
+
assert.throws(() => {
|
|
1070
|
+
psbt.validateSignaturesOfInput(f.nonExistantIndex, validator);
|
|
1071
|
+
}, new RegExp('No signatures to validate'));
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
it('Correctly validates a signature against a pubkey', () => {
|
|
1075
|
+
const psbt = Psbt.fromBase64(f.psbt);
|
|
1076
|
+
assert.strictEqual(
|
|
1077
|
+
psbt.validateSignaturesOfInput(f.index, validator, f.pubkey as any),
|
|
1078
|
+
true,
|
|
1079
|
+
);
|
|
1080
|
+
assert.throws(() => {
|
|
1081
|
+
psbt.validateSignaturesOfInput(f.index, validator, f.incorrectPubkey as any);
|
|
1082
|
+
}, new RegExp('No signatures for this pubkey'));
|
|
1083
|
+
});
|
|
1084
|
+
});
|
|
1085
|
+
|
|
1086
|
+
describe('validateSignaturesOfTapKeyInput', () => {
|
|
1087
|
+
const f = fixtures.validateSignaturesOfTapKeyInput;
|
|
1088
|
+
it('Correctly validates all signatures', () => {
|
|
1089
|
+
initEccLib(ecc as unknown as EccLib);
|
|
1090
|
+
const psbt = Psbt.fromBase64(f.psbt);
|
|
1091
|
+
assert.strictEqual(psbt.validateSignaturesOfInput(f.index, schnorrValidator), true);
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
it('Correctly validates a signature against a pubkey', () => {
|
|
1095
|
+
initEccLib(ecc as unknown as EccLib);
|
|
1096
|
+
const psbt = Psbt.fromBase64(f.psbt);
|
|
1097
|
+
assert.strictEqual(
|
|
1098
|
+
psbt.validateSignaturesOfInput(f.index, schnorrValidator, f.pubkey as any),
|
|
1099
|
+
true,
|
|
1100
|
+
);
|
|
1101
|
+
assert.throws(() => {
|
|
1102
|
+
psbt.validateSignaturesOfInput(f.index, schnorrValidator, f.incorrectPubkey as any);
|
|
1103
|
+
}, new RegExp('No signatures for this pubkey'));
|
|
1104
|
+
});
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
describe('validateSignaturesOfTapScriptInput', () => {
|
|
1108
|
+
const f = fixtures.validateSignaturesOfTapScriptInput;
|
|
1109
|
+
it('Correctly validates all signatures', () => {
|
|
1110
|
+
initEccLib(ecc as unknown as EccLib);
|
|
1111
|
+
const psbt = Psbt.fromBase64(f.psbt);
|
|
1112
|
+
assert.strictEqual(psbt.validateSignaturesOfInput(f.index, schnorrValidator), true);
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
it('Correctly validates a signature against a pubkey', () => {
|
|
1116
|
+
initEccLib(ecc as unknown as EccLib);
|
|
1117
|
+
const psbt = Psbt.fromBase64(f.psbt);
|
|
1118
|
+
assert.strictEqual(
|
|
1119
|
+
psbt.validateSignaturesOfInput(f.index, schnorrValidator, f.pubkey as any),
|
|
1120
|
+
true,
|
|
1121
|
+
);
|
|
1122
|
+
assert.throws(() => {
|
|
1123
|
+
psbt.validateSignaturesOfInput(f.index, schnorrValidator, f.incorrectPubkey as any);
|
|
1124
|
+
}, new RegExp('No signatures for this pubkey'));
|
|
1125
|
+
});
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
describe('tapTreeToList/tapTreeFromList', () => {
|
|
1129
|
+
it('Correctly converts a Taptree to a Tapleaf list and back', () => {
|
|
1130
|
+
taprootFixtures.valid
|
|
1131
|
+
.filter((f) => f.arguments.scriptTree)
|
|
1132
|
+
.map((f) => f.arguments.scriptTree)
|
|
1133
|
+
.forEach((scriptTree) => {
|
|
1134
|
+
const originalTree = convertScriptTree(scriptTree, LEAF_VERSION_TAPSCRIPT);
|
|
1135
|
+
const list = tapTreeToList(originalTree);
|
|
1136
|
+
const treeFromList = tapTreeFromList(list);
|
|
1137
|
+
|
|
1138
|
+
assert.deepStrictEqual(treeFromList, originalTree);
|
|
1139
|
+
});
|
|
1140
|
+
});
|
|
1141
|
+
|
|
1142
|
+
it('Throws if too many leaves on a given level', () => {
|
|
1143
|
+
const list = Array.from({ length: 5 }).map(() => ({
|
|
1144
|
+
depth: 2,
|
|
1145
|
+
leafVersion: LEAF_VERSION_TAPSCRIPT,
|
|
1146
|
+
script: Buffer.from([]),
|
|
1147
|
+
}));
|
|
1148
|
+
assert.throws(() => {
|
|
1149
|
+
tapTreeFromList(list);
|
|
1150
|
+
}, new RegExp('No room left to insert tapleaf in tree'));
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
it('Throws if taptree depth is exceeded', () => {
|
|
1154
|
+
let tree: Taptree = [{ output: Buffer.from([]) }, { output: Buffer.from([]) }];
|
|
1155
|
+
Array.from({ length: 129 }).forEach(() => (tree = [tree, { output: Buffer.from([]) }]));
|
|
1156
|
+
assert.throws(() => {
|
|
1157
|
+
tapTreeToList(tree as Taptree);
|
|
1158
|
+
}, new RegExp('Max taptree depth exceeded.'));
|
|
1159
|
+
});
|
|
1160
|
+
|
|
1161
|
+
it('Throws if tapleaf depth is to high', () => {
|
|
1162
|
+
const list = [
|
|
1163
|
+
{
|
|
1164
|
+
depth: 129,
|
|
1165
|
+
leafVersion: LEAF_VERSION_TAPSCRIPT,
|
|
1166
|
+
script: Buffer.from([]),
|
|
1167
|
+
},
|
|
1168
|
+
];
|
|
1169
|
+
assert.throws(() => {
|
|
1170
|
+
tapTreeFromList(list);
|
|
1171
|
+
}, new RegExp('Max taptree depth exceeded.'));
|
|
1172
|
+
});
|
|
1173
|
+
|
|
1174
|
+
it('Throws if not a valid taptree structure', () => {
|
|
1175
|
+
const tree = Array.from({ length: 3 }).map(() => ({
|
|
1176
|
+
output: Buffer.from([]),
|
|
1177
|
+
}));
|
|
1178
|
+
|
|
1179
|
+
assert.throws(() => {
|
|
1180
|
+
tapTreeToList(tree as unknown as Taptree);
|
|
1181
|
+
}, new RegExp('Cannot convert taptree to tapleaf list. Expecting a tapree structure.'));
|
|
1182
|
+
});
|
|
1183
|
+
});
|
|
1184
|
+
|
|
1185
|
+
describe('getFeeRate', () => {
|
|
1186
|
+
it('Throws error if called before inputs are finalized', () => {
|
|
1187
|
+
const f = fixtures.getFeeRate;
|
|
1188
|
+
const psbt = Psbt.fromBase64(f.psbt);
|
|
1189
|
+
|
|
1190
|
+
assert.throws(() => {
|
|
1191
|
+
psbt.getFeeRate();
|
|
1192
|
+
}, new RegExp('PSBT must be finalized to calculate fee rate'));
|
|
1193
|
+
|
|
1194
|
+
psbt.finalizeAllInputs();
|
|
1195
|
+
|
|
1196
|
+
assert.strictEqual(psbt.getFeeRate(), f.fee);
|
|
1197
|
+
// Calling again returns the same cached value
|
|
1198
|
+
assert.strictEqual(psbt.getFeeRate(), f.fee);
|
|
1199
|
+
});
|
|
1200
|
+
});
|
|
1201
|
+
|
|
1202
|
+
describe('getFee and getFeeRate return correct values', () => {
|
|
1203
|
+
it('computes fee as inputAmount - outputAmount for nonWitnessUtxo', () => {
|
|
1204
|
+
const alice = ECPair.fromWIF('L2uPYXe17xSTqbCjZvL2DsyXPCbXspvcu5mHLDYUgzdUbZGSKrSr');
|
|
1205
|
+
const psbt = new Psbt();
|
|
1206
|
+
const inputValue = 90_000n;
|
|
1207
|
+
const outputValue = 80_000n as Satoshi;
|
|
1208
|
+
|
|
1209
|
+
psbt.addInput({
|
|
1210
|
+
hash: '7d067b4a697a09d2c3cff7d4d9506c9955e93bff41bf82d439da7d030382bc3e',
|
|
1211
|
+
index: 0,
|
|
1212
|
+
nonWitnessUtxo: Buffer.from(
|
|
1213
|
+
'0200000001f9f34e95b9d5c8abcd20fc5bd4a825d1517be62f0f775e5f36da944d9' +
|
|
1214
|
+
'452e550000000006b483045022100c86e9a111afc90f64b4904bd609e9eaed80d48' +
|
|
1215
|
+
'ca17c162b1aca0a788ac3526f002207bb79b60d4fc6526329bf18a77135dc566020' +
|
|
1216
|
+
'9e761da46e1c2f1152ec013215801210211755115eabf846720f5cb18f248666fec' +
|
|
1217
|
+
'631e5e1e66009ce3710ceea5b1ad13ffffffff01905f0100000000001976a9148bb' +
|
|
1218
|
+
'c95d2709c71607c60ee3f097c1217482f518d88ac00000000',
|
|
1219
|
+
'hex',
|
|
1220
|
+
),
|
|
1221
|
+
sighashType: 1,
|
|
1222
|
+
});
|
|
1223
|
+
psbt.addOutput({
|
|
1224
|
+
address: '1KRMKfeZcmosxALVYESdPNez1AP1mEtywp',
|
|
1225
|
+
value: outputValue,
|
|
1226
|
+
});
|
|
1227
|
+
psbt.signInput(0, alice);
|
|
1228
|
+
psbt.finalizeAllInputs();
|
|
1229
|
+
|
|
1230
|
+
const expectedFee = Number(inputValue - outputValue); // 10,000
|
|
1231
|
+
const fee = psbt.getFee();
|
|
1232
|
+
assert.strictEqual(fee, expectedFee, `fee should be ${expectedFee}, got ${fee}`);
|
|
1233
|
+
|
|
1234
|
+
const tx = psbt.extractTransaction(true);
|
|
1235
|
+
const vsize = tx.virtualSize();
|
|
1236
|
+
const expectedFeeRate = Math.floor(expectedFee / vsize);
|
|
1237
|
+
const feeRate = psbt.getFeeRate();
|
|
1238
|
+
assert.strictEqual(
|
|
1239
|
+
feeRate,
|
|
1240
|
+
expectedFeeRate,
|
|
1241
|
+
`feeRate should be fee/vsize = ${expectedFee}/${vsize} = ${expectedFeeRate}, got ${feeRate}`,
|
|
1242
|
+
);
|
|
1243
|
+
});
|
|
1244
|
+
|
|
1245
|
+
it('computes fee as inputAmount - outputAmount for witnessUtxo', () => {
|
|
1246
|
+
const alice = ECPair.fromWIF('L2uPYXe17xSTqbCjZvL2DsyXPCbXspvcu5mHLDYUgzdUbZGSKrSr');
|
|
1247
|
+
const p2wpkh = payments.p2wpkh({ pubkey: alice.publicKey as PublicKey });
|
|
1248
|
+
const inputValue = 50_000n;
|
|
1249
|
+
const outputValue = 40_000n as Satoshi;
|
|
1250
|
+
|
|
1251
|
+
const psbt = new Psbt();
|
|
1252
|
+
psbt.addInput({
|
|
1253
|
+
hash: '0000000000000000000000000000000000000000000000000000000000000001',
|
|
1254
|
+
index: 0,
|
|
1255
|
+
witnessUtxo: {
|
|
1256
|
+
script: p2wpkh.output! as Script,
|
|
1257
|
+
value: inputValue as Satoshi,
|
|
1258
|
+
},
|
|
1259
|
+
});
|
|
1260
|
+
psbt.addOutput({
|
|
1261
|
+
address: '1KRMKfeZcmosxALVYESdPNez1AP1mEtywp',
|
|
1262
|
+
value: outputValue,
|
|
1263
|
+
});
|
|
1264
|
+
psbt.signInput(0, alice);
|
|
1265
|
+
psbt.finalizeAllInputs();
|
|
1266
|
+
|
|
1267
|
+
const expectedFee = Number(inputValue - outputValue); // 10,000
|
|
1268
|
+
const fee = psbt.getFee();
|
|
1269
|
+
assert.strictEqual(fee, expectedFee, `fee should be ${expectedFee}, got ${fee}`);
|
|
1270
|
+
|
|
1271
|
+
const tx = psbt.extractTransaction(true);
|
|
1272
|
+
const vsize = tx.virtualSize();
|
|
1273
|
+
const expectedFeeRate = Math.floor(expectedFee / vsize);
|
|
1274
|
+
const feeRate = psbt.getFeeRate();
|
|
1275
|
+
assert.strictEqual(
|
|
1276
|
+
feeRate,
|
|
1277
|
+
expectedFeeRate,
|
|
1278
|
+
`feeRate should be fee/vsize = ${expectedFee}/${vsize} = ${expectedFeeRate}, got ${feeRate}`,
|
|
1279
|
+
);
|
|
1280
|
+
assert.ok(feeRate > 0, 'feeRate must be positive');
|
|
1281
|
+
});
|
|
1282
|
+
|
|
1283
|
+
it('computes fee correctly with multiple inputs and outputs', () => {
|
|
1284
|
+
const alice = ECPair.fromWIF('L2uPYXe17xSTqbCjZvL2DsyXPCbXspvcu5mHLDYUgzdUbZGSKrSr');
|
|
1285
|
+
const p2wpkh = payments.p2wpkh({ pubkey: alice.publicKey as PublicKey });
|
|
1286
|
+
|
|
1287
|
+
const input1Value = 30_000n;
|
|
1288
|
+
const input2Value = 25_000n;
|
|
1289
|
+
const output1Value = 20_000n as Satoshi;
|
|
1290
|
+
const output2Value = 15_000n as Satoshi;
|
|
1291
|
+
|
|
1292
|
+
const psbt = new Psbt();
|
|
1293
|
+
psbt.addInput({
|
|
1294
|
+
hash: '0000000000000000000000000000000000000000000000000000000000000001',
|
|
1295
|
+
index: 0,
|
|
1296
|
+
witnessUtxo: {
|
|
1297
|
+
script: p2wpkh.output! as Script,
|
|
1298
|
+
value: input1Value as Satoshi,
|
|
1299
|
+
},
|
|
1300
|
+
});
|
|
1301
|
+
psbt.addInput({
|
|
1302
|
+
hash: '0000000000000000000000000000000000000000000000000000000000000002',
|
|
1303
|
+
index: 0,
|
|
1304
|
+
witnessUtxo: {
|
|
1305
|
+
script: p2wpkh.output! as Script,
|
|
1306
|
+
value: input2Value as Satoshi,
|
|
1307
|
+
},
|
|
1308
|
+
});
|
|
1309
|
+
psbt.addOutput({
|
|
1310
|
+
address: '1KRMKfeZcmosxALVYESdPNez1AP1mEtywp',
|
|
1311
|
+
value: output1Value,
|
|
1312
|
+
});
|
|
1313
|
+
psbt.addOutput({
|
|
1314
|
+
address: '1KRMKfeZcmosxALVYESdPNez1AP1mEtywp',
|
|
1315
|
+
value: output2Value,
|
|
1316
|
+
});
|
|
1317
|
+
psbt.signAllInputs(alice);
|
|
1318
|
+
psbt.finalizeAllInputs();
|
|
1319
|
+
|
|
1320
|
+
const totalIn = input1Value + input2Value; // 55,000
|
|
1321
|
+
const totalOut = output1Value + output2Value; // 35,000
|
|
1322
|
+
const expectedFee = Number(totalIn - totalOut); // 20,000
|
|
1323
|
+
const fee = psbt.getFee();
|
|
1324
|
+
assert.strictEqual(fee, expectedFee, `fee should be ${expectedFee}, got ${fee}`);
|
|
1325
|
+
|
|
1326
|
+
const tx = psbt.extractTransaction(true);
|
|
1327
|
+
const vsize = tx.virtualSize();
|
|
1328
|
+
const expectedFeeRate = Math.floor(expectedFee / vsize);
|
|
1329
|
+
const feeRate = psbt.getFeeRate();
|
|
1330
|
+
assert.strictEqual(
|
|
1331
|
+
feeRate,
|
|
1332
|
+
expectedFeeRate,
|
|
1333
|
+
`feeRate should be ${expectedFee}/${vsize} = ${expectedFeeRate}, got ${feeRate}`,
|
|
1334
|
+
);
|
|
1335
|
+
assert.ok(feeRate > 0, 'feeRate must be positive');
|
|
1336
|
+
});
|
|
1337
|
+
});
|
|
1338
|
+
|
|
1339
|
+
describe('create 1-to-1 transaction', () => {
|
|
1340
|
+
it('creates and signs a 1-to-1 transaction correctly', () => {
|
|
1341
|
+
const alice = ECPair.fromWIF('L2uPYXe17xSTqbCjZvL2DsyXPCbXspvcu5mHLDYUgzdUbZGSKrSr');
|
|
1342
|
+
const psbt = new Psbt();
|
|
1343
|
+
psbt.addInput({
|
|
1344
|
+
hash: '7d067b4a697a09d2c3cff7d4d9506c9955e93bff41bf82d439da7d030382bc3e',
|
|
1345
|
+
index: 0,
|
|
1346
|
+
nonWitnessUtxo: Buffer.from(
|
|
1347
|
+
'0200000001f9f34e95b9d5c8abcd20fc5bd4a825d1517be62f0f775e5f36da944d9' +
|
|
1348
|
+
'452e550000000006b483045022100c86e9a111afc90f64b4904bd609e9eaed80d48' +
|
|
1349
|
+
'ca17c162b1aca0a788ac3526f002207bb79b60d4fc6526329bf18a77135dc566020' +
|
|
1350
|
+
'9e761da46e1c2f1152ec013215801210211755115eabf846720f5cb18f248666fec' +
|
|
1351
|
+
'631e5e1e66009ce3710ceea5b1ad13ffffffff01905f0100000000001976a9148bb' +
|
|
1352
|
+
'c95d2709c71607c60ee3f097c1217482f518d88ac00000000',
|
|
1353
|
+
'hex',
|
|
1354
|
+
),
|
|
1355
|
+
sighashType: 1,
|
|
1356
|
+
});
|
|
1357
|
+
psbt.addOutput({
|
|
1358
|
+
address: '1KRMKfeZcmosxALVYESdPNez1AP1mEtywp',
|
|
1359
|
+
value: 80000n as Satoshi,
|
|
1360
|
+
});
|
|
1361
|
+
psbt.signInput(0, alice);
|
|
1362
|
+
assert.throws(() => {
|
|
1363
|
+
psbt.setVersion(3);
|
|
1364
|
+
}, new RegExp('Can not modify transaction, signatures exist.'));
|
|
1365
|
+
psbt.validateSignaturesOfInput(0, validator);
|
|
1366
|
+
psbt.finalizeAllInputs();
|
|
1367
|
+
assert.throws(() => {
|
|
1368
|
+
psbt.setVersion(3);
|
|
1369
|
+
}, new RegExp('Can not modify transaction, signatures exist.'));
|
|
1370
|
+
assert.strictEqual(psbt.inputHasPubkey(0, alice.publicKey), true);
|
|
1371
|
+
assert.strictEqual(psbt.outputHasPubkey(0, alice.publicKey), false);
|
|
1372
|
+
assert.strictEqual(
|
|
1373
|
+
psbt.extractTransaction().toHex(),
|
|
1374
|
+
'02000000013ebc8203037dda39d482bf41ff3be955996c50d9d4f7cfc3d2097a694a7' +
|
|
1375
|
+
'b067d000000006b483045022100931b6db94aed25d5486884d83fc37160f37f3368c0' +
|
|
1376
|
+
'd7f48c757112abefec983802205fda64cff98c849577026eb2ce916a50ea70626a766' +
|
|
1377
|
+
'9f8596dd89b720a26b4d501210365db9da3f8a260078a7e8f8b708a1161468fb2323f' +
|
|
1378
|
+
'fda5ec16b261ec1056f455ffffffff0180380100000000001976a914ca0d36044e0dc' +
|
|
1379
|
+
'08a22724efa6f6a07b0ec4c79aa88ac00000000',
|
|
1380
|
+
);
|
|
1381
|
+
});
|
|
1382
|
+
});
|
|
1383
|
+
|
|
1384
|
+
describe('Method return types', () => {
|
|
1385
|
+
it('fromBuffer returns Psbt type (not base class)', () => {
|
|
1386
|
+
const psbt = Psbt.fromBuffer(
|
|
1387
|
+
Buffer.from(
|
|
1388
|
+
'70736274ff01000a01000000000000000000000000',
|
|
1389
|
+
'hex',
|
|
1390
|
+
),
|
|
1391
|
+
);
|
|
1392
|
+
assert.strictEqual(psbt instanceof Psbt, true);
|
|
1393
|
+
assert.strictEqual(typeof psbt.version, 'number');
|
|
1394
|
+
});
|
|
1395
|
+
it('fromBase64 returns Psbt type (not base class)', () => {
|
|
1396
|
+
const psbt = Psbt.fromBase64('cHNidP8BAAoBAAAAAAAAAAAAAAAA');
|
|
1397
|
+
assert.strictEqual(psbt instanceof Psbt, true);
|
|
1398
|
+
assert.strictEqual(typeof psbt.version, 'number');
|
|
1399
|
+
});
|
|
1400
|
+
it('fromHex returns Psbt type (not base class)', () => {
|
|
1401
|
+
const psbt = Psbt.fromHex('70736274ff01000a01000000000000000000000000');
|
|
1402
|
+
assert.strictEqual(psbt instanceof Psbt, true);
|
|
1403
|
+
assert.strictEqual(typeof psbt.version, 'number');
|
|
1404
|
+
});
|
|
1405
|
+
});
|
|
1406
|
+
|
|
1407
|
+
describe('Cache', () => {
|
|
1408
|
+
it('non-witness UTXOs are stored after updateInput', () => {
|
|
1409
|
+
const f = fixtures.cache.nonWitnessUtxo;
|
|
1410
|
+
const psbt = Psbt.fromBase64(f.psbt);
|
|
1411
|
+
const index = f.inputIndex;
|
|
1412
|
+
|
|
1413
|
+
// nonWitnessUtxo is not set before updateInput
|
|
1414
|
+
assert.strictEqual(psbt.data.inputs[index].nonWitnessUtxo, undefined);
|
|
1415
|
+
|
|
1416
|
+
// After updateInput, the nonWitnessUtxo is stored on the input
|
|
1417
|
+
psbt.updateInput(index, {
|
|
1418
|
+
nonWitnessUtxo: f.nonWitnessUtxo as any,
|
|
1419
|
+
});
|
|
1420
|
+
assert.ok(psbt.data.inputs[index].nonWitnessUtxo);
|
|
1421
|
+
assert.ok(
|
|
1422
|
+
equals(
|
|
1423
|
+
psbt.data.inputs[index].nonWitnessUtxo!,
|
|
1424
|
+
f.nonWitnessUtxo as any,
|
|
1425
|
+
),
|
|
1426
|
+
);
|
|
1427
|
+
});
|
|
1428
|
+
|
|
1429
|
+
it('nonWitnessUtxo remains a plain data property (no defineProperty)', () => {
|
|
1430
|
+
const f = fixtures.cache.nonWitnessUtxo;
|
|
1431
|
+
const psbt = Psbt.fromBase64(f.psbt);
|
|
1432
|
+
const index = f.inputIndex;
|
|
1433
|
+
|
|
1434
|
+
psbt.updateInput(index, {
|
|
1435
|
+
nonWitnessUtxo: f.nonWitnessUtxo as any,
|
|
1436
|
+
});
|
|
1437
|
+
|
|
1438
|
+
const input = psbt.data.inputs[index];
|
|
1439
|
+
const desc = Object.getOwnPropertyDescriptor(input, 'nonWitnessUtxo');
|
|
1440
|
+
assert.ok(desc, 'property should exist');
|
|
1441
|
+
assert.strictEqual(desc!.get, undefined, 'should not have a getter');
|
|
1442
|
+
assert.strictEqual(desc!.set, undefined, 'should not have a setter');
|
|
1443
|
+
});
|
|
1444
|
+
});
|
|
1445
|
+
|
|
1446
|
+
describe('Transaction properties', () => {
|
|
1447
|
+
it('.version is exposed and is settable', () => {
|
|
1448
|
+
const psbt = new Psbt();
|
|
1449
|
+
|
|
1450
|
+
assert.strictEqual(psbt.version, 2);
|
|
1451
|
+
|
|
1452
|
+
psbt.version = 1;
|
|
1453
|
+
assert.strictEqual(psbt.version, 1);
|
|
1454
|
+
});
|
|
1455
|
+
|
|
1456
|
+
it('.locktime is exposed and is settable', () => {
|
|
1457
|
+
const psbt = new Psbt();
|
|
1458
|
+
|
|
1459
|
+
assert.strictEqual(psbt.locktime, 0);
|
|
1460
|
+
|
|
1461
|
+
psbt.locktime = 123;
|
|
1462
|
+
assert.strictEqual(psbt.locktime, 123);
|
|
1463
|
+
});
|
|
1464
|
+
|
|
1465
|
+
it('.txInputs is exposed as a readonly clone', () => {
|
|
1466
|
+
const psbt = new Psbt();
|
|
1467
|
+
const hash = Buffer.alloc(32) as unknown as Bytes32;
|
|
1468
|
+
const index = 0;
|
|
1469
|
+
psbt.addInput({ hash, index });
|
|
1470
|
+
|
|
1471
|
+
const input = psbt.txInputs[0];
|
|
1472
|
+
const originalHash = new Uint8Array(input.hash);
|
|
1473
|
+
const originalIndex = input.index;
|
|
1474
|
+
const originalSequence = input.sequence;
|
|
1475
|
+
|
|
1476
|
+
// Mutate the returned clone
|
|
1477
|
+
input.hash[0] = 123;
|
|
1478
|
+
input.index = 123;
|
|
1479
|
+
input.sequence = 123;
|
|
1480
|
+
|
|
1481
|
+
// Internal state should be unchanged
|
|
1482
|
+
const fresh = psbt.txInputs[0];
|
|
1483
|
+
assert.ok(equals(fresh.hash, originalHash));
|
|
1484
|
+
assert.strictEqual(fresh.index, originalIndex);
|
|
1485
|
+
assert.strictEqual(fresh.sequence, originalSequence);
|
|
1486
|
+
});
|
|
1487
|
+
|
|
1488
|
+
it('.txOutputs is exposed as a readonly clone', () => {
|
|
1489
|
+
const psbt = new Psbt();
|
|
1490
|
+
const address = '1LukeQU5jwebXbMLDVydeH4vFSobRV9rkj';
|
|
1491
|
+
const value = 100000n as Satoshi;
|
|
1492
|
+
psbt.addOutput({ address, value });
|
|
1493
|
+
|
|
1494
|
+
const output = psbt.txOutputs[0];
|
|
1495
|
+
assert.strictEqual(output.address, address);
|
|
1496
|
+
|
|
1497
|
+
const originalScript = new Uint8Array(output.script);
|
|
1498
|
+
const originalValue = output.value;
|
|
1499
|
+
|
|
1500
|
+
// Mutate the returned clone
|
|
1501
|
+
output.script[0] = 123;
|
|
1502
|
+
output.value = 123n;
|
|
1503
|
+
|
|
1504
|
+
// Internal state should be unchanged
|
|
1505
|
+
const fresh = psbt.txOutputs[0];
|
|
1506
|
+
assert.ok(equals(fresh.script, originalScript));
|
|
1507
|
+
assert.strictEqual(fresh.value, originalValue);
|
|
1508
|
+
});
|
|
1509
|
+
});
|
|
1510
|
+
});
|