@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.
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 +94 -1
  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,172 @@
1
+ import { TransactionType } from '../../enums/TransactionType.js';
2
+ import { RawChallenge } from '../../../epoch/interfaces/IChallengeSolution.js';
3
+
4
+ /**
5
+ * Discriminated union for type-specific serialized data
6
+ */
7
+ export type TypeSpecificData =
8
+ | FundingSpecificData
9
+ | DeploymentSpecificData
10
+ | InteractionSpecificData
11
+ | MultiSigSpecificData
12
+ | CustomScriptSpecificData
13
+ | CancelSpecificData;
14
+
15
+ /**
16
+ * Funding transaction specific data
17
+ */
18
+ export interface FundingSpecificData {
19
+ readonly type: TransactionType.FUNDING;
20
+ /** Amount to send in satoshis */
21
+ readonly amount: string; // bigint as string
22
+ /** Number of outputs to split into */
23
+ readonly splitInputsInto: number;
24
+ }
25
+
26
+ /**
27
+ * Deployment transaction specific data
28
+ */
29
+ export interface DeploymentSpecificData {
30
+ readonly type: TransactionType.DEPLOYMENT;
31
+ /** Compressed bytecode (hex) */
32
+ readonly bytecode: string;
33
+ /** Constructor calldata (hex) */
34
+ readonly calldata?: string;
35
+ /** Challenge solution for epoch */
36
+ readonly challenge: RawChallenge;
37
+ /** Reveal MLDSA public key in transaction */
38
+ readonly revealMLDSAPublicKey?: boolean;
39
+ /** Link MLDSA public key to legacy address */
40
+ readonly linkMLDSAPublicKeyToAddress?: boolean;
41
+ /** Hashed MLDSA public key (hex) */
42
+ readonly hashedPublicKey?: string;
43
+ }
44
+
45
+ /**
46
+ * Interaction transaction specific data
47
+ */
48
+ export interface InteractionSpecificData {
49
+ readonly type: TransactionType.INTERACTION;
50
+ /** Compressed calldata (hex) */
51
+ readonly calldata: string;
52
+ /** Contract address/identifier */
53
+ readonly contract?: string;
54
+ /** Challenge solution for epoch */
55
+ readonly challenge: RawChallenge;
56
+ /** Loaded storage for access list */
57
+ readonly loadedStorage?: SerializedLoadedStorage;
58
+ /** Whether this is a cancellation */
59
+ readonly isCancellation?: boolean;
60
+ /** Disable auto refund */
61
+ readonly disableAutoRefund?: boolean;
62
+ /** Reveal MLDSA public key in transaction */
63
+ readonly revealMLDSAPublicKey?: boolean;
64
+ /** Link MLDSA public key to legacy address */
65
+ readonly linkMLDSAPublicKeyToAddress?: boolean;
66
+ /** Hashed MLDSA public key (hex) */
67
+ readonly hashedPublicKey?: string;
68
+ }
69
+
70
+ /**
71
+ * Loaded storage serialization format
72
+ */
73
+ export interface SerializedLoadedStorage {
74
+ [key: string]: string[];
75
+ }
76
+
77
+ /**
78
+ * MultiSig transaction specific data
79
+ */
80
+ export interface MultiSigSpecificData {
81
+ readonly type: TransactionType.MULTI_SIG;
82
+ /** Public keys (hex array) */
83
+ readonly pubkeys: string[];
84
+ /** M of N (minimum signatures required) */
85
+ readonly minimumSignatures: number;
86
+ /** Receiver address */
87
+ readonly receiver: string;
88
+ /** Requested amount to send */
89
+ readonly requestedAmount: string; // bigint as string
90
+ /** Refund vault address */
91
+ readonly refundVault: string;
92
+ /** Original input count (for partial signing) */
93
+ readonly originalInputCount: number;
94
+ /** Existing PSBT state (base64) if partially signed */
95
+ readonly existingPsbtBase64?: string;
96
+ }
97
+
98
+ /**
99
+ * Serialized script element for custom scripts
100
+ */
101
+ export interface SerializedScriptElement {
102
+ /** Element type */
103
+ readonly elementType: 'buffer' | 'opcode';
104
+ /** Value as hex (buffer) or opcode number */
105
+ readonly value: string | number;
106
+ }
107
+
108
+ /**
109
+ * Custom script transaction specific data
110
+ */
111
+ export interface CustomScriptSpecificData {
112
+ readonly type: TransactionType.CUSTOM_CODE;
113
+ /** Bitcoin script elements */
114
+ readonly scriptElements: SerializedScriptElement[];
115
+ /** Witnesses (hex array) */
116
+ readonly witnesses: string[];
117
+ /** Optional annex data (hex) */
118
+ readonly annex?: string;
119
+ }
120
+
121
+ /**
122
+ * Cancel transaction specific data
123
+ */
124
+ export interface CancelSpecificData {
125
+ readonly type: TransactionType.CANCEL;
126
+ /** Compiled target script to cancel (hex) */
127
+ readonly compiledTargetScript: string;
128
+ }
129
+
130
+ /**
131
+ * Type guard for FundingSpecificData
132
+ */
133
+ export function isFundingSpecificData(data: TypeSpecificData): data is FundingSpecificData {
134
+ return data.type === TransactionType.FUNDING;
135
+ }
136
+
137
+ /**
138
+ * Type guard for DeploymentSpecificData
139
+ */
140
+ export function isDeploymentSpecificData(data: TypeSpecificData): data is DeploymentSpecificData {
141
+ return data.type === TransactionType.DEPLOYMENT;
142
+ }
143
+
144
+ /**
145
+ * Type guard for InteractionSpecificData
146
+ */
147
+ export function isInteractionSpecificData(data: TypeSpecificData): data is InteractionSpecificData {
148
+ return data.type === TransactionType.INTERACTION;
149
+ }
150
+
151
+ /**
152
+ * Type guard for MultiSigSpecificData
153
+ */
154
+ export function isMultiSigSpecificData(data: TypeSpecificData): data is MultiSigSpecificData {
155
+ return data.type === TransactionType.MULTI_SIG;
156
+ }
157
+
158
+ /**
159
+ * Type guard for CustomScriptSpecificData
160
+ */
161
+ export function isCustomScriptSpecificData(
162
+ data: TypeSpecificData,
163
+ ): data is CustomScriptSpecificData {
164
+ return data.type === TransactionType.CUSTOM_CODE;
165
+ }
166
+
167
+ /**
168
+ * Type guard for CancelSpecificData
169
+ */
170
+ export function isCancelSpecificData(data: TypeSpecificData): data is CancelSpecificData {
171
+ return data.type === TransactionType.CANCEL;
172
+ }
@@ -0,0 +1,2 @@
1
+ export * from './ISerializableState.js';
2
+ export * from './ITypeSpecificData.js';
@@ -43,6 +43,7 @@ import { Buffer } from 'buffer';
43
43
  import { P2WDADetector } from '../../p2wda/P2WDADetector.js';
