@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
package/src/keypair/Address.ts
CHANGED
|
@@ -36,7 +36,12 @@ export class Address extends Uint8Array {
|
|
|
36
36
|
#originalMDLSAPublicKey: Uint8Array | undefined;
|
|
37
37
|
#mldsaLevel: MLDSASecurityLevel | undefined;
|
|
38
38
|
|
|
39
|
-
|
|
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
|
-
|
|
50
|
-
this
|
|
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.
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
458
|
-
|
|
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
|
-
|
|
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
|
|
581
|
-
|
|
582
|
-
|
|
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
|
|
777
|
-
this
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
11
|
+
export interface ICancelTransactionParameters extends Omit<
|
|
12
|
+
ITransactionParameters,
|
|
13
|
+
'priorityFee' | 'gasSatFee'
|
|
14
|
+
> {
|
|
13
15
|
readonly compiledTargetScript: string | Buffer;
|
|
14
16
|
}
|
|
15
17
|
|