@btc-vision/transaction 1.1.16 → 1.1.17
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/index.js +1 -1
- package/browser/keypair/Address.d.ts +2 -0
- package/browser/signer/SignerUtils.d.ts +6 -0
- package/browser/transaction/shared/TweakedTransaction.d.ts +5 -6
- package/browser/utxo/interfaces/IUTXO.d.ts +1 -0
- package/build/_version.d.ts +1 -1
- package/build/_version.js +1 -1
- package/build/keypair/Address.d.ts +2 -0
- package/build/keypair/Address.js +9 -0
- package/build/signer/SignerUtils.d.ts +6 -0
- package/build/signer/SignerUtils.js +56 -0
- package/build/transaction/browser/extensions/UnisatSigner.js +5 -32
- package/build/transaction/browser/extensions/XverseSigner.js +5 -48
- package/build/transaction/builders/FundingTransaction.js +6 -1
- package/build/transaction/builders/TransactionBuilder.js +3 -1
- package/build/transaction/shared/TweakedTransaction.d.ts +5 -6
- package/build/transaction/shared/TweakedTransaction.js +121 -91
- package/build/utils/BitcoinUtils.js +4 -4
- package/build/utxo/OPNetLimitedProvider.js +1 -0
- package/build/utxo/interfaces/IUTXO.d.ts +1 -0
- package/package.json +2 -5
- package/src/_version.ts +1 -1
- package/src/keypair/Address.ts +15 -0
- package/src/signer/SignerUtils.ts +78 -0
- package/src/transaction/TransactionFactory.ts +0 -253
- package/src/transaction/browser/extensions/UnisatSigner.ts +4 -40
- package/src/transaction/browser/extensions/XverseSigner.ts +9 -68
- package/src/transaction/builders/FundingTransaction.ts +7 -2
- package/src/transaction/builders/TransactionBuilder.ts +3 -1
- package/src/transaction/shared/TweakedTransaction.ts +224 -77
- package/src/utils/BitcoinUtils.ts +4 -4
- package/src/utxo/OPNetLimitedProvider.ts +1 -0
- package/src/utxo/interfaces/IUTXO.ts +2 -0
|
@@ -9,11 +9,13 @@ export declare class Address extends Uint8Array {
|
|
|
9
9
|
static wrap(bytes: ArrayLike<number>): Address;
|
|
10
10
|
toHex(): string;
|
|
11
11
|
toBuffer(): Buffer;
|
|
12
|
+
originalPublicKeyBuffer(): Buffer;
|
|
12
13
|
equals(a: Address): boolean;
|
|
13
14
|
lessThan(a: Address): boolean;
|
|
14
15
|
greaterThan(a: Address): boolean;
|
|
15
16
|
set(publicKey: ArrayLike<number>): void;
|
|
16
17
|
isValid(network: Network): boolean;
|
|
18
|
+
p2pk(): string;
|
|
17
19
|
p2wpkh(network: Network): string;
|
|
18
20
|
p2pkh(network: Network): string;
|
|
19
21
|
p2shp2wpkh(network: Network): string;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { PsbtInput } from '@btc-vision/bitcoin';
|
|
2
|
+
export declare function isTaprootInput(input: PsbtInput): boolean;
|
|
3
|
+
export declare function getInputRelevantScript(input: PsbtInput): Buffer | null;
|
|
4
|
+
export declare function canSignNonTaprootInput(input: PsbtInput, publicKey: Buffer): boolean;
|
|
5
|
+
export declare function pubkeyPositionInScript(pubkey: Buffer, script: Buffer): number;
|
|
6
|
+
export declare function pubkeyInScript(pubkey: Buffer, script: Buffer): boolean;
|
|
@@ -48,7 +48,7 @@ export declare abstract class TweakedTransaction extends Logger {
|
|
|
48
48
|
protected generateTapData(): Payment;
|
|
49
49
|
protected generateScriptAddress(): Payment;
|
|
50
50
|
protected getSignerKey(): Signer | ECPairInterface;
|
|
51
|
-
protected signInput(transaction: Psbt, input: PsbtInput, i: number, signer: Signer | ECPairInterface, reverse?: boolean): Promise<void>;
|
|
51
|
+
protected signInput(transaction: Psbt, input: PsbtInput, i: number, signer: Signer | ECPairInterface, reverse?: boolean, errored?: boolean): Promise<void>;
|
|
52
52
|
protected splitArray<T>(arr: T[], chunkSize: number): T[][];
|
|
53
53
|
protected signInputs(transaction: Psbt): Promise<void>;
|
|
54
54
|
protected internalPubKeyToXOnly(): Buffer;
|
|
@@ -60,6 +60,10 @@ export declare abstract class TweakedTransaction extends Logger {
|
|
|
60
60
|
redeemScript: Buffer;
|
|
61
61
|
outputScript: Buffer;
|
|
62
62
|
} | undefined;
|
|
63
|
+
protected generateP2SHP2PKHRedeemScript(inputAddr: string): {
|
|
64
|
+
redeemScript: Buffer;
|
|
65
|
+
outputScript: Buffer;
|
|
66
|
+
} | undefined;
|
|
63
67
|
protected generatePsbtInputExtended(utxo: UTXO, i: number): PsbtInputExtended;
|
|
64
68
|
protected customFinalizerP2SH: (inputIndex: number, input: PsbtInput, scriptA: Buffer, isSegwit: boolean, isP2SH: boolean, isP2WSH: boolean) => {
|
|
65
69
|
finalScriptSig: Buffer | undefined;
|
|
@@ -68,11 +72,6 @@ export declare abstract class TweakedTransaction extends Logger {
|
|
|
68
72
|
protected signInputsWalletBased(transaction: Psbt): Promise<void>;
|
|
69
73
|
private attemptSignTaproot;
|
|
70
74
|
private isTaprootScriptSpend;
|
|
71
|
-
private isTaprootInput;
|
|
72
|
-
private canSignNonTaprootInput;
|
|
73
|
-
private getInputRelevantScript;
|
|
74
|
-
private pubkeyInScript;
|
|
75
|
-
private pubkeyPositionInScript;
|
|
76
75
|
private signTaprootInput;
|
|
77
76
|
private signNonTaprootInput;
|
|
78
77
|
}
|
package/build/_version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "1.1.
|
|
1
|
+
export declare const version = "1.1.17";
|
package/build/_version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '1.1.
|
|
1
|
+
export const version = '1.1.17';
|
|
@@ -9,11 +9,13 @@ export declare class Address extends Uint8Array {
|
|
|
9
9
|
static wrap(bytes: ArrayLike<number>): Address;
|
|
10
10
|
toHex(): string;
|
|
11
11
|
toBuffer(): Buffer;
|
|
12
|
+
originalPublicKeyBuffer(): Buffer;
|
|
12
13
|
equals(a: Address): boolean;
|
|
13
14
|
lessThan(a: Address): boolean;
|
|
14
15
|
greaterThan(a: Address): boolean;
|
|
15
16
|
set(publicKey: ArrayLike<number>): void;
|
|
16
17
|
isValid(network: Network): boolean;
|
|
18
|
+
p2pk(): string;
|
|
17
19
|
p2wpkh(network: Network): string;
|
|
18
20
|
p2pkh(network: Network): string;
|
|
19
21
|
p2shp2wpkh(network: Network): string;
|
package/build/keypair/Address.js
CHANGED
|
@@ -63,6 +63,12 @@ export class Address extends Uint8Array {
|
|
|
63
63
|
toBuffer() {
|
|
64
64
|
return Buffer.from(this);
|
|
65
65
|
}
|
|
66
|
+
originalPublicKeyBuffer() {
|
|
67
|
+
if (!__classPrivateFieldGet(this, _Address_originalPublicKey, "f")) {
|
|
68
|
+
throw new Error('Public key not set');
|
|
69
|
+
}
|
|
70
|
+
return Buffer.from(__classPrivateFieldGet(this, _Address_originalPublicKey, "f"));
|
|
71
|
+
}
|
|
66
72
|
equals(a) {
|
|
67
73
|
const b = this;
|
|
68
74
|
if (a.length !== b.length) {
|
|
@@ -123,6 +129,9 @@ export class Address extends Uint8Array {
|
|
|
123
129
|
isValid(network) {
|
|
124
130
|
return AddressVerificator.isValidPublicKey(Buffer.from(this).toString('hex'), network);
|
|
125
131
|
}
|
|
132
|
+
p2pk() {
|
|
133
|
+
return this.toHex();
|
|
134
|
+
}
|
|
126
135
|
p2wpkh(network) {
|
|
127
136
|
return EcKeyPair.getP2WPKHAddress(this.keyPair, network);
|
|
128
137
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { PsbtInput } from '@btc-vision/bitcoin';
|
|
2
|
+
export declare function isTaprootInput(input: PsbtInput): boolean;
|
|
3
|
+
export declare function getInputRelevantScript(input: PsbtInput): Buffer | null;
|
|
4
|
+
export declare function canSignNonTaprootInput(input: PsbtInput, publicKey: Buffer): boolean;
|
|
5
|
+
export declare function pubkeyPositionInScript(pubkey: Buffer, script: Buffer): number;
|
|
6
|
+
export declare function pubkeyInScript(pubkey: Buffer, script: Buffer): boolean;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { crypto as bitCrypto } from '@btc-vision/bitcoin';
|
|
2
|
+
import { isP2TR } from '@btc-vision/bitcoin/src/psbt/psbtutils.js';
|
|
3
|
+
import { toXOnly } from '@btc-vision/bitcoin/src/psbt/bip371.js';
|
|
4
|
+
import * as bscript from '@btc-vision/bitcoin/src/script.js';
|
|
5
|
+
export function isTaprootInput(input) {
|
|
6
|
+
return (input &&
|
|
7
|
+
!!(input.tapInternalKey ||
|
|
8
|
+
input.tapMerkleRoot ||
|
|
9
|
+
(input.tapLeafScript && input.tapLeafScript.length) ||
|
|
10
|
+
(input.tapBip32Derivation && input.tapBip32Derivation.length) ||
|
|
11
|
+
(input.witnessUtxo && isP2TR(input.witnessUtxo.script))));
|
|
12
|
+
}
|
|
13
|
+
export function getInputRelevantScript(input) {
|
|
14
|
+
if (input.redeemScript) {
|
|
15
|
+
return input.redeemScript;
|
|
16
|
+
}
|
|
17
|
+
if (input.witnessScript) {
|
|
18
|
+
return input.witnessScript;
|
|
19
|
+
}
|
|
20
|
+
if (input.witnessUtxo) {
|
|
21
|
+
return input.witnessUtxo.script;
|
|
22
|
+
}
|
|
23
|
+
if (input.nonWitnessUtxo) {
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
export function canSignNonTaprootInput(input, publicKey) {
|
|
28
|
+
if ((input.nonWitnessUtxo &&
|
|
29
|
+
!input.redeemScript &&
|
|
30
|
+
!input.witnessScript &&
|
|
31
|
+
!input.witnessUtxo) ||
|
|
32
|
+
input.redeemScript) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
const script = getInputRelevantScript(input);
|
|
36
|
+
if (script) {
|
|
37
|
+
return pubkeyInScript(publicKey, script);
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
export function pubkeyPositionInScript(pubkey, script) {
|
|
42
|
+
const pubkeyHash = bitCrypto.hash160(pubkey);
|
|
43
|
+
const pubkeyXOnly = toXOnly(pubkey);
|
|
44
|
+
const decompiled = bscript.decompile(script);
|
|
45
|
+
if (decompiled === null)
|
|
46
|
+
throw new Error('Unknown script error');
|
|
47
|
+
const a = decompiled.findIndex((element) => {
|
|
48
|
+
if (typeof element === 'number')
|
|
49
|
+
return false;
|
|
50
|
+
return element.equals(pubkey) || element.equals(pubkeyHash) || element.equals(pubkeyXOnly);
|
|
51
|
+
});
|
|
52
|
+
return a;
|
|
53
|
+
}
|
|
54
|
+
export function pubkeyInScript(pubkey, script) {
|
|
55
|
+
return pubkeyPositionInScript(pubkey, script) !== -1;
|
|
56
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { crypto as bitCrypto, networks,
|
|
1
|
+
import { crypto as bitCrypto, networks, Psbt, script as bitScript, } from '@btc-vision/bitcoin';
|
|
2
2
|
import { EcKeyPair } from '../../../keypair/EcKeyPair.js';
|
|
3
3
|
import { CustomKeypair } from '../BrowserSignerBase.js';
|
|
4
4
|
import { UnisatNetwork } from '../types/Unisat.js';
|
|
5
5
|
import { toXOnly } from '@btc-vision/bitcoin/src/psbt/bip371.js';
|
|
6
|
+
import { canSignNonTaprootInput, isTaprootInput } from '../../../signer/SignerUtils.js';
|
|
6
7
|
export class UnisatSigner extends CustomKeypair {
|
|
7
8
|
constructor() {
|
|
8
9
|
super();
|
|
@@ -147,12 +148,9 @@ export class UnisatSigner extends CustomKeypair {
|
|
|
147
148
|
}
|
|
148
149
|
}
|
|
149
150
|
}
|
|
150
|
-
else {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
needsToSign = true;
|
|
154
|
-
viaTaproot = false;
|
|
155
|
-
}
|
|
151
|
+
else if (canSignNonTaprootInput(input, this.publicKey)) {
|
|
152
|
+
needsToSign = true;
|
|
153
|
+
viaTaproot = false;
|
|
156
154
|
}
|
|
157
155
|
if (needsToSign) {
|
|
158
156
|
return {
|
|
@@ -248,31 +246,6 @@ export class UnisatSigner extends CustomKeypair {
|
|
|
248
246
|
return nonDuplicate;
|
|
249
247
|
}
|
|
250
248
|
}
|
|
251
|
-
function isTaprootInput(input) {
|
|
252
|
-
if (input.tapInternalKey || input.tapKeySig || input.tapScriptSig || input.tapLeafScript) {
|
|
253
|
-
return true;
|
|
254
|
-
}
|
|
255
|
-
if (input.witnessUtxo) {
|
|
256
|
-
const script = input.witnessUtxo.script;
|
|
257
|
-
return script.length === 34 && script[0] === opcodes.OP_1 && script[1] === 0x20;
|
|
258
|
-
}
|
|
259
|
-
return false;
|
|
260
|
-
}
|
|
261
|
-
function getInputRelevantScript(input) {
|
|
262
|
-
if (input.redeemScript) {
|
|
263
|
-
return input.redeemScript;
|
|
264
|
-
}
|
|
265
|
-
if (input.witnessScript) {
|
|
266
|
-
return input.witnessScript;
|
|
267
|
-
}
|
|
268
|
-
if (input.witnessUtxo) {
|
|
269
|
-
return input.witnessUtxo.script;
|
|
270
|
-
}
|
|
271
|
-
if (input.nonWitnessUtxo) {
|
|
272
|
-
return null;
|
|
273
|
-
}
|
|
274
|
-
return null;
|
|
275
|
-
}
|
|
276
249
|
function pubkeyInScript(pubkey, script) {
|
|
277
250
|
return pubkeyPositionInScript(pubkey, script) !== -1;
|
|
278
251
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { networks, Psbt } from '@btc-vision/bitcoin';
|
|
2
2
|
import { toXOnly } from '@btc-vision/bitcoin/src/psbt/bip371.js';
|
|
3
3
|
import { EcKeyPair } from '../../../keypair/EcKeyPair.js';
|
|
4
4
|
import { CustomKeypair } from '../BrowserSignerBase.js';
|
|
5
|
+
import { canSignNonTaprootInput, isTaprootInput, pubkeyInScript, } from '../../../signer/SignerUtils.js';
|
|
5
6
|
export class XverseSigner extends CustomKeypair {
|
|
6
7
|
constructor() {
|
|
7
8
|
super();
|
|
@@ -145,12 +146,9 @@ export class XverseSigner extends CustomKeypair {
|
|
|
145
146
|
}
|
|
146
147
|
}
|
|
147
148
|
}
|
|
148
|
-
else {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
needsToSign = true;
|
|
152
|
-
viaTaproot = false;
|
|
153
|
-
}
|
|
149
|
+
else if (canSignNonTaprootInput(input, this.publicKey)) {
|
|
150
|
+
needsToSign = true;
|
|
151
|
+
viaTaproot = false;
|
|
154
152
|
}
|
|
155
153
|
if (needsToSign) {
|
|
156
154
|
return {
|
|
@@ -256,44 +254,3 @@ export class XverseSigner extends CustomKeypair {
|
|
|
256
254
|
return nonDuplicate;
|
|
257
255
|
}
|
|
258
256
|
}
|
|
259
|
-
function isTaprootInput(input) {
|
|
260
|
-
if (input.tapInternalKey || input.tapKeySig || input.tapScriptSig || input.tapLeafScript) {
|
|
261
|
-
return true;
|
|
262
|
-
}
|
|
263
|
-
if (input.witnessUtxo) {
|
|
264
|
-
const script = input.witnessUtxo.script;
|
|
265
|
-
return script.length === 34 && script[0] === opcodes.OP_1 && script[1] === 0x20;
|
|
266
|
-
}
|
|
267
|
-
return false;
|
|
268
|
-
}
|
|
269
|
-
function getInputRelevantScript(input) {
|
|
270
|
-
if (input.redeemScript) {
|
|
271
|
-
return input.redeemScript;
|
|
272
|
-
}
|
|
273
|
-
if (input.witnessScript) {
|
|
274
|
-
return input.witnessScript;
|
|
275
|
-
}
|
|
276
|
-
if (input.witnessUtxo) {
|
|
277
|
-
return input.witnessUtxo.script;
|
|
278
|
-
}
|
|
279
|
-
if (input.nonWitnessUtxo) {
|
|
280
|
-
return null;
|
|
281
|
-
}
|
|
282
|
-
return null;
|
|
283
|
-
}
|
|
284
|
-
function pubkeyInScript(pubkey, script) {
|
|
285
|
-
return pubkeyPositionInScript(pubkey, script) !== -1;
|
|
286
|
-
}
|
|
287
|
-
function pubkeyPositionInScript(pubkey, script) {
|
|
288
|
-
const pubkeyHash = bitCrypto.hash160(pubkey);
|
|
289
|
-
const pubkeyXOnly = toXOnly(pubkey);
|
|
290
|
-
const decompiled = bitScript.decompile(script);
|
|
291
|
-
if (decompiled === null)
|
|
292
|
-
throw new Error('Unknown script error');
|
|
293
|
-
return decompiled.findIndex((element) => {
|
|
294
|
-
if (typeof element === 'number')
|
|
295
|
-
return false;
|
|
296
|
-
return (Buffer.isBuffer(element) &&
|
|
297
|
-
(element.equals(pubkey) || element.equals(pubkeyHash) || element.equals(pubkeyXOnly)));
|
|
298
|
-
});
|
|
299
|
-
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TransactionType } from '../enums/TransactionType.js';
|
|
2
|
+
import { opcodes, script } from '@btc-vision/bitcoin';
|
|
2
3
|
import { TransactionBuilder } from './TransactionBuilder.js';
|
|
3
4
|
export class FundingTransaction extends TransactionBuilder {
|
|
4
5
|
constructor(parameters) {
|
|
@@ -17,9 +18,13 @@ export class FundingTransaction extends TransactionBuilder {
|
|
|
17
18
|
this.splitInputs(this.amount);
|
|
18
19
|
}
|
|
19
20
|
else if (this.isPubKeyDestination) {
|
|
21
|
+
const p2pkScript = script.compile([
|
|
22
|
+
Buffer.from(this.to.replace('0x', ''), 'hex'),
|
|
23
|
+
opcodes.OP_CHECKSIG,
|
|
24
|
+
]);
|
|
20
25
|
this.addOutput({
|
|
21
26
|
value: Number(this.amount),
|
|
22
|
-
script:
|
|
27
|
+
script: p2pkScript,
|
|
23
28
|
});
|
|
24
29
|
}
|
|
25
30
|
else {
|
|
@@ -25,7 +25,9 @@ export class TransactionBuilder extends TweakedTransaction {
|
|
|
25
25
|
this.priorityFee = parameters.priorityFee ?? 0n;
|
|
26
26
|
this.utxos = parameters.utxos;
|
|
27
27
|
this.to = parameters.to || undefined;
|
|
28
|
-
this.isPubKeyDestination = this.to
|
|
28
|
+
this.isPubKeyDestination = this.to
|
|
29
|
+
? AddressVerificator.isValidPublicKey(this.to, this.network)
|
|
30
|
+
: false;
|
|
29
31
|
this.optionalOutputs = parameters.optionalOutputs;
|
|
30
32
|
this.from = TransactionBuilder.getFrom(parameters.from, this.signer, this.network);
|
|
31
33
|
this.totalInputAmount = this.calculateTotalUTXOAmount();
|
|
@@ -48,7 +48,7 @@ export declare abstract class TweakedTransaction extends Logger {
|
|
|
48
48
|
protected generateTapData(): Payment;
|
|
49
49
|
protected generateScriptAddress(): Payment;
|
|
50
50
|
protected getSignerKey(): Signer | ECPairInterface;
|
|
51
|
-
protected signInput(transaction: Psbt, input: PsbtInput, i: number, signer: Signer | ECPairInterface, reverse?: boolean): Promise<void>;
|
|
51
|
+
protected signInput(transaction: Psbt, input: PsbtInput, i: number, signer: Signer | ECPairInterface, reverse?: boolean, errored?: boolean): Promise<void>;
|
|
52
52
|
protected splitArray<T>(arr: T[], chunkSize: number): T[][];
|
|
53
53
|
protected signInputs(transaction: Psbt): Promise<void>;
|
|
54
54
|
protected internalPubKeyToXOnly(): Buffer;
|
|
@@ -60,6 +60,10 @@ export declare abstract class TweakedTransaction extends Logger {
|
|
|
60
60
|
redeemScript: Buffer;
|
|
61
61
|
outputScript: Buffer;
|
|
62
62
|
} | undefined;
|
|
63
|
+
protected generateP2SHP2PKHRedeemScript(inputAddr: string): {
|
|
64
|
+
redeemScript: Buffer;
|
|
65
|
+
outputScript: Buffer;
|
|
66
|
+
} | undefined;
|
|
63
67
|
protected generatePsbtInputExtended(utxo: UTXO, i: number): PsbtInputExtended;
|
|
64
68
|
protected customFinalizerP2SH: (inputIndex: number, input: PsbtInput, scriptA: Buffer, isSegwit: boolean, isP2SH: boolean, isP2WSH: boolean) => {
|
|
65
69
|
finalScriptSig: Buffer | undefined;
|
|
@@ -68,11 +72,6 @@ export declare abstract class TweakedTransaction extends Logger {
|
|
|
68
72
|
protected signInputsWalletBased(transaction: Psbt): Promise<void>;
|
|
69
73
|
private attemptSignTaproot;
|
|
70
74
|
private isTaprootScriptSpend;
|
|
71
|
-
private isTaprootInput;
|
|
72
|
-
private canSignNonTaprootInput;
|
|
73
|
-
private getInputRelevantScript;
|
|
74
|
-
private pubkeyInScript;
|
|
75
|
-
private pubkeyPositionInScript;
|
|
76
75
|
private signTaprootInput;
|
|
77
76
|
private signNonTaprootInput;
|
|
78
77
|
}
|
|
@@ -2,9 +2,9 @@ import { Logger } from '@btc-vision/logger';
|
|
|
2
2
|
import { address as bitAddress, crypto as bitCrypto, getFinalScripts, opcodes, payments, script, } from '@btc-vision/bitcoin';
|
|
3
3
|
import { TweakedSigner } from '../../signer/TweakedSigner.js';
|
|
4
4
|
import { toXOnly } from '@btc-vision/bitcoin/src/psbt/bip371.js';
|
|
5
|
-
import { AddressTypes, AddressVerificator } from '../../keypair/AddressVerificator.js';
|
|
6
5
|
import { varuint } from '@btc-vision/bitcoin/src/bufferutils.js';
|
|
7
|
-
import
|
|
6
|
+
import { canSignNonTaprootInput, isTaprootInput, pubkeyInScript, } from '../../signer/SignerUtils.js';
|
|
7
|
+
import { isP2MS, isP2PK, isP2PKH, isP2SHScript, isP2TR, isP2WPKH, isP2WSHScript, } from '@btc-vision/bitcoin/src/psbt/psbtutils.js';
|
|
8
8
|
export var TransactionSequence;
|
|
9
9
|
(function (TransactionSequence) {
|
|
10
10
|
TransactionSequence[TransactionSequence["REPLACE_BY_FEE"] = 4294967293] = "REPLACE_BY_FEE";
|
|
@@ -154,36 +154,42 @@ export class TweakedTransaction extends Logger {
|
|
|
154
154
|
getSignerKey() {
|
|
155
155
|
return this.signer;
|
|
156
156
|
}
|
|
157
|
-
async signInput(transaction, input, i, signer, reverse = false) {
|
|
157
|
+
async signInput(transaction, input, i, signer, reverse = false, errored = false) {
|
|
158
158
|
const publicKey = signer.publicKey;
|
|
159
|
-
let isTaproot =
|
|
159
|
+
let isTaproot = isTaprootInput(input);
|
|
160
160
|
if (reverse) {
|
|
161
161
|
isTaproot = !isTaproot;
|
|
162
162
|
}
|
|
163
163
|
let signed = false;
|
|
164
|
+
let didError = false;
|
|
164
165
|
if (isTaproot) {
|
|
165
166
|
try {
|
|
166
167
|
await this.attemptSignTaproot(transaction, input, i, signer, publicKey);
|
|
167
168
|
signed = true;
|
|
168
169
|
}
|
|
169
170
|
catch (e) {
|
|
170
|
-
this.error(`Failed to sign Taproot script path input ${i}: ${e}`);
|
|
171
|
+
this.error(`Failed to sign Taproot script path input ${i} (reverse: ${reverse}): ${e.message}`);
|
|
172
|
+
didError = true;
|
|
171
173
|
}
|
|
172
174
|
}
|
|
173
175
|
else {
|
|
174
|
-
if (!reverse ?
|
|
176
|
+
if (!reverse ? canSignNonTaprootInput(input, publicKey) : true) {
|
|
175
177
|
try {
|
|
176
178
|
await this.signNonTaprootInput(signer, transaction, i);
|
|
177
179
|
signed = true;
|
|
178
180
|
}
|
|
179
181
|
catch (e) {
|
|
180
|
-
this.error(`Failed to sign non-Taproot input ${i}: ${e}`);
|
|
182
|
+
this.error(`Failed to sign non-Taproot input ${i}: ${e.stack}`);
|
|
183
|
+
didError = true;
|
|
181
184
|
}
|
|
182
185
|
}
|
|
183
186
|
}
|
|
184
187
|
if (!signed) {
|
|
188
|
+
if (didError && errored) {
|
|
189
|
+
throw new Error(`Failed to sign input ${i} with the provided signer.`);
|
|
190
|
+
}
|
|
185
191
|
try {
|
|
186
|
-
await this.signInput(transaction, input, i, signer, true);
|
|
192
|
+
await this.signInput(transaction, input, i, signer, true, didError);
|
|
187
193
|
}
|
|
188
194
|
catch {
|
|
189
195
|
throw new Error(`Cannot sign input ${i} with the provided signer.`);
|
|
@@ -296,51 +302,127 @@ export class TweakedTransaction extends Logger {
|
|
|
296
302
|
}
|
|
297
303
|
return;
|
|
298
304
|
}
|
|
305
|
+
generateP2SHP2PKHRedeemScript(inputAddr) {
|
|
306
|
+
const pubkey = Buffer.isBuffer(this.signer.publicKey)
|
|
307
|
+
? this.signer.publicKey
|
|
308
|
+
: Buffer.from(this.signer.publicKey, 'hex');
|
|
309
|
+
const w = payments.p2wpkh({
|
|
310
|
+
pubkey: pubkey,
|
|
311
|
+
network: this.network,
|
|
312
|
+
});
|
|
313
|
+
const p = payments.p2sh({
|
|
314
|
+
redeem: w,
|
|
315
|
+
network: this.network,
|
|
316
|
+
});
|
|
317
|
+
const address = p.address;
|
|
318
|
+
const redeemScript = p.redeem?.output;
|
|
319
|
+
if (!redeemScript) {
|
|
320
|
+
throw new Error('Failed to generate P2SH-P2WPKH redeem script');
|
|
321
|
+
}
|
|
322
|
+
if (address === inputAddr && p.redeem && p.redeem.output && p.output) {
|
|
323
|
+
return {
|
|
324
|
+
redeemScript: p.redeem.output,
|
|
325
|
+
outputScript: p.output,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
299
330
|
generatePsbtInputExtended(utxo, i) {
|
|
331
|
+
const script = Buffer.from(utxo.scriptPubKey.hex, 'hex');
|
|
300
332
|
const input = {
|
|
301
333
|
hash: utxo.transactionId,
|
|
302
334
|
index: utxo.outputIndex,
|
|
303
335
|
sequence: this.sequence,
|
|
304
336
|
witnessUtxo: {
|
|
305
337
|
value: Number(utxo.value),
|
|
306
|
-
script
|
|
338
|
+
script,
|
|
307
339
|
},
|
|
308
340
|
};
|
|
309
|
-
if (
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
341
|
+
if (isP2PKH(script)) {
|
|
342
|
+
if (utxo.nonWitnessUtxo) {
|
|
343
|
+
input.nonWitnessUtxo = Buffer.isBuffer(utxo.nonWitnessUtxo)
|
|
344
|
+
? utxo.nonWitnessUtxo
|
|
345
|
+
: Buffer.from(utxo.nonWitnessUtxo, 'hex');
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
throw new Error('Missing nonWitnessUtxo for P2PKH UTXO');
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
else if (isP2WPKH(script)) {
|
|
352
|
+
}
|
|
353
|
+
else if (isP2WSHScript(script)) {
|
|
354
|
+
if (!utxo.witnessScript) {
|
|
355
|
+
throw new Error('Missing witnessScript for P2WSH UTXO');
|
|
356
|
+
}
|
|
357
|
+
input.witnessScript = Buffer.isBuffer(utxo.witnessScript)
|
|
358
|
+
? utxo.witnessScript
|
|
359
|
+
: Buffer.from(utxo.witnessScript, 'hex');
|
|
360
|
+
}
|
|
361
|
+
else if (isP2SHScript(script)) {
|
|
362
|
+
let redeemScriptBuf;
|
|
363
|
+
if (utxo.redeemScript) {
|
|
364
|
+
redeemScriptBuf = Buffer.isBuffer(utxo.redeemScript)
|
|
365
|
+
? utxo.redeemScript
|
|
366
|
+
: Buffer.from(utxo.redeemScript, 'hex');
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
if (!utxo.scriptPubKey.address) {
|
|
370
|
+
throw new Error('Missing redeemScript and no address to regenerate it for P2SH UTXO');
|
|
371
|
+
}
|
|
372
|
+
const legacyScripts = this.generateP2SHP2PKHRedeemScript(utxo.scriptPubKey.address);
|
|
373
|
+
if (!legacyScripts) {
|
|
374
|
+
throw new Error('Missing redeemScript for P2SH UTXO and unable to regenerate');
|
|
319
375
|
}
|
|
376
|
+
redeemScriptBuf = legacyScripts.redeemScript;
|
|
320
377
|
}
|
|
321
|
-
|
|
322
|
-
|
|
378
|
+
input.redeemScript = redeemScriptBuf;
|
|
379
|
+
const payment = payments.p2sh({ redeem: { output: input.redeemScript } });
|
|
380
|
+
if (!payment.redeem) {
|
|
381
|
+
throw new Error('Failed to extract redeem script from P2SH UTXO');
|
|
382
|
+
}
|
|
383
|
+
const redeemOutput = payment.redeem.output;
|
|
384
|
+
if (!redeemOutput) {
|
|
385
|
+
throw new Error('Failed to extract redeem output from P2SH UTXO');
|
|
386
|
+
}
|
|
387
|
+
if (utxo.nonWitnessUtxo) {
|
|
388
|
+
input.nonWitnessUtxo = Buffer.isBuffer(utxo.nonWitnessUtxo)
|
|
389
|
+
? utxo.nonWitnessUtxo
|
|
390
|
+
: Buffer.from(utxo.nonWitnessUtxo, 'hex');
|
|
391
|
+
}
|
|
392
|
+
if (isP2WPKH(redeemOutput)) {
|
|
393
|
+
delete input.nonWitnessUtxo;
|
|
394
|
+
}
|
|
395
|
+
else if (isP2WSHScript(redeemOutput)) {
|
|
396
|
+
delete input.nonWitnessUtxo;
|
|
397
|
+
if (!input.witnessScript) {
|
|
398
|
+
throw new Error('Missing witnessScript for P2SH-P2WSH UTXO');
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
delete input.witnessUtxo;
|
|
323
403
|
}
|
|
324
404
|
}
|
|
325
|
-
if (
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
405
|
+
else if (isP2TR(script)) {
|
|
406
|
+
if (this.sighashTypes) {
|
|
407
|
+
const inputSign = TweakedTransaction.calculateSignHash(this.sighashTypes);
|
|
408
|
+
if (inputSign)
|
|
409
|
+
input.sighashType = inputSign;
|
|
410
|
+
}
|
|
411
|
+
this.tweakSigner();
|
|
412
|
+
input.tapInternalKey = this.internalPubKeyToXOnly();
|
|
413
|
+
}
|
|
414
|
+
else if (isP2PK(script) || isP2MS(script)) {
|
|
415
|
+
if (utxo.nonWitnessUtxo) {
|
|
416
|
+
input.nonWitnessUtxo = Buffer.isBuffer(utxo.nonWitnessUtxo)
|
|
417
|
+
? utxo.nonWitnessUtxo
|
|
418
|
+
: Buffer.from(utxo.nonWitnessUtxo, 'hex');
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
throw new Error('Missing nonWitnessUtxo for P2PK or P2MS UTXO');
|
|
338
422
|
}
|
|
339
423
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
if (inputSign)
|
|
343
|
-
input.sighashType = inputSign;
|
|
424
|
+
else {
|
|
425
|
+
this.error(`Unknown or unsupported script type for output: ${utxo.scriptPubKey.hex}`);
|
|
344
426
|
}
|
|
345
427
|
if (this.tapLeafScript) {
|
|
346
428
|
input.tapLeafScript = [this.tapLeafScript];
|
|
@@ -348,11 +430,6 @@ export class TweakedTransaction extends Logger {
|
|
|
348
430
|
if (i === 0 && this.nonWitnessUtxo) {
|
|
349
431
|
input.nonWitnessUtxo = this.nonWitnessUtxo;
|
|
350
432
|
}
|
|
351
|
-
if (utxo.scriptPubKey.address &&
|
|
352
|
-
AddressVerificator.isValidP2TRAddress(utxo.scriptPubKey.address, this.network)) {
|
|
353
|
-
this.tweakSigner();
|
|
354
|
-
input.tapInternalKey = this.internalPubKeyToXOnly();
|
|
355
|
-
}
|
|
356
433
|
return input;
|
|
357
434
|
}
|
|
358
435
|
async signInputsWalletBased(transaction) {
|
|
@@ -389,60 +466,13 @@ export class TweakedTransaction extends Logger {
|
|
|
389
466
|
isTaprootScriptSpend(input, publicKey) {
|
|
390
467
|
if (input.tapLeafScript && input.tapLeafScript.length > 0) {
|
|
391
468
|
for (const tapLeafScript of input.tapLeafScript) {
|
|
392
|
-
if (
|
|
469
|
+
if (pubkeyInScript(publicKey, tapLeafScript.script)) {
|
|
393
470
|
return true;
|
|
394
471
|
}
|
|
395
472
|
}
|
|
396
473
|
}
|
|
397
474
|
return false;
|
|
398
475
|
}
|
|
399
|
-
isTaprootInput(input) {
|
|
400
|
-
if (input.tapInternalKey || input.tapKeySig || input.tapScriptSig || input.tapLeafScript) {
|
|
401
|
-
return true;
|
|
402
|
-
}
|
|
403
|
-
if (input.witnessUtxo) {
|
|
404
|
-
const script = input.witnessUtxo.script;
|
|
405
|
-
return script.length === 34 && script[0] === opcodes.OP_1 && script[1] === 0x20;
|
|
406
|
-
}
|
|
407
|
-
return false;
|
|
408
|
-
}
|
|
409
|
-
canSignNonTaprootInput(input, publicKey) {
|
|
410
|
-
const script = this.getInputRelevantScript(input);
|
|
411
|
-
if (script) {
|
|
412
|
-
return this.pubkeyInScript(publicKey, script);
|
|
413
|
-
}
|
|
414
|
-
return false;
|
|
415
|
-
}
|
|
416
|
-
getInputRelevantScript(input) {
|
|
417
|
-
if (input.redeemScript) {
|
|
418
|
-
return input.redeemScript;
|
|
419
|
-
}
|
|
420
|
-
if (input.witnessScript) {
|
|
421
|
-
return input.witnessScript;
|
|
422
|
-
}
|
|
423
|
-
if (input.witnessUtxo) {
|
|
424
|
-
return input.witnessUtxo.script;
|
|
425
|
-
}
|
|
426
|
-
if (input.nonWitnessUtxo) {
|
|
427
|
-
return null;
|
|
428
|
-
}
|
|
429
|
-
return null;
|
|
430
|
-
}
|
|
431
|
-
pubkeyInScript(pubkey, script) {
|
|
432
|
-
return this.pubkeyPositionInScript(pubkey, script) !== -1;
|
|
433
|
-
}
|
|
434
|
-
pubkeyPositionInScript(pubkey, script) {
|
|
435
|
-
const pubkeyHash = bitCrypto.hash160(pubkey);
|
|
436
|
-
const pubkeyXOnly = toXOnly(pubkey);
|
|
437
|
-
const decompiled = bscript.decompile(script);
|
|
438
|
-
if (decompiled === null)
|
|
439
|
-
throw new Error('Unknown script error');
|
|
440
|
-
return decompiled.findIndex((element) => {
|
|
441
|
-
if (typeof element === 'number')
|
|
442
|
-
return false;
|
|
443
|
-
return (element.equals(pubkey) || element.equals(pubkeyHash) || element.equals(pubkeyXOnly));
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
476
|
async signTaprootInput(signer, transaction, i, tapLeafHash) {
|
|
447
477
|
if ('signTaprootInput' in signer) {
|
|
448
478
|
try {
|