@btc-vision/transaction 1.7.19 → 1.7.22

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.
Files changed (92) hide show
  1. package/LICENSE +190 -21
  2. package/README.md +1 -1
  3. package/browser/_version.d.ts +1 -1
  4. package/browser/generators/builders/HashCommitmentGenerator.d.ts +49 -0
  5. package/browser/index.js +1 -1
  6. package/browser/keypair/Address.d.ts +3 -1
  7. package/browser/opnet.d.ts +6 -1
  8. package/browser/signer/AddressRotation.d.ts +12 -0
  9. package/browser/transaction/TransactionFactory.d.ts +14 -0
  10. package/browser/transaction/builders/ConsolidatedInteractionTransaction.d.ts +44 -0
  11. package/browser/transaction/enums/TransactionType.d.ts +3 -1
  12. package/browser/transaction/interfaces/IConsolidatedTransactionParameters.d.ts +31 -0
  13. package/browser/transaction/interfaces/ITransactionParameters.d.ts +2 -0
  14. package/browser/transaction/offline/OfflineTransactionManager.d.ts +69 -0
  15. package/browser/transaction/offline/TransactionReconstructor.d.ts +28 -0
  16. package/browser/transaction/offline/TransactionSerializer.d.ts +50 -0
  17. package/browser/transaction/offline/TransactionStateCapture.d.ts +52 -0
  18. package/browser/transaction/offline/index.d.ts +5 -0
  19. package/browser/transaction/offline/interfaces/ISerializableState.d.ts +62 -0
  20. package/browser/transaction/offline/interfaces/ITypeSpecificData.d.ts +62 -0
  21. package/browser/transaction/offline/interfaces/index.d.ts +2 -0
  22. package/browser/transaction/shared/TweakedTransaction.d.ts +12 -1
  23. package/browser/utxo/interfaces/IUTXO.d.ts +2 -0
  24. package/build/_version.d.ts +1 -1
  25. package/build/_version.js +1 -1
  26. package/build/generators/builders/HashCommitmentGenerator.d.ts +49 -0
  27. package/build/generators/builders/HashCommitmentGenerator.js +229 -0
  28. package/build/keypair/Address.d.ts +3 -1
  29. package/build/keypair/Address.js +87 -54
  30. package/build/opnet.d.ts +6 -1
  31. package/build/opnet.js +6 -1
  32. package/build/signer/AddressRotation.d.ts +12 -0
  33. package/build/signer/AddressRotation.js +16 -0
  34. package/build/transaction/TransactionFactory.d.ts +14 -0
  35. package/build/transaction/TransactionFactory.js +36 -0
  36. package/build/transaction/builders/ConsolidatedInteractionTransaction.d.ts +44 -0
  37. package/build/transaction/builders/ConsolidatedInteractionTransaction.js +259 -0
  38. package/build/transaction/builders/TransactionBuilder.js +2 -0
  39. package/build/transaction/enums/TransactionType.d.ts +3 -1
  40. package/build/transaction/enums/TransactionType.js +2 -0
  41. package/build/transaction/interfaces/IConsolidatedTransactionParameters.d.ts +31 -0
  42. package/build/transaction/interfaces/IConsolidatedTransactionParameters.js +1 -0
  43. package/build/transaction/interfaces/ITransactionParameters.d.ts +2 -0
  44. package/build/transaction/offline/OfflineTransactionManager.d.ts +69 -0
  45. package/build/transaction/offline/OfflineTransactionManager.js +255 -0
  46. package/build/transaction/offline/TransactionReconstructor.d.ts +28 -0
  47. package/build/transaction/offline/TransactionReconstructor.js +243 -0
  48. package/build/transaction/offline/TransactionSerializer.d.ts +50 -0
  49. package/build/transaction/offline/TransactionSerializer.js +700 -0
  50. package/build/transaction/offline/TransactionStateCapture.d.ts +52 -0
  51. package/build/transaction/offline/TransactionStateCapture.js +275 -0
  52. package/build/transaction/offline/index.d.ts +5 -0
  53. package/build/transaction/offline/index.js +5 -0
  54. package/build/transaction/offline/interfaces/ISerializableState.d.ts +62 -0
  55. package/build/transaction/offline/interfaces/ISerializableState.js +2 -0
  56. package/build/transaction/offline/interfaces/ITypeSpecificData.d.ts +62 -0
  57. package/build/transaction/offline/interfaces/ITypeSpecificData.js +19 -0
  58. package/build/transaction/offline/interfaces/index.d.ts +2 -0
  59. package/build/transaction/offline/interfaces/index.js +2 -0
  60. package/build/transaction/shared/TweakedTransaction.d.ts +12 -1
  61. package/build/transaction/shared/TweakedTransaction.js +75 -8
  62. package/build/utxo/interfaces/IUTXO.d.ts +2 -0
  63. package/documentation/README.md +5 -0
  64. package/documentation/offline-transaction-signing.md +650 -0
  65. package/documentation/transaction-building.md +603 -0
  66. package/package.json +2 -2
  67. package/src/_version.ts +1 -1
  68. package/src/generators/builders/HashCommitmentGenerator.ts +495 -0
  69. package/src/keypair/Address.ts +123 -70
  70. package/src/opnet.ts +8 -1
  71. package/src/signer/AddressRotation.ts +72 -0
  72. package/src/transaction/TransactionFactory.ts +90 -0
  73. package/src/transaction/builders/CancelTransaction.ts +4 -2
  74. package/src/transaction/builders/ConsolidatedInteractionTransaction.ts +568 -0
  75. package/src/transaction/builders/CustomScriptTransaction.ts +4 -2
  76. package/src/transaction/builders/MultiSignTransaction.ts +4 -2
  77. package/src/transaction/builders/TransactionBuilder.ts +8 -2
  78. package/src/transaction/enums/TransactionType.ts +2 -0
  79. package/src/transaction/interfaces/IConsolidatedTransactionParameters.ts +78 -0
  80. package/src/transaction/interfaces/ITransactionParameters.ts +8 -0
  81. package/src/transaction/offline/OfflineTransactionManager.ts +630 -0
  82. package/src/transaction/offline/TransactionReconstructor.ts +402 -0
  83. package/src/transaction/offline/TransactionSerializer.ts +920 -0
  84. package/src/transaction/offline/TransactionStateCapture.ts +469 -0
  85. package/src/transaction/offline/index.ts +8 -0
  86. package/src/transaction/offline/interfaces/ISerializableState.ts +141 -0
  87. package/src/transaction/offline/interfaces/ITypeSpecificData.ts +172 -0
  88. package/src/transaction/offline/interfaces/index.ts +2 -0
  89. package/src/transaction/shared/TweakedTransaction.ts +156 -9
  90. package/src/utxo/interfaces/IUTXO.ts +8 -0
  91. package/test/address-rotation.test.ts +553 -0
  92. package/test/offline-transaction.test.ts +2065 -0