44
44
  import { QuantumBIP32Interface } from '@btc-vision/bip32';
45
45
  import { MessageSigner } from '../../keypair/MessageSigner.js';
46
+ import { AddressRotationConfig, RotationSigner, SignerMap } from '../../signer/AddressRotation.js';
46
47
 
47
48
  export type SupportedTransactionVersion = 1 | 2 | 3;
48
49
 
@@ -55,6 +56,12 @@ export interface ITweakedTransactionData {
55
56
  readonly noSignatures?: boolean;
56
57
  readonly unlockScript?: Buffer[];
57
58
  readonly txVersion?: SupportedTransactionVersion;
59
+
60
+ /**
61
+ * Address rotation configuration for per-UTXO signing.
62
+ * When enabled, each UTXO can be signed by a different signer.
63
+ */
64
+ readonly addressRotation?: AddressRotationConfig;
58
65
  }
59
66
 
60
67
  /**
@@ -165,6 +172,28 @@ export abstract class TweakedTransaction extends Logger {
165
172
  protected readonly _mldsaSigner: QuantumBIP32Interface | null = null;
166
173
  protected readonly _hashedPublicKey: Buffer | null = null;
167
174
 
175
+ /**
176
+ * Whether address rotation mode is enabled.
177
+ * When true, each UTXO can be signed by a different signer.
178
+ */
179
+ protected readonly addressRotationEnabled: boolean = false;
180
+
181
+ /**
182
+ * Map of addresses to their respective signers for address rotation mode.
183
+ */
184
+ protected readonly signerMap: SignerMap = new Map();
185
+
186
+ /**
187
+ * Map of input indices to their signers (resolved from UTXOs or signerMap).
188
+ * Populated during input addition.
189
+ */
190
+ protected readonly inputSignerMap: Map<number, RotationSigner> = new Map();
191
+
192
+ /**
193
+ * Cache of tweaked signers per input for address rotation mode.
194
+ */
195
+ protected readonly tweakedSignerCache: Map<number, ECPairInterface | undefined> = new Map();
196
+
168
197
  protected constructor(data: ITweakedTransactionData) {
169
198
  super();
170
199
 
@@ -185,6 +214,12 @@ export abstract class TweakedTransaction extends Logger {
185
214
  this._mldsaSigner = data.mldsaSigner;
186
215
  this._hashedPublicKey = MessageSigner.sha256(this._mldsaSigner.publicKey);
187
216
  }
217
+
218
+ // Initialize address rotation
219
+ if (data.addressRotation?.enabled) {
220
+ this.addressRotationEnabled = true;
221
+ this.signerMap = data.addressRotation.signerMap;
222
+ }
188
223
  }
