@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
|
@@ -1,694 +0,0 @@
|
|
|
1
|
-
import assert from 'assert';
|
|
2
|
-
import { BIP32Factory } from '@btc-vision/bip32';
|
|
3
|
-
import * as ecc from 'tiny-secp256k1';
|
|
4
|
-
import { randomBytes } from 'crypto';
|
|
5
|
-
import { describe, it } from 'vitest';
|
|
6
|
-
|
|
7
|
-
import { initEccLib, Psbt, payments, crypto, Transaction } from '../src/index.js';
|
|
8
|
-
import type { EccLib, Bytes32, Script, Satoshi, PublicKey } from '../src/types.js';
|
|
9
|
-
import type { ValidateSigFunction } from '../src/psbt/types.js';
|
|
10
|
-
import { toXOnly } from '../src/pubkey.js';
|
|
11
|
-
|
|
12
|
-
initEccLib(ecc as unknown as EccLib);
|
|
13
|
-
const bip32 = BIP32Factory(ecc);
|
|
14
|
-
|
|
15
|
-
// Helper to create a taproot keypair
|
|
16
|
-
function createTaprootKeyPair() {
|
|
17
|
-
const node = bip32.fromSeed(randomBytes(64));
|
|
18
|
-
const xOnlyPubkey = toXOnly(node.publicKey as PublicKey);
|
|
19
|
-
const tweakedNode = node.tweak(crypto.taggedHash('TapTweak', xOnlyPubkey));
|
|
20
|
-
return { node, xOnlyPubkey, tweakedNode };
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Helper to create a fake prev tx
|
|
24
|
-
function createFakePrevTx(outputScript: Uint8Array, value: bigint, nonce: number): { tx: Uint8Array; txId: Bytes32 } {
|
|
25
|
-
const tx = new Transaction();
|
|
26
|
-
tx.version = 2;
|
|
27
|
-
const inputHash = Buffer.alloc(32);
|
|
28
|
-
inputHash.writeUInt32LE(nonce, 0);
|
|
29
|
-
tx.addInput(inputHash as unknown as Bytes32, 0);
|
|
30
|
-
tx.addOutput(outputScript as Script, value as Satoshi);
|
|
31
|
-
const txBuf = tx.toBuffer();
|
|
32
|
-
const hash1 = crypto.sha256(txBuf);
|
|
33
|
-
const hash2 = crypto.sha256(hash1);
|
|
34
|
-
const txId = Buffer.from(hash2).reverse();
|
|
35
|
-
return { tx: txBuf, txId: txId as unknown as Bytes32 };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
describe('Taproot Hash Cache', () => {
|
|
39
|
-
describe('cache population', () => {
|
|
40
|
-
it('should populate cache after first input signing', () => {
|
|
41
|
-
const { xOnlyPubkey, tweakedNode } = createTaprootKeyPair();
|
|
42
|
-
const { output } = payments.p2tr({ internalPubkey: xOnlyPubkey });
|
|
43
|
-
const { tx: prevTx, txId } = createFakePrevTx(output!, 10000n, 1);
|
|
44
|
-
|
|
45
|
-
const psbt = new Psbt();
|
|
46
|
-
psbt.addInput({
|
|
47
|
-
hash: txId,
|
|
48
|
-
index: 0,
|
|
49
|
-
nonWitnessUtxo: prevTx,
|
|
50
|
-
tapInternalKey: xOnlyPubkey,
|
|
51
|
-
});
|
|
52
|
-
psbt.addOutput({ script: output!, value: 9000n as Satoshi });
|
|
53
|
-
|
|
54
|
-
// Cache should be empty before signing
|
|
55
|
-
const cache = (psbt as any).__CACHE;
|
|
56
|
-
assert.strictEqual(cache.taprootHashCache, undefined);
|
|
57
|
-
|
|
58
|
-
psbt.signInput(0, tweakedNode);
|
|
59
|
-
|
|
60
|
-
// Cache should be populated after signing
|
|
61
|
-
assert.notStrictEqual(cache.taprootHashCache, undefined);
|
|
62
|
-
assert.ok(cache.taprootHashCache.hashPrevouts instanceof Uint8Array);
|
|
63
|
-
assert.ok(cache.taprootHashCache.hashAmounts instanceof Uint8Array);
|
|
64
|
-
assert.ok(cache.taprootHashCache.hashScriptPubKeys instanceof Uint8Array);
|
|
65
|
-
assert.ok(cache.taprootHashCache.hashSequences instanceof Uint8Array);
|
|
66
|
-
assert.ok(cache.taprootHashCache.hashOutputs instanceof Uint8Array);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('should reuse cache for multiple input signing', () => {
|
|
70
|
-
const { xOnlyPubkey, tweakedNode } = createTaprootKeyPair();
|
|
71
|
-
const { output } = payments.p2tr({ internalPubkey: xOnlyPubkey });
|
|
72
|
-
|
|
73
|
-
const psbt = new Psbt();
|
|
74
|
-
for (let i = 0; i < 5; i++) {
|
|
75
|
-
const { tx: prevTx, txId } = createFakePrevTx(output!, 10000n, i);
|
|
76
|
-
psbt.addInput({
|
|
77
|
-
hash: txId,
|
|
78
|
-
index: 0,
|
|
79
|
-
nonWitnessUtxo: prevTx,
|
|
80
|
-
tapInternalKey: xOnlyPubkey,
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
psbt.addOutput({ script: output!, value: 45000n as Satoshi });
|
|
84
|
-
|
|
85
|
-
// Sign first input
|
|
86
|
-
psbt.signInput(0, tweakedNode);
|
|
87
|
-
const cache = (psbt as any).__CACHE;
|
|
88
|
-
const cachedHash = cache.taprootHashCache;
|
|
89
|
-
assert.notStrictEqual(cachedHash, undefined);
|
|
90
|
-
|
|
91
|
-
// Sign remaining inputs - cache should be reused (same object reference)
|
|
92
|
-
for (let i = 1; i < 5; i++) {
|
|
93
|
-
psbt.signInput(i, tweakedNode);
|
|
94
|
-
assert.strictEqual(cache.taprootHashCache, cachedHash);
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
describe('cache invalidation on addInput', () => {
|
|
100
|
-
it('should invalidate cache when new input is added', () => {
|
|
101
|
-
const { xOnlyPubkey, tweakedNode } = createTaprootKeyPair();
|
|
102
|
-
const { output } = payments.p2tr({ internalPubkey: xOnlyPubkey });
|
|
103
|
-
|
|
104
|
-
const psbt = new Psbt();
|
|
105
|
-
const { tx: prevTx1, txId: txId1 } = createFakePrevTx(output!, 10000n, 1);
|
|
106
|
-
psbt.addInput({
|
|
107
|
-
hash: txId1,
|
|
108
|
-
index: 0,
|
|
109
|
-
nonWitnessUtxo: prevTx1,
|
|
110
|
-
tapInternalKey: xOnlyPubkey,
|
|
111
|
-
});
|
|
112
|
-
psbt.addOutput({ script: output!, value: 9000n as Satoshi });
|
|
113
|
-
|
|
114
|
-
// Sign to populate cache
|
|
115
|
-
psbt.signInput(0, tweakedNode);
|
|
116
|
-
const cache = (psbt as any).__CACHE;
|
|
117
|
-
assert.notStrictEqual(cache.taprootHashCache, undefined);
|
|
118
|
-
|
|
119
|
-
// Add another input - cache should be invalidated
|
|
120
|
-
const { tx: prevTx2, txId: txId2 } = createFakePrevTx(output!, 10000n, 2);
|
|
121
|
-
psbt.addInput({
|
|
122
|
-
hash: txId2,
|
|
123
|
-
index: 0,
|
|
124
|
-
nonWitnessUtxo: prevTx2,
|
|
125
|
-
tapInternalKey: xOnlyPubkey,
|
|
126
|
-
}, false); // skip partial sig check
|
|
127
|
-
|
|
128
|
-
assert.strictEqual(cache.taprootHashCache, undefined);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it('should produce correct signatures after cache invalidation from addInput', () => {
|
|
132
|
-
const { xOnlyPubkey, tweakedNode } = createTaprootKeyPair();
|
|
133
|
-
const { output } = payments.p2tr({ internalPubkey: xOnlyPubkey });
|
|
134
|
-
|
|
135
|
-
const psbt = new Psbt();
|
|
136
|
-
const { tx: prevTx1, txId: txId1 } = createFakePrevTx(output!, 10000n, 1);
|
|
137
|
-
psbt.addInput({
|
|
138
|
-
hash: txId1,
|
|
139
|
-
index: 0,
|
|
140
|
-
nonWitnessUtxo: prevTx1,
|
|
141
|
-
tapInternalKey: xOnlyPubkey,
|
|
142
|
-
});
|
|
143
|
-
psbt.addOutput({ script: output!, value: 9000n as Satoshi });
|
|
144
|
-
|
|
145
|
-
// Populate cache by signing
|
|
146
|
-
psbt.signInput(0, tweakedNode);
|
|
147
|
-
const cache = (psbt as any).__CACHE;
|
|
148
|
-
assert.notStrictEqual(cache.taprootHashCache, undefined);
|
|
149
|
-
|
|
150
|
-
// Create a fresh PSBT with 2 inputs to test cache works after invalidation
|
|
151
|
-
const psbt2 = new Psbt();
|
|
152
|
-
psbt2.addInput({
|
|
153
|
-
hash: txId1,
|
|
154
|
-
index: 0,
|
|
155
|
-
nonWitnessUtxo: prevTx1,
|
|
156
|
-
tapInternalKey: xOnlyPubkey,
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
const { tx: prevTx2, txId: txId2 } = createFakePrevTx(output!, 10000n, 2);
|
|
160
|
-
psbt2.addInput({
|
|
161
|
-
hash: txId2,
|
|
162
|
-
index: 0,
|
|
163
|
-
nonWitnessUtxo: prevTx2,
|
|
164
|
-
tapInternalKey: xOnlyPubkey,
|
|
165
|
-
});
|
|
166
|
-
psbt2.addOutput({ script: output!, value: 18000n as Satoshi });
|
|
167
|
-
|
|
168
|
-
// Sign first input (populates cache)
|
|
169
|
-
psbt2.signInput(0, tweakedNode);
|
|
170
|
-
const cache2 = (psbt2 as any).__CACHE;
|
|
171
|
-
const cachedHash = cache2.__TAPROOT_HASH_CACHE;
|
|
172
|
-
|
|
173
|
-
// Sign second input (should reuse cache)
|
|
174
|
-
psbt2.signInput(1, tweakedNode);
|
|
175
|
-
assert.strictEqual(cache2.__TAPROOT_HASH_CACHE, cachedHash);
|
|
176
|
-
|
|
177
|
-
// Both signatures should be valid
|
|
178
|
-
const validator: ValidateSigFunction = (pubkey, msghash, signature) =>
|
|
179
|
-
ecc.verifySchnorr(msghash, pubkey, signature);
|
|
180
|
-
|
|
181
|
-
assert.ok(psbt2.validateSignaturesOfInput(0, validator));
|
|
182
|
-
assert.ok(psbt2.validateSignaturesOfInput(1, validator));
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
describe('cache invalidation on addOutput', () => {
|
|
187
|
-
it('should invalidate cache when new output is added', () => {
|
|
188
|
-
const { xOnlyPubkey, tweakedNode } = createTaprootKeyPair();
|
|
189
|
-
const { output } = payments.p2tr({ internalPubkey: xOnlyPubkey });
|
|
190
|
-
|
|
191
|
-
const psbt = new Psbt();
|
|
192
|
-
const { tx: prevTx, txId } = createFakePrevTx(output!, 20000n, 1);
|
|
193
|
-
psbt.addInput({
|
|
194
|
-
hash: txId,
|
|
195
|
-
index: 0,
|
|
196
|
-
nonWitnessUtxo: prevTx,
|
|
197
|
-
tapInternalKey: xOnlyPubkey,
|
|
198
|
-
});
|
|
199
|
-
psbt.addOutput({ script: output!, value: 9000n as Satoshi });
|
|
200
|
-
|
|
201
|
-
// Sign to populate cache
|
|
202
|
-
psbt.signInput(0, tweakedNode);
|
|
203
|
-
const cache = (psbt as any).__CACHE;
|
|
204
|
-
assert.notStrictEqual(cache.taprootHashCache, undefined);
|
|
205
|
-
|
|
206
|
-
// Add another output - cache should be invalidated
|
|
207
|
-
psbt.addOutput({ script: output!, value: 9000n as Satoshi }, false);
|
|
208
|
-
|
|
209
|
-
assert.strictEqual(cache.taprootHashCache, undefined);
|
|
210
|
-
});
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
describe('signature correctness', () => {
|
|
214
|
-
it('should produce valid signatures with cache enabled', () => {
|
|
215
|
-
const { xOnlyPubkey, tweakedNode } = createTaprootKeyPair();
|
|
216
|
-
const { output } = payments.p2tr({ internalPubkey: xOnlyPubkey });
|
|
217
|
-
|
|
218
|
-
const psbt = new Psbt();
|
|
219
|
-
for (let i = 0; i < 10; i++) {
|
|
220
|
-
const { tx: prevTx, txId } = createFakePrevTx(output!, 10000n, i);
|
|
221
|
-
psbt.addInput({
|
|
222
|
-
hash: txId,
|
|
223
|
-
index: 0,
|
|
224
|
-
nonWitnessUtxo: prevTx,
|
|
225
|
-
tapInternalKey: xOnlyPubkey,
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
psbt.addOutput({ script: output!, value: 95000n as Satoshi });
|
|
229
|
-
|
|
230
|
-
psbt.signAllInputs(tweakedNode);
|
|
231
|
-
|
|
232
|
-
const validator: ValidateSigFunction = (pubkey, msghash, signature) =>
|
|
233
|
-
ecc.verifySchnorr(msghash, pubkey, signature);
|
|
234
|
-
|
|
235
|
-
for (let i = 0; i < 10; i++) {
|
|
236
|
-
assert.ok(psbt.validateSignaturesOfInput(i, validator), `Input ${i} signature invalid`);
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
it('should produce identical signatures with and without cache', () => {
|
|
241
|
-
const seed = randomBytes(64);
|
|
242
|
-
const { xOnlyPubkey, tweakedNode } = (() => {
|
|
243
|
-
const node = bip32.fromSeed(seed);
|
|
244
|
-
const xOnlyPubkey = toXOnly(node.publicKey as PublicKey);
|
|
245
|
-
const tweakedNode = node.tweak(crypto.taggedHash('TapTweak', xOnlyPubkey));
|
|
246
|
-
return { xOnlyPubkey, tweakedNode };
|
|
247
|
-
})();
|
|
248
|
-
const { output } = payments.p2tr({ internalPubkey: xOnlyPubkey });
|
|
249
|
-
|
|
250
|
-
// Create deterministic prev txs
|
|
251
|
-
const prevTxs: { tx: Uint8Array; txId: Bytes32 }[] = [];
|
|
252
|
-
for (let i = 0; i < 5; i++) {
|
|
253
|
-
prevTxs.push(createFakePrevTx(output!, 10000n, i));
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Create PSBT with cache
|
|
257
|
-
const psbt1 = new Psbt();
|
|
258
|
-
for (let i = 0; i < 5; i++) {
|
|
259
|
-
psbt1.addInput({
|
|
260
|
-
hash: prevTxs[i].txId,
|
|
261
|
-
index: 0,
|
|
262
|
-
nonWitnessUtxo: prevTxs[i].tx,
|
|
263
|
-
tapInternalKey: xOnlyPubkey,
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
psbt1.addOutput({ script: output!, value: 45000n as Satoshi });
|
|
267
|
-
psbt1.signAllInputs(tweakedNode);
|
|
268
|
-
|
|
269
|
-
// Signatures should be deterministic (Schnorr with BIP340 uses aux randomness,
|
|
270
|
-
// but the sighash computation should be identical)
|
|
271
|
-
// We verify by checking all signatures are valid
|
|
272
|
-
const validator: ValidateSigFunction = (pubkey, msghash, signature) =>
|
|
273
|
-
ecc.verifySchnorr(msghash, pubkey, signature);
|
|
274
|
-
|
|
275
|
-
assert.ok(psbt1.validateSignaturesOfAllInputs(validator));
|
|
276
|
-
});
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
describe('fuzz testing', () => {
|
|
280
|
-
it('should handle random number of inputs correctly', () => {
|
|
281
|
-
for (let trial = 0; trial < 10; trial++) {
|
|
282
|
-
const numInputs = Math.floor(Math.random() * 20) + 1;
|
|
283
|
-
const { xOnlyPubkey, tweakedNode } = createTaprootKeyPair();
|
|
284
|
-
const { output } = payments.p2tr({ internalPubkey: xOnlyPubkey });
|
|
285
|
-
|
|
286
|
-
const psbt = new Psbt();
|
|
287
|
-
for (let i = 0; i < numInputs; i++) {
|
|
288
|
-
const { tx: prevTx, txId } = createFakePrevTx(output!, 10000n, i);
|
|
289
|
-
psbt.addInput({
|
|
290
|
-
hash: txId,
|
|
291
|
-
index: 0,
|
|
292
|
-
nonWitnessUtxo: prevTx,
|
|
293
|
-
tapInternalKey: xOnlyPubkey,
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
psbt.addOutput({ script: output!, value: BigInt(numInputs * 9000) as Satoshi });
|
|
297
|
-
|
|
298
|
-
psbt.signAllInputs(tweakedNode);
|
|
299
|
-
|
|
300
|
-
const validator: ValidateSigFunction = (pubkey, msghash, signature) =>
|
|
301
|
-
ecc.verifySchnorr(msghash, pubkey, signature);
|
|
302
|
-
|
|
303
|
-
assert.ok(
|
|
304
|
-
psbt.validateSignaturesOfAllInputs(validator),
|
|
305
|
-
`Failed with ${numInputs} inputs on trial ${trial}`,
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
it('should handle cache invalidation correctly when inputs/outputs change', () => {
|
|
311
|
-
const { xOnlyPubkey, tweakedNode } = createTaprootKeyPair();
|
|
312
|
-
const { output } = payments.p2tr({ internalPubkey: xOnlyPubkey });
|
|
313
|
-
|
|
314
|
-
const psbt = new Psbt();
|
|
315
|
-
|
|
316
|
-
// Add first input
|
|
317
|
-
const { tx: prevTx1, txId: txId1 } = createFakePrevTx(output!, 10000n, 1);
|
|
318
|
-
psbt.addInput({
|
|
319
|
-
hash: txId1,
|
|
320
|
-
index: 0,
|
|
321
|
-
nonWitnessUtxo: prevTx1,
|
|
322
|
-
tapInternalKey: xOnlyPubkey,
|
|
323
|
-
});
|
|
324
|
-
psbt.addOutput({ script: output!, value: 5000n as Satoshi });
|
|
325
|
-
|
|
326
|
-
// Sign to populate cache
|
|
327
|
-
psbt.signInput(0, tweakedNode);
|
|
328
|
-
const cache = (psbt as any).__CACHE;
|
|
329
|
-
const originalCache = cache.taprootHashCache;
|
|
330
|
-
assert.notStrictEqual(originalCache, undefined);
|
|
331
|
-
|
|
332
|
-
// Add more inputs - cache should be invalidated
|
|
333
|
-
const { tx: prevTx2, txId: txId2 } = createFakePrevTx(output!, 10000n, 2);
|
|
334
|
-
psbt.addInput({
|
|
335
|
-
hash: txId2,
|
|
336
|
-
index: 0,
|
|
337
|
-
nonWitnessUtxo: prevTx2,
|
|
338
|
-
tapInternalKey: xOnlyPubkey,
|
|
339
|
-
}, false);
|
|
340
|
-
assert.strictEqual(cache.taprootHashCache, undefined, 'Cache should be invalidated after addInput');
|
|
341
|
-
|
|
342
|
-
// Add output - cache already undefined, should stay undefined
|
|
343
|
-
psbt.addOutput({ script: output!, value: 5000n as Satoshi }, false);
|
|
344
|
-
assert.strictEqual(cache.taprootHashCache, undefined);
|
|
345
|
-
|
|
346
|
-
// Sign second input - should create new cache
|
|
347
|
-
psbt.signInput(1, tweakedNode);
|
|
348
|
-
assert.notStrictEqual(cache.taprootHashCache, undefined);
|
|
349
|
-
assert.notStrictEqual(cache.taprootHashCache, originalCache, 'Should be a new cache');
|
|
350
|
-
|
|
351
|
-
// Note: First signature is now invalid because sighash changed when we added inputs/outputs
|
|
352
|
-
// This is expected Bitcoin behavior with SIGHASH_ALL
|
|
353
|
-
const validator: ValidateSigFunction = (pubkey, msghash, signature) =>
|
|
354
|
-
ecc.verifySchnorr(msghash, pubkey, signature);
|
|
355
|
-
|
|
356
|
-
// Second input should be valid (signed after all inputs/outputs were added)
|
|
357
|
-
assert.ok(psbt.validateSignaturesOfInput(1, validator), 'Input 1 should be valid');
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
it('should handle multiple sign calls on same input', () => {
|
|
361
|
-
const { xOnlyPubkey, tweakedNode } = createTaprootKeyPair();
|
|
362
|
-
const { output } = payments.p2tr({ internalPubkey: xOnlyPubkey });
|
|
363
|
-
|
|
364
|
-
const psbt = new Psbt();
|
|
365
|
-
const { tx: prevTx, txId } = createFakePrevTx(output!, 10000n, 1);
|
|
366
|
-
psbt.addInput({
|
|
367
|
-
hash: txId,
|
|
368
|
-
index: 0,
|
|
369
|
-
nonWitnessUtxo: prevTx,
|
|
370
|
-
tapInternalKey: xOnlyPubkey,
|
|
371
|
-
});
|
|
372
|
-
psbt.addOutput({ script: output!, value: 9000n as Satoshi });
|
|
373
|
-
|
|
374
|
-
// Sign same input
|
|
375
|
-
psbt.signInput(0, tweakedNode);
|
|
376
|
-
|
|
377
|
-
// Second sign should throw because already signed (duplicate data)
|
|
378
|
-
assert.throws(() => {
|
|
379
|
-
psbt.signInput(0, tweakedNode);
|
|
380
|
-
}, /duplicate/i);
|
|
381
|
-
});
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
describe('Transaction.getTaprootHashCache', () => {
|
|
385
|
-
it('should compute correct hash values', () => {
|
|
386
|
-
const tx = new Transaction();
|
|
387
|
-
tx.version = 2;
|
|
388
|
-
|
|
389
|
-
// Add some inputs
|
|
390
|
-
for (let i = 0; i < 3; i++) {
|
|
391
|
-
const hash = Buffer.alloc(32);
|
|
392
|
-
hash.writeUInt32LE(i, 0);
|
|
393
|
-
tx.addInput(hash as unknown as Bytes32, i);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// Add some outputs
|
|
397
|
-
const script = Buffer.from('0014' + '00'.repeat(20), 'hex') as unknown as Script;
|
|
398
|
-
tx.addOutput(script, 1000n as Satoshi);
|
|
399
|
-
tx.addOutput(script, 2000n as Satoshi);
|
|
400
|
-
|
|
401
|
-
const prevOutScripts = [script, script, script];
|
|
402
|
-
const values = [5000n as Satoshi, 6000n as Satoshi, 7000n as Satoshi];
|
|
403
|
-
|
|
404
|
-
const cache = tx.getTaprootHashCache(prevOutScripts, values);
|
|
405
|
-
|
|
406
|
-
// Verify all fields are 32 bytes (SHA256 output)
|
|
407
|
-
assert.strictEqual(cache.hashPrevouts.length, 32);
|
|
408
|
-
assert.strictEqual(cache.hashAmounts.length, 32);
|
|
409
|
-
assert.strictEqual(cache.hashScriptPubKeys.length, 32);
|
|
410
|
-
assert.strictEqual(cache.hashSequences.length, 32);
|
|
411
|
-
assert.strictEqual(cache.hashOutputs.length, 32);
|
|
412
|
-
|
|
413
|
-
// Verify determinism - same inputs should produce same cache
|
|
414
|
-
const cache2 = tx.getTaprootHashCache(prevOutScripts, values);
|
|
415
|
-
assert.deepStrictEqual(cache.hashPrevouts, cache2.hashPrevouts);
|
|
416
|
-
assert.deepStrictEqual(cache.hashAmounts, cache2.hashAmounts);
|
|
417
|
-
assert.deepStrictEqual(cache.hashScriptPubKeys, cache2.hashScriptPubKeys);
|
|
418
|
-
assert.deepStrictEqual(cache.hashSequences, cache2.hashSequences);
|
|
419
|
-
assert.deepStrictEqual(cache.hashOutputs, cache2.hashOutputs);
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
it('should produce different hashes for different transactions', () => {
|
|
423
|
-
const script = Buffer.from('0014' + '00'.repeat(20), 'hex') as unknown as Script;
|
|
424
|
-
|
|
425
|
-
const tx1 = new Transaction();
|
|
426
|
-
tx1.version = 2;
|
|
427
|
-
tx1.addInput(Buffer.alloc(32, 1) as unknown as Bytes32, 0);
|
|
428
|
-
tx1.addOutput(script, 1000n as Satoshi);
|
|
429
|
-
|
|
430
|
-
const tx2 = new Transaction();
|
|
431
|
-
tx2.version = 2;
|
|
432
|
-
tx2.addInput(Buffer.alloc(32, 2) as unknown as Bytes32, 0); // Different input
|
|
433
|
-
tx2.addOutput(script, 1000n as Satoshi);
|
|
434
|
-
|
|
435
|
-
const cache1 = tx1.getTaprootHashCache([script], [5000n as Satoshi]);
|
|
436
|
-
const cache2 = tx2.getTaprootHashCache([script], [5000n as Satoshi]);
|
|
437
|
-
|
|
438
|
-
// hashPrevouts should differ (different input hashes)
|
|
439
|
-
assert.notDeepStrictEqual(cache1.hashPrevouts, cache2.hashPrevouts);
|
|
440
|
-
});
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
describe('edge cases', () => {
|
|
444
|
-
it('should handle single input correctly', () => {
|
|
445
|
-
const { xOnlyPubkey, tweakedNode } = createTaprootKeyPair();
|
|
446
|
-
const { output } = payments.p2tr({ internalPubkey: xOnlyPubkey });
|
|
447
|
-
|
|
448
|
-
const psbt = new Psbt();
|
|
449
|
-
const { tx: prevTx, txId } = createFakePrevTx(output!, 10000n, 1);
|
|
450
|
-
psbt.addInput({
|
|
451
|
-
hash: txId,
|
|
452
|
-
index: 0,
|
|
453
|
-
nonWitnessUtxo: prevTx,
|
|
454
|
-
tapInternalKey: xOnlyPubkey,
|
|
455
|
-
});
|
|
456
|
-
psbt.addOutput({ script: output!, value: 9000n as Satoshi });
|
|
457
|
-
|
|
458
|
-
psbt.signInput(0, tweakedNode);
|
|
459
|
-
|
|
460
|
-
const validator: ValidateSigFunction = (pubkey, msghash, signature) =>
|
|
461
|
-
ecc.verifySchnorr(msghash, pubkey, signature);
|
|
462
|
-
|
|
463
|
-
assert.ok(psbt.validateSignaturesOfInput(0, validator));
|
|
464
|
-
});
|
|
465
|
-
|
|
466
|
-
it('should handle large number of outputs', () => {
|
|
467
|
-
const { xOnlyPubkey, tweakedNode } = createTaprootKeyPair();
|
|
468
|
-
const { output } = payments.p2tr({ internalPubkey: xOnlyPubkey });
|
|
469
|
-
|
|
470
|
-
const psbt = new Psbt();
|
|
471
|
-
const { tx: prevTx, txId } = createFakePrevTx(output!, 1000000n, 1);
|
|
472
|
-
psbt.addInput({
|
|
473
|
-
hash: txId,
|
|
474
|
-
index: 0,
|
|
475
|
-
nonWitnessUtxo: prevTx,
|
|
476
|
-
tapInternalKey: xOnlyPubkey,
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
// Add many outputs
|
|
480
|
-
for (let i = 0; i < 50; i++) {
|
|
481
|
-
psbt.addOutput({ script: output!, value: 1000n as Satoshi });
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
psbt.signInput(0, tweakedNode);
|
|
485
|
-
|
|
486
|
-
const validator: ValidateSigFunction = (pubkey, msghash, signature) =>
|
|
487
|
-
ecc.verifySchnorr(msghash, pubkey, signature);
|
|
488
|
-
|
|
489
|
-
assert.ok(psbt.validateSignaturesOfInput(0, validator));
|
|
490
|
-
});
|
|
491
|
-
|
|
492
|
-
it('should handle witnessUtxo inputs', () => {
|
|
493
|
-
const { xOnlyPubkey, tweakedNode } = createTaprootKeyPair();
|
|
494
|
-
const { output } = payments.p2tr({ internalPubkey: xOnlyPubkey });
|
|
495
|
-
|
|
496
|
-
const psbt = new Psbt();
|
|
497
|
-
for (let i = 0; i < 5; i++) {
|
|
498
|
-
const hash = Buffer.alloc(32);
|
|
499
|
-
hash.writeUInt32LE(i, 0);
|
|
500
|
-
psbt.addInput({
|
|
501
|
-
hash: hash as unknown as Bytes32,
|
|
502
|
-
index: 0,
|
|
503
|
-
witnessUtxo: { script: output!, value: 10000n as Satoshi },
|
|
504
|
-
tapInternalKey: xOnlyPubkey,
|
|
505
|
-
});
|
|
506
|
-
}
|
|
507
|
-
psbt.addOutput({ script: output!, value: 45000n as Satoshi });
|
|
508
|
-
|
|
509
|
-
psbt.signAllInputs(tweakedNode);
|
|
510
|
-
|
|
511
|
-
const validator: ValidateSigFunction = (pubkey, msghash, signature) =>
|
|
512
|
-
ecc.verifySchnorr(msghash, pubkey, signature);
|
|
513
|
-
|
|
514
|
-
assert.ok(psbt.validateSignaturesOfAllInputs(validator));
|
|
515
|
-
});
|
|
516
|
-
|
|
517
|
-
it('should not persist cache after PSBT serialization/deserialization', () => {
|
|
518
|
-
const { xOnlyPubkey, tweakedNode } = createTaprootKeyPair();
|
|
519
|
-
const { output } = payments.p2tr({ internalPubkey: xOnlyPubkey });
|
|
520
|
-
|
|
521
|
-
const psbt = new Psbt();
|
|
522
|
-
for (let i = 0; i < 3; i++) {
|
|
523
|
-
const { tx: prevTx, txId } = createFakePrevTx(output!, 10000n, i);
|
|
524
|
-
psbt.addInput({
|
|
525
|
-
hash: txId,
|
|
526
|
-
index: 0,
|
|
527
|
-
nonWitnessUtxo: prevTx,
|
|
528
|
-
tapInternalKey: xOnlyPubkey,
|
|
529
|
-
});
|
|
530
|
-
}
|
|
531
|
-
psbt.addOutput({ script: output!, value: 25000n as Satoshi });
|
|
532
|
-
|
|
533
|
-
// Sign to populate cache
|
|
534
|
-
psbt.signInput(0, tweakedNode);
|
|
535
|
-
const cache = (psbt as any).__CACHE;
|
|
536
|
-
assert.notStrictEqual(cache.taprootHashCache, undefined);
|
|
537
|
-
|
|
538
|
-
// Serialize and deserialize
|
|
539
|
-
const hex = psbt.toHex();
|
|
540
|
-
const psbt2 = Psbt.fromHex(hex);
|
|
541
|
-
|
|
542
|
-
// New PSBT should not have cache populated
|
|
543
|
-
const cache2 = (psbt2 as any).__CACHE;
|
|
544
|
-
assert.strictEqual(cache2.__TAPROOT_HASH_CACHE, undefined);
|
|
545
|
-
|
|
546
|
-
// Should still be able to sign remaining inputs
|
|
547
|
-
psbt2.signInput(1, tweakedNode);
|
|
548
|
-
psbt2.signInput(2, tweakedNode);
|
|
549
|
-
|
|
550
|
-
const validator: ValidateSigFunction = (pubkey, msghash, signature) =>
|
|
551
|
-
ecc.verifySchnorr(msghash, pubkey, signature);
|
|
552
|
-
|
|
553
|
-
assert.ok(psbt2.validateSignaturesOfInput(1, validator));
|
|
554
|
-
assert.ok(psbt2.validateSignaturesOfInput(2, validator));
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
it('should work correctly with async signing', async () => {
|
|
558
|
-
const { xOnlyPubkey, tweakedNode } = createTaprootKeyPair();
|
|
559
|
-
const { output } = payments.p2tr({ internalPubkey: xOnlyPubkey });
|
|
560
|
-
|
|
561
|
-
const psbt = new Psbt();
|
|
562
|
-
for (let i = 0; i < 5; i++) {
|
|
563
|
-
const { tx: prevTx, txId } = createFakePrevTx(output!, 10000n, i);
|
|
564
|
-
psbt.addInput({
|
|
565
|
-
hash: txId,
|
|
566
|
-
index: 0,
|
|
567
|
-
nonWitnessUtxo: prevTx,
|
|
568
|
-
tapInternalKey: xOnlyPubkey,
|
|
569
|
-
});
|
|
570
|
-
}
|
|
571
|
-
psbt.addOutput({ script: output!, value: 45000n as Satoshi });
|
|
572
|
-
|
|
573
|
-
// Sign asynchronously
|
|
574
|
-
await psbt.signAllInputsAsync(tweakedNode);
|
|
575
|
-
|
|
576
|
-
const validator: ValidateSigFunction = (pubkey, msghash, signature) =>
|
|
577
|
-
ecc.verifySchnorr(msghash, pubkey, signature);
|
|
578
|
-
|
|
579
|
-
assert.ok(psbt.validateSignaturesOfAllInputs(validator));
|
|
580
|
-
});
|
|
581
|
-
|
|
582
|
-
it('should produce different hashOutputs for different output values', () => {
|
|
583
|
-
const script = Buffer.from('0014' + '00'.repeat(20), 'hex') as unknown as Script;
|
|
584
|
-
|
|
585
|
-
const tx = new Transaction();
|
|
586
|
-
tx.version = 2;
|
|
587
|
-
tx.addInput(Buffer.alloc(32, 1) as unknown as Bytes32, 0);
|
|
588
|
-
tx.addOutput(script, 1000n as Satoshi);
|
|
589
|
-
|
|
590
|
-
const cache1 = tx.getTaprootHashCache([script], [5000n as Satoshi]);
|
|
591
|
-
|
|
592
|
-
// Change output value
|
|
593
|
-
tx.outs[0].value = 2000n as Satoshi;
|
|
594
|
-
const cache2 = tx.getTaprootHashCache([script], [5000n as Satoshi]);
|
|
595
|
-
|
|
596
|
-
// hashOutputs should differ
|
|
597
|
-
assert.notDeepStrictEqual(cache1.hashOutputs, cache2.hashOutputs);
|
|
598
|
-
// Other hashes should be the same
|
|
599
|
-
assert.deepStrictEqual(cache1.hashPrevouts, cache2.hashPrevouts);
|
|
600
|
-
assert.deepStrictEqual(cache1.hashAmounts, cache2.hashAmounts);
|
|
601
|
-
});
|
|
602
|
-
|
|
603
|
-
it('should produce different hashSequences for different sequences', () => {
|
|
604
|
-
const script = Buffer.from('0014' + '00'.repeat(20), 'hex') as unknown as Script;
|
|
605
|
-
|
|
606
|
-
const tx1 = new Transaction();
|
|
607
|
-
tx1.version = 2;
|
|
608
|
-
tx1.addInput(Buffer.alloc(32, 1) as unknown as Bytes32, 0, 0xffffffff); // default sequence
|
|
609
|
-
tx1.addOutput(script, 1000n as Satoshi);
|
|
610
|
-
|
|
611
|
-
const tx2 = new Transaction();
|
|
612
|
-
tx2.version = 2;
|
|
613
|
-
tx2.addInput(Buffer.alloc(32, 1) as unknown as Bytes32, 0, 0xfffffffe); // RBF sequence
|
|
614
|
-
tx2.addOutput(script, 1000n as Satoshi);
|
|
615
|
-
|
|
616
|
-
const cache1 = tx1.getTaprootHashCache([script], [5000n as Satoshi]);
|
|
617
|
-
const cache2 = tx2.getTaprootHashCache([script], [5000n as Satoshi]);
|
|
618
|
-
|
|
619
|
-
// hashSequences should differ
|
|
620
|
-
assert.notDeepStrictEqual(cache1.hashSequences, cache2.hashSequences);
|
|
621
|
-
// hashPrevouts should be the same (same input hash)
|
|
622
|
-
assert.deepStrictEqual(cache1.hashPrevouts, cache2.hashPrevouts);
|
|
623
|
-
});
|
|
624
|
-
|
|
625
|
-
it('should handle prevOuts cache invalidation together with taproot cache', () => {
|
|
626
|
-
const { xOnlyPubkey, tweakedNode } = createTaprootKeyPair();
|
|
627
|
-
const { output } = payments.p2tr({ internalPubkey: xOnlyPubkey });
|
|
628
|
-
|
|
629
|
-
const psbt = new Psbt();
|
|
630
|
-
const { tx: prevTx1, txId: txId1 } = createFakePrevTx(output!, 10000n, 1);
|
|
631
|
-
psbt.addInput({
|
|
632
|
-
hash: txId1,
|
|
633
|
-
index: 0,
|
|
634
|
-
nonWitnessUtxo: prevTx1,
|
|
635
|
-
tapInternalKey: xOnlyPubkey,
|
|
636
|
-
});
|
|
637
|
-
psbt.addOutput({ script: output!, value: 9000n as Satoshi });
|
|
638
|
-
|
|
639
|
-
// Sign to populate all caches
|
|
640
|
-
psbt.signInput(0, tweakedNode);
|
|
641
|
-
const cache = (psbt as any).__CACHE;
|
|
642
|
-
assert.notStrictEqual(cache.prevOuts, undefined);
|
|
643
|
-
assert.notStrictEqual(cache.signingScripts, undefined);
|
|
644
|
-
assert.notStrictEqual(cache.values, undefined);
|
|
645
|
-
assert.notStrictEqual(cache.taprootHashCache, undefined);
|
|
646
|
-
|
|
647
|
-
// Add new input - all caches should be invalidated
|
|
648
|
-
const { tx: prevTx2, txId: txId2 } = createFakePrevTx(output!, 10000n, 2);
|
|
649
|
-
psbt.addInput({
|
|
650
|
-
hash: txId2,
|
|
651
|
-
index: 0,
|
|
652
|
-
nonWitnessUtxo: prevTx2,
|
|
653
|
-
tapInternalKey: xOnlyPubkey,
|
|
654
|
-
}, false);
|
|
655
|
-
|
|
656
|
-
assert.strictEqual(cache.prevOuts, undefined);
|
|
657
|
-
assert.strictEqual(cache.signingScripts, undefined);
|
|
658
|
-
assert.strictEqual(cache.values, undefined);
|
|
659
|
-
assert.strictEqual(cache.taprootHashCache, undefined);
|
|
660
|
-
});
|
|
661
|
-
|
|
662
|
-
it('should handle stress test with many sequential operations', () => {
|
|
663
|
-
const { xOnlyPubkey, tweakedNode } = createTaprootKeyPair();
|
|
664
|
-
const { output } = payments.p2tr({ internalPubkey: xOnlyPubkey });
|
|
665
|
-
|
|
666
|
-
// Build PSBT with many inputs
|
|
667
|
-
const psbt = new Psbt();
|
|
668
|
-
const inputCount = 50;
|
|
669
|
-
for (let i = 0; i < inputCount; i++) {
|
|
670
|
-
const { tx: prevTx, txId } = createFakePrevTx(output!, 10000n, i);
|
|
671
|
-
psbt.addInput({
|
|
672
|
-
hash: txId,
|
|
673
|
-
index: 0,
|
|
674
|
-
nonWitnessUtxo: prevTx,
|
|
675
|
-
tapInternalKey: xOnlyPubkey,
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
psbt.addOutput({ script: output!, value: BigInt(inputCount * 9000) as Satoshi });
|
|
679
|
-
|
|
680
|
-
// Sign all inputs
|
|
681
|
-
psbt.signAllInputs(tweakedNode);
|
|
682
|
-
|
|
683
|
-
// Verify cache was used (should be populated)
|
|
684
|
-
const cache = (psbt as any).__CACHE;
|
|
685
|
-
assert.notStrictEqual(cache.taprootHashCache, undefined);
|
|
686
|
-
|
|
687
|
-
// Validate all signatures
|
|
688
|
-
const validator: ValidateSigFunction = (pubkey, msghash, signature) =>
|
|
689
|
-
ecc.verifySchnorr(msghash, pubkey, signature);
|
|
690
|
-
|
|
691
|
-
assert.ok(psbt.validateSignaturesOfAllInputs(validator));
|
|
692
|
-
});
|
|
693
|
-
});
|
|
694
|
-
});
|