@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
@@ -36,7 +36,12 @@ export class Address extends Uint8Array {
36
36
  #originalMDLSAPublicKey: Uint8Array | undefined;
37
37
  #mldsaLevel: MLDSASecurityLevel | undefined;
38
38
 
39
- private legacyPublicKey: Uint8Array | undefined;
39
+ // Lazy loading state - defers expensive EC operations until actually needed
40
+ #pendingLegacyKey: Uint8Array | undefined;
41
+ #legacyProcessed: boolean = false;
42
+
43
+ // After processing, this is 32-byte tweaked x-only (same as original behavior)
44
+ #legacyPublicKey: Uint8Array | undefined;
40
45
 
41
46
  public constructor(mldsaPublicKey?: ArrayLike<number>, publicKeyOrTweak?: ArrayLike<number>) {
42
47
  super(ADDRESS_BYTE_LENGTH);
@@ -46,11 +51,12 @@ export class Address extends Uint8Array {
46
51
  }
47
52
 
48
53
  if (publicKeyOrTweak) {
49
- this.legacyPublicKey = new Uint8Array(publicKeyOrTweak.length);
50
- this.legacyPublicKey.set(publicKeyOrTweak);
54
+ // Store but don't process yet - defer EC operations
55
+ this.#pendingLegacyKey = new Uint8Array(publicKeyOrTweak.length);
56
+ this.#pendingLegacyKey.set(publicKeyOrTweak);
51
57
  }
52
58
 
53
- this.set(mldsaPublicKey);
59
+ this.setMldsaKey(mldsaPublicKey);
54
60
  }
55
61
 
56
62
  public get mldsaLevel(): MLDSASecurityLevel | undefined {
@@ -74,6 +80,7 @@ export class Address extends Uint8Array {
74
80
  * @returns {Uint8Array} The original public key used to create the address.
75
81
  */
76
82
  public get originalPublicKey(): Uint8Array | undefined {
83
+ this.ensureLegacyProcessed();
77
84
  return this.#originalPublicKey;
78
85
  }
79
86
 
@@ -81,11 +88,21 @@ export class Address extends Uint8Array {
81
88
  return this.#mldsaPublicKey;
82
89
  }
83
90
 
91
+ /**
92
+ * Get the legacy public key (32-byte tweaked x-only after processing).
93
+ * Triggers lazy processing if not yet done.
94
+ */
95
+ private get legacyPublicKey(): Uint8Array | undefined {
96
+ this.ensureLegacyProcessed();
97
+ return this.#legacyPublicKey;
98
+ }
99
+
84
100
  /**
85
101
  * Get the key pair for the address
86
102
  * @description This is only for internal use. Please use address.tweakedBytes instead.
87
103
  */
88
104
  private get keyPair(): ECPairInterface {
105
+ this.ensureLegacyProcessed();
89
106
  if (!this.#keyPair) {
90
107
  throw new Error('Legacy public key not set for address');
91
108
  }
@@ -291,11 +308,12 @@ export class Address extends Uint8Array {
291
308
  * @returns {string} The hex string
292
309
  */
293
310
  public tweakedToHex(): string {
294
- if (!this.legacyPublicKey) {
311
+ const key = this.legacyPublicKey;
312
+ if (!key) {
295
313
  throw new Error('Legacy public key not set');
296
314
  }
297
315
 
298
- return '0x' + Buffer.from(this.legacyPublicKey).toString('hex');
316
+ return '0x' + Buffer.from(key).toString('hex');
299
317
  }
300
318
 
301
319
  /**
@@ -311,14 +329,16 @@ export class Address extends Uint8Array {
311
329
  * @returns {Buffer} The buffer
312
330
  */
313
331
  public tweakedPublicKeyToBuffer(): Buffer {
314
- if (!this.legacyPublicKey) {
332
+ const key = this.legacyPublicKey;
333
+ if (!key) {
315
334
  throw new Error('Legacy public key not set');
316
335
  }
317
336
 
318
- return Buffer.from(this.legacyPublicKey);
337
+ return Buffer.from(key);
319
338
  }
320
339
 
321
340
  public toUncompressedHex(): string {
341
+ this.ensureLegacyProcessed();
322
342
  if (!this.#uncompressed) {
323
343
  throw new Error('Legacy public key not set');
324
344
  }
@@ -327,6 +347,7 @@ export class Address extends Uint8Array {
327
347
  }
328
348
 
329
349
  public toUncompressedBuffer(): Buffer {
350
+ this.ensureLegacyProcessed();
330
351
  if (!this.#uncompressed) {
331
352
  throw new Error('Legacy public key not set');
332
353
  }
@@ -335,6 +356,7 @@ export class Address extends Uint8Array {
335
356
  }
336
357
 
337
358
  public toHybridPublicKeyHex(): string {
359
+ this.ensureLegacyProcessed();
338
360
  if (!this.#uncompressed) {
339
361
  throw new Error('Legacy public key not set');
340
362
  }
@@ -343,6 +365,7 @@ export class Address extends Uint8Array {
343
365
  }
344
366
 
345
367
  public toHybridPublicKeyBuffer(): Buffer {
368
+ this.ensureLegacyProcessed();
346
369
  if (!this.#uncompressed) {
347
370
  throw new Error('Legacy public key not set');
348
371
  }
@@ -351,6 +374,7 @@ export class Address extends Uint8Array {
351
374
  }
352
375
 
353
376
  public originalPublicKeyBuffer(): Buffer {
377
+ this.ensureLegacyProcessed();
354
378
  if (!this.#originalPublicKey) {
355
379
  throw new Error('Legacy public key not set');
356
380
  }
@@ -454,53 +478,8 @@ export class Address extends Uint8Array {
454
478
  * @returns {void}
455
479
  */
456
480
  public override set(mldsaPublicKey: ArrayLike<number>): void {
457
- if (this.legacyPublicKey) {
458
- const validLengths = [ADDRESS_BYTE_LENGTH, 33, 65];
459
- if (!validLengths.includes(this.legacyPublicKey.length)) {
460
- throw new Error(`Invalid public key length ${this.legacyPublicKey.length}`);
461
- }
462
-
463
- if (this.legacyPublicKey.length === ADDRESS_BYTE_LENGTH) {
464
- const buf = Buffer.alloc(ADDRESS_BYTE_LENGTH);
465
- buf.set(this.legacyPublicKey);
466
-
467
- this.#tweakedUncompressed = ContractAddress.generateHybridKeyFromHash(buf);
468
- } else {
469
- this.autoFormat(this.legacyPublicKey);
470
- }
471
- }
472
-
473
- // THIS is the SHA256(ORIGINAL_ML_DSA_PUBLIC_KEY)
474
- if (mldsaPublicKey.length === ADDRESS_BYTE_LENGTH) {
475
- const buf = new Uint8Array(ADDRESS_BYTE_LENGTH);
476
- buf.set(mldsaPublicKey);
477
-
478
- super.set(buf);
479
- } else {
480
- // Validate ML-DSA public key lengths according to BIP360 and FIPS 204
481
- // ML-DSA-44 (Level 2): 1312 bytes public key
482
- // ML-DSA-65 (Level 3): 1952 bytes public key
483
- // ML-DSA-87 (Level 5): 2592 bytes public key
484
- const validMLDSALengths = [1312, 1952, 2592];
485
-
486
- if (!validMLDSALengths.includes(mldsaPublicKey.length)) {
487
- throw new Error(
488
- `Invalid ML-DSA public key length: ${mldsaPublicKey.length}. ` +
489
- `Expected 1312 (ML-DSA-44/LEVEL2), 1952 (ML-DSA-65/LEVEL3), or 2592 (ML-DSA-87/LEVEL5) bytes.`,
490
- );
491
- }
492
-
493
- // Store the original ML-DSA public key
494
- this.#mldsaPublicKey = new Uint8Array(mldsaPublicKey.length);
495
- this.#mldsaPublicKey.set(mldsaPublicKey);
496
-
497
- // Hash the ML-DSA public key to get the 32-byte address
498
- const hashedPublicKey = sha256(new Uint8Array(mldsaPublicKey));
499
- const buf = new Uint8Array(ADDRESS_BYTE_LENGTH);
500
- buf.set(hashedPublicKey);
501
-
502
- super.set(buf);
503
- }
481
+ // Legacy key processing is now deferred via ensureLegacyProcessed()
482
+ this.setMldsaKey(mldsaPublicKey);
504
483
  }
505
484
 
506
485
  /**
@@ -509,14 +488,12 @@ export class Address extends Uint8Array {
509
488
  * @returns {boolean} If the public key is valid
510
489
  */
511
490
  public isValidLegacyPublicKey(network: Network): boolean {
512
- if (!this.legacyPublicKey) {
491
+ const key = this.legacyPublicKey;
492
+ if (!key) {
513
493
  throw new Error(`Legacy key not set.`);
514
494
  }
515
495
 
516
- return AddressVerificator.isValidPublicKey(
517
- Buffer.from(this.legacyPublicKey).toString('hex'),
518
- network,
519
- );
496
+ return AddressVerificator.isValidPublicKey(Buffer.from(key).toString('hex'), network);
520
497
  }
521
498
 
522
499
  /**
@@ -569,18 +546,16 @@ export class Address extends Uint8Array {
569
546
  * @param {Network} network The network
570
547
  */
571
548
  public p2tr(network: Network): string {
572
- if (!this.legacyPublicKey) {
573
- throw new Error('Legacy public key not set');
574
- }
575
-
576
549
  if (this.#p2tr && this.#network === network) {
577
550
  return this.#p2tr;
578
551
  }
579
552
 
580
- const p2trAddy: string | undefined = EcKeyPair.tweakedPubKeyBufferToAddress(
581
- this.legacyPublicKey,
582
- network,
583
- );
553
+ const key = this.legacyPublicKey;
554
+ if (!key) {
555
+ throw new Error('Legacy public key not set');
556
+ }
557
+
558
+ const p2trAddy: string | undefined = EcKeyPair.tweakedPubKeyBufferToAddress(key, network);
584
559
 
585
560
  if (p2trAddy) {
586
561
  this.#network = network;
@@ -619,6 +594,7 @@ export class Address extends Uint8Array {
619
594
  return this.#p2wda;
620
595
  }
621
596
 
597
+ this.ensureLegacyProcessed();
622
598
  if (!this.#originalPublicKey) {
623
599
  throw new Error('Cannot create P2WDA address: public key not set');
624
600
  }
@@ -665,6 +641,7 @@ export class Address extends Uint8Array {
665
641
 
666
642
  // We need the original public key in compressed format for the script
667
643
  // Your class stores this in #originalPublicKey when a key is set
644
+ this.ensureLegacyProcessed();
668
645
  if (!this.#originalPublicKey) {
669
646
  throw new Error('Cannot create CSV address: public key not set');
670
647
  }
@@ -698,6 +675,7 @@ export class Address extends Uint8Array {
698
675
 
699
676
  // We need the original public key in compressed format for the script
700
677
  // Your class stores this in #originalPublicKey when a key is set
678
+ this.ensureLegacyProcessed();
701
679
  if (!this.#originalPublicKey) {
702
680
  throw new Error('Cannot create CSV address: public key not set');
703
681
  }
@@ -726,6 +704,8 @@ export class Address extends Uint8Array {
726
704
  return this.#p2op;
727
705
  }
728
706
 
707
+ // p2op only uses the MLDSA hash (this Uint8Array), no legacy key processing needed.
708
+ // This is the HOT PATH for parsing - stays fast without triggering EC operations.
729
709
  const p2opAddy: string | undefined = EcKeyPair.p2op(this, network);
730
710
  if (p2opAddy) {
731
711
  this.#network = network;
@@ -738,6 +718,7 @@ export class Address extends Uint8Array {
738
718
  }
739
719
 
740
720
  public toTweakedHybridPublicKeyHex(): string {
721
+ this.ensureLegacyProcessed();
741
722
  if (!this.#tweakedUncompressed) {
742
723
  throw new Error('Legacy public key not set');
743
724
  }
@@ -746,6 +727,7 @@ export class Address extends Uint8Array {
746
727
  }
747
728
 
748
729
  public toTweakedHybridPublicKeyBuffer(): Buffer {
730
+ this.ensureLegacyProcessed();
749
731
  if (!this.#tweakedUncompressed) {
750
732
  throw new Error('Legacy public key not set');
751
733
  }
@@ -753,6 +735,77 @@ export class Address extends Uint8Array {
753
735
  return this.#tweakedUncompressed;
754
736
  }
755
737
 
738
+ /**
739
+ * Sets the MLDSA key portion of the address.
740
+ * @param {ArrayLike<number>} mldsaPublicKey - The MLDSA public key or its hash
741
+ */
742
+ private setMldsaKey(mldsaPublicKey: ArrayLike<number>): void {
743
+ // THIS is the SHA256(ORIGINAL_ML_DSA_PUBLIC_KEY)
744
+ if (mldsaPublicKey.length === ADDRESS_BYTE_LENGTH) {
745
+ const buf = new Uint8Array(ADDRESS_BYTE_LENGTH);
746
+ buf.set(mldsaPublicKey);
747
+
748
+ super.set(buf);
749
+ } else {
750
+ // Validate ML-DSA public key lengths according to BIP360 and FIPS 204
751
+ // ML-DSA-44 (Level 2): 1312 bytes public key
752
+ // ML-DSA-65 (Level 3): 1952 bytes public key
753
+ // ML-DSA-87 (Level 5): 2592 bytes public key
754
+ const validMLDSALengths = [1312, 1952, 2592];
755
+
756
+ if (!validMLDSALengths.includes(mldsaPublicKey.length)) {
757
+ throw new Error(
758
+ `Invalid ML-DSA public key length: ${mldsaPublicKey.length}. ` +
759
+ `Expected 1312 (ML-DSA-44/LEVEL2), 1952 (ML-DSA-65/LEVEL3), or 2592 (ML-DSA-87/LEVEL5) bytes.`,
760
+ );
761
+ }
762
+
763
+ // Store the original ML-DSA public key
764
+ this.#mldsaPublicKey = new Uint8Array(mldsaPublicKey.length);
765
+ this.#mldsaPublicKey.set(mldsaPublicKey);
766
+
767
+ // Hash the ML-DSA public key to get the 32-byte address
768
+ const hashedPublicKey = sha256(new Uint8Array(mldsaPublicKey));
769
+ const buf = new Uint8Array(ADDRESS_BYTE_LENGTH);
770
+ buf.set(hashedPublicKey);
771
+
772
+ super.set(buf);
773
+ }
774
+ }
775
+
776
+ /**
777
+ * Lazy processing of legacy key - defers expensive EC operations until actually needed.
778
+ * Does the EXACT same logic as the original set() method did for legacy keys.
779
+ */
780
+ private ensureLegacyProcessed(): void {
781
+ if (this.#legacyProcessed) return;
782
+ this.#legacyProcessed = true;
783
+
784
+ const pending = this.#pendingLegacyKey;
785
+ if (!pending) return;
786
+
787
+ const validLengths = [ADDRESS_BYTE_LENGTH, 33, 65];
788
+ if (!validLengths.includes(pending.length)) {
789
+ throw new Error(`Invalid public key length ${pending.length}`);
790
+ }
791
+
792
+ if (pending.length === ADDRESS_BYTE_LENGTH) {
793
+ // 32-byte input: already tweaked x-only, just generate hybrid
794
+ const buf = Buffer.alloc(ADDRESS_BYTE_LENGTH);
795
+ buf.set(pending);
796
+
797
+ this.#tweakedUncompressed = ContractAddress.generateHybridKeyFromHash(buf);
798
+ this.#legacyPublicKey = pending;
799
+ } else {
800
+ // 33 or 65 bytes: full autoFormat processing with EC operations
801
+ this.autoFormat(pending);
802
+ }
803
+ }
804
+
805
+ /**
806
+ * Processes a 33 or 65 byte public key, performing EC operations.
807
+ * Sets #legacyPublicKey to 32-byte tweaked x-only (same as original behavior).
808
+ */
756
809
  private autoFormat(publicKey: ArrayLike<number>): void {
757
810
  const firstByte = publicKey[0];
758
811
 
@@ -773,7 +826,7 @@ export class Address extends Uint8Array {
773
826
 
774
827
  this.#tweakedUncompressed = ContractAddress.generateHybridKeyFromHash(tweakedBytes);
775
828
 
776
- this.legacyPublicKey = new Uint8Array(ADDRESS_BYTE_LENGTH);
777
- this.legacyPublicKey.set(tweakedBytes);
829
+ this.#legacyPublicKey = new Uint8Array(ADDRESS_BYTE_LENGTH);
830
+ this.#legacyPublicKey.set(tweakedBytes);
778
831
  }
779
832
  }
package/src/opnet.ts CHANGED
@@ -9,6 +9,7 @@ export * from './bytecode/Compressor.js';
9
9
  export * from './generators/builders/CalldataGenerator.js';
10
10
  export * from './generators/builders/CustomGenerator.js';
11
11
  export * from './generators/builders/DeploymentGenerator.js';
12
+ export * from './generators/builders/HashCommitmentGenerator.js';
12
13
  export * from './generators/builders/LegacyCalldataGenerator.js';
13
14
  export * from './generators/builders/MultiSignGenerator.js';
14
15
  export * from './generators/builders/P2WDAGenerator.js';
@@ -54,14 +55,18 @@ export * from './network/ChainId.js';
54
55
 
55
56
  /** Signer */
56
57
  export * from './signer/TweakedSigner.js';
58
+ export * from './signer/AddressRotation.js';
57
59
 
58
60
  /** Transaction */
59
61
  export * from './transaction/enums/TransactionType.js';
60
62
  export * from './transaction/interfaces/ITransactionParameters.js';
63
+ export * from './transaction/interfaces/IConsolidatedTransactionParameters.js';
61
64
  export * from './transaction/interfaces/Tap.js';
62
65
  export * from './transaction/TransactionFactory.js';
63
66
 
64
67
  /** Builders */
68
+ export * from './transaction/builders/CancelTransaction.js';
69
+ export * from './transaction/builders/ConsolidatedInteractionTransaction.js';
65
70
  export * from './transaction/builders/CustomScriptTransaction.js';
66
71
  export * from './transaction/builders/DeploymentTransaction.js';
67
72
  export * from './transaction/builders/FundingTransaction.js';
@@ -70,7 +75,9 @@ export * from './transaction/builders/InteractionTransactionP2WDA.js';
70
75
  export * from './transaction/builders/MultiSignTransaction.js';
71
76
  export * from './transaction/builders/SharedInteractionTransaction.js';
72
77
  export * from './transaction/builders/TransactionBuilder.js';
73
- export * from './transaction/builders/CancelTransaction.js';
78
+
79
+ /** Offline Transaction Signing */
80
+ export * from './transaction/offline/index.js';
74
81
 
75
82
  /** Epoch */
76
83
  export * from './epoch/interfaces/IChallengeSolution.js';
@@ -0,0 +1,72 @@
1
+ import { Signer } from '@btc-vision/bitcoin';
2
+ import { ECPairInterface } from 'ecpair';
3
+ import { UnisatSigner } from '../transaction/browser/extensions/UnisatSigner.js';
4
+
5
+ /**
6
+ * Supported signer types for address rotation
7
+ */
8
+ export type RotationSigner = Signer | ECPairInterface | UnisatSigner;
9
+
10
+ /**
11
+ * Map of addresses to their respective signers for address rotation mode.
12
+ * Each UTXO address can have its own dedicated signer.
13
+ */
14
+ export type SignerMap = Map<string, RotationSigner>;
15
+
16
+ /**
17
+ * Configuration for address rotation mode.
18
+ * When enabled, each UTXO can be signed by a different signer based on its address.
19
+ */
20
+ export interface AddressRotationConfig {
21
+ /**
22
+ * Whether address rotation mode is enabled.
23
+ * When true, the signerMap will be used to find the appropriate signer for each UTXO.
24
+ * When false, the default single signer will be used for all inputs.
25
+ */
26
+ readonly enabled: boolean;
27
+
28
+ /**
29
+ * Map of addresses to their respective signers.
30
+ * The key is the address (from UTXO.scriptPubKey.address).
31
+ * The value is the signer that controls that address.
32
+ */
33
+ readonly signerMap: SignerMap;
34
+ }
35
+
36
+ /**
37
+ * Creates a new SignerMap from an array of address-signer pairs.
38
+ * @param pairs - Array of [address, signer] tuples
39
+ * @returns A SignerMap ready for use with address rotation
40
+ */
41
+ export function createSignerMap(
42
+ pairs: ReadonlyArray<readonly [string, RotationSigner]>,
43
+ ): SignerMap {
44
+ return new Map(pairs);
45
+ }
46
+
47
+ /**
48
+ * Creates an AddressRotationConfig with the given signers.
49
+ * @param signers - Map or array of address-signer pairs
50
+ * @returns AddressRotationConfig ready for use
51
+ */
52
+ export function createAddressRotation(
53
+ signers: SignerMap | ReadonlyArray<readonly [string, RotationSigner]>,
54
+ ): AddressRotationConfig {
55
+ const signerMap = signers instanceof Map ? signers : createSignerMap(signers);
56
+
57
+ return {
58
+ enabled: true,
59
+ signerMap,
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Creates a disabled address rotation config (single signer mode).
65
+ * @returns AddressRotationConfig with rotation disabled
66
+ */
67
+ export function disabledAddressRotation(): AddressRotationConfig {
68
+ return {
69
+ enabled: false,
70
+ signerMap: new Map(),
71
+ };
72
+ }
@@ -1,7 +1,10 @@
1
1
  import { Transaction, TxOutput } from '@btc-vision/bitcoin';
2
2
  import { currentConsensus } from '../consensus/ConsensusConfig.js';
3
3
  import { UTXO } from '../utxo/interfaces/IUTXO.js';
4
- import { CustomScriptTransaction, ICustomTransactionParameters, } from './builders/CustomScriptTransaction.js';
4
+ import {
5
+ CustomScriptTransaction,
6
+ ICustomTransactionParameters,
7
+ } from './builders/CustomScriptTransaction.js';
5
8
  import { DeploymentTransaction } from './builders/DeploymentTransaction.js';
6
9
  import { FundingTransaction } from './builders/FundingTransaction.js';
7
10
  import { InteractionTransaction } from './builders/InteractionTransaction.js';
@@ -28,6 +31,11 @@ import { ChallengeSolution } from '../epoch/ChallengeSolution.js';
28
31
  import { Address } from '../keypair/Address.js';
29
32
  import { BitcoinUtils } from '../utils/BitcoinUtils.js';
30
33
  import { CancelTransaction, ICancelTransactionParameters } from './builders/CancelTransaction.js';
34
+ import { ConsolidatedInteractionTransaction } from './builders/ConsolidatedInteractionTransaction.js';
35
+ import {
36
+ IConsolidatedInteractionParameters,
37
+ IConsolidatedInteractionResult,
38
+ } from './interfaces/IConsolidatedTransactionParameters.js';
31
39
 
32
40
  export interface DeploymentResult {
33
41
  readonly transaction: [string, string];
@@ -75,6 +83,33 @@ export interface CancelledTransaction {
75
83
  readonly inputUtxos: UTXO[];
76
84
  }
77
85
 
86
+ /**
87
+ * Response from signConsolidatedInteraction.
88
+ * Contains both setup and reveal transactions for the CHCT system.
89
+ */
90
+ export interface ConsolidatedInteractionResponse {
91
+ /** Setup transaction hex - creates P2WSH commitment outputs */
92
+ readonly setupTransaction: string;
93
+ /** Reveal transaction hex - spends commitments, reveals data in witnesses */
94
+ readonly revealTransaction: string;
95
+ /** Setup transaction ID */
96
+ readonly setupTxId: string;
97
+ /** Reveal transaction ID */
98
+ readonly revealTxId: string;
99
+ /** Total fees across both transactions in satoshis */
100
+ readonly totalFees: bigint;
101
+ /** Number of data chunks */
102
+ readonly chunkCount: number;
103
+ /** Total compiled data size in bytes */
104
+ readonly dataSize: number;
105
+ /** Challenge for the interaction */
106
+ readonly challenge: RawChallenge;
107
+ /** Input UTXOs used */
108
+ readonly inputUtxos: UTXO[];
109
+ /** Compiled target script (same as InteractionTransaction) */
110
+ readonly compiledTargetScript: string;
111
+ }
112
+
78
113
  export class TransactionFactory {
79
114
  public debug: boolean = false;
80
115
 
@@ -318,6 +353,64 @@ export class TransactionFactory {
318
353
  };
319
354
  }
320
355
 
356
+ /**
357
+ * @description Generates a consolidated interaction transaction (CHCT system).
358
+ *
359
+ * Drop-in replacement for signInteraction that bypasses BIP110/Bitcoin Knots censorship.
360
+ * Uses P2WSH with HASH160 commitments instead of Tapscript (which uses OP_IF and gets censored).
361
+ *
362
+ * Returns two transactions:
363
+ * - Setup: Creates P2WSH outputs with hash commitments to data chunks
364
+ * - Reveal: Spends those outputs, revealing data in witnesses
365
+ *
366
+ * Data integrity is consensus-enforced - if data is stripped/modified,
367
+ * HASH160(data) != committed_hash and the transaction is INVALID.
368
+ *
369
+ * @param {IConsolidatedInteractionParameters} interactionParameters - Same parameters as signInteraction
370
+ * @returns {Promise<ConsolidatedInteractionResponse>} - Both setup and reveal transactions
371
+ */
372
+ public async signConsolidatedInteraction(
373
+ interactionParameters: IConsolidatedInteractionParameters,
374
+ ): Promise<ConsolidatedInteractionResponse> {
375
+ if (!interactionParameters.to) {
376
+ throw new Error('Field "to" not provided.');
377
+ }
378
+ if (!interactionParameters.from) {
379
+ throw new Error('Field "from" not provided.');
380
+ }
381
+ if (!interactionParameters.utxos[0]) {
382
+ throw new Error('Missing at least one UTXO.');
383
+ }
384
+ if (!('signer' in interactionParameters)) {
385
+ throw new Error('Field "signer" not provided.');
386
+ }
387
+ if (!interactionParameters.challenge) {
388
+ throw new Error('Field "challenge" not provided.');
389
+ }
390
+
391
+ const inputs = this.parseOptionalInputs(interactionParameters.optionalInputs);
392
+
393
+ const consolidatedTx = new ConsolidatedInteractionTransaction({
394
+ ...interactionParameters,
395
+ optionalInputs: inputs,
396
+ });
397
+
398
+ const result = await consolidatedTx.build();
399
+
400
+ return {
401
+ setupTransaction: result.setup.txHex,
402
+ revealTransaction: result.reveal.txHex,
403
+ setupTxId: result.setup.txId,
404
+ revealTxId: result.reveal.txId,
405
+ totalFees: result.totalFees,
406
+ chunkCount: result.setup.chunkCount,
407
+ dataSize: result.setup.totalDataSize,
408
+ challenge: consolidatedTx.getChallenge().toRaw(),
409
+ inputUtxos: interactionParameters.utxos,
410
+ compiledTargetScript: consolidatedTx.exportCompiledTargetScript().toString('hex'),
411
+ };
412
+ }
413
+
321
414
  /**
322
415
  * @description Generates the required transactions.
323
416
  * @param {IDeploymentParameters} deploymentParameters - The deployment parameters
@@ -8,8 +8,10 @@ import {
8
8
  } from '../interfaces/ITransactionParameters.js';
9
9
  import { UnisatSigner } from '../browser/extensions/UnisatSigner.js';
10
10
 
11
- export interface ICancelTransactionParameters
12
- extends Omit<ITransactionParameters, 'priorityFee' | 'gasSatFee'> {
11
+ export interface ICancelTransactionParameters extends Omit<
12
+ ITransactionParameters,
13
+ 'priorityFee' | 'gasSatFee'
14
+ > {
13
15
  readonly compiledTargetScript: string | Buffer;
14
16
  }
15
17