@btc-vision/transaction 1.6.1 → 1.6.5
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/browser/_version.d.ts +1 -1
- package/browser/epoch/ChallengeSolution.d.ts +3 -3
- package/browser/epoch/validator/EpochValidator.d.ts +5 -6
- package/browser/generators/builders/P2WDAGenerator.d.ts +13 -0
- package/browser/index.js +1 -1
- package/browser/keypair/Address.d.ts +3 -2
- package/browser/keypair/AddressVerificator.d.ts +13 -1
- package/browser/keypair/Wallet.d.ts +3 -0
- package/browser/opnet.d.ts +4 -0
- package/browser/p2wda/P2WDADetector.d.ts +16 -0
- package/browser/transaction/TransactionFactory.d.ts +3 -1
- package/browser/transaction/builders/DeploymentTransaction.d.ts +3 -3
- package/browser/transaction/builders/InteractionTransactionP2WDA.d.ts +37 -0
- package/browser/transaction/builders/SharedInteractionTransaction.d.ts +4 -4
- package/browser/transaction/builders/TransactionBuilder.d.ts +3 -0
- package/browser/transaction/interfaces/ITransactionParameters.d.ts +1 -0
- package/browser/transaction/mineable/IP2WSHAddress.d.ts +4 -0
- package/browser/transaction/mineable/TimelockGenerator.d.ts +2 -5
- package/browser/transaction/shared/TweakedTransaction.d.ts +23 -0
- package/build/_version.d.ts +1 -1
- package/build/_version.js +1 -1
- package/build/epoch/ChallengeSolution.d.ts +3 -3
- package/build/epoch/ChallengeSolution.js +3 -3
- package/build/epoch/validator/EpochValidator.d.ts +5 -6
- package/build/epoch/validator/EpochValidator.js +11 -12
- package/build/generators/builders/P2WDAGenerator.d.ts +13 -0
- package/build/generators/builders/P2WDAGenerator.js +62 -0
- package/build/keypair/Address.d.ts +3 -2
- package/build/keypair/Address.js +28 -2
- package/build/keypair/AddressVerificator.d.ts +13 -1
- package/build/keypair/AddressVerificator.js +82 -1
- package/build/keypair/Wallet.d.ts +3 -0
- package/build/keypair/Wallet.js +4 -0
- package/build/opnet.d.ts +4 -0
- package/build/opnet.js +4 -0
- package/build/p2wda/P2WDADetector.d.ts +16 -0
- package/build/p2wda/P2WDADetector.js +97 -0
- package/build/transaction/TransactionFactory.d.ts +3 -1
- package/build/transaction/TransactionFactory.js +35 -4
- package/build/transaction/builders/DeploymentTransaction.d.ts +3 -3
- package/build/transaction/builders/DeploymentTransaction.js +1 -1
- package/build/transaction/builders/InteractionTransactionP2WDA.d.ts +37 -0
- package/build/transaction/builders/InteractionTransactionP2WDA.js +205 -0
- package/build/transaction/builders/SharedInteractionTransaction.d.ts +4 -4
- package/build/transaction/builders/SharedInteractionTransaction.js +3 -3
- package/build/transaction/builders/TransactionBuilder.d.ts +3 -0
- package/build/transaction/builders/TransactionBuilder.js +18 -3
- package/build/transaction/interfaces/ITransactionParameters.d.ts +1 -0
- package/build/transaction/mineable/IP2WSHAddress.d.ts +4 -0
- package/build/transaction/mineable/IP2WSHAddress.js +1 -0
- package/build/transaction/mineable/TimelockGenerator.d.ts +2 -5
- package/build/transaction/shared/TweakedTransaction.d.ts +23 -0
- package/build/transaction/shared/TweakedTransaction.js +154 -18
- package/doc/README.md +0 -0
- package/doc/addresses/P2OP.md +1 -0
- package/doc/addresses/P2WDA.md +240 -0
- package/package.json +2 -2
- package/src/_version.ts +1 -1
- package/src/epoch/ChallengeSolution.ts +4 -4
- package/src/epoch/validator/EpochValidator.ts +12 -16
- package/src/generators/builders/P2WDAGenerator.ts +174 -0
- package/src/keypair/Address.ts +58 -3
- package/src/keypair/AddressVerificator.ts +147 -2
- package/src/keypair/Wallet.ts +16 -0
- package/src/opnet.ts +4 -0
- package/src/p2wda/P2WDADetector.ts +218 -0
- package/src/transaction/TransactionFactory.ts +79 -5
- package/src/transaction/builders/DeploymentTransaction.ts +4 -3
- package/src/transaction/builders/InteractionTransactionP2WDA.ts +376 -0
- package/src/transaction/builders/SharedInteractionTransaction.ts +7 -6
- package/src/transaction/builders/TransactionBuilder.ts +30 -3
- package/src/transaction/interfaces/ITransactionParameters.ts +1 -0
- package/src/transaction/mineable/IP2WSHAddress.ts +4 -0
- package/src/transaction/mineable/TimelockGenerator.ts +2 -6
- package/src/transaction/shared/TweakedTransaction.ts +246 -23
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
address as bitAddress,
|
|
4
4
|
crypto as bitCrypto,
|
|
5
5
|
getFinalScripts,
|
|
6
|
+
isP2A,
|
|
6
7
|
isP2MS,
|
|
7
8
|
isP2PK,
|
|
8
9
|
isP2PKH,
|
|
@@ -14,7 +15,6 @@ import {
|
|
|
14
15
|
Network,
|
|
15
16
|
opcodes,
|
|
16
17
|
P2TRPayment,
|
|
17
|
-
Payment,
|
|
18
18
|
payments,
|
|
19
19
|
PaymentType,
|
|
20
20
|
Psbt,
|
|
@@ -38,6 +38,11 @@ import {
|
|
|
38
38
|
isTaprootInput,
|
|
39
39
|
pubkeyInScript,
|
|
40
40
|
} from '../../signer/SignerUtils.js';
|
|
41
|
+
import { TransactionBuilder } from '../builders/TransactionBuilder.js';
|
|
42
|
+
import { Buffer } from 'buffer';
|
|
43
|
+
import { P2WDADetector } from '../../p2wda/P2WDADetector.js';
|
|
44
|
+
|
|
45
|
+
export type SupportedTransactionVersion = 1 | 2 | 3;
|
|
41
46
|
|
|
42
47
|
export interface ITweakedTransactionData {
|
|
43
48
|
readonly signer: Signer | ECPairInterface | UnisatSigner;
|
|
@@ -46,6 +51,7 @@ export interface ITweakedTransactionData {
|
|
|
46
51
|
readonly nonWitnessUtxo?: Buffer;
|
|
47
52
|
readonly noSignatures?: boolean;
|
|
48
53
|
readonly unlockScript?: Buffer[];
|
|
54
|
+
readonly txVersion?: SupportedTransactionVersion;
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
/**
|
|
@@ -56,6 +62,13 @@ export enum TransactionSequence {
|
|
|
56
62
|
FINAL = 0xffffffff,
|
|
57
63
|
}
|
|
58
64
|
|
|
65
|
+
export enum CSVModes {
|
|
66
|
+
BLOCKS = 0,
|
|
67
|
+
TIMESTAMPS = 1,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const CSV_ENABLED_BLOCKS_MASK = 0x3fffffff;
|
|
71
|
+
|
|
59
72
|
/**
|
|
60
73
|
* @description PSBT Transaction processor.
|
|
61
74
|
* */
|
|
@@ -88,33 +101,40 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
88
101
|
* @protected
|
|
89
102
|
*/
|
|
90
103
|
protected abstract readonly transaction: Psbt;
|
|
104
|
+
|
|
91
105
|
/**
|
|
92
106
|
* @description The sighash types of the transaction
|
|
93
107
|
* @protected
|
|
94
108
|
*/
|
|
95
109
|
protected sighashTypes: number[] | undefined;
|
|
110
|
+
|
|
96
111
|
/**
|
|
97
112
|
* @description The script data of the transaction
|
|
98
113
|
*/
|
|
99
114
|
protected scriptData: P2TRPayment | null = null;
|
|
115
|
+
|
|
100
116
|
/**
|
|
101
117
|
* @description The tap data of the transaction
|
|
102
118
|
*/
|
|
103
119
|
protected tapData: P2TRPayment | null = null;
|
|
120
|
+
|
|
104
121
|
/**
|
|
105
122
|
* @description The inputs of the transaction
|
|
106
123
|
*/
|
|
107
124
|
protected readonly inputs: PsbtInputExtended[] = [];
|
|
125
|
+
|
|
108
126
|
/**
|
|
109
127
|
* @description The sequence of the transaction
|
|
110
128
|
* @protected
|
|
111
129
|
*/
|
|
112
130
|
protected sequence: number = TransactionSequence.REPLACE_BY_FEE;
|
|
131
|
+
|
|
113
132
|
/**
|
|
114
133
|
* The tap leaf script
|
|
115
134
|
* @protected
|
|
116
135
|
*/
|
|
117
136
|
protected tapLeafScript: TapLeafScript | null = null;
|
|
137
|
+
|
|
118
138
|
/**
|
|
119
139
|
* Add a non-witness utxo to the transaction
|
|
120
140
|
* @protected
|
|
@@ -127,11 +147,20 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
127
147
|
*/
|
|
128
148
|
protected readonly isBrowser: boolean = false;
|
|
129
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Track which inputs contain CSV scripts
|
|
152
|
+
* @protected
|
|
153
|
+
*/
|
|
154
|
+
protected csvInputIndices: Set<number> = new Set();
|
|
155
|
+
protected anchorInputIndices: Set<number> = new Set();
|
|
156
|
+
|
|
130
157
|
protected regenerated: boolean = false;
|
|
131
158
|
protected ignoreSignatureErrors: boolean = false;
|
|
132
159
|
protected noSignatures: boolean = false;
|
|
133
160
|
protected unlockScript: Buffer[] | undefined;
|
|
134
161
|
|
|
162
|
+
protected txVersion: SupportedTransactionVersion = 2;
|
|
163
|
+
|
|
135
164
|
protected constructor(data: ITweakedTransactionData) {
|
|
136
165
|
super();
|
|
137
166
|
|
|
@@ -143,6 +172,10 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
143
172
|
this.unlockScript = data.unlockScript;
|
|
144
173
|
|
|
145
174
|
this.isBrowser = typeof window !== 'undefined';
|
|
175
|
+
|
|
176
|
+
if (data.txVersion) {
|
|
177
|
+
this.txVersion = data.txVersion;
|
|
178
|
+
}
|
|
146
179
|
}
|
|
147
180
|
|
|
148
181
|
/**
|
|
@@ -303,6 +336,11 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
303
336
|
this.sequence = TransactionSequence.FINAL;
|
|
304
337
|
|
|
305
338
|
for (const input of this.inputs) {
|
|
339
|
+
// This would disable CSV! You need to check if the input has CSV
|
|
340
|
+
if (this.csvInputIndices.has(this.inputs.indexOf(input))) {
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
|
|
306
344
|
input.sequence = TransactionSequence.FINAL;
|
|
307
345
|
}
|
|
308
346
|
}
|
|
@@ -402,6 +440,8 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
402
440
|
reverse: boolean = false,
|
|
403
441
|
errored: boolean = false,
|
|
404
442
|
): Promise<void> {
|
|
443
|
+
if (this.anchorInputIndices.has(i)) return;
|
|
444
|
+
|
|
405
445
|
const publicKey = signer.publicKey;
|
|
406
446
|
|
|
407
447
|
let isTaproot = isTaprootInput(input);
|
|
@@ -672,7 +712,7 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
672
712
|
i: number,
|
|
673
713
|
_extra: boolean = false,
|
|
674
714
|
): PsbtInputExtended {
|
|
675
|
-
const
|
|
715
|
+
const scriptPub = Buffer.from(utxo.scriptPubKey.hex, 'hex');
|
|
676
716
|
|
|
677
717
|
const input: PsbtInputExtended = {
|
|
678
718
|
hash: utxo.transactionId,
|
|
@@ -680,12 +720,12 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
680
720
|
sequence: this.sequence,
|
|
681
721
|
witnessUtxo: {
|
|
682
722
|
value: Number(utxo.value),
|
|
683
|
-
script,
|
|
723
|
+
script: scriptPub,
|
|
684
724
|
},
|
|
685
725
|
};
|
|
686
726
|
|
|
687
727
|
// Handle P2PKH (Legacy)
|
|
688
|
-
if (isP2PKH(
|
|
728
|
+
if (isP2PKH(scriptPub)) {
|
|
689
729
|
// Legacy input requires nonWitnessUtxo
|
|
690
730
|
if (utxo.nonWitnessUtxo) {
|
|
691
731
|
//delete input.witnessUtxo;
|
|
@@ -698,28 +738,18 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
698
738
|
}
|
|
699
739
|
|
|
700
740
|
// Handle P2WPKH (SegWit)
|
|
701
|
-
else if (isP2WPKH(
|
|
741
|
+
else if (isP2WPKH(scriptPub) || isUnknownSegwitVersion(scriptPub)) {
|
|
702
742
|
// No redeemScript required for pure P2WPKH
|
|
703
743
|
// witnessUtxo is enough, no nonWitnessUtxo needed.
|
|
704
744
|
}
|
|
705
745
|
|
|
706
746
|
// Handle P2WSH (SegWit)
|
|
707
|
-
else if (isP2WSHScript(
|
|
708
|
-
|
|
709
|
-
if (!utxo.witnessScript) {
|
|
710
|
-
// Can't just invent a witnessScript out of thin air. If not provided, it's an error.
|
|
711
|
-
throw new Error('Missing witnessScript for P2WSH UTXO');
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
input.witnessScript = Buffer.isBuffer(utxo.witnessScript)
|
|
715
|
-
? utxo.witnessScript
|
|
716
|
-
: Buffer.from(utxo.witnessScript, 'hex');
|
|
717
|
-
|
|
718
|
-
// No nonWitnessUtxo needed for segwit
|
|
747
|
+
else if (isP2WSHScript(scriptPub)) {
|
|
748
|
+
this.processP2WSHInput(utxo, input, i);
|
|
719
749
|
}
|
|
720
750
|
|
|
721
751
|
// Handle P2SH (Can be legacy or wrapping segwit)
|
|
722
|
-
else if (isP2SHScript(
|
|
752
|
+
else if (isP2SHScript(scriptPub)) {
|
|
723
753
|
// Redeem script is required for P2SH
|
|
724
754
|
let redeemScriptBuf: Buffer | undefined;
|
|
725
755
|
|
|
@@ -771,9 +801,8 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
771
801
|
// P2SH-P2WSH
|
|
772
802
|
// Use witnessUtxo + redeemScript + witnessScript
|
|
773
803
|
delete input.nonWitnessUtxo; // ensure we do NOT have nonWitnessUtxo
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
}
|
|
804
|
+
|
|
805
|
+
this.processP2WSHInput(utxo, input, i);
|
|
777
806
|
} else {
|
|
778
807
|
// Legacy P2SH
|
|
779
808
|
// Use nonWitnessUtxo
|
|
@@ -782,7 +811,7 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
782
811
|
}
|
|
783
812
|
|
|
784
813
|
// Handle P2TR (Taproot)
|
|
785
|
-
else if (isP2TR(
|
|
814
|
+
else if (isP2TR(scriptPub)) {
|
|
786
815
|
// Taproot inputs do not require nonWitnessUtxo, witnessUtxo is sufficient.
|
|
787
816
|
|
|
788
817
|
// If there's a configured sighash type
|
|
@@ -796,8 +825,15 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
796
825
|
input.tapInternalKey = this.internalPubKeyToXOnly();
|
|
797
826
|
}
|
|
798
827
|
|
|
828
|
+
// Handle P2A (Any SegWit version, future versions)
|
|
829
|
+
else if (isP2A(scriptPub)) {
|
|
830
|
+
this.anchorInputIndices.add(i);
|
|
831
|
+
|
|
832
|
+
input.isPayToAnchor = true;
|
|
833
|
+
}
|
|
834
|
+
|
|
799
835
|
// Handle P2PK (legacy) or P2MS (bare multisig)
|
|
800
|
-
else if (isP2PK(
|
|
836
|
+
else if (isP2PK(scriptPub) || isP2MS(scriptPub)) {
|
|
801
837
|
// These are legacy scripts, need nonWitnessUtxo
|
|
802
838
|
if (utxo.nonWitnessUtxo) {
|
|
803
839
|
input.nonWitnessUtxo = Buffer.isBuffer(utxo.nonWitnessUtxo)
|
|
@@ -836,6 +872,56 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
836
872
|
return input;
|
|
837
873
|
}
|
|
838
874
|
|
|
875
|
+
protected processP2WSHInput(utxo: UTXO, input: PsbtInputExtended, i: number): void {
|
|
876
|
+
// P2WSH requires a witnessScript
|
|
877
|
+
if (!utxo.witnessScript) {
|
|
878
|
+
// Can't just invent a witnessScript out of thin air. If not provided, it's an error.
|
|
879
|
+
throw new Error('Missing witnessScript for P2WSH UTXO');
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
input.witnessScript = Buffer.isBuffer(utxo.witnessScript)
|
|
883
|
+
? utxo.witnessScript
|
|
884
|
+
: Buffer.from(utxo.witnessScript, 'hex');
|
|
885
|
+
|
|
886
|
+
// No nonWitnessUtxo needed for segwit
|
|
887
|
+
|
|
888
|
+
const decompiled = script.decompile(input.witnessScript);
|
|
889
|
+
if (decompiled && this.isCSVScript(decompiled)) {
|
|
890
|
+
const decompiled = script.decompile(input.witnessScript);
|
|
891
|
+
if (decompiled && this.isCSVScript(decompiled)) {
|
|
892
|
+
this.csvInputIndices.add(i);
|
|
893
|
+
|
|
894
|
+
// Extract CSV value from witness script
|
|
895
|
+
const csvBlocks = this.extractCSVBlocks(decompiled);
|
|
896
|
+
|
|
897
|
+
console.log('csvBlocks', csvBlocks);
|
|
898
|
+
|
|
899
|
+
// Use the setCSVSequence method to properly set the sequence
|
|
900
|
+
input.sequence = this.setCSVSequence(csvBlocks, this.sequence);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
protected secondsToCSVTimeUnits(seconds: number): number {
|
|
906
|
+
return Math.floor(seconds / 512);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
protected createTimeBasedCSV(seconds: number): number {
|
|
910
|
+
const timeUnits = this.secondsToCSVTimeUnits(seconds);
|
|
911
|
+
if (timeUnits > 0xffff) {
|
|
912
|
+
throw new Error(`Time units ${timeUnits} exceeds maximum of 65,535`);
|
|
913
|
+
}
|
|
914
|
+
return timeUnits | (1 << 22);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
protected isCSVEnabled(sequence: number): boolean {
|
|
918
|
+
return (sequence & (1 << 31)) === 0;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
protected extractCSVValue(sequence: number): number {
|
|
922
|
+
return sequence & 0x0000ffff;
|
|
923
|
+
}
|
|
924
|
+
|
|
839
925
|
protected customFinalizerP2SH = (
|
|
840
926
|
inputIndex: number,
|
|
841
927
|
input: PsbtInput,
|
|
@@ -858,6 +944,38 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
858
944
|
};
|
|
859
945
|
}
|
|
860
946
|
|
|
947
|
+
if (this.anchorInputIndices.has(inputIndex)) {
|
|
948
|
+
return {
|
|
949
|
+
finalScriptSig: undefined,
|
|
950
|
+
finalScriptWitness: Buffer.from([0]),
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
if (isP2WSH && isSegwit && input.witnessScript) {
|
|
955
|
+
if (!input.partialSig || input.partialSig.length === 0) {
|
|
956
|
+
throw new Error(`No signatures for P2WSH input #${inputIndex}`);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
const isP2WDA = P2WDADetector.isP2WDAWitnessScript(input.witnessScript);
|
|
960
|
+
if (isP2WDA) {
|
|
961
|
+
return this.finalizeSecondaryP2WDA(inputIndex, input);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// Check if this is a CSV input
|
|
965
|
+
const isCSVInput = this.csvInputIndices.has(inputIndex);
|
|
966
|
+
if (isCSVInput) {
|
|
967
|
+
// For CSV P2WSH, the witness stack should be: [signature, witnessScript]
|
|
968
|
+
const witnessStack = [input.partialSig[0].signature, input.witnessScript];
|
|
969
|
+
return {
|
|
970
|
+
finalScriptSig: undefined,
|
|
971
|
+
finalScriptWitness:
|
|
972
|
+
TransactionBuilder.witnessStackToScriptWitness(witnessStack),
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// For non-CSV P2WSH, use default finalization
|
|
977
|
+
}
|
|
978
|
+
|
|
861
979
|
return getFinalScripts(
|
|
862
980
|
inputIndex,
|
|
863
981
|
input,
|
|
@@ -870,6 +988,35 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
870
988
|
);
|
|
871
989
|
};
|
|
872
990
|
|
|
991
|
+
/**
|
|
992
|
+
* Finalize secondary P2WDA inputs with empty data
|
|
993
|
+
*/
|
|
994
|
+
protected finalizeSecondaryP2WDA(
|
|
995
|
+
inputIndex: number,
|
|
996
|
+
input: PsbtInput,
|
|
997
|
+
): {
|
|
998
|
+
finalScriptWitness: Buffer | undefined;
|
|
999
|
+
finalScriptSig: Buffer | undefined;
|
|
1000
|
+
} {
|
|
1001
|
+
if (!input.partialSig || input.partialSig.length === 0) {
|
|
1002
|
+
throw new Error(`No signature for P2WDA input #${inputIndex}`);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
if (!input.witnessScript) {
|
|
1006
|
+
throw new Error(`No witness script for P2WDA input #${inputIndex}`);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
const witnessStack = P2WDADetector.createSimpleP2WDAWitness(
|
|
1010
|
+
input.partialSig[0].signature,
|
|
1011
|
+
input.witnessScript,
|
|
1012
|
+
);
|
|
1013
|
+
|
|
1014
|
+
return {
|
|
1015
|
+
finalScriptSig: undefined,
|
|
1016
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness(witnessStack),
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
|
|
873
1020
|
protected async signInputsWalletBased(transaction: Psbt): Promise<void> {
|
|
874
1021
|
const signer: UnisatSigner = this.signer as UnisatSigner;
|
|
875
1022
|
|
|
@@ -884,6 +1031,82 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
884
1031
|
this.finalized = true;
|
|
885
1032
|
}
|
|
886
1033
|
|
|
1034
|
+
protected isCSVScript(decompiled: (number | Buffer)[]): boolean {
|
|
1035
|
+
return decompiled.some((op) => op === opcodes.OP_CHECKSEQUENCEVERIFY);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
protected setCSVSequence(csvBlocks: number, currentSequence: number): number {
|
|
1039
|
+
if (this.txVersion < 2) {
|
|
1040
|
+
throw new Error('CSV requires transaction version 2 or higher');
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
if (csvBlocks > 0xffff) {
|
|
1044
|
+
throw new Error(`CSV blocks ${csvBlocks} exceeds maximum of 65,535`);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
// Layout of nSequence field (32 bits) when CSV is active (bit 31 = 0):
|
|
1048
|
+
// Bit 31: Must be 0 (CSV enable flag)
|
|
1049
|
+
// Bits 23-30: Unused by BIP68 (available for custom use)
|
|
1050
|
+
// Bit 22: Time flag (0 = blocks, 1 = time)
|
|
1051
|
+
// Bits 16-21: Unused by BIP68 (available for custom use)
|
|
1052
|
+
// Bits 0-15: CSV lock-time value
|
|
1053
|
+
|
|
1054
|
+
// Extract the time flag if it's set in csvBlocks
|
|
1055
|
+
const isTimeBased = (csvBlocks & (1 << 22)) !== 0;
|
|
1056
|
+
|
|
1057
|
+
// Start with the CSV value
|
|
1058
|
+
let sequence = csvBlocks & 0x0000ffff;
|
|
1059
|
+
|
|
1060
|
+
// Preserve the time flag if set
|
|
1061
|
+
if (isTimeBased) {
|
|
1062
|
+
sequence |= 1 << 22;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
if (currentSequence === (TransactionSequence.REPLACE_BY_FEE as number)) {
|
|
1066
|
+
// Set bit 25 as our explicit RBF flag
|
|
1067
|
+
// This is in the unused range (bits 23-30) when CSV is active
|
|
1068
|
+
sequence |= 1 << 25;
|
|
1069
|
+
|
|
1070
|
+
// We could use other unused bits for version/features
|
|
1071
|
+
// sequence |= (1 << 26); // Could indicate tx flags if we wanted
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// Final safety check: ensure bit 31 is 0 (CSV enabled)
|
|
1075
|
+
sequence = sequence & 0x7fffffff;
|
|
1076
|
+
|
|
1077
|
+
return sequence;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
protected getCSVType(csvValue: number): CSVModes {
|
|
1081
|
+
// Bit 22 determines if it's time-based (1) or block-based (0)
|
|
1082
|
+
return csvValue & (1 << 22) ? CSVModes.TIMESTAMPS : CSVModes.BLOCKS;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
private extractCSVBlocks(decompiled: (number | Buffer)[]): number {
|
|
1086
|
+
for (let i = 0; i < decompiled.length; i++) {
|
|
1087
|
+
if (decompiled[i] === opcodes.OP_CHECKSEQUENCEVERIFY && i > 0) {
|
|
1088
|
+
const csvValue = decompiled[i - 1];
|
|
1089
|
+
if (Buffer.isBuffer(csvValue)) {
|
|
1090
|
+
return script.number.decode(csvValue);
|
|
1091
|
+
} else if (typeof csvValue === 'number') {
|
|
1092
|
+
// Handle OP_N directly
|
|
1093
|
+
if (csvValue === opcodes.OP_0 || csvValue === opcodes.OP_FALSE) {
|
|
1094
|
+
return 0;
|
|
1095
|
+
} else if (csvValue === opcodes.OP_1NEGATE) {
|
|
1096
|
+
return -1;
|
|
1097
|
+
} else if (csvValue >= opcodes.OP_1 && csvValue <= opcodes.OP_16) {
|
|
1098
|
+
return csvValue - opcodes.OP_1 + 1;
|
|
1099
|
+
} else {
|
|
1100
|
+
// For other numbers, they should have been Buffers
|
|
1101
|
+
// This shouldn't happen in properly decompiled scripts
|
|
1102
|
+
throw new Error(`Unexpected raw number in script: ${csvValue}`);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
return 0;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
887
1110
|
private async attemptSignTaproot(
|
|
888
1111
|
transaction: Psbt,
|
|
889
1112
|
input: PsbtInput,
|