@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,402 @@
1
+ import { Network, networks, PsbtOutputExtended, Signer, Stack } from '@btc-vision/bitcoin';
2
+ import { ECPairInterface } from 'ecpair';
3
+ import { QuantumBIP32Interface } from '@btc-vision/bip32';
4
+ import { UTXO } from '../../utxo/interfaces/IUTXO.js';
5
+ import { AddressRotationConfig, SignerMap } from '../../signer/AddressRotation.js';
6
+ import { ChallengeSolution } from '../../epoch/ChallengeSolution.js';
7
+ import { TransactionType } from '../enums/TransactionType.js';
8
+ import { TransactionBuilder } from '../builders/TransactionBuilder.js';
9
+ import { FundingTransaction } from '../builders/FundingTransaction.js';
10
+ import { DeploymentTransaction } from '../builders/DeploymentTransaction.js';
11
+ import { InteractionTransaction } from '../builders/InteractionTransaction.js';
12
+ import { MultiSignTransaction } from '../builders/MultiSignTransaction.js';
13
+ import { CustomScriptTransaction } from '../builders/CustomScriptTransaction.js';
14
+ import { CancelTransaction } from '../builders/CancelTransaction.js';
15
+ import { ISerializableTransactionState, SerializedOutput, SerializedUTXO, } from './interfaces/ISerializableState.js';
16
+ import {
17
+ CancelSpecificData,
18
+ CustomScriptSpecificData,
19
+ DeploymentSpecificData,
20
+ FundingSpecificData,
21
+ InteractionSpecificData,
22
+ isCancelSpecificData,
23
+ isCustomScriptSpecificData,
24
+ isDeploymentSpecificData,
25
+ isFundingSpecificData,
26
+ isInteractionSpecificData,
27
+ isMultiSigSpecificData,
28
+ MultiSigSpecificData,
29
+ } from './interfaces/ITypeSpecificData.js';
30
+ import {
31
+ IDeploymentParameters,
32
+ IFundingTransactionParameters,
33
+ IInteractionParameters,
34
+ ITransactionParameters,
35
+ } from '../interfaces/ITransactionParameters.js';
36
+ import { SupportedTransactionVersion } from '../shared/TweakedTransaction.js';
37
+
38
+ /**
39
+ * Options for reconstructing a transaction from serialized state
40
+ */
41
+ export interface ReconstructionOptions {
42
+ /** Primary signer (used for normal mode or as default in rotation mode) */
43
+ signer: Signer | ECPairInterface;
44
+
45
+ /** Optional: Override fee rate for fee bumping */
46
+ newFeeRate?: number;
47
+
48
+ /** Optional: Override priority fee */
49
+ newPriorityFee?: bigint;
50
+
51
+ /** Optional: Override gas sat fee */
52
+ newGasSatFee?: bigint;
53
+
54
+ /** Signer map for address rotation mode (keyed by address) */
55
+ signerMap?: SignerMap;
56
+
57
+ /** MLDSA signer (for quantum-resistant features) */
58
+ mldsaSigner?: QuantumBIP32Interface | null;
59
+ }
60
+
61
+ /**
62
+ * Reconstructs transaction builders from serialized state.
63
+ * Supports fee bumping by allowing parameter overrides during reconstruction.
64
+ */
65
+ export class TransactionReconstructor {
66
+ /**
67
+ * Reconstruct and optionally rebuild transaction with new parameters
68
+ * @param state - Serialized transaction state
69
+ * @param options - Signer(s) and optional fee overrides
70
+ * @returns Reconstructed transaction builder ready for signing
71
+ */
72
+ public static reconstruct(
73
+ state: ISerializableTransactionState,
74
+ options: ReconstructionOptions,
75
+ ): TransactionBuilder<TransactionType> {
76
+ const network = this.nameToNetwork(state.baseParams.networkName);
77
+ const utxos = this.deserializeUTXOs(state.utxos);
78
+ const optionalInputs = this.deserializeUTXOs(state.optionalInputs);
79
+ const optionalOutputs = this.deserializeOutputs(state.optionalOutputs);
80
+
81
+ // Build address rotation config
82
+ const addressRotation = this.buildAddressRotationConfig(
83
+ state.addressRotationEnabled,
84
+ options.signerMap,
85
+ );
86
+
87
+ // Apply fee overrides
88
+ const feeRate = options.newFeeRate ?? state.baseParams.feeRate;
89
+ const priorityFee = options.newPriorityFee ?? BigInt(state.baseParams.priorityFee);
90
+ const gasSatFee = options.newGasSatFee ?? BigInt(state.baseParams.gasSatFee);
91
+
92
+ // Build base params
93
+ const baseParams: ITransactionParameters = {
94
+ signer: options.signer,
95
+ mldsaSigner: options.mldsaSigner ?? null,
96
+ network,
97
+ chainId: state.header.chainId,
98
+ utxos,
99
+ optionalInputs,
100
+ optionalOutputs,
101
+ from: state.baseParams.from,
102
+ to: state.baseParams.to,
103
+ feeRate,
104
+ priorityFee,
105
+ gasSatFee,
106
+ txVersion: state.baseParams.txVersion as SupportedTransactionVersion,
107
+ note: state.baseParams.note ? Buffer.from(state.baseParams.note, 'hex') : undefined,
108
+ anchor: state.baseParams.anchor,
109
+ debugFees: state.baseParams.debugFees,
110
+ addressRotation,
111
+ estimatedFees: state.precomputedData.estimatedFees
112
+ ? BigInt(state.precomputedData.estimatedFees)
113
+ : undefined,
114
+ compiledTargetScript: state.precomputedData.compiledTargetScript
115
+ ? Buffer.from(state.precomputedData.compiledTargetScript, 'hex')
116
+ : undefined,
117
+ };
118
+
119
+ // Dispatch based on transaction type
120
+ const typeData = state.typeSpecificData;
121
+
122
+ if (isFundingSpecificData(typeData)) {
123
+ return this.reconstructFunding(baseParams, typeData);
124
+ } else if (isDeploymentSpecificData(typeData)) {
125
+ return this.reconstructDeployment(baseParams, typeData, state);
126
+ } else if (isInteractionSpecificData(typeData)) {
127
+ return this.reconstructInteraction(baseParams, typeData, state);
128
+ } else if (isMultiSigSpecificData(typeData)) {
129
+ return this.reconstructMultiSig(baseParams, typeData);
130
+ } else if (isCustomScriptSpecificData(typeData)) {
131
+ return this.reconstructCustomScript(baseParams, typeData, state);
132
+ } else if (isCancelSpecificData(typeData)) {
133
+ return this.reconstructCancel(baseParams, typeData);
134
+ }
135
+
136
+ throw new Error(`Unsupported transaction type: ${state.header.transactionType}`);
137
+ }
138
+
139
+ /**
140
+ * Reconstruct a FundingTransaction
141
+ */
142
+ private static reconstructFunding(
143
+ baseParams: ITransactionParameters,
144
+ data: FundingSpecificData,
145
+ ): FundingTransaction {
146
+ const params: IFundingTransactionParameters = {
147
+ ...baseParams,
148
+ amount: BigInt(data.amount),
149
+ splitInputsInto: data.splitInputsInto,
150
+ };
151
+
152
+ return new FundingTransaction(params);
153
+ }
154
+
155
+ /**
156
+ * Reconstruct a DeploymentTransaction
157
+ */
158
+ private static reconstructDeployment(
159
+ baseParams: ITransactionParameters,
160
+ data: DeploymentSpecificData,
161
+ state: ISerializableTransactionState,
162
+ ): DeploymentTransaction {
163
+ const challenge = new ChallengeSolution(data.challenge);
164
+
165
+ const params: IDeploymentParameters = {
166
+ ...baseParams,
167
+ bytecode: Buffer.from(data.bytecode, 'hex'),
168
+ calldata: data.calldata ? Buffer.from(data.calldata, 'hex') : undefined,
169
+ challenge,
170
+ randomBytes: state.precomputedData.randomBytes
171
+ ? Buffer.from(state.precomputedData.randomBytes, 'hex')
172
+ : undefined,
173
+ revealMLDSAPublicKey: data.revealMLDSAPublicKey,
174
+ linkMLDSAPublicKeyToAddress: data.linkMLDSAPublicKeyToAddress,
175
+ };
176
+
177
+ return new DeploymentTransaction(params);
178
+ }
179
+
180
+ /**
181
+ * Reconstruct an InteractionTransaction
182
+ */
183
+ private static reconstructInteraction(
184
+ baseParams: ITransactionParameters,
185
+ data: InteractionSpecificData,
186
+ state: ISerializableTransactionState,
187
+ ): InteractionTransaction {
188
+ const challenge = new ChallengeSolution(data.challenge);
189
+
190
+ if (!baseParams.to) {
191
+ throw new Error('InteractionTransaction requires a "to" address');
192
+ }
193
+
194
+ const params: IInteractionParameters = {
195
+ ...baseParams,
196
+ to: baseParams.to,
197
+ calldata: Buffer.from(data.calldata, 'hex'),
198
+ contract: data.contract,
199
+ challenge,
200
+ randomBytes: state.precomputedData.randomBytes
201
+ ? Buffer.from(state.precomputedData.randomBytes, 'hex')
202
+ : undefined,
203
+ loadedStorage: data.loadedStorage,
204
+ isCancellation: data.isCancellation,
205
+ disableAutoRefund: data.disableAutoRefund,
206
+ revealMLDSAPublicKey: data.revealMLDSAPublicKey,
207
+ linkMLDSAPublicKeyToAddress: data.linkMLDSAPublicKeyToAddress,
208
+ };
209
+
210
+ return new InteractionTransaction(params);
211
+ }
212
+
213
+ /**
214
+ * Reconstruct a MultiSignTransaction
215
+ */
216
+ private static reconstructMultiSig(
217
+ baseParams: ITransactionParameters,
218
+ data: MultiSigSpecificData,
219
+ ): MultiSignTransaction {
220
+ const pubkeys = data.pubkeys.map((pk) => Buffer.from(pk, 'hex'));
221
+
222
+ // If there's an existing PSBT, use fromBase64 to preserve partial signatures
223
+ if (data.existingPsbtBase64) {
224
+ return MultiSignTransaction.fromBase64({
225
+ mldsaSigner: baseParams.mldsaSigner,
226
+ network: baseParams.network,
227
+ chainId: baseParams.chainId,
228
+ utxos: baseParams.utxos,
229
+ optionalInputs: baseParams.optionalInputs,
230
+ optionalOutputs: baseParams.optionalOutputs,
231
+ feeRate: baseParams.feeRate,
232
+ pubkeys,
233
+ minimumSignatures: data.minimumSignatures,
234
+ receiver: data.receiver,
235
+ requestedAmount: BigInt(data.requestedAmount),
236
+ refundVault: data.refundVault,
237
+ psbt: data.existingPsbtBase64,
238
+ });
239
+ }
240
+
241
+ // No existing PSBT - create fresh transaction
242
+ const params = {
243
+ mldsaSigner: baseParams.mldsaSigner,
244
+ network: baseParams.network,
245
+ chainId: baseParams.chainId,
246
+ utxos: baseParams.utxos,
247
+ optionalInputs: baseParams.optionalInputs,
248
+ optionalOutputs: baseParams.optionalOutputs,
249
+ feeRate: baseParams.feeRate,
250
+ pubkeys,
251
+ minimumSignatures: data.minimumSignatures,
252
+ receiver: data.receiver,
253
+ requestedAmount: BigInt(data.requestedAmount),
254
+ refundVault: data.refundVault,
255
+ };
256
+
257
+ return new MultiSignTransaction(params);
258
+ }
259
+
260
+ /**
261
+ * Reconstruct a CustomScriptTransaction
262
+ */
263
+ private static reconstructCustomScript(
264
+ baseParams: ITransactionParameters,
265
+ data: CustomScriptSpecificData,
266
+ state: ISerializableTransactionState,
267
+ ): CustomScriptTransaction {
268
+ // Convert serialized elements to (Buffer | Stack)[]
269
+ const scriptElements: (Buffer | Stack)[] = data.scriptElements.map((el) => {
270
+ if (el.elementType === 'buffer') {
271
+ return Buffer.from(el.value as string, 'hex');
272
+ }
273
+ // Opcodes stored as numbers - wrap in array for Stack type
274
+ return [el.value as number] as Stack;
275
+ });
276
+
277
+ const witnesses = data.witnesses.map((w) => Buffer.from(w, 'hex'));
278
+ const annex = data.annex ? Buffer.from(data.annex, 'hex') : undefined;
279
+
280
+ if (!baseParams.to) {
281
+ throw new Error('CustomScriptTransaction requires a "to" address');
282
+ }
283
+
284
+ const params = {
285
+ ...baseParams,
286
+ to: baseParams.to,
287
+ script: scriptElements,
288
+ witnesses,
289
+ annex,
290
+ randomBytes: state.precomputedData.randomBytes
291
+ ? Buffer.from(state.precomputedData.randomBytes, 'hex')
292
+ : undefined,
293
+ };
294
+
295
+ return new CustomScriptTransaction(params);
296
+ }
297
+
298
+ /**
299
+ * Reconstruct a CancelTransaction
300
+ */
301
+ private static reconstructCancel(
302
+ baseParams: ITransactionParameters,
303
+ data: CancelSpecificData,
304
+ ): CancelTransaction {
305
+ const params = {
306
+ ...baseParams,
307
+ compiledTargetScript: Buffer.from(data.compiledTargetScript, 'hex'),
308
+ };
309
+
310
+ return new CancelTransaction(params);
311
+ }
312
+
313
+ /**
314
+ * Build address rotation config from options
315
+ */
316
+ private static buildAddressRotationConfig(
317
+ enabled: boolean,
318
+ signerMap?: SignerMap,
319
+ ): AddressRotationConfig | undefined {
320
+ if (!enabled) {
321
+ return undefined;
322
+ }
323
+
324
+ if (!signerMap || signerMap.size === 0) {
325
+ throw new Error(
326
+ 'Address rotation enabled but no signerMap provided in reconstruction options',
327
+ );
328
+ }
329
+
330
+ return {
331
+ enabled: true,
332
+ signerMap,
333
+ };
334
+ }
335
+
336
+ /**
337
+ * Deserialize UTXOs from serialized format
338
+ */
339
+ private static deserializeUTXOs(serialized: SerializedUTXO[]): UTXO[] {
340
+ return serialized.map((s) => ({
341
+ transactionId: s.transactionId,
342
+ outputIndex: s.outputIndex,
343
+ value: BigInt(s.value),
344
+ scriptPubKey: {
345
+ hex: s.scriptPubKeyHex,
346
+ address: s.scriptPubKeyAddress,
347
+ },
348
+ redeemScript: s.redeemScript ? Buffer.from(s.redeemScript, 'hex') : undefined,
349
+ witnessScript: s.witnessScript ? Buffer.from(s.witnessScript, 'hex') : undefined,
350
+ nonWitnessUtxo: s.nonWitnessUtxo ? Buffer.from(s.nonWitnessUtxo, 'hex') : undefined,
351
+ }));
352
+ }
353
+
354
+ /**
355
+ * Deserialize outputs from serialized format
356
+ */
357
+ private static deserializeOutputs(serialized: SerializedOutput[]): PsbtOutputExtended[] {
358
+ return serialized.map((s): PsbtOutputExtended => {
359
+ const tapInternalKey = s.tapInternalKey
360
+ ? Buffer.from(s.tapInternalKey, 'hex')
361
+ : undefined;
362
+
363
+ // PsbtOutputExtended is a union type - either has address OR script, not both
364
+ if (s.address) {
365
+ return {
366
+ value: s.value,
367
+ address: s.address,
368
+ tapInternalKey,
369
+ };
370
+ } else if (s.script) {
371
+ return {
372
+ value: s.value,
373
+ script: Buffer.from(s.script, 'hex'),
374
+ tapInternalKey,
375
+ };
376
+ } else {
377
+ // Fallback - shouldn't happen with valid data
378
+ return {
379
+ value: s.value,
380
+ address: '',
381
+ tapInternalKey,
382
+ };
383
+ }
384
+ });
385
+ }
386
+
387
+ /**
388
+ * Convert network name to Network object
389
+ */
390
+ private static nameToNetwork(name: 'mainnet' | 'testnet' | 'regtest'): Network {
391
+ switch (name) {
392
+ case 'mainnet':
393
+ return networks.bitcoin;
394
+ case 'testnet':
395
+ return networks.testnet;
396
+ case 'regtest':
397
+ return networks.regtest;
398
+ default:
399
+ throw new Error(`Unknown network: ${name}`);
400
+ }
401
+ }
402
+ }