189
224
 
190
225
  /**
@@ -323,6 +358,13 @@ export abstract class TweakedTransaction extends Logger {
323
358
  return signHash || 0;
324
359
  }
325
360
 
361
+ /**
362
+ * Check if address rotation mode is enabled.
363
+ */
364
+ public isAddressRotationEnabled(): boolean {
365
+ return this.addressRotationEnabled;
366
+ }
367
+
326
368
  public ignoreSignatureError(): void {
327
369
  this.ignoreSignatureErrors = true;
328
370
  }
@@ -425,6 +467,93 @@ export abstract class TweakedTransaction extends Logger {
425
467
  return vSize * feeRate;
426
468
  }
427
469
 
470
+ /**
471
+ * Get the signer for a specific input index.
472
+ * Returns the input-specific signer if in rotation mode, otherwise the default signer.
473
+ * @param inputIndex - The index of the input
474
+ */
475
+ protected getSignerForInput(inputIndex: number): RotationSigner {
476
+ if (this.addressRotationEnabled) {
477
+ const inputSigner = this.inputSignerMap.get(inputIndex);
478
+ if (inputSigner) {
479
+ return inputSigner;
480
+ }
481
+ }
482
+ return this.signer;
483
+ }
484
+
485
+ /**
486
+ * Register a signer for a specific input index.
487
+ * Called during UTXO processing to map each input to its signer.
488
+ * @param inputIndex - The index of the input
489
+ * @param utxo - The UTXO being added
490
+ */
491
+ protected registerInputSigner(inputIndex: number, utxo: UTXO): void {
492
+ if (!this.addressRotationEnabled) {
493
+ return;
494
+ }
495
+
496
+ // Priority 1: UTXO has an explicit signer attached
497
+ if (utxo.signer) {
498
+ this.inputSignerMap.set(inputIndex, utxo.signer);
499
+ return;
500
+ }
501
+
502
+ // Priority 2: Look up signer from signerMap by address
503
+ const address = utxo.scriptPubKey?.address;
504
+ if (address && this.signerMap.has(address)) {
505
+ const signer = this.signerMap.get(address);
506
+ if (signer) {
507
+ this.inputSignerMap.set(inputIndex, signer);
508
+ return;
509
+ }
510
+ }
511
+
512
+ // Fallback: Use default signer (no entry in inputSignerMap)
513
+ }
514
+
515
+ /**
516
+ * Get the x-only public key for a specific input's signer.
517
+ * Used for taproot inputs in address rotation mode.
518
+ * @param inputIndex - The index of the input
519
+ */
520
+ protected internalPubKeyToXOnlyForInput(inputIndex: number): Buffer {
521
+ const signer = this.getSignerForInput(inputIndex);
522
+ return toXOnly(Buffer.from(signer.publicKey));
523
+ }
524
+
525
+ /**
526
+ * Get the tweaked signer for a specific input.
527
+ * Caches the result for efficiency.
528
+ * @param inputIndex - The index of the input
529
+ * @param useTweakedHash - Whether to use the tweaked hash
530
+ */
531
+ protected getTweakedSignerForInput(
532
+ inputIndex: number,
533
+ useTweakedHash: boolean = false,
534
+ ): ECPairInterface | undefined {
535
+ if (!this.addressRotationEnabled) {
536
+ // Fall back to original behavior
537
+ if (useTweakedHash) {
538
+ this.tweakSigner();
539
+ return this.tweakedSigner;
540
+ }
541
+ return this.getTweakedSigner(useTweakedHash);
542
+ }
543
+
544
+ // Check cache
545
+ const cacheKey = inputIndex * 2 + (useTweakedHash ? 1 : 0);
546
+ if (this.tweakedSignerCache.has(cacheKey)) {
547
+ return this.tweakedSignerCache.get(cacheKey);
548
+ }
549
+
550
+ const signer = this.getSignerForInput(inputIndex);
551
+ const tweaked = this.getTweakedSigner(useTweakedHash, signer);
552
+ this.tweakedSignerCache.set(cacheKey, tweaked);
553
+
554
+ return tweaked;
555
+ }
556
+
428
557
  protected generateTapData(): P2TRPayment {
429
558
  return {
430
559
  internalPubkey: this.internalPubKeyToXOnly(),
@@ -567,7 +696,9 @@ export abstract class TweakedTransaction extends Logger {
567
696
  const input = batch[j];
568
697
 
569
698
  try {
570
- promises.push(this.signInput(transaction, input, index, this.signer));
699
+ // Use per-input signer in address rotation mode
700
+ const inputSigner = this.getSignerForInput(index);
701
+ promises.push(this.signInput(transaction, input, index, inputSigner));
571
702
  } catch (e) {
572
703
  this.log(`Failed to sign input ${index}: ${(e as Error).stack}`);
573
704
  }
@@ -696,15 +827,24 @@ export abstract class TweakedTransaction extends Logger {
696
827
  return;
697
828
  }
698
829
 
699
- protected generateP2SHP2PKHRedeemScript(inputAddr: string):
830
+ protected generateP2SHP2PKHRedeemScript(
831
+ inputAddr: string,
832
+ inputIndex?: number,
833
+ ):
700
834
  | {
701
835
  redeemScript: Buffer;
702
836
  outputScript: Buffer;
703
837
  }
704
838
  | undefined {
705
- const pubkey = Buffer.isBuffer(this.signer.publicKey)
706
- ? this.signer.publicKey
707
- : Buffer.from(this.signer.publicKey, 'hex');
839
+ // Use per-input signer in address rotation mode
840
+ const signer =
841
+ this.addressRotationEnabled && inputIndex !== undefined
842
+ ? this.getSignerForInput(inputIndex)
843
+ : this.signer;
844
+
845
+ const pubkey = Buffer.isBuffer(signer.publicKey)
846
+ ? signer.publicKey
847
+ : Buffer.from(signer.publicKey, 'hex');
708
848
 
709
849
  const w = payments.p2wpkh({
710
850
  pubkey: pubkey,
@@ -798,7 +938,10 @@ export abstract class TweakedTransaction extends Logger {
798
938
  );
799
939
  }
800
940
 
801
- const legacyScripts = this.generateP2SHP2PKHRedeemScript(utxo.scriptPubKey.address);
941
+ const legacyScripts = this.generateP2SHP2PKHRedeemScript(
942
+ utxo.scriptPubKey.address,
943
+ i,
944
+ );
802
945
  if (!legacyScripts) {
803
946
  throw new Error('Missing redeemScript for P2SH UTXO and unable to regenerate');
804
947
  }
@@ -853,9 +996,13 @@ export abstract class TweakedTransaction extends Logger {
853
996
  if (inputSign) input.sighashType = inputSign;
854
997
  }
855
998
 
856
- // Taproot internal key
857
- this.tweakSigner();
858
- input.tapInternalKey = this.internalPubKeyToXOnly();
999
+ // Taproot internal key - use per-input signer in address rotation mode
1000
+ if (this.addressRotationEnabled) {
1001
+ input.tapInternalKey = this.internalPubKeyToXOnlyForInput(i);
1002
+ } else {
1003
+ this.tweakSigner();
1004
+ input.tapInternalKey = this.internalPubKeyToXOnly();
1005
+ }
859
1006
  }
860
1007
 
861
1008
  // Handle P2A (Any SegWit version, future versions)
@@ -1,4 +1,5 @@
1
1
  import { ScriptPubKey } from '@btc-vision/bitcoin-rpc';
2
+ import { RotationSigner } from '../../signer/AddressRotation.js';
2
3
 
3
4
  export interface UTXO {
4
5
  readonly transactionId: string;
@@ -9,6 +10,13 @@ export interface UTXO {
9
10
  redeemScript?: string | Buffer;
10
11
  witnessScript?: string | Buffer;
11
12
  nonWitnessUtxo?: string | Buffer;
13
+
14
+ /**
15
+ * Optional signer for this specific UTXO.
16
+ * Used in address rotation mode where each UTXO may have its own signer.
17
+ * If not provided, the signer will be resolved from the signerMap or the default signer.
18
+ */
19
+ signer?: RotationSigner;
12
20
  }
13
21
 
14
22
  export interface FetchUTXOParams {