@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.
- package/browser/_version.d.ts +1 -1
- package/browser/deterministic/Map.d.ts +1 -0
- package/browser/event/NetEvent.d.ts +3 -3
- package/browser/generators/builders/LegacyCalldataGenerator.d.ts +8 -0
- package/browser/index.js +1 -1
- package/browser/keypair/Address.d.ts +1 -0
- package/browser/keypair/AddressVerificator.d.ts +2 -3
- package/browser/opnet.d.ts +1 -0
- package/browser/transaction/shared/TweakedTransaction.d.ts +9 -0
- package/browser/utxo/interfaces/IUTXO.d.ts +3 -0
- package/build/_version.d.ts +1 -1
- package/build/_version.js +1 -1
- package/build/deterministic/Map.d.ts +1 -0
- package/build/deterministic/Map.js +7 -0
- package/build/event/NetEvent.d.ts +3 -3
- package/build/event/NetEvent.js +3 -3
- package/build/generators/builders/LegacyCalldataGenerator.d.ts +8 -0
- package/build/generators/builders/LegacyCalldataGenerator.js +75 -0
- package/build/keypair/Address.d.ts +1 -0
- package/build/keypair/Address.js +3 -0
- package/build/keypair/AddressVerificator.d.ts +2 -3
- package/build/keypair/AddressVerificator.js +16 -9
- package/build/opnet.d.ts +1 -0
- package/build/opnet.js +1 -0
- package/build/transaction/builders/TransactionBuilder.js +1 -1
- package/build/transaction/shared/TweakedTransaction.d.ts +9 -0
- package/build/transaction/shared/TweakedTransaction.js +93 -3
- package/build/utxo/interfaces/IUTXO.d.ts +3 -0
- package/package.json +1 -1
- package/src/_version.ts +1 -1
- package/src/deterministic/Map.ts +8 -0
- package/src/event/NetEvent.ts +2 -2
- package/src/generators/builders/LegacyCalldataGenerator.ts +141 -0
- package/src/keypair/Address.ts +8 -0
- package/src/keypair/AddressVerificator.ts +25 -12
- package/src/opnet.ts +1 -0
- package/src/transaction/builders/TransactionBuilder.ts +1 -1
- package/src/transaction/shared/TweakedTransaction.ts +157 -2
- package/src/utxo/interfaces/IUTXO.ts +3 -0
package/src/keypair/Address.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 {
|