@btc-vision/transaction 1.1.0 → 1.1.2
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/transaction/shared/TweakedTransaction.d.ts +9 -1
- package/build/_version.d.ts +1 -1
- package/build/_version.js +1 -1
- package/build/transaction/shared/TweakedTransaction.d.ts +9 -1
- package/build/transaction/shared/TweakedTransaction.js +117 -45
- package/package.json +4 -1
- package/src/_version.ts +1 -1
- package/src/transaction/shared/TweakedTransaction.ts +160 -84
|
@@ -47,7 +47,7 @@ export declare abstract class TweakedTransaction extends Logger {
|
|
|
47
47
|
protected generateTapData(): Payment;
|
|
48
48
|
protected generateScriptAddress(): Payment;
|
|
49
49
|
protected getSignerKey(): Signer | ECPairInterface;
|
|
50
|
-
protected signInput(transaction: Psbt, input: PsbtInput, i: number, signer
|
|
50
|
+
protected signInput(transaction: Psbt, input: PsbtInput, i: number, signer: Signer | ECPairInterface): Promise<void>;
|
|
51
51
|
protected splitArray<T>(arr: T[], chunkSize: number): T[][];
|
|
52
52
|
protected signInputs(transaction: Psbt): Promise<void>;
|
|
53
53
|
protected internalPubKeyToXOnly(): Buffer;
|
|
@@ -64,4 +64,12 @@ export declare abstract class TweakedTransaction extends Logger {
|
|
|
64
64
|
finalScriptSig: Buffer | undefined;
|
|
65
65
|
finalScriptWitness: Buffer | undefined;
|
|
66
66
|
};
|
|
67
|
+
private isTaprootScriptSpend;
|
|
68
|
+
private isTaprootInput;
|
|
69
|
+
private canSignNonTaprootInput;
|
|
70
|
+
private getInputRelevantScript;
|
|
71
|
+
private pubkeyInScript;
|
|
72
|
+
private pubkeyPositionInScript;
|
|
73
|
+
private signTaprootInput;
|
|
74
|
+
private signNonTaprootInput;
|
|
67
75
|
}
|
package/build/_version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "1.1.
|
|
1
|
+
export declare const version = "1.1.2";
|
package/build/_version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '1.1.
|
|
1
|
+
export const version = '1.1.2';
|
|
@@ -47,7 +47,7 @@ export declare abstract class TweakedTransaction extends Logger {
|
|
|
47
47
|
protected generateTapData(): Payment;
|
|
48
48
|
protected generateScriptAddress(): Payment;
|
|
49
49
|
protected getSignerKey(): Signer | ECPairInterface;
|
|
50
|
-
protected signInput(transaction: Psbt, input: PsbtInput, i: number, signer
|
|
50
|
+
protected signInput(transaction: Psbt, input: PsbtInput, i: number, signer: Signer | ECPairInterface): Promise<void>;
|
|
51
51
|
protected splitArray<T>(arr: T[], chunkSize: number): T[][];
|
|
52
52
|
protected signInputs(transaction: Psbt): Promise<void>;
|
|
53
53
|
protected internalPubKeyToXOnly(): Buffer;
|
|
@@ -64,4 +64,12 @@ export declare abstract class TweakedTransaction extends Logger {
|
|
|
64
64
|
finalScriptSig: Buffer | undefined;
|
|
65
65
|
finalScriptWitness: Buffer | undefined;
|
|
66
66
|
};
|
|
67
|
+
private isTaprootScriptSpend;
|
|
68
|
+
private isTaprootInput;
|
|
69
|
+
private canSignNonTaprootInput;
|
|
70
|
+
private getInputRelevantScript;
|
|
71
|
+
private pubkeyInScript;
|
|
72
|
+
private pubkeyPositionInScript;
|
|
73
|
+
private signTaprootInput;
|
|
74
|
+
private signNonTaprootInput;
|
|
67
75
|
}
|
|
@@ -4,6 +4,7 @@ import { TweakedSigner } from '../../signer/TweakedSigner.js';
|
|
|
4
4
|
import { toXOnly } from '@btc-vision/bitcoin/src/psbt/bip371.js';
|
|
5
5
|
import { AddressTypes, AddressVerificator } from '../../keypair/AddressVerificator.js';
|
|
6
6
|
import { varuint } from '@btc-vision/bitcoin/src/bufferutils.js';
|
|
7
|
+
import * as bscript from '@btc-vision/bitcoin/src/script.js';
|
|
7
8
|
export var TransactionSequence;
|
|
8
9
|
(function (TransactionSequence) {
|
|
9
10
|
TransactionSequence[TransactionSequence["REPLACE_BY_FEE"] = 4294967293] = "REPLACE_BY_FEE";
|
|
@@ -27,10 +28,7 @@ export class TweakedTransaction extends Logger {
|
|
|
27
28
|
const inputDecoded = this.inputs[inputIndex];
|
|
28
29
|
if (isP2SH && input.partialSig && inputDecoded && inputDecoded.redeemScript) {
|
|
29
30
|
const signatures = input.partialSig.map((sig) => sig.signature);
|
|
30
|
-
const scriptSig = script.compile([
|
|
31
|
-
...signatures,
|
|
32
|
-
inputDecoded.redeemScript,
|
|
33
|
-
]);
|
|
31
|
+
const scriptSig = script.compile([...signatures, inputDecoded.redeemScript]);
|
|
34
32
|
return {
|
|
35
33
|
finalScriptSig: scriptSig,
|
|
36
34
|
finalScriptWitness: undefined,
|
|
@@ -157,56 +155,58 @@ export class TweakedTransaction extends Logger {
|
|
|
157
155
|
return this.signer;
|
|
158
156
|
}
|
|
159
157
|
async signInput(transaction, input, i, signer) {
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
158
|
+
const publicKey = signer.publicKey;
|
|
159
|
+
const isTaproot = this.isTaprootInput(input);
|
|
160
|
+
let signed = false;
|
|
161
|
+
if (isTaproot) {
|
|
162
|
+
const isScriptSpend = this.isTaprootScriptSpend(input, publicKey);
|
|
163
|
+
if (isScriptSpend) {
|
|
164
|
+
try {
|
|
165
|
+
await this.signTaprootInput(signer, transaction, i);
|
|
166
|
+
signed = true;
|
|
167
|
+
}
|
|
168
|
+
catch (e) {
|
|
169
|
+
this.error(`Failed to sign Taproot script path input ${i}: ${e}`);
|
|
170
|
+
}
|
|
171
171
|
}
|
|
172
172
|
else {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
if (
|
|
179
|
-
|
|
173
|
+
let tweakedSigner;
|
|
174
|
+
if (signer !== this.signer) {
|
|
175
|
+
tweakedSigner = this.getTweakedSigner(true, signer);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
if (!this.tweakedSigner)
|
|
179
|
+
this.tweakSigner();
|
|
180
|
+
tweakedSigner = this.tweakedSigner;
|
|
181
|
+
}
|
|
182
|
+
if (tweakedSigner) {
|
|
183
|
+
try {
|
|
184
|
+
await this.signTaprootInput(tweakedSigner, transaction, i);
|
|
185
|
+
signed = true;
|
|
180
186
|
}
|
|
181
|
-
|
|
182
|
-
|
|
187
|
+
catch (e) {
|
|
188
|
+
this.error(`Failed to sign Taproot key path input ${i}: ${e}`);
|
|
183
189
|
}
|
|
184
|
-
return;
|
|
185
190
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
try {
|
|
190
|
-
if ('signInput' in signer) {
|
|
191
|
-
return await signer.signInput(transaction, i, signHash);
|
|
192
|
-
}
|
|
193
|
-
else {
|
|
194
|
-
transaction.signInput(i, signer, signHash);
|
|
191
|
+
else {
|
|
192
|
+
this.error(`Failed to obtain tweaked signer for input ${i}.`);
|
|
193
|
+
}
|
|
195
194
|
}
|
|
196
195
|
}
|
|
197
|
-
|
|
198
|
-
if (
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
else if (this.tweakedSigner) {
|
|
203
|
-
transaction.signTaprootInput(i, this.tweakedSigner, undefined, signHash);
|
|
196
|
+
else {
|
|
197
|
+
if (this.canSignNonTaprootInput(input, publicKey)) {
|
|
198
|
+
try {
|
|
199
|
+
await this.signNonTaprootInput(signer, transaction, i);
|
|
200
|
+
signed = true;
|
|
204
201
|
}
|
|
205
|
-
|
|
206
|
-
|
|
202
|
+
catch (e) {
|
|
203
|
+
this.error(`Failed to sign non-Taproot input ${i}: ${e}`);
|
|
207
204
|
}
|
|
208
205
|
}
|
|
209
206
|
}
|
|
207
|
+
if (!signed) {
|
|
208
|
+
throw new Error(`Cannot sign input ${i} with the provided signer.`);
|
|
209
|
+
}
|
|
210
210
|
}
|
|
211
211
|
splitArray(arr, chunkSize) {
|
|
212
212
|
if (chunkSize <= 0) {
|
|
@@ -230,7 +230,7 @@ export class TweakedTransaction extends Logger {
|
|
|
230
230
|
const index = offset + j;
|
|
231
231
|
const input = batch[j];
|
|
232
232
|
try {
|
|
233
|
-
promises.push(this.signInput(transaction, input, index));
|
|
233
|
+
promises.push(this.signInput(transaction, input, index, this.signer));
|
|
234
234
|
}
|
|
235
235
|
catch (e) {
|
|
236
236
|
this.log(`Failed to sign input ${index}: ${e.stack}`);
|
|
@@ -365,7 +365,6 @@ export class TweakedTransaction extends Logger {
|
|
|
365
365
|
}
|
|
366
366
|
if (i === 0 && this.nonWitnessUtxo) {
|
|
367
367
|
input.nonWitnessUtxo = this.nonWitnessUtxo;
|
|
368
|
-
this.log(`Using non-witness utxo for input ${i}`);
|
|
369
368
|
}
|
|
370
369
|
if (utxo.scriptPubKey.address &&
|
|
371
370
|
AddressVerificator.isValidP2TRAddress(utxo.scriptPubKey.address, this.network)) {
|
|
@@ -374,4 +373,77 @@ export class TweakedTransaction extends Logger {
|
|
|
374
373
|
}
|
|
375
374
|
return input;
|
|
376
375
|
}
|
|
376
|
+
isTaprootScriptSpend(input, publicKey) {
|
|
377
|
+
if (input.tapLeafScript && input.tapLeafScript.length > 0) {
|
|
378
|
+
for (const tapLeafScript of input.tapLeafScript) {
|
|
379
|
+
if (this.pubkeyInScript(publicKey, tapLeafScript.script)) {
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
isTaprootInput(input) {
|
|
387
|
+
if (input.tapInternalKey || input.tapKeySig || input.tapScriptSig || input.tapLeafScript) {
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
if (input.witnessUtxo) {
|
|
391
|
+
const script = input.witnessUtxo.script;
|
|
392
|
+
return script.length === 34 && script[0] === opcodes.OP_1 && script[1] === 0x20;
|
|
393
|
+
}
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
canSignNonTaprootInput(input, publicKey) {
|
|
397
|
+
const script = this.getInputRelevantScript(input);
|
|
398
|
+
if (script) {
|
|
399
|
+
return this.pubkeyInScript(publicKey, script);
|
|
400
|
+
}
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
getInputRelevantScript(input) {
|
|
404
|
+
if (input.redeemScript) {
|
|
405
|
+
return input.redeemScript;
|
|
406
|
+
}
|
|
407
|
+
if (input.witnessScript) {
|
|
408
|
+
return input.witnessScript;
|
|
409
|
+
}
|
|
410
|
+
if (input.witnessUtxo) {
|
|
411
|
+
return input.witnessUtxo.script;
|
|
412
|
+
}
|
|
413
|
+
if (input.nonWitnessUtxo) {
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
pubkeyInScript(pubkey, script) {
|
|
419
|
+
return this.pubkeyPositionInScript(pubkey, script) !== -1;
|
|
420
|
+
}
|
|
421
|
+
pubkeyPositionInScript(pubkey, script) {
|
|
422
|
+
const pubkeyHash = bitCrypto.hash160(pubkey);
|
|
423
|
+
const pubkeyXOnly = toXOnly(pubkey);
|
|
424
|
+
const decompiled = bscript.decompile(script);
|
|
425
|
+
if (decompiled === null)
|
|
426
|
+
throw new Error('Unknown script error');
|
|
427
|
+
return decompiled.findIndex((element) => {
|
|
428
|
+
if (typeof element === 'number')
|
|
429
|
+
return false;
|
|
430
|
+
return (element.equals(pubkey) || element.equals(pubkeyHash) || element.equals(pubkeyXOnly));
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
async signTaprootInput(signer, transaction, i, tapLeafHash) {
|
|
434
|
+
if ('signTaprootInput' in signer) {
|
|
435
|
+
await signer.signTaprootInput(transaction, i, tapLeafHash);
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
transaction.signTaprootInput(i, signer);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
async signNonTaprootInput(signer, transaction, i) {
|
|
442
|
+
if ('signInput' in signer) {
|
|
443
|
+
await signer.signInput(transaction, i);
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
transaction.signInput(i, signer);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
377
449
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@btc-vision/transaction",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.2",
|
|
5
5
|
"author": "BlobMaster41",
|
|
6
6
|
"description": "OPNet transaction library allows you to create and sign transactions for the OPNet network.",
|
|
7
7
|
"engines": {
|
|
@@ -63,6 +63,9 @@
|
|
|
63
63
|
"browserBuild": "webpack --mode production",
|
|
64
64
|
"docs": "typedoc --out docs --exclude 'src/tests/*.ts' --tsconfig tsconfig.json --readme README.md --name OPNet --plugin typedoc-material-theme --themeColor '#cb9820' --exclude src/tests/test.ts --exclude src/index.ts src"
|
|
65
65
|
},
|
|
66
|
+
"peerDependencies": {
|
|
67
|
+
"@btc-vision/bitcoin": "^6.3.0"
|
|
68
|
+
},
|
|
66
69
|
"devDependencies": {
|
|
67
70
|
"@babel/core": "^7.26.0",
|
|
68
71
|
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
package/src/_version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '1.1.
|
|
1
|
+
export const version = '1.1.2';
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
Signer,
|
|
15
15
|
Transaction,
|
|
16
16
|
} from '@btc-vision/bitcoin';
|
|
17
|
+
|
|
17
18
|
import { TweakedSigner, TweakSettings } from '../../signer/TweakedSigner.js';
|
|
18
19
|
import { ECPairInterface } from 'ecpair';
|
|
19
20
|
import { toXOnly } from '@btc-vision/bitcoin/src/psbt/bip371.js';
|
|
@@ -22,6 +23,7 @@ import { TapLeafScript } from '../interfaces/Tap.js';
|
|
|
22
23
|
import { AddressTypes, AddressVerificator } from '../../keypair/AddressVerificator.js';
|
|
23
24
|
import { ChainId } from '../../network/ChainId.js';
|
|
24
25
|
import { varuint } from '@btc-vision/bitcoin/src/bufferutils.js';
|
|
26
|
+
import * as bscript from '@btc-vision/bitcoin/src/script.js';
|
|
25
27
|
|
|
26
28
|
export interface ITweakedTransactionData {
|
|
27
29
|
readonly signer: Signer | ECPairInterface;
|
|
@@ -360,78 +362,65 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
360
362
|
* @param {Psbt} transaction - The transaction to sign
|
|
361
363
|
* @param {PsbtInput} input - The input to sign
|
|
362
364
|
* @param {number} i - The index of the input
|
|
363
|
-
* @param {Signer}
|
|
365
|
+
* @param {Signer} signer - The signer to use
|
|
364
366
|
* @protected
|
|
365
367
|
*/
|
|
366
368
|
protected async signInput(
|
|
367
369
|
transaction: Psbt,
|
|
368
370
|
input: PsbtInput,
|
|
369
371
|
i: number,
|
|
370
|
-
signer
|
|
372
|
+
signer: Signer | ECPairInterface,
|
|
371
373
|
): Promise<void> {
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
? [TweakedTransaction.calculateSignHash(this.sighashTypes)]
|
|
375
|
-
: undefined;
|
|
374
|
+
const publicKey = signer.publicKey;
|
|
375
|
+
const isTaproot = this.isTaprootInput(input);
|
|
376
376
|
|
|
377
|
-
|
|
377
|
+
let signed = false;
|
|
378
378
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
if (!this.tweakedSigner) this.tweakSigner();
|
|
379
|
+
if (isTaproot) {
|
|
380
|
+
const isScriptSpend = this.isTaprootScriptSpend(input, publicKey);
|
|
382
381
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
382
|
+
if (isScriptSpend) {
|
|
383
|
+
try {
|
|
384
|
+
await this.signTaprootInput(signer, transaction, i);
|
|
385
|
+
signed = true;
|
|
386
|
+
} catch (e) {
|
|
387
|
+
this.error(`Failed to sign Taproot script path input ${i}: ${e}`);
|
|
388
|
+
}
|
|
386
389
|
} else {
|
|
387
|
-
tweakedSigner
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
390
|
+
let tweakedSigner: ECPairInterface | undefined;
|
|
391
|
+
if (signer !== this.signer) {
|
|
392
|
+
tweakedSigner = this.getTweakedSigner(true, signer);
|
|
393
|
+
} else {
|
|
394
|
+
if (!this.tweakedSigner) this.tweakSigner();
|
|
395
|
+
tweakedSigner = this.tweakedSigner;
|
|
396
|
+
}
|
|
392
397
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
signHash,
|
|
400
|
-
) as Promise<void>);
|
|
401
|
-
} else {
|
|
402
|
-
transaction.signTaprootInput(i, tweakedSigner, undefined, signHash);
|
|
398
|
+
if (tweakedSigner) {
|
|
399
|
+
try {
|
|
400
|
+
await this.signTaprootInput(tweakedSigner, transaction, i);
|
|
401
|
+
signed = true;
|
|
402
|
+
} catch (e) {
|
|
403
|
+
this.error(`Failed to sign Taproot key path input ${i}: ${e}`);
|
|
403
404
|
}
|
|
404
|
-
|
|
405
|
-
return;
|
|
406
|
-
} catch {}
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
try {
|
|
411
|
-
if ('signInput' in signer) {
|
|
412
|
-
// @ts-expect-error - we know it's a signer
|
|
413
|
-
return await (signer.signInput(transaction, i, signHash) as Promise<void>);
|
|
414
|
-
} else {
|
|
415
|
-
transaction.signInput(i, signer, signHash);
|
|
416
|
-
}
|
|
417
|
-
} catch (e) {
|
|
418
|
-
if (!testedTap) {
|
|
419
|
-
// and we try again taproot...
|
|
420
|
-
|
|
421
|
-
if ('signTaprootInput' in signer) {
|
|
422
|
-
// @ts-expect-error - we know it's a taproot signer
|
|
423
|
-
return await (signer.signTaprootInput(
|
|
424
|
-
transaction,
|
|
425
|
-
i,
|
|
426
|
-
signHash,
|
|
427
|
-
) as Promise<void>);
|
|
428
|
-
} else if (this.tweakedSigner) {
|
|
429
|
-
transaction.signTaprootInput(i, this.tweakedSigner, undefined, signHash);
|
|
430
405
|
} else {
|
|
431
|
-
|
|
406
|
+
this.error(`Failed to obtain tweaked signer for input ${i}.`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
} else {
|
|
410
|
+
// Non-Taproot input
|
|
411
|
+
if (this.canSignNonTaprootInput(input, publicKey)) {
|
|
412
|
+
try {
|
|
413
|
+
await this.signNonTaprootInput(signer, transaction, i);
|
|
414
|
+
signed = true;
|
|
415
|
+
} catch (e) {
|
|
416
|
+
this.error(`Failed to sign non-Taproot input ${i}: ${e}`);
|
|
432
417
|
}
|
|
433
418
|
}
|
|
434
419
|
}
|
|
420
|
+
|
|
421
|
+
if (!signed) {
|
|
422
|
+
throw new Error(`Cannot sign input ${i} with the provided signer.`);
|
|
423
|
+
}
|
|
435
424
|
}
|
|
436
425
|
|
|
437
426
|
protected splitArray<T>(arr: T[], chunkSize: number): T[][] {
|
|
@@ -440,7 +429,6 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
440
429
|
}
|
|
441
430
|
|
|
442
431
|
const result: T[][] = [];
|
|
443
|
-
|
|
444
432
|
for (let i = 0; i < arr.length; i += chunkSize) {
|
|
445
433
|
result.push(arr.slice(i, i + chunkSize));
|
|
446
434
|
}
|
|
@@ -470,7 +458,7 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
470
458
|
const input = batch[j];
|
|
471
459
|
|
|
472
460
|
try {
|
|
473
|
-
promises.push(this.signInput(transaction, input, index));
|
|
461
|
+
promises.push(this.signInput(transaction, input, index, this.signer));
|
|
474
462
|
} catch (e) {
|
|
475
463
|
this.log(`Failed to sign input ${index}: ${(e as Error).stack}`);
|
|
476
464
|
}
|
|
@@ -678,10 +666,9 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
678
666
|
|
|
679
667
|
if (i === 0 && this.nonWitnessUtxo) {
|
|
680
668
|
input.nonWitnessUtxo = this.nonWitnessUtxo;
|
|
681
|
-
this.log(`Using non-witness utxo for input ${i}`);
|
|
682
669
|
}
|
|
683
670
|
|
|
684
|
-
//
|
|
671
|
+
// Automatically detect P2TR inputs.
|
|
685
672
|
if (
|
|
686
673
|
utxo.scriptPubKey.address &&
|
|
687
674
|
AddressVerificator.isValidP2TRAddress(utxo.scriptPubKey.address, this.network)
|
|
@@ -695,11 +682,11 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
695
682
|
}
|
|
696
683
|
|
|
697
684
|
protected customFinalizerP2SH = (
|
|
698
|
-
inputIndex: number,
|
|
699
|
-
input: PsbtInput,
|
|
700
|
-
scriptA: Buffer,
|
|
701
|
-
isSegwit: boolean,
|
|
702
|
-
isP2SH: boolean,
|
|
685
|
+
inputIndex: number,
|
|
686
|
+
input: PsbtInput,
|
|
687
|
+
scriptA: Buffer,
|
|
688
|
+
isSegwit: boolean,
|
|
689
|
+
isP2SH: boolean,
|
|
703
690
|
isP2WSH: boolean,
|
|
704
691
|
): {
|
|
705
692
|
finalScriptSig: Buffer | undefined;
|
|
@@ -708,31 +695,120 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
708
695
|
const inputDecoded = this.inputs[inputIndex];
|
|
709
696
|
if (isP2SH && input.partialSig && inputDecoded && inputDecoded.redeemScript) {
|
|
710
697
|
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
|
-
]);
|
|
698
|
+
const scriptSig = script.compile([...signatures, inputDecoded.redeemScript]);
|
|
729
699
|
|
|
730
700
|
return {
|
|
731
|
-
finalScriptSig: scriptSig,
|
|
732
|
-
finalScriptWitness: undefined,
|
|
701
|
+
finalScriptSig: scriptSig,
|
|
702
|
+
finalScriptWitness: undefined,
|
|
733
703
|
};
|
|
734
704
|
}
|
|
735
705
|
|
|
736
706
|
return getFinalScripts(inputIndex, input, scriptA, isSegwit, isP2SH, isP2WSH);
|
|
737
707
|
};
|
|
708
|
+
|
|
709
|
+
private isTaprootScriptSpend(input: PsbtInput, publicKey: Buffer): boolean {
|
|
710
|
+
if (input.tapLeafScript && input.tapLeafScript.length > 0) {
|
|
711
|
+
// Check if the signer's public key is involved in any tapLeafScript
|
|
712
|
+
for (const tapLeafScript of input.tapLeafScript) {
|
|
713
|
+
if (this.pubkeyInScript(publicKey, tapLeafScript.script)) {
|
|
714
|
+
// The public key is in the script; it's a script spend
|
|
715
|
+
return true;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
return false;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// Helper method to determine if an input is Taproot
|
|
723
|
+
private isTaprootInput(input: PsbtInput): boolean {
|
|
724
|
+
if (input.tapInternalKey || input.tapKeySig || input.tapScriptSig || input.tapLeafScript) {
|
|
725
|
+
return true;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
if (input.witnessUtxo) {
|
|
729
|
+
const script = input.witnessUtxo.script;
|
|
730
|
+
// Check if the script is a P2TR output (OP_1 [32-byte key])
|
|
731
|
+
return script.length === 34 && script[0] === opcodes.OP_1 && script[1] === 0x20;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
return false;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Check if the signer can sign the non-Taproot input
|
|
738
|
+
private canSignNonTaprootInput(input: PsbtInput, publicKey: Buffer): boolean {
|
|
739
|
+
const script = this.getInputRelevantScript(input);
|
|
740
|
+
if (script) {
|
|
741
|
+
return this.pubkeyInScript(publicKey, script);
|
|
742
|
+
}
|
|
743
|
+
return false;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// Helper method to extract the relevant script from the input
|
|
747
|
+
private getInputRelevantScript(input: PsbtInput): Buffer | null {
|
|
748
|
+
if (input.redeemScript) {
|
|
749
|
+
return input.redeemScript;
|
|
750
|
+
}
|
|
751
|
+
if (input.witnessScript) {
|
|
752
|
+
return input.witnessScript;
|
|
753
|
+
}
|
|
754
|
+
if (input.witnessUtxo) {
|
|
755
|
+
return input.witnessUtxo.script;
|
|
756
|
+
}
|
|
757
|
+
if (input.nonWitnessUtxo) {
|
|
758
|
+
// Additional logic can be added to extract script from nonWitnessUtxo
|
|
759
|
+
return null;
|
|
760
|
+
}
|
|
761
|
+
return null;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// Helper method to check if a public key is in a script
|
|
765
|
+
private pubkeyInScript(pubkey: Buffer, script: Buffer): boolean {
|
|
766
|
+
return this.pubkeyPositionInScript(pubkey, script) !== -1;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
private pubkeyPositionInScript(pubkey: Buffer, script: Buffer): number {
|
|
770
|
+
const pubkeyHash = bitCrypto.hash160(pubkey);
|
|
771
|
+
const pubkeyXOnly = toXOnly(pubkey);
|
|
772
|
+
|
|
773
|
+
const decompiled = bscript.decompile(script);
|
|
774
|
+
if (decompiled === null) throw new Error('Unknown script error');
|
|
775
|
+
|
|
776
|
+
return decompiled.findIndex((element) => {
|
|
777
|
+
if (typeof element === 'number') return false;
|
|
778
|
+
return (
|
|
779
|
+
element.equals(pubkey) || element.equals(pubkeyHash) || element.equals(pubkeyXOnly)
|
|
780
|
+
);
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
private async signTaprootInput(
|
|
785
|
+
signer: Signer | ECPairInterface,
|
|
786
|
+
transaction: Psbt,
|
|
787
|
+
i: number,
|
|
788
|
+
tapLeafHash?: Buffer,
|
|
789
|
+
): Promise<void> {
|
|
790
|
+
if ('signTaprootInput' in signer) {
|
|
791
|
+
await (
|
|
792
|
+
signer.signTaprootInput as (
|
|
793
|
+
tx: Psbt,
|
|
794
|
+
i: number,
|
|
795
|
+
tapLeafHash?: Buffer,
|
|
796
|
+
) => Promise<void>
|
|
797
|
+
)(transaction, i, tapLeafHash);
|
|
798
|
+
} else {
|
|
799
|
+
transaction.signTaprootInput(i, signer); //tapLeafHash
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
private async signNonTaprootInput(
|
|
804
|
+
signer: Signer | ECPairInterface,
|
|
805
|
+
transaction: Psbt,
|
|
806
|
+
i: number,
|
|
807
|
+
): Promise<void> {
|
|
808
|
+
if ('signInput' in signer) {
|
|
809
|
+
await (signer.signInput as (tx: Psbt, i: number) => Promise<void>)(transaction, i);
|
|
810
|
+
} else {
|
|
811
|
+
transaction.signInput(i, signer);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
738
814
|
}
|