@@ -0,0 +1,553 @@
1
+ import { describe, expect, it, beforeAll } from 'vitest';
2
+ import {
3
+ createAddressRotation,
4
+ createSignerMap,
5
+ disabledAddressRotation,
6
+ EcKeyPair,
7
+ FundingTransaction,
8
+ SignerMap,
9
+ RotationSigner,
10
+ AddressRotationConfig,
11
+ } from '../build/opnet.js';
12
+ import { networks, payments, toXOnly } from '@btc-vision/bitcoin';
13
+ import { ECPairInterface } from 'ecpair';
14
+ import { UTXO } from '../build/opnet.js';
15
+
16
+ describe('Address Rotation', () => {
17
+ const network = networks.regtest;
18
+
19
+ // Generate test keypairs
20
+ let signer1: ECPairInterface;
21
+ let signer2: ECPairInterface;
22
+ let signer3: ECPairInterface;
23
+ let defaultSigner: ECPairInterface;
24
+
25
+ let address1: string;
26
+ let address2: string;
27
+ let address3: string;
28
+ let defaultAddress: string;
29
+
30
+ beforeAll(() => {
31
+ signer1 = EcKeyPair.generateRandomKeyPair(network);
32
+ signer2 = EcKeyPair.generateRandomKeyPair(network);
33
+ signer3 = EcKeyPair.generateRandomKeyPair(network);
34
+ defaultSigner = EcKeyPair.generateRandomKeyPair(network);
35
+
36
+ address1 = EcKeyPair.getTaprootAddress(signer1, network);
37
+ address2 = EcKeyPair.getTaprootAddress(signer2, network);
38
+ address3 = EcKeyPair.getTaprootAddress(signer3, network);
39
+ defaultAddress = EcKeyPair.getTaprootAddress(defaultSigner, network);
40
+ });
41
+
42
+ // Helper to create a taproot UTXO
43
+ const createTaprootUtxo = (
44
+ address: string,
45
+ value: bigint,
46
+ txId: string = '0'.repeat(64),
47
+ index: number = 0,
48
+ ): UTXO => {
49
+ const p2tr = payments.p2tr({
50
+ address,
51
+ network,
52
+ });
53
+
54
+ return {
55
+ transactionId: txId,
56
+ outputIndex: index,
57
+ value,
58
+ scriptPubKey: {
59
+ hex: p2tr.output!.toString('hex'),
60
+ address,
61
+ },
62
+ };
63
+ };
64
+
65
+ describe('createSignerMap', () => {
66
+ it('should create a SignerMap from an array of pairs', () => {
67
+ const pairs: ReadonlyArray<readonly [string, RotationSigner]> = [
68
+ [address1, signer1],
69
+ [address2, signer2],
70
+ ];
71
+
72
+ const map = createSignerMap(pairs);
73
+
74
+ expect(map).toBeInstanceOf(Map);
75
+ expect(map.size).toBe(2);
76
+ expect(map.get(address1)).toBe(signer1);
77
+ expect(map.get(address2)).toBe(signer2);
78
+ });
79
+
80
+ it('should create an empty map from empty array', () => {
81
+ const map = createSignerMap([]);
82
+
83
+ expect(map.size).toBe(0);
84
+ });
85
+
86
+ it('should handle duplicate addresses by using the last value', () => {
87
+ const pairs: ReadonlyArray<readonly [string, RotationSigner]> = [
88
+ [address1, signer1],
89
+ [address1, signer2], // Same address, different signer
90
+ ];
91
+
92
+ const map = createSignerMap(pairs);
93
+
94
+ expect(map.size).toBe(1);
95
+ expect(map.get(address1)).toBe(signer2);
96
+ });
97
+ });
98
+
99
+ describe('createAddressRotation', () => {
100
+ it('should create an enabled AddressRotationConfig from SignerMap', () => {
101
+ const signerMap: SignerMap = new Map([
102
+ [address1, signer1],
103
+ [address2, signer2],
104
+ ]);
105
+
106
+ const config = createAddressRotation(signerMap);
107
+
108
+ expect(config.enabled).toBe(true);
109
+ expect(config.signerMap).toBe(signerMap);
110
+ expect(config.signerMap.size).toBe(2);
111
+ });
112
+
113
+ it('should create an enabled AddressRotationConfig from array of pairs', () => {
114
+ const pairs: ReadonlyArray<readonly [string, RotationSigner]> = [
115
+ [address1, signer1],
116
+ [address2, signer2],
117
+ [address3, signer3],
118
+ ];
119
+
120
+ const config = createAddressRotation(pairs);
121
+
122
+ expect(config.enabled).toBe(true);
123
+ expect(config.signerMap.size).toBe(3);
124
+ expect(config.signerMap.get(address1)).toBe(signer1);
125
+ expect(config.signerMap.get(address2)).toBe(signer2);
126
+ expect(config.signerMap.get(address3)).toBe(signer3);
127
+ });
128
+ });
129
+
130
+ describe('disabledAddressRotation', () => {
131
+ it('should create a disabled AddressRotationConfig', () => {
132
+ const config = disabledAddressRotation();
133
+
134
+ expect(config.enabled).toBe(false);
135
+ expect(config.signerMap).toBeInstanceOf(Map);
136
+ expect(config.signerMap.size).toBe(0);
137
+ });
138
+ });
139
+
140
+ describe('UTXO with signer', () => {
141
+ it('should allow attaching a signer directly to UTXO', () => {
142
+ const utxo = createTaprootUtxo(address1, 10000n);
143
+ const utxoWithSigner: UTXO = {
144
+ ...utxo,
145
+ signer: signer1,
146
+ };
147
+
148
+ expect(utxoWithSigner.signer).toBe(signer1);
149
+ });
150
+ });
151
+
152
+ describe('FundingTransaction with address rotation', () => {
153
+ it('should create transaction with single signer (backward compatible)', async () => {
154
+ const utxo = createTaprootUtxo(defaultAddress, 100000n);
155
+
156
+ const tx = new FundingTransaction({
157
+ signer: defaultSigner,
158
+ network,
159
+ utxos: [utxo],
160
+ to: address1,
161
+ amount: 50000n,
162
+ feeRate: 1,
163
+ priorityFee: 0n,
164
+ gasSatFee: 0n,
165
+ mldsaSigner: null,
166
+ });
167
+
168
+ expect(tx.isAddressRotationEnabled()).toBe(false);
169
+
170
+ const signedTx = await tx.signTransaction();
171
+ expect(signedTx).toBeDefined();
172
+ expect(signedTx.ins.length).toBe(1);
173
+ });
174
+
175
+ it('should create transaction with address rotation using signerMap', async () => {
176
+ const utxo1 = createTaprootUtxo(address1, 50000n, 'a'.repeat(64), 0);
177
+ const utxo2 = createTaprootUtxo(address2, 50000n, 'b'.repeat(64), 0);
178
+
179
+ const signerMap: SignerMap = new Map([
180
+ [address1, signer1],
181
+ [address2, signer2],
182
+ ]);
183
+
184
+ const tx = new FundingTransaction({
185
+ signer: defaultSigner, // fallback
186
+ network,
187
+ utxos: [utxo1, utxo2],
188
+ to: address3,
189
+ amount: 80000n,
190
+ feeRate: 1,
191
+ priorityFee: 0n,
192
+ gasSatFee: 0n,
193
+ mldsaSigner: null,
194
+ addressRotation: createAddressRotation(signerMap),
195
+ });
196
+
197
+ expect(tx.isAddressRotationEnabled()).toBe(true);
198
+
199
+ const signedTx = await tx.signTransaction();
200
+ expect(signedTx).toBeDefined();
201
+ expect(signedTx.ins.length).toBe(2);
202
+ });
203
+
204
+ it('should create transaction with UTXOs having embedded signers', async () => {
205
+ const utxo1: UTXO = {
206
+ ...createTaprootUtxo(address1, 50000n, 'c'.repeat(64), 0),
207
+ signer: signer1,
208
+ };
209
+ const utxo2: UTXO = {
210
+ ...createTaprootUtxo(address2, 50000n, 'd'.repeat(64), 0),
211
+ signer: signer2,
212
+ };
213
+
214
+ const tx = new FundingTransaction({
215
+ signer: defaultSigner,
216
+ network,
217
+ utxos: [utxo1, utxo2],
218
+ to: address3,
219
+ amount: 80000n,
220
+ feeRate: 1,
221
+ priorityFee: 0n,
222
+ gasSatFee: 0n,
223
+ mldsaSigner: null,
224
+ addressRotation: {
225
+ enabled: true,
226
+ signerMap: new Map(),
227
+ },
228
+ });
229
+
230
+ expect(tx.isAddressRotationEnabled()).toBe(true);
231
+
232
+ const signedTx = await tx.signTransaction();
233
+ expect(signedTx).toBeDefined();
234
+ expect(signedTx.ins.length).toBe(2);
235
+ });
236
+
237
+ it('should prioritize UTXO embedded signer over signerMap', async () => {
238
+ // UTXO has address1 but we attach signer2
239
+ const utxo: UTXO = {
240
+ ...createTaprootUtxo(address1, 100000n, 'e'.repeat(64), 0),
241
+ signer: signer1, // Embedded signer
242
+ };
243
+
244
+ // SignerMap has a different signer for address1
245
+ const signerMap: SignerMap = new Map([
246
+ [address1, signer3], // This should be ignored
247
+ ]);
248
+
249
+ const tx = new FundingTransaction({
250
+ signer: defaultSigner,
251
+ network,
252
+ utxos: [utxo],
253
+ to: address2,
254
+ amount: 50000n,
255
+ feeRate: 1,
256
+ priorityFee: 0n,
257
+ gasSatFee: 0n,
258
+ mldsaSigner: null,
259
+ addressRotation: createAddressRotation(signerMap),
260
+ });
261
+
262
+ // Should successfully sign with embedded signer (signer1)
263
+ const signedTx = await tx.signTransaction();
264
+ expect(signedTx).toBeDefined();
265
+ });
266
+
267
+ it('should fall back to default signer when address not in signerMap', async () => {
268
+ // Create UTXO from defaultAddress (not in signerMap)
269
+ const utxo = createTaprootUtxo(defaultAddress, 100000n, 'f'.repeat(64), 0);
270
+
271
+ const signerMap: SignerMap = new Map([
272
+ [address1, signer1],
273
+ [address2, signer2],
274
+ ]);
275
+
276
+ const tx = new FundingTransaction({
277
+ signer: defaultSigner, // Will be used as fallback
278
+ network,
279
+ utxos: [utxo],
280
+ to: address1,
281
+ amount: 50000n,
282
+ feeRate: 1,
283
+ priorityFee: 0n,
284
+ gasSatFee: 0n,
285
+ mldsaSigner: null,
286
+ addressRotation: createAddressRotation(signerMap),
287
+ });
288
+
289
+ // Should successfully sign with default signer
290
+ const signedTx = await tx.signTransaction();
291
+ expect(signedTx).toBeDefined();
292
+ });
293
+
294
+ it('should handle mixed UTXOs (some in map, some with embedded, some fallback)', async () => {
295
+ const utxo1 = createTaprootUtxo(address1, 30000n, '1'.repeat(64), 0);
296
+ const utxo2: UTXO = {
297
+ ...createTaprootUtxo(address2, 30000n, '2'.repeat(64), 0),
298
+ signer: signer2, // Embedded
299
+ };
300
+ const utxo3 = createTaprootUtxo(defaultAddress, 40000n, '3'.repeat(64), 0);
301
+
302
+ const signerMap: SignerMap = new Map([
303
+ [address1, signer1], // For utxo1
304
+ // address2 not in map, but utxo2 has embedded signer
305
+ // defaultAddress not in map, will use fallback
306
+ ]);
307
+
308
+ const tx = new FundingTransaction({
309
+ signer: defaultSigner,
310
+ network,
311
+ utxos: [utxo1, utxo2, utxo3],
312
+ to: address3,
313
+ amount: 80000n,
314
+ feeRate: 1,
315
+ priorityFee: 0n,
316
+ gasSatFee: 0n,
317
+ mldsaSigner: null,
318
+ addressRotation: createAddressRotation(signerMap),
319
+ });
320
+
321
+ const signedTx = await tx.signTransaction();
322
+ expect(signedTx).toBeDefined();
323
+ expect(signedTx.ins.length).toBe(3);
324
+ });
325
+
326
+ it('should work with optionalInputs in address rotation mode', async () => {
327
+ const utxo = createTaprootUtxo(address1, 50000n, '4'.repeat(64), 0);
328
+ const optionalUtxo = createTaprootUtxo(address2, 50000n, '5'.repeat(64), 0);
329
+
330
+ const signerMap: SignerMap = new Map([
331
+ [address1, signer1],
332
+ [address2, signer2],
333
+ ]);
334
+
335
+ const tx = new FundingTransaction({
336
+ signer: defaultSigner,
337
+ network,
338
+ utxos: [utxo],
339
+ optionalInputs: [optionalUtxo],
340
+ to: address3,
341
+ amount: 80000n,
342
+ feeRate: 1,
343
+ priorityFee: 0n,
344
+ gasSatFee: 0n,
345
+ mldsaSigner: null,
346
+ addressRotation: createAddressRotation(signerMap),
347
+ });
348
+
349
+ const signedTx = await tx.signTransaction();
350
+ expect(signedTx).toBeDefined();
351
+ expect(signedTx.ins.length).toBe(2);
352
+ });
353
+ });
354
+
355
+ describe('AddressRotationConfig interface', () => {
356
+ it('should accept a properly typed config object', () => {
357
+ const config: AddressRotationConfig = {
358
+ enabled: true,
359
+ signerMap: new Map([[address1, signer1]]),
360
+ };
361
+
362
+ expect(config.enabled).toBe(true);
363
+ expect(config.signerMap.get(address1)).toBe(signer1);
364
+ });
365
+
366
+ it('should work with readonly signerMap', () => {
367
+ const signerMap: SignerMap = new Map([[address1, signer1]]);
368
+
369
+ // Config signerMap is readonly
370
+ const config: AddressRotationConfig = {
371
+ enabled: true,
372
+ signerMap,
373
+ };
374
+
375
+ // Verify the signerMap is accessible
376
+ expect(config.signerMap.size).toBe(1);
377
+ });
378
+ });
379
+
380
+ describe('Edge cases', () => {
381
+ it('should handle transaction with single UTXO and address rotation enabled', async () => {
382
+ const utxo = createTaprootUtxo(address1, 100000n, '6'.repeat(64), 0);
383
+
384
+ const tx = new FundingTransaction({
385
+ signer: defaultSigner,
386
+ network,
387
+ utxos: [utxo],
388
+ to: address2,
389
+ amount: 50000n,
390
+ feeRate: 1,
391
+ priorityFee: 0n,
392
+ gasSatFee: 0n,
393
+ mldsaSigner: null,
394
+ addressRotation: createAddressRotation([[address1, signer1]]),
395
+ });
396
+
397
+ const signedTx = await tx.signTransaction();
398
+ expect(signedTx).toBeDefined();
399
+ expect(signedTx.ins.length).toBe(1);
400
+ });
401
+
402
+ it('should handle many UTXOs from different addresses', async () => {
403
+ const signers: ECPairInterface[] = [];
404
+ const addresses: string[] = [];
405
+ const utxos: UTXO[] = [];
406
+
407
+ // Create 5 different signers and UTXOs
408
+ for (let i = 0; i < 5; i++) {
409
+ const signer = EcKeyPair.generateRandomKeyPair(network);
410
+ const addr = EcKeyPair.getTaprootAddress(signer, network);
411
+ signers.push(signer);
412
+ addresses.push(addr);
413
+ utxos.push(createTaprootUtxo(addr, 20000n, i.toString().repeat(64), 0));
414
+ }
415
+
416
+ const pairs: [string, ECPairInterface][] = addresses.map((addr, i) => [
417
+ addr,
418
+ signers[i],
419
+ ]);
420
+
421
+ const tx = new FundingTransaction({
422
+ signer: defaultSigner,
423
+ network,
424
+ utxos,
425
+ to: defaultAddress,
426
+ amount: 80000n,
427
+ feeRate: 1,
428
+ priorityFee: 0n,
429
+ gasSatFee: 0n,
430
+ mldsaSigner: null,
431
+ addressRotation: createAddressRotation(pairs),
432
+ });
433
+
434
+ const signedTx = await tx.signTransaction();
435
+ expect(signedTx).toBeDefined();
436
+ expect(signedTx.ins.length).toBe(5);
437
+ });
438
+
439
+ it('should correctly report isAddressRotationEnabled state', () => {
440
+ const utxo = createTaprootUtxo(address1, 100000n);
441
+
442
+ // Without address rotation
443
+ const tx1 = new FundingTransaction({
444
+ signer: defaultSigner,
445
+ network,
446
+ utxos: [utxo],
447
+ to: address2,
448
+ amount: 50000n,
449
+ feeRate: 1,
450
+ priorityFee: 0n,
451
+ gasSatFee: 0n,
452
+ mldsaSigner: null,
453
+ });
454
+ expect(tx1.isAddressRotationEnabled()).toBe(false);
455
+
456
+ // With disabled address rotation
457
+ const tx2 = new FundingTransaction({
458
+ signer: defaultSigner,
459
+ network,
460
+ utxos: [utxo],
461
+ to: address2,
462
+ amount: 50000n,
463
+ feeRate: 1,
464
+ priorityFee: 0n,
465
+ gasSatFee: 0n,
466
+ mldsaSigner: null,
467
+ addressRotation: disabledAddressRotation(),
468
+ });
469
+ expect(tx2.isAddressRotationEnabled()).toBe(false);
470
+
471
+ // With enabled address rotation
472
+ const tx3 = new FundingTransaction({
473
+ signer: signer1, // Must use correct signer for this address
474
+ network,
475
+ utxos: [utxo],
476
+ to: address2,
477
+ amount: 50000n,
478
+ feeRate: 1,
479
+ priorityFee: 0n,
480
+ gasSatFee: 0n,
481
+ mldsaSigner: null,
482
+ addressRotation: createAddressRotation([[address1, signer1]]),
483
+ });
484
+ expect(tx3.isAddressRotationEnabled()).toBe(true);
485
+ });
486
+ });
487
+
488
+ describe('Taproot key verification', () => {
489
+ it('should use correct tapInternalKey for each input in rotation mode', async () => {
490
+ const utxo1 = createTaprootUtxo(address1, 50000n, 'a1'.padEnd(64, '0'), 0);
491
+ const utxo2 = createTaprootUtxo(address2, 50000n, 'a2'.padEnd(64, '0'), 0);
492
+
493
+ const signerMap: SignerMap = new Map([
494
+ [address1, signer1],
495
+ [address2, signer2],
496
+ ]);
497
+
498
+ const tx = new FundingTransaction({
499
+ signer: defaultSigner,
500
+ network,
501
+ utxos: [utxo1, utxo2],
502
+ to: address3,
503
+ amount: 80000n,
504
+ feeRate: 1,
505
+ priorityFee: 0n,
506
+ gasSatFee: 0n,
507
+ mldsaSigner: null,
508
+ addressRotation: createAddressRotation(signerMap),
509
+ });
510
+
511
+ // Generate minimal signatures to populate inputs
512
+ await tx.generateTransactionMinimalSignatures();
513
+
514
+ // Get inputs after building
515
+ const inputs = tx.getInputs();
516
+
517
+ // Verify each input has the correct tapInternalKey
518
+ const expectedKey1 = toXOnly(Buffer.from(signer1.publicKey));
519
+ const expectedKey2 = toXOnly(Buffer.from(signer2.publicKey));
520
+
521
+ expect(inputs[0].tapInternalKey).toBeDefined();
522
+ expect(inputs[1].tapInternalKey).toBeDefined();
523
+ expect(inputs[0].tapInternalKey!.equals(expectedKey1)).toBe(true);
524
+ expect(inputs[1].tapInternalKey!.equals(expectedKey2)).toBe(true);
525
+ });
526
+
527
+ it('should use default signer tapInternalKey when rotation disabled', async () => {
528
+ // Create UTXO from defaultSigner's address
529
+ const utxo = createTaprootUtxo(defaultAddress, 100000n, 'b1'.padEnd(64, '0'), 0);
530
+
531
+ const tx = new FundingTransaction({
532
+ signer: defaultSigner,
533
+ network,
534
+ utxos: [utxo],
535
+ to: address1,
536
+ amount: 50000n,
537
+ feeRate: 1,
538
+ priorityFee: 0n,
539
+ gasSatFee: 0n,
540
+ mldsaSigner: null,
541
+ });
542
+
543
+ // Generate minimal signatures to populate inputs
544
+ await tx.generateTransactionMinimalSignatures();
545
+
546
+ const inputs = tx.getInputs();
547
+ const expectedKey = toXOnly(Buffer.from(defaultSigner.publicKey));
548
+
549
+ expect(inputs[0].tapInternalKey).toBeDefined();
550
+ expect(inputs[0].tapInternalKey!.equals(expectedKey)).toBe(true);
551
+ });
552
+ });
553
+ });