@btc-vision/transaction 1.1.15 → 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/opnet.d.ts +1 -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/opnet.d.ts +1 -0
- package/build/opnet.js +1 -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/opnet.ts +2 -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
|
@@ -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 {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
2
|
export class BitcoinUtils {
|
|
3
3
|
static btcToSatoshi(btc) {
|
|
4
4
|
return BigInt(btc * 100000000);
|
|
@@ -15,13 +15,13 @@ export class BitcoinUtils {
|
|
|
15
15
|
window.crypto.getRandomValues(array);
|
|
16
16
|
return Buffer.from(array);
|
|
17
17
|
}
|
|
18
|
-
else if (crypto && typeof crypto.getRandomValues === 'function') {
|
|
18
|
+
else if (globalThis.crypto && typeof globalThis.crypto.getRandomValues === 'function') {
|
|
19
19
|
const array = new Uint8Array(length);
|
|
20
|
-
crypto.getRandomValues(array);
|
|
20
|
+
globalThis.crypto.getRandomValues(array);
|
|
21
21
|
return Buffer.from(array);
|
|
22
22
|
}
|
|
23
23
|
else {
|
|
24
|
-
console.log(`No secure random number generator available. Please upgrade your environment.`, globalThis.window.crypto, crypto);
|
|
24
|
+
console.log(`No secure random number generator available. Please upgrade your environment.`, globalThis.window.crypto, globalThis.crypto);
|
|
25
25
|
throw new Error('No secure random number generator available. Please upgrade your environment.');
|
|
26
26
|
}
|
|
27
27
|
}
|
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.17",
|
|
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,9 +63,6 @@
|
|
|
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
|
-
},
|
|
69
66
|
"devDependencies": {
|
|
70
67
|
"@babel/core": "^7.26.0",
|
|
71
68
|
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
|
@@ -94,7 +91,7 @@
|
|
|
94
91
|
"dependencies": {
|
|
95
92
|
"@babel/plugin-proposal-object-rest-spread": "^7.20.7",
|
|
96
93
|
"@bitcoinerlab/secp256k1": "^1.1.1",
|
|
97
|
-
"@btc-vision/bitcoin": "^6.3.
|
|
94
|
+
"@btc-vision/bitcoin": "^6.3.1",
|
|
98
95
|
"@btc-vision/bsi-bitcoin-rpc": "^1.0.29",
|
|
99
96
|
"@btc-vision/logger": "^1.0.6",
|
|
100
97
|
"@eslint/js": "^9.14.0",
|
package/src/_version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '1.1.
|
|
1
|
+
export const version = '1.1.17';
|
package/src/keypair/Address.ts
CHANGED
|
@@ -104,6 +104,14 @@ export class Address extends Uint8Array {
|
|
|
104
104
|
return Buffer.from(this);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
public originalPublicKeyBuffer(): Buffer {
|
|
108
|
+
if (!this.#originalPublicKey) {
|
|
109
|
+
throw new Error('Public key not set');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return Buffer.from(this.#originalPublicKey);
|
|
113
|
+
}
|
|
114
|
+
|
|
107
115
|
public equals(a: Address): boolean {
|
|
108
116
|
const b: Address = this as Address;
|
|
109
117
|
|
|
@@ -200,6 +208,13 @@ export class Address extends Uint8Array {
|
|
|
200
208
|
return AddressVerificator.isValidPublicKey(Buffer.from(this).toString('hex'), network);
|
|
201
209
|
}
|
|
202
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Get the public key as address
|
|
213
|
+
*/
|
|
214
|
+
public p2pk(): string {
|
|
215
|
+
return this.toHex();
|
|
216
|
+
}
|
|
217
|
+
|
|
203
218
|
/**
|
|
204
219
|
* Get the address in p2wpkh format
|
|
205
220
|
* @param {Network} network The network
|
package/src/opnet.ts
CHANGED
|
@@ -47,6 +47,7 @@ export * from './transaction/builders/TransactionBuilder.js';
|
|
|
47
47
|
|
|
48
48
|
/** Utils */
|
|
49
49
|
export * from './utils/BitcoinUtils.js';
|
|
50
|
+
export * from './utils/lengths.js';
|
|
50
51
|
|
|
51
52
|
/** UTXO */
|
|
52
53
|
export * from './utxo/interfaces/IUTXO.js';
|
|
@@ -89,3 +90,4 @@ export * from './transaction/browser/types/Xverse.js';
|
|
|
89
90
|
|
|
90
91
|
export * from './metadata/tokens.js';
|
|
91
92
|
export * from './transaction/browser/Web3Provider.js';
|
|
93
|
+
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Helper functions
|
|
2
|
+
import { crypto as bitCrypto, PsbtInput } from '@btc-vision/bitcoin';
|
|
3
|
+
import { isP2TR } from '@btc-vision/bitcoin/src/psbt/psbtutils.js';
|
|
4
|
+
import { toXOnly } from '@btc-vision/bitcoin/src/psbt/bip371.js';
|
|
5
|
+
import * as bscript from '@btc-vision/bitcoin/src/script.js';
|
|
6
|
+
|
|
7
|
+
export function isTaprootInput(input: PsbtInput): boolean {
|
|
8
|
+
return (
|
|
9
|
+
input &&
|
|
10
|
+
!!(
|
|
11
|
+
input.tapInternalKey ||
|
|
12
|
+
input.tapMerkleRoot ||
|
|
13
|
+
(input.tapLeafScript && input.tapLeafScript.length) ||
|
|
14
|
+
(input.tapBip32Derivation && input.tapBip32Derivation.length) ||
|
|
15
|
+
(input.witnessUtxo && isP2TR(input.witnessUtxo.script))
|
|
16
|
+
)
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getInputRelevantScript(input: PsbtInput): Buffer | null {
|
|
21
|
+
if (input.redeemScript) {
|
|
22
|
+
return input.redeemScript;
|
|
23
|
+
}
|
|
24
|
+
if (input.witnessScript) {
|
|
25
|
+
return input.witnessScript;
|
|
26
|
+
}
|
|
27
|
+
if (input.witnessUtxo) {
|
|
28
|
+
return input.witnessUtxo.script;
|
|
29
|
+
}
|
|
30
|
+
if (input.nonWitnessUtxo) {
|
|
31
|
+
// Parse the full transaction from nonWitnessUtxo
|
|
32
|
+
/*const tx = Transaction.fromBuffer(input.nonWitnessUtxo);
|
|
33
|
+
// Retrieve the output referenced by the input index
|
|
34
|
+
const out = tx.outs[input.index];
|
|
35
|
+
if (!out) {
|
|
36
|
+
throw new Error(`No output at index ${input.index} in nonWitnessUtxo`);
|
|
37
|
+
}
|
|
38
|
+
return out.script;*/
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function canSignNonTaprootInput(input: PsbtInput, publicKey: Buffer): boolean {
|
|
44
|
+
if (
|
|
45
|
+
(input.nonWitnessUtxo &&
|
|
46
|
+
!input.redeemScript &&
|
|
47
|
+
!input.witnessScript &&
|
|
48
|
+
!input.witnessUtxo) ||
|
|
49
|
+
input.redeemScript
|
|
50
|
+
) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const script = getInputRelevantScript(input);
|
|
55
|
+
if (script) {
|
|
56
|
+
return pubkeyInScript(publicKey, script);
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function pubkeyPositionInScript(pubkey: Buffer, script: Buffer): number {
|
|
62
|
+
const pubkeyHash = bitCrypto.hash160(pubkey);
|
|
63
|
+
const pubkeyXOnly = toXOnly(pubkey);
|
|
64
|
+
|
|
65
|
+
const decompiled = bscript.decompile(script);
|
|
66
|
+
if (decompiled === null) throw new Error('Unknown script error');
|
|
67
|
+
|
|
68
|
+
const a = decompiled.findIndex((element) => {
|
|
69
|
+
if (typeof element === 'number') return false;
|
|
70
|
+
return element.equals(pubkey) || element.equals(pubkeyHash) || element.equals(pubkeyXOnly);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return a;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function pubkeyInScript(pubkey: Buffer, script: Buffer): boolean {
|
|
77
|
+
return pubkeyPositionInScript(pubkey, script) !== -1;
|
|
78
|
+
}
|