@btc-vision/transaction 1.1.1 → 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 +123 -18
- package/package.json +4 -1
- package/src/_version.ts +1 -1
- package/src/transaction/shared/TweakedTransaction.ts +160 -104
|
@@ -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,
|
|
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,
|
|
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,
|
|
@@ -156,24 +154,59 @@ export class TweakedTransaction extends Logger {
|
|
|
156
154
|
getSignerKey() {
|
|
157
155
|
return this.signer;
|
|
158
156
|
}
|
|
159
|
-
async signInput(transaction,
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
157
|
+
async signInput(transaction, input, i, signer) {
|
|
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
|
+
}
|
|
163
171
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
172
|
+
else {
|
|
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;
|
|
186
|
+
}
|
|
187
|
+
catch (e) {
|
|
188
|
+
this.error(`Failed to sign Taproot key path input ${i}: ${e}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
this.error(`Failed to obtain tweaked signer for input ${i}.`);
|
|
170
193
|
}
|
|
171
|
-
transaction.signTaprootInput(i, signer);
|
|
172
194
|
}
|
|
173
|
-
|
|
174
|
-
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
if (this.canSignNonTaprootInput(input, publicKey)) {
|
|
198
|
+
try {
|
|
199
|
+
await this.signNonTaprootInput(signer, transaction, i);
|
|
200
|
+
signed = true;
|
|
201
|
+
}
|
|
202
|
+
catch (e) {
|
|
203
|
+
this.error(`Failed to sign non-Taproot input ${i}: ${e}`);
|
|
204
|
+
}
|
|
175
205
|
}
|
|
176
206
|
}
|
|
207
|
+
if (!signed) {
|
|
208
|
+
throw new Error(`Cannot sign input ${i} with the provided signer.`);
|
|
209
|
+
}
|
|
177
210
|
}
|
|
178
211
|
splitArray(arr, chunkSize) {
|
|
179
212
|
if (chunkSize <= 0) {
|
|
@@ -332,7 +365,6 @@ export class TweakedTransaction extends Logger {
|
|
|
332
365
|
}
|
|
333
366
|
if (i === 0 && this.nonWitnessUtxo) {
|
|
334
367
|
input.nonWitnessUtxo = this.nonWitnessUtxo;
|
|
335
|
-
this.log(`Using non-witness utxo for input ${i}`);
|
|
336
368
|
}
|
|
337
369
|
if (utxo.scriptPubKey.address &&
|
|
338
370
|
AddressVerificator.isValidP2TRAddress(utxo.scriptPubKey.address, this.network)) {
|
|
@@ -341,4 +373,77 @@ export class TweakedTransaction extends Logger {
|
|
|
341
373
|
}
|
|
342
374
|
return input;
|
|
343
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
|
+
}
|
|
344
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;
|
|
@@ -358,100 +360,67 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
358
360
|
/**
|
|
359
361
|
* Signs an input of the transaction.
|
|
360
362
|
* @param {Psbt} transaction - The transaction to sign
|
|
361
|
-
* @param {PsbtInput}
|
|
363
|
+
* @param {PsbtInput} input - The input to sign
|
|
362
364
|
* @param {number} i - The index of the input
|
|
363
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
372
|
signer: Signer | ECPairInterface,
|
|
371
373
|
): Promise<void> {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
// @ts-expect-error - we know it's a signer
|
|
375
|
-
return await (signer.signInput(transaction, i) as Promise<void>);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
transaction.signInput(i, signer);
|
|
379
|
-
} catch {
|
|
380
|
-
try {
|
|
381
|
-
if ('signTaprootInput' in signer) {
|
|
382
|
-
// @ts-expect-error - we know it's a taproot signer
|
|
383
|
-
return await (signer.signTaprootInput(transaction, i) as Promise<void>);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
transaction.signTaprootInput(i, signer);
|
|
387
|
-
} catch {
|
|
388
|
-
throw new Error('Failed to sign input');
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
/*const signHash =
|
|
393
|
-
this.sighashTypes && this.sighashTypes.length
|
|
394
|
-
? [TweakedTransaction.calculateSignHash(this.sighashTypes)]
|
|
395
|
-
: undefined;
|
|
374
|
+
const publicKey = signer.publicKey;
|
|
375
|
+
const isTaproot = this.isTaprootInput(input);
|
|
396
376
|
|
|
397
|
-
|
|
377
|
+
let signed = false;
|
|
398
378
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
if (!this.tweakedSigner) this.tweakSigner();
|
|
379
|
+
if (isTaproot) {
|
|
380
|
+
const isScriptSpend = this.isTaprootScriptSpend(input, publicKey);
|
|
402
381
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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
|
+
}
|
|
406
389
|
} else {
|
|
407
|
-
tweakedSigner
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
+
}
|
|
412
397
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
signHash,
|
|
420
|
-
) as Promise<void>);
|
|
421
|
-
} else {
|
|
422
|
-
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}`);
|
|
423
404
|
}
|
|
424
|
-
|
|
425
|
-
return;
|
|
426
|
-
} catch {}
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
try {
|
|
431
|
-
if ('signInput' in signer) {
|
|
432
|
-
// @ts-expect-error - we know it's a signer
|
|
433
|
-
return await (signer.signInput(transaction, i, signHash) as Promise<void>);
|
|
434
|
-
} else {
|
|
435
|
-
transaction.signInput(i, signer, signHash);
|
|
436
|
-
}
|
|
437
|
-
} catch (e) {
|
|
438
|
-
if (!testedTap) {
|
|
439
|
-
// and we try again taproot...
|
|
440
|
-
|
|
441
|
-
if ('signTaprootInput' in signer) {
|
|
442
|
-
// @ts-expect-error - we know it's a taproot signer
|
|
443
|
-
return await (signer.signTaprootInput(
|
|
444
|
-
transaction,
|
|
445
|
-
i,
|
|
446
|
-
signHash,
|
|
447
|
-
) as Promise<void>);
|
|
448
|
-
} else if (this.tweakedSigner) {
|
|
449
|
-
transaction.signTaprootInput(i, this.tweakedSigner, undefined, signHash);
|
|
450
405
|
} else {
|
|
451
|
-
|
|
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}`);
|
|
452
417
|
}
|
|
453
418
|
}
|
|
454
|
-
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (!signed) {
|
|
422
|
+
throw new Error(`Cannot sign input ${i} with the provided signer.`);
|
|
423
|
+
}
|
|
455
424
|
}
|
|
456
425
|
|
|
457
426
|
protected splitArray<T>(arr: T[], chunkSize: number): T[][] {
|
|
@@ -460,7 +429,6 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
460
429
|
}
|
|
461
430
|
|
|
462
431
|
const result: T[][] = [];
|
|
463
|
-
|
|
464
432
|
for (let i = 0; i < arr.length; i += chunkSize) {
|
|
465
433
|
result.push(arr.slice(i, i + chunkSize));
|
|
466
434
|
}
|
|
@@ -698,10 +666,9 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
698
666
|
|
|
699
667
|
if (i === 0 && this.nonWitnessUtxo) {
|
|
700
668
|
input.nonWitnessUtxo = this.nonWitnessUtxo;
|
|
701
|
-
this.log(`Using non-witness utxo for input ${i}`);
|
|
702
669
|
}
|
|
703
670
|
|
|
704
|
-
//
|
|
671
|
+
// Automatically detect P2TR inputs.
|
|
705
672
|
if (
|
|
706
673
|
utxo.scriptPubKey.address &&
|
|
707
674
|
AddressVerificator.isValidP2TRAddress(utxo.scriptPubKey.address, this.network)
|
|
@@ -715,11 +682,11 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
715
682
|
}
|
|
716
683
|
|
|
717
684
|
protected customFinalizerP2SH = (
|
|
718
|
-
inputIndex: number,
|
|
719
|
-
input: PsbtInput,
|
|
720
|
-
scriptA: Buffer,
|
|
721
|
-
isSegwit: boolean,
|
|
722
|
-
isP2SH: boolean,
|
|
685
|
+
inputIndex: number,
|
|
686
|
+
input: PsbtInput,
|
|
687
|
+
scriptA: Buffer,
|
|
688
|
+
isSegwit: boolean,
|
|
689
|
+
isP2SH: boolean,
|
|
723
690
|
isP2WSH: boolean,
|
|
724
691
|
): {
|
|
725
692
|
finalScriptSig: Buffer | undefined;
|
|
@@ -728,31 +695,120 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
728
695
|
const inputDecoded = this.inputs[inputIndex];
|
|
729
696
|
if (isP2SH && input.partialSig && inputDecoded && inputDecoded.redeemScript) {
|
|
730
697
|
const signatures = input.partialSig.map((sig) => sig.signature);
|
|
731
|
-
|
|
732
|
-
/*const fakeSignature = Buffer.from([
|
|
733
|
-
0x30,
|
|
734
|
-
0x45, // DER prefix: 0x30 (Compound), 0x45 (length = 69 bytes)
|
|
735
|
-
0x02,
|
|
736
|
-
0x20, // Integer marker: 0x02 (integer), 0x20 (length = 32 bytes)
|
|
737
|
-
...Buffer.alloc(32, 0x00), // 32-byte fake 'r' value (all zeros)
|
|
738
|
-
0x02,
|
|
739
|
-
0x21, // Integer marker: 0x02 (integer), 0x21 (length = 33 bytes)
|
|
740
|
-
...Buffer.alloc(33, 0x00), // 33-byte fake 's' value (all zeros)
|
|
741
|
-
0x01, // SIGHASH_ALL flag (0x01)
|
|
742
|
-
]);*/
|
|
743
|
-
|
|
744
|
-
const scriptSig = script.compile([
|
|
745
|
-
...signatures,
|
|
746
|
-
//fakeSignature,
|
|
747
|
-
inputDecoded.redeemScript,
|
|
748
|
-
]);
|
|
698
|
+
const scriptSig = script.compile([...signatures, inputDecoded.redeemScript]);
|
|
749
699
|
|
|
750
700
|
return {
|
|
751
|
-
finalScriptSig: scriptSig,
|
|
752
|
-
finalScriptWitness: undefined,
|
|
701
|
+
finalScriptSig: scriptSig,
|
|
702
|
+
finalScriptWitness: undefined,
|
|
753
703
|
};
|
|
754
704
|
}
|
|
755
705
|
|
|
756
706
|
return getFinalScripts(inputIndex, input, scriptA, isSegwit, isP2SH, isP2WSH);
|
|
757
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
|
+
}
|
|
758
814
|
}
|