@btc-vision/transaction 1.7.18 → 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.
- package/LICENSE +190 -21
- package/README.md +1 -1
- package/browser/_version.d.ts +1 -1
- package/browser/generators/builders/HashCommitmentGenerator.d.ts +49 -0
- package/browser/index.js +1 -1
- package/browser/keypair/Address.d.ts +3 -1
- package/browser/opnet.d.ts +6 -1
- package/browser/signer/AddressRotation.d.ts +12 -0
- package/browser/transaction/TransactionFactory.d.ts +14 -0
- package/browser/transaction/builders/ConsolidatedInteractionTransaction.d.ts +44 -0
- package/browser/transaction/enums/TransactionType.d.ts +3 -1
- package/browser/transaction/interfaces/IConsolidatedTransactionParameters.d.ts +31 -0
- package/browser/transaction/interfaces/ITransactionParameters.d.ts +2 -0
- package/browser/transaction/offline/OfflineTransactionManager.d.ts +69 -0
- package/browser/transaction/offline/TransactionReconstructor.d.ts +28 -0
- package/browser/transaction/offline/TransactionSerializer.d.ts +50 -0
- package/browser/transaction/offline/TransactionStateCapture.d.ts +52 -0
- package/browser/transaction/offline/index.d.ts +5 -0
- package/browser/transaction/offline/interfaces/ISerializableState.d.ts +62 -0
- package/browser/transaction/offline/interfaces/ITypeSpecificData.d.ts +62 -0
- package/browser/transaction/offline/interfaces/index.d.ts +2 -0
- package/browser/transaction/shared/TweakedTransaction.d.ts +12 -1
- package/browser/utxo/interfaces/IUTXO.d.ts +2 -0
- package/build/_version.d.ts +1 -1
- package/build/_version.js +1 -1
- package/build/generators/builders/HashCommitmentGenerator.d.ts +49 -0
- package/build/generators/builders/HashCommitmentGenerator.js +229 -0
- package/build/keypair/Address.d.ts +3 -1
- package/build/keypair/Address.js +87 -54
- package/build/opnet.d.ts +6 -1
- package/build/opnet.js +6 -1
- package/build/signer/AddressRotation.d.ts +12 -0
- package/build/signer/AddressRotation.js +16 -0
- package/build/transaction/TransactionFactory.d.ts +14 -0
- package/build/transaction/TransactionFactory.js +36 -0
- package/build/transaction/builders/ConsolidatedInteractionTransaction.d.ts +44 -0
- package/build/transaction/builders/ConsolidatedInteractionTransaction.js +259 -0
- package/build/transaction/builders/TransactionBuilder.js +2 -0
- package/build/transaction/enums/TransactionType.d.ts +3 -1
- package/build/transaction/enums/TransactionType.js +2 -0
- package/build/transaction/interfaces/IConsolidatedTransactionParameters.d.ts +31 -0
- package/build/transaction/interfaces/IConsolidatedTransactionParameters.js +1 -0
- package/build/transaction/interfaces/ITransactionParameters.d.ts +2 -0
- package/build/transaction/offline/OfflineTransactionManager.d.ts +69 -0
- package/build/transaction/offline/OfflineTransactionManager.js +255 -0
- package/build/transaction/offline/TransactionReconstructor.d.ts +28 -0
- package/build/transaction/offline/TransactionReconstructor.js +243 -0
- package/build/transaction/offline/TransactionSerializer.d.ts +50 -0
- package/build/transaction/offline/TransactionSerializer.js +700 -0
- package/build/transaction/offline/TransactionStateCapture.d.ts +52 -0
- package/build/transaction/offline/TransactionStateCapture.js +275 -0
- package/build/transaction/offline/index.d.ts +5 -0
- package/build/transaction/offline/index.js +5 -0
- package/build/transaction/offline/interfaces/ISerializableState.d.ts +62 -0
- package/build/transaction/offline/interfaces/ISerializableState.js +2 -0
- package/build/transaction/offline/interfaces/ITypeSpecificData.d.ts +62 -0
- package/build/transaction/offline/interfaces/ITypeSpecificData.js +19 -0
- package/build/transaction/offline/interfaces/index.d.ts +2 -0
- package/build/transaction/offline/interfaces/index.js +2 -0
- package/build/transaction/shared/TweakedTransaction.d.ts +12 -1
- package/build/transaction/shared/TweakedTransaction.js +75 -8
- package/build/utxo/interfaces/IUTXO.d.ts +2 -0
- package/documentation/README.md +5 -0
- package/documentation/offline-transaction-signing.md +650 -0
- package/documentation/transaction-building.md +603 -0
- package/package.json +2 -2
- package/src/_version.ts +1 -1
- package/src/generators/builders/HashCommitmentGenerator.ts +495 -0
- package/src/keypair/Address.ts +123 -70
- package/src/opnet.ts +8 -1
- package/src/signer/AddressRotation.ts +72 -0
- package/src/transaction/TransactionFactory.ts +94 -1
- package/src/transaction/builders/CancelTransaction.ts +4 -2
- package/src/transaction/builders/ConsolidatedInteractionTransaction.ts +568 -0
- package/src/transaction/builders/CustomScriptTransaction.ts +4 -2
- package/src/transaction/builders/MultiSignTransaction.ts +4 -2
- package/src/transaction/builders/TransactionBuilder.ts +8 -2
- package/src/transaction/enums/TransactionType.ts +2 -0
- package/src/transaction/interfaces/IConsolidatedTransactionParameters.ts +78 -0
- package/src/transaction/interfaces/ITransactionParameters.ts +8 -0
- package/src/transaction/offline/OfflineTransactionManager.ts +630 -0
- package/src/transaction/offline/TransactionReconstructor.ts +402 -0
- package/src/transaction/offline/TransactionSerializer.ts +920 -0
- package/src/transaction/offline/TransactionStateCapture.ts +469 -0
- package/src/transaction/offline/index.ts +8 -0
- package/src/transaction/offline/interfaces/ISerializableState.ts +141 -0
- package/src/transaction/offline/interfaces/ITypeSpecificData.ts +172 -0
- package/src/transaction/offline/interfaces/index.ts +2 -0
- package/src/transaction/shared/TweakedTransaction.ts +156 -9
- package/src/utxo/interfaces/IUTXO.ts +8 -0
- package/test/address-rotation.test.ts +553 -0
- 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
|
+
});
|