@btc-vision/transaction 1.0.117 → 1.0.119

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 (39) hide show
  1. package/browser/_version.d.ts +1 -1
  2. package/browser/deterministic/Map.d.ts +1 -0
  3. package/browser/event/NetEvent.d.ts +3 -3
  4. package/browser/generators/builders/LegacyCalldataGenerator.d.ts +8 -0
  5. package/browser/index.js +1 -1
  6. package/browser/keypair/Address.d.ts +1 -0
  7. package/browser/keypair/AddressVerificator.d.ts +2 -3
  8. package/browser/opnet.d.ts +1 -0
  9. package/browser/transaction/shared/TweakedTransaction.d.ts +9 -0
  10. package/browser/utxo/interfaces/IUTXO.d.ts +3 -0
  11. package/build/_version.d.ts +1 -1
  12. package/build/_version.js +1 -1
  13. package/build/deterministic/Map.d.ts +1 -0
  14. package/build/deterministic/Map.js +7 -0
  15. package/build/event/NetEvent.d.ts +3 -3
  16. package/build/event/NetEvent.js +3 -3
  17. package/build/generators/builders/LegacyCalldataGenerator.d.ts +8 -0
  18. package/build/generators/builders/LegacyCalldataGenerator.js +75 -0
  19. package/build/keypair/Address.d.ts +1 -0
  20. package/build/keypair/Address.js +3 -0
  21. package/build/keypair/AddressVerificator.d.ts +2 -3
  22. package/build/keypair/AddressVerificator.js +16 -9
  23. package/build/opnet.d.ts +1 -0
  24. package/build/opnet.js +1 -0
  25. package/build/transaction/builders/TransactionBuilder.js +1 -1
  26. package/build/transaction/shared/TweakedTransaction.d.ts +9 -0
  27. package/build/transaction/shared/TweakedTransaction.js +93 -3
  28. package/build/utxo/interfaces/IUTXO.d.ts +3 -0
  29. package/package.json +1 -1
  30. package/src/_version.ts +1 -1
  31. package/src/deterministic/Map.ts +8 -0
  32. package/src/event/NetEvent.ts +2 -2
  33. package/src/generators/builders/LegacyCalldataGenerator.ts +141 -0
  34. package/src/keypair/Address.ts +8 -0
  35. package/src/keypair/AddressVerificator.ts +25 -12
  36. package/src/opnet.ts +1 -0
  37. package/src/transaction/builders/TransactionBuilder.ts +1 -1
  38. package/src/transaction/shared/TweakedTransaction.ts +157 -2
  39. package/src/utxo/interfaces/IUTXO.ts +3 -0
@@ -82,6 +82,14 @@ export class Address extends Uint8Array {
82
82
  return '0x' + Buffer.from(this).toString('hex');
83
83
  }
84
84
 
