@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.
Files changed (75) hide show
  1. package/browser/_version.d.ts +1 -1
  2. package/browser/epoch/ChallengeSolution.d.ts +3 -3
  3. package/browser/epoch/validator/EpochValidator.d.ts +5 -6
  4. package/browser/generators/builders/P2WDAGenerator.d.ts +13 -0
  5. package/browser/index.js +1 -1
  6. package/browser/keypair/Address.d.ts +3 -2
  7. package/browser/keypair/AddressVerificator.d.ts +13 -1
  8. package/browser/keypair/Wallet.d.ts +3 -0
  9. package/browser/opnet.d.ts +4 -0
  10. package/browser/p2wda/P2WDADetector.d.ts +16 -0
  11. package/browser/transaction/TransactionFactory.d.ts +3 -1
  12. package/browser/transaction/builders/DeploymentTransaction.d.ts +3 -3
  13. package/browser/transaction/builders/InteractionTransactionP2WDA.d.ts +37 -0
  14. package/browser/transaction/builders/SharedInteractionTransaction.d.ts +4 -4
  15. package/browser/transaction/builders/TransactionBuilder.d.ts +3 -0
  16. package/browser/transaction/interfaces/ITransactionParameters.d.ts +1 -0
  17. package/browser/transaction/mineable/IP2WSHAddress.d.ts +4 -0
  18. package/browser/transaction/mineable/TimelockGenerator.d.ts +2 -5
  19. package/browser/transaction/shared/TweakedTransaction.d.ts +23 -0
  20. package/build/_version.d.ts +1 -1
  21. package/build/_version.js +1 -1
  22. package/build/epoch/ChallengeSolution.d.ts +3 -3
  23. package/build/epoch/ChallengeSolution.js +3 -3
  24. package/build/epoch/validator/EpochValidator.d.ts +5 -6
  25. package/build/epoch/validator/EpochValidator.js +11 -12
  26. package/build/generators/builders/P2WDAGenerator.d.ts +13 -0
  27. package/build/generators/builders/P2WDAGenerator.js +62 -0
  28. package/build/keypair/Address.d.ts +3 -2
  29. package/build/keypair/Address.js +28 -2
  30. package/build/keypair/AddressVerificator.d.ts +13 -1
  31. package/build/keypair/AddressVerificator.js +82 -1
  32. package/build/keypair/Wallet.d.ts +3 -0
  33. package/build/keypair/Wallet.js +4 -0
  34. package/build/opnet.d.ts +4 -0
  35. package/build/opnet.js +4 -0
  36. package/build/p2wda/P2WDADetector.d.ts +16 -0
  37. package/build/p2wda/P2WDADetector.js +97 -0
  38. package/build/transaction/TransactionFactory.d.ts +3 -1
  39. package/build/transaction/TransactionFactory.js +35 -4
  40. package/build/transaction/builders/DeploymentTransaction.d.ts +3 -3
  41. package/build/transaction/builders/DeploymentTransaction.js +1 -1
  42. package/build/transaction/builders/InteractionTransactionP2WDA.d.ts +37 -0
  43. package/build/transaction/builders/InteractionTransactionP2WDA.js +205 -0
  44. package/build/transaction/builders/SharedInteractionTransaction.d.ts +4 -4
  45. package/build/transaction/builders/SharedInteractionTransaction.js +3 -3
  46. package/build/transaction/builders/TransactionBuilder.d.ts +3 -0
  47. package/build/transaction/builders/TransactionBuilder.js +18 -3
  48. package/build/transaction/interfaces/ITransactionParameters.d.ts +1 -0
  49. package/build/transaction/mineable/IP2WSHAddress.d.ts +4 -0
  50. package/build/transaction/mineable/IP2WSHAddress.js +1 -0
  51. package/build/transaction/mineable/TimelockGenerator.d.ts +2 -5
  52. package/build/transaction/shared/TweakedTransaction.d.ts +23 -0
  53. package/build/transaction/shared/TweakedTransaction.js +154 -18
  54. package/doc/README.md +0 -0
  55. package/doc/addresses/P2OP.md +1 -0
  56. package/doc/addresses/P2WDA.md +240 -0
  57. package/package.json +2 -2
  58. package/src/_version.ts +1 -1
  59. package/src/epoch/ChallengeSolution.ts +4 -4
  60. package/src/epoch/validator/EpochValidator.ts +12 -16
  61. package/src/generators/builders/P2WDAGenerator.ts +174 -0
  62. package/src/keypair/Address.ts +58 -3
  63. package/src/keypair/AddressVerificator.ts +147 -2
  64. package/src/keypair/Wallet.ts +16 -0
  65. package/src/opnet.ts +4 -0
  66. package/src/p2wda/P2WDADetector.ts +218 -0
  67. package/src/transaction/TransactionFactory.ts +79 -5
  68. package/src/transaction/builders/DeploymentTransaction.ts +4 -3
  69. package/src/transaction/builders/InteractionTransactionP2WDA.ts +376 -0
  70. package/src/transaction/builders/SharedInteractionTransaction.ts +7 -6
  71. package/src/transaction/builders/TransactionBuilder.ts +30 -3
  72. package/src/transaction/interfaces/ITransactionParameters.ts +1 -0
  73. package/src/transaction/mineable/IP2WSHAddress.ts +4 -0
  74. package/src/transaction/mineable/TimelockGenerator.ts +2 -6
  75. 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 script = Buffer.from(utxo.scriptPubKey.hex, 'hex');
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(script)) {
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(script) || isUnknownSegwitVersion(script)) {
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(script)) {
708
- // P2WSH requires a witnessScript
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(script)) {
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
- if (!input.witnessScript) {
775
- throw new Error('Missing witnessScript for P2SH-P2WSH UTXO');
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(script)) {
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(script) || isP2MS(script)) {
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,