85
+ /**
86
+ * Converts the address to a buffer
87
+ * @returns {Buffer} The buffer
88
+ */
89
+ public toBuffer(): Buffer {
90
+ return Buffer.from(this);
91
+ }
92
+
85
93
  public equals(a: Address): boolean {
86
94
  const b = this.isP2TROnly ? this : (this.#tweakedBytes as Uint8Array);
87
95
  const c = a.isP2TROnly ? a : (a.#tweakedBytes as Uint8Array);
@@ -6,8 +6,6 @@ initEccLib(ecc);
6
6
 
7
7
  export enum AddressTypes {
8
8
  P2PKH = 'P2PKH',
9
- P2SH = 'P2SH',
10
- P2SH_P2WPKH = 'P2SH-P2WPKH',
11
9
  P2SH_OR_P2SH_P2WPKH = 'P2SH_OR_P2SH-P2WPKH',
12
10
  P2PK = 'P2PK',
13
11
  P2TR = 'P2TR',
@@ -118,14 +116,34 @@ export class AddressVerificator {
118
116
 
119
117
  return true;
120
118
  }
121
- } catch (error) {
122
- // If any error occurs (invalid public key, etc.), return false
119
+ } catch {
123
120
  return false;
124
121
  }
125
122
 
126
123
  return false; // Not a valid public key
127
124
  }
128
125
 
126
+ /**
127
+ * Checks if the address requires a redeem script to spend funds.
128
+ * @param {string} addy - The address to check.
129
+ * @param {Network} network - The network to validate against.
130
+ * @returns {boolean} - True if the address requires a redeem script, false otherwise.
131
+ */
132
+ public static requireRedeemScript(addy: string, network: Network): boolean {
133
+ try {
134
+ // First, try to decode as a Base58Check address (P2PKH, P2SH, or P2SH-P2WPKH)
135
+ const decodedBase58 = address.fromBase58Check(addy);
136
+
137
+ if (decodedBase58.version === network.pubKeyHash) {
138
+ return false;
139
+ }
140
+
141
+ return decodedBase58.version === network.scriptHash;
142
+ } catch {
143
+ return false;
144
+ }
145
+ }
146
+
129
147
  /**
130
148
  * Validates if a given Bitcoin address is of the specified type and network.
131
149
  * - P2PKH (Legacy address starting with '1' for mainnet or 'm/n' for testnet)
@@ -139,7 +157,7 @@ export class AddressVerificator {
139
157
  * @param network - The Bitcoin network to validate against (mainnet, testnet, etc.).
140
158
  * @returns - The type of the valid Bitcoin address, or null if invalid.
141
159
  */
142
- public static validateBitcoinAddress(addy: string, network: Network): AddressTypes | null {
160
+ public static detectAddressType(addy: string, network: Network): AddressTypes | null {
143
161
  if (AddressVerificator.isValidPublicKey(addy, network)) {
144
162
  return AddressTypes.P2PK;
145
163
  }
@@ -156,9 +174,7 @@ export class AddressVerificator {
156
174
  // P2SH: Could be P2SH (general) or P2SH-P2WPKH (wrapped SegWit)
157
175
  return AddressTypes.P2SH_OR_P2SH_P2WPKH;
158
176
  }
159
- } catch (error) {
160
- // Ignore errors from Base58 decoding, it could be a Bech32 address
161
- }
177
+ } catch {}
162
178
 
163
179
  try {
164
180
  // Try to decode as a Bech32 or Bech32m address (P2WPKH or P2TR)
@@ -174,10 +190,7 @@ export class AddressVerificator {
174
190
  return AddressTypes.P2TR;
175
191
  }
176
192
  }
177
- } catch (error) {
178
- console.log(error);
179
- // Ignore errors from Bech32/Bech32m decoding
180
- }
193
+ } catch {}
181
194
 
182
195
  return null; // Not a valid or recognized Bitcoin address type
183
196
  }
package/src/opnet.ts CHANGED
@@ -6,6 +6,7 @@ export * from './bytecode/Compressor.js';
6
6
  /** Generators */
7
7
  export * from './generators/Generator.js';
8
8
  export * from './generators/builders/CalldataGenerator.js';
9
+ export * from './generators/builders/LegacyCalldataGenerator.js';
9
10
  export * from './generators/builders/DeploymentGenerator.js';
10
11
  export * from './generators/builders/CustomGenerator.js';
11
12
  export * from './generators/builders/MultiSignGenerator.js';
@@ -37,7 +37,7 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
37
37
  //opcodes.OP_VERIFY, - verify that this is not needed.
38
38
  ]);
39
39
 
40
- public static readonly MINIMUM_DUST: bigint = 330n;
40
+ public static readonly MINIMUM_DUST: bigint = 50n;
41
41
 
42
42
  public abstract readonly type: T;
43
43
  public readonly logColor: string = '#785def';
@@ -1,11 +1,16 @@
1
1
  import { Logger } from '@btc-vision/logger';
2
2
  import {
3
+ address as bitAddress,
4
+ crypto as bitCrypto,
5
+ getFinalScripts,
3
6
  Network,
7
+ opcodes,
4
8
  Payment,
5
9
  payments,
6
10
  Psbt,
7
11
  PsbtInput,
8
12
  PsbtInputExtended,
13
+ script,
9
14
  Signer,
10
15
  Transaction,
11
16
  } from 'bitcoinjs-lib';
@@ -14,7 +19,7 @@ import { ECPairInterface } from 'ecpair';
14
19
  import { toXOnly } from 'bitcoinjs-lib/src/psbt/bip371.js';
15
20
  import { UTXO } from '../../utxo/interfaces/IUTXO.js';
16
21
  import { TapLeafScript } from '../interfaces/Tap.js';
17
- import { AddressVerificator } from '../../keypair/AddressVerificator.js';
22
+ import { AddressTypes, AddressVerificator } from '../../keypair/AddressVerificator.js';
18
23
  import { ChainId } from '../../network/ChainId.js';
19
24
  import { varuint } from 'bitcoinjs-lib/src/bufferutils.js';
20
25
 
@@ -474,6 +479,8 @@ export abstract class TweakedTransaction extends Logger {
474
479
  await Promise.all(promises);
475
480
  }
476
481
 
482
+ transaction.finalizeInput(0, this.customFinalizerP2SH);
483
+
477
484
  try {
478
485
  transaction.finalizeAllInputs();
479
486
 
@@ -536,6 +543,65 @@ export abstract class TweakedTransaction extends Logger {
536
543
  return TweakedSigner.tweakSigner(signer as unknown as ECPairInterface, settings);
537
544
  }
538
545
 
546
+ protected generateP2SHRedeemScript(customWitnessScript: Buffer): Buffer | undefined {
547
+ const p2wsh = payments.p2wsh({
548
+ redeem: { output: customWitnessScript },
549
+ network: this.network,
550
+ });
551
+
552
+ // Step 2: Wrap the P2WSH inside a P2SH (Pay-to-Script-Hash)
553
+ const p2sh = payments.p2sh({
554
+ redeem: p2wsh,
555
+ network: this.network,
556
+ });
557
+
558
+ return p2sh.output;
559
+ }
560
+
561
+ protected generateP2SHRedeemScriptLegacy(inputAddr: string):
562
+ | {
563
+ redeemScript: Buffer;
564
+ outputScript: Buffer;
565
+ }
566
+ | undefined {
567
+ const pubKeyHash = bitCrypto.hash160(this.signer.publicKey);
568
+ const redeemScript: Buffer = script.compile([
569
+ opcodes.OP_DUP,
570
+ opcodes.OP_HASH160,
571
+ pubKeyHash,
572
+ opcodes.OP_EQUALVERIFY,
573
+ opcodes.OP_CHECKSIG,
574
+ ]);
575
+
576
+ const redeemScriptHash = bitCrypto.hash160(redeemScript);
577
+ const outputScript = script.compile([
578
+ opcodes.OP_HASH160,
579
+ redeemScriptHash,
580
+ opcodes.OP_EQUAL,
581
+ ]);
582
+
583
+ const p2wsh = payments.p2wsh({
584
+ redeem: { output: redeemScript }, // Use the custom redeem script
585
+ network: this.network,
586
+ });
587
+
588
+ // Step 3: Wrap the P2WSH in a P2SH
589
+ const p2sh = payments.p2sh({
590
+ redeem: p2wsh, // The P2WSH is wrapped inside the P2SH
591
+ network: this.network,
592
+ });
593
+
594
+ const address = bitAddress.fromOutputScript(outputScript, this.network);
595
+ if (address === inputAddr && p2sh.redeem && p2sh.redeem.output) {
596
+ return {
597
+ redeemScript,
598
+ outputScript: p2sh.redeem.output,
599
+ };
600
+ }
601
+
602
+ return;
603
+ }
604
+
539
605
  /**
540
606
  * Generate the PSBT input extended
541
607
  * @param {UTXO} utxo The UTXO
@@ -547,13 +613,60 @@ export abstract class TweakedTransaction extends Logger {
547
613
  const input: PsbtInputExtended = {
548
614
  hash: utxo.transactionId,
549
615
  index: utxo.outputIndex,
616
+ sequence: this.sequence,
550
617
  witnessUtxo: {
551
618
  value: Number(utxo.value),
552
619
  script: Buffer.from(utxo.scriptPubKey.hex, 'hex'),
553
620
  },
554
- sequence: this.sequence,
555
621
  };
556
622
 
623
+ if (utxo.scriptPubKey.address) {
624
+ // auto detect for potential p2sh utxos
625
+ try {
626
+ const addressType: AddressTypes | null = AddressVerificator.detectAddressType(
627
+ utxo.scriptPubKey.address,
628
+ this.network,
629
+ );
630
+
631
+ if (addressType === AddressTypes.P2SH_OR_P2SH_P2WPKH) {
632
+ // We can automatically reconstruct the redeem script.
633
+ const redeemScript = this.generateP2SHRedeemScriptLegacy(
634
+ utxo.scriptPubKey.address,
635
+ );
636
+
637
+ if (!redeemScript) {
638
+ throw new Error('Failed to generate redeem script');
639
+ }
640
+
641
+ input.redeemScript = redeemScript.outputScript;
642
+ input.witnessScript = redeemScript.redeemScript;
643
+ }
644
+ } catch (e) {
645
+ this.error(`Failed to detect address type for ${utxo.scriptPubKey.address} - ${e}`);
646
+ }
647
+ }
648
+
649
+ // LEGACY P2SH SUPPORT
650
+ if (utxo.nonWitnessUtxo) {
651
+ input.nonWitnessUtxo = Buffer.isBuffer(utxo.nonWitnessUtxo)
652
+ ? utxo.nonWitnessUtxo
653
+ : Buffer.from(utxo.nonWitnessUtxo, 'hex');
654
+ }
655
+
656
+ // SEGWIT SUPPORT
657
+ if (utxo.redeemScript) {
658
+ input.redeemScript = Buffer.isBuffer(utxo.redeemScript)
659
+ ? utxo.redeemScript
660
+ : Buffer.from(utxo.redeemScript, 'hex');
661
+
662
+ if (utxo.witnessScript) {
663
+ input.witnessScript = Buffer.isBuffer(utxo.witnessScript)
664
+ ? utxo.witnessScript
665
+ : Buffer.from(utxo.witnessScript, 'hex');
666
+ }
667
+ }
668
+
669
+ // TAPROOT.
557
670
  if (this.sighashTypes) {
558
671
  const inputSign = TweakedTransaction.calculateSignHash(this.sighashTypes);
559
672
  if (inputSign) input.sighashType = inputSign;
@@ -580,4 +693,46 @@ export abstract class TweakedTransaction extends Logger {
580
693
 
581
694
  return input;
582
695
  }
696
+
697
+ protected customFinalizerP2SH = (
698
+ inputIndex: number, // Which input is it?
699
+ input: PsbtInput, // The PSBT input contents
700
+ scriptA: Buffer, // The "meaningful" locking script Buffer (redeemScript for P2SH etc.)
701
+ isSegwit: boolean, // Is it segwit?
702
+ isP2SH: boolean, // Is it P2SH?
703
+ isP2WSH: boolean,
704
+ ): {
705
+ finalScriptSig: Buffer | undefined;
706
+ finalScriptWitness: Buffer | undefined;
707
+ } => {
708
+ const inputDecoded = this.inputs[inputIndex];
709
+ if (isP2SH && input.partialSig && inputDecoded && inputDecoded.redeemScript) {
710
+ const signatures = input.partialSig.map((sig) => sig.signature);
711
+
712
+ /*const fakeSignature = Buffer.from([
713
+ 0x30,
714
+ 0x45, // DER prefix: 0x30 (Compound), 0x45 (length = 69 bytes)
715
+ 0x02,
716
+ 0x20, // Integer marker: 0x02 (integer), 0x20 (length = 32 bytes)
717
+ ...Buffer.alloc(32, 0x00), // 32-byte fake 'r' value (all zeros)
718
+ 0x02,
719
+ 0x21, // Integer marker: 0x02 (integer), 0x21 (length = 33 bytes)
720
+ ...Buffer.alloc(33, 0x00), // 33-byte fake 's' value (all zeros)
721
+ 0x01, // SIGHASH_ALL flag (0x01)
722
+ ]);*/
723
+
724
+ const scriptSig = script.compile([
725
+ ...signatures,
726
+ //fakeSignature,
727
+ inputDecoded.redeemScript,
728
+ ]);
729
+
730
+ return {
731
+ finalScriptSig: scriptSig, // Manually set the final scriptSig
732
+ finalScriptWitness: undefined, // Manually set the final scriptWitness
733
+ };
734
+ }
735
+
736
+ return getFinalScripts(inputIndex, input, scriptA, isSegwit, isP2SH, isP2WSH);
737
+ };
583
738
  }
@@ -5,6 +5,9 @@ export interface UTXO {
5
5
  readonly outputIndex: number;
6
6
  readonly value: bigint;
7
7
  readonly scriptPubKey: ScriptPubKey;
8
+ redeemScript?: string | Buffer;
9
+ witnessScript?: string | Buffer;
10
+ nonWitnessUtxo?: string | Buffer;
8
11
  }
9
12
 
10
13
  export interface FetchUTXOParams {