Add fee checking before extract

This commit is contained in:
junderw 2019-07-05 12:28:04 +09:00
parent ba5f336e02
commit 14eeb309df
No known key found for this signature in database
GPG key ID: B256185D3A971908
3 changed files with 324 additions and 48 deletions

View file

@ -11,6 +11,45 @@ const bscript = require('./script');
const transaction_1 = require('./transaction'); const transaction_1 = require('./transaction');
const varuint = require('varuint-bitcoin'); const varuint = require('varuint-bitcoin');
class Psbt extends bip174_1.Psbt { class Psbt extends bip174_1.Psbt {
constructor(opts = {}) {
super();
this.__NON_WITNESS_UTXO_TX_CACHE = [];
this.__NON_WITNESS_UTXO_BUF_CACHE = [];
// set defaults
this.opts = Object.assign({}, DEFAULT_OPTS, opts);
this.__TX = transaction_1.Transaction.fromBuffer(this.globalMap.unsignedTx);
this.setVersion(2);
// set cache
const self = this;
delete this.globalMap.unsignedTx;
Object.defineProperty(this.globalMap, 'unsignedTx', {
enumerable: true,
get() {
if (self.__TX_BUF_CACHE !== undefined) {
return self.__TX_BUF_CACHE;
} else {
self.__TX_BUF_CACHE = self.__TX.toBuffer();
return self.__TX_BUF_CACHE;
}
},
set(data) {
self.__TX_BUF_CACHE = data;
},
});
// Make data hidden when enumerating
const dpew = (obj, attr, enumerable, writable) =>
Object.defineProperty(obj, attr, {
enumerable,
writable,
});
dpew(this, '__TX', false, true);
dpew(this, '__EXTRACTED_TX', false, true);
dpew(this, '__FEE_RATE', false, true);
dpew(this, '__TX_BUF_CACHE', false, true);
dpew(this, '__NON_WITNESS_UTXO_TX_CACHE', false, true);
dpew(this, '__NON_WITNESS_UTXO_BUF_CACHE', false, true);
dpew(this, 'opts', false, true);
}
static fromTransaction(txBuf) { static fromTransaction(txBuf) {
const tx = transaction_1.Transaction.fromBuffer(txBuf); const tx = transaction_1.Transaction.fromBuffer(txBuf);
checkTxEmpty(tx); checkTxEmpty(tx);
@ -46,44 +85,16 @@ class Psbt extends bip174_1.Psbt {
psbt.__TX = tx; psbt.__TX = tx;
return psbt; return psbt;
} }
constructor(opts = {}) { setMaximumFeeRate(satoshiPerByte) {
super(); check32Bit(satoshiPerByte); // 42.9 BTC per byte IS excessive... so throw
// set defaults this.opts.maximumFeeRate = satoshiPerByte;
this.opts = Object.assign({}, DEFAULT_OPTS, opts);
this.__TX = transaction_1.Transaction.fromBuffer(this.globalMap.unsignedTx);
this.setVersion(2);
// set cache
const self = this;
delete this.globalMap.unsignedTx;
Object.defineProperty(this.globalMap, 'unsignedTx', {
enumerable: true,
get() {
if (self.__TX_BUF_CACHE !== undefined) {
return self.__TX_BUF_CACHE;
} else {
self.__TX_BUF_CACHE = self.__TX.toBuffer();
return self.__TX_BUF_CACHE;
}
},
set(data) {
self.__TX_BUF_CACHE = data;
},
});
// Make data hidden when enumerating
const dpew = (obj, attr, enumerable, writable) =>
Object.defineProperty(obj, attr, {
enumerable,
writable,
});
dpew(this, '__TX', false, true);
dpew(this, '__TX_BUF_CACHE', false, true);
dpew(this, 'opts', false, true);
} }
setVersion(version) { setVersion(version) {
check32Bit(version); check32Bit(version);
checkInputsForPartialSig(this.inputs, 'setVersion'); checkInputsForPartialSig(this.inputs, 'setVersion');
this.__TX.version = version; this.__TX.version = version;
this.__TX_BUF_CACHE = undefined; this.__TX_BUF_CACHE = undefined;
this.__EXTRACTED_TX = undefined;
return this; return this;
} }
setLocktime(locktime) { setLocktime(locktime) {
@ -91,6 +102,7 @@ class Psbt extends bip174_1.Psbt {
checkInputsForPartialSig(this.inputs, 'setLocktime'); checkInputsForPartialSig(this.inputs, 'setLocktime');
this.__TX.locktime = locktime; this.__TX.locktime = locktime;
this.__TX_BUF_CACHE = undefined; this.__TX_BUF_CACHE = undefined;
this.__EXTRACTED_TX = undefined;
return this; return this;
} }
setSequence(inputIndex, sequence) { setSequence(inputIndex, sequence) {
@ -101,6 +113,7 @@ class Psbt extends bip174_1.Psbt {
} }
this.__TX.ins[inputIndex].sequence = sequence; this.__TX.ins[inputIndex].sequence = sequence;
this.__TX_BUF_CACHE = undefined; this.__TX_BUF_CACHE = undefined;
this.__EXTRACTED_TX = undefined;
return this; return this;
} }
addInput(inputData) { addInput(inputData) {
@ -159,8 +172,29 @@ class Psbt extends bip174_1.Psbt {
}; };
return super.addOutput(outputData, true, outputAdder); return super.addOutput(outputData, true, outputAdder);
} }
extractTransaction() { addNonWitnessUtxoToInput(inputIndex, nonWitnessUtxo) {
super.addNonWitnessUtxoToInput(inputIndex, nonWitnessUtxo);
const input = this.inputs[inputIndex];
addNonWitnessTxCache(this, input, inputIndex);
return this;
}
extractTransaction(disableFeeCheck) {
if (!this.inputs.every(isFinalized)) throw new Error('Not finalized'); if (!this.inputs.every(isFinalized)) throw new Error('Not finalized');
if (!disableFeeCheck) {
const feeRate = this.__FEE_RATE || this.getFeeRate();
const vsize = this.__EXTRACTED_TX.virtualSize();
const satoshis = feeRate * vsize;
if (feeRate >= this.opts.maximumFeeRate) {
throw new Error(
`Warning: You are paying around ${satoshis / 1e8} in fees, which ` +
`is ${feeRate} satoshi per byte for a transaction with a VSize of ` +
`${vsize} bytes (segwit counted as 0.25 byte per byte)\n` +
`Use setMaximumFeeRate method to raise your threshold, or pass ` +
`true to the first arg of extractTransaction.`,
);
}
}
if (this.__EXTRACTED_TX) return this.__EXTRACTED_TX;
const tx = this.__TX.clone(); const tx = this.__TX.clone();
this.inputs.forEach((input, idx) => { this.inputs.forEach((input, idx) => {
if (input.finalScriptSig) tx.ins[idx].script = input.finalScriptSig; if (input.finalScriptSig) tx.ins[idx].script = input.finalScriptSig;
@ -170,8 +204,51 @@ class Psbt extends bip174_1.Psbt {
); );
} }
}); });
this.__EXTRACTED_TX = tx;
return tx; return tx;
} }
getFeeRate() {
if (!this.inputs.every(isFinalized))
throw new Error('PSBT must be finalized to calculate fee rate');
if (this.__FEE_RATE) return this.__FEE_RATE;
let tx;
let inputAmount = 0;
let mustFinalize = true;
if (this.__EXTRACTED_TX) {
tx = this.__EXTRACTED_TX;
mustFinalize = false;
} else {
tx = this.__TX.clone();
}
this.inputs.forEach((input, idx) => {
if (mustFinalize && input.finalScriptSig)
tx.ins[idx].script = input.finalScriptSig;
if (mustFinalize && input.finalScriptWitness) {
tx.ins[idx].witness = scriptWitnessToWitnessStack(
input.finalScriptWitness,
);
}
if (input.witnessUtxo) {
inputAmount += input.witnessUtxo.value;
} else if (input.nonWitnessUtxo) {
// @ts-ignore
if (!this.__NON_WITNESS_UTXO_TX_CACHE[idx]) {
addNonWitnessTxCache(this, input, idx);
}
const vout = this.__TX.ins[idx].index;
const out = this.__NON_WITNESS_UTXO_TX_CACHE[idx].outs[vout];
inputAmount += out.value;
} else {
throw new Error('Missing input value: index #' + idx);
}
});
this.__EXTRACTED_TX = tx;
const outputAmount = tx.outs.reduce((total, o) => total + o.value, 0);
const fee = inputAmount - outputAmount;
const bytes = tx.virtualSize();
this.__FEE_RATE = Math.floor(fee / bytes);
return this.__FEE_RATE;
}
finalizeAllInputs() { finalizeAllInputs() {
const inputResults = range(this.inputs.length).map(idx => const inputResults = range(this.inputs.length).map(idx =>
this.finalizeInput(idx), this.finalizeInput(idx),
@ -188,6 +265,7 @@ class Psbt extends bip174_1.Psbt {
inputIndex, inputIndex,
input, input,
this.__TX, this.__TX,
this,
); );
if (!script) return false; if (!script) return false;
const scriptType = classifyScript(script); const scriptType = classifyScript(script);
@ -216,6 +294,7 @@ class Psbt extends bip174_1.Psbt {
inputIndex, inputIndex,
keyPair.publicKey, keyPair.publicKey,
this.__TX, this.__TX,
this,
); );
const partialSig = { const partialSig = {
pubkey: keyPair.publicKey, pubkey: keyPair.publicKey,
@ -232,6 +311,7 @@ class Psbt extends bip174_1.Psbt {
inputIndex, inputIndex,
keyPair.publicKey, keyPair.publicKey,
this.__TX, this.__TX,
this,
); );
Promise.resolve(keyPair.sign(hash)).then(signature => { Promise.resolve(keyPair.sign(hash)).then(signature => {
const partialSig = { const partialSig = {
@ -247,16 +327,50 @@ class Psbt extends bip174_1.Psbt {
exports.Psbt = Psbt; exports.Psbt = Psbt;
const DEFAULT_OPTS = { const DEFAULT_OPTS = {
network: networks_1.bitcoin, network: networks_1.bitcoin,
maximumFeeRate: 5000,
}; };
function addNonWitnessTxCache(psbt, input, inputIndex) {
// @ts-ignore
psbt.__NON_WITNESS_UTXO_BUF_CACHE[inputIndex] = input.nonWitnessUtxo;
const tx = transaction_1.Transaction.fromBuffer(input.nonWitnessUtxo);
// @ts-ignore
psbt.__NON_WITNESS_UTXO_TX_CACHE[inputIndex] = tx;
const self = psbt;
const selfIndex = inputIndex;
delete input.nonWitnessUtxo;
Object.defineProperty(input, 'nonWitnessUtxo', {
enumerable: true,
get() {
// @ts-ignore
if (self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] !== undefined) {
// @ts-ignore
return self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex];
} else {
// @ts-ignore
self.__NON_WITNESS_UTXO_BUF_CACHE[
selfIndex
// @ts-ignore
] = self.__NON_WITNESS_UTXO_TX_CACHE[selfIndex].toBuffer();
// @ts-ignore
return self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex];
}
},
set(data) {
// @ts-ignore
self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] = data;
},
});
}
function isFinalized(input) { function isFinalized(input) {
return !!input.finalScriptSig || !!input.finalScriptWitness; return !!input.finalScriptSig || !!input.finalScriptWitness;
} }
function getHashAndSighashType(inputs, inputIndex, pubkey, unsignedTx) { function getHashAndSighashType(inputs, inputIndex, pubkey, unsignedTx, psbt) {
const input = utils_1.checkForInput(inputs, inputIndex); const input = utils_1.checkForInput(inputs, inputIndex);
const { hash, sighashType, script } = getHashForSig( const { hash, sighashType, script } = getHashForSig(
inputIndex, inputIndex,
input, input,
unsignedTx, unsignedTx,
psbt,
); );
checkScriptForPubkey(pubkey, script); checkScriptForPubkey(pubkey, script);
return { return {
@ -375,15 +489,18 @@ function checkScriptForPubkey(pubkey, script) {
); );
} }
} }
const getHashForSig = (inputIndex, input, unsignedTx) => { const getHashForSig = (inputIndex, input, unsignedTx, psbt) => {
const sighashType = const sighashType =
input.sighashType || transaction_1.Transaction.SIGHASH_ALL; input.sighashType || transaction_1.Transaction.SIGHASH_ALL;
let hash; let hash;
let script; let script;
if (input.nonWitnessUtxo) { if (input.nonWitnessUtxo) {
const nonWitnessUtxoTx = transaction_1.Transaction.fromBuffer( // @ts-ignore
input.nonWitnessUtxo, if (!psbt.__NON_WITNESS_UTXO_TX_CACHE[inputIndex]) {
); addNonWitnessTxCache(psbt, input, inputIndex);
}
// @ts-ignore
const nonWitnessUtxoTx = psbt.__NON_WITNESS_UTXO_TX_CACHE[inputIndex];
const prevoutHash = unsignedTx.ins[inputIndex].hash; const prevoutHash = unsignedTx.ins[inputIndex].hash;
const utxoHash = nonWitnessUtxoTx.getHash(); const utxoHash = nonWitnessUtxoTx.getHash();
// If a non-witness UTXO is provided, its hash must match the hash specified in the prevout // If a non-witness UTXO is provided, its hash must match the hash specified in the prevout
@ -494,7 +611,7 @@ const classifyScript = script => {
if (isP2PK(script)) return 'pubkey'; if (isP2PK(script)) return 'pubkey';
return 'nonstandard'; return 'nonstandard';
}; };
function getScriptFromInput(inputIndex, input, unsignedTx) { function getScriptFromInput(inputIndex, input, unsignedTx, psbt) {
const res = { const res = {
script: null, script: null,
isSegwit: false, isSegwit: false,
@ -506,9 +623,12 @@ function getScriptFromInput(inputIndex, input, unsignedTx) {
res.isP2SH = true; res.isP2SH = true;
res.script = input.redeemScript; res.script = input.redeemScript;
} else { } else {
const nonWitnessUtxoTx = transaction_1.Transaction.fromBuffer( // @ts-ignore
input.nonWitnessUtxo, if (!psbt.__NON_WITNESS_UTXO_TX_CACHE[inputIndex]) {
); addNonWitnessTxCache(psbt, input, inputIndex);
}
// @ts-ignore
const nonWitnessUtxoTx = psbt.__NON_WITNESS_UTXO_TX_CACHE[inputIndex];
const prevoutIndex = unsignedTx.ins[inputIndex].index; const prevoutIndex = unsignedTx.ins[inputIndex].index;
res.script = nonWitnessUtxoTx.outs[prevoutIndex].script; res.script = nonWitnessUtxoTx.outs[prevoutIndex].script;
} }

View file

@ -1,5 +1,6 @@
import { Psbt as PsbtBase } from 'bip174'; import { Psbt as PsbtBase } from 'bip174';
import { import {
NonWitnessUtxo,
PartialSig, PartialSig,
PsbtInput, PsbtInput,
TransactionInput, TransactionInput,
@ -13,7 +14,7 @@ import { Signer, SignerAsync } from './ecpair';
import { bitcoin as btcNetwork, Network } from './networks'; import { bitcoin as btcNetwork, Network } from './networks';
import * as payments from './payments'; import * as payments from './payments';
import * as bscript from './script'; import * as bscript from './script';
import { Transaction } from './transaction'; import { Output, Transaction } from './transaction';
const varuint = require('varuint-bitcoin'); const varuint = require('varuint-bitcoin');
export class Psbt extends PsbtBase { export class Psbt extends PsbtBase {
@ -65,6 +66,10 @@ export class Psbt extends PsbtBase {
} }
private __TX: Transaction; private __TX: Transaction;
private __TX_BUF_CACHE?: Buffer; private __TX_BUF_CACHE?: Buffer;
private __FEE_RATE?: number;
private __EXTRACTED_TX?: Transaction;
private __NON_WITNESS_UTXO_TX_CACHE: Transaction[] = [];
private __NON_WITNESS_UTXO_BUF_CACHE: Buffer[] = [];
private opts: PsbtOpts; private opts: PsbtOpts;
constructor(opts: PsbtOptsOptional = {}) { constructor(opts: PsbtOptsOptional = {}) {
super(); super();
@ -103,15 +108,25 @@ export class Psbt extends PsbtBase {
writable, writable,
}); });
dpew(this, '__TX', false, true); dpew(this, '__TX', false, true);
dpew(this, '__EXTRACTED_TX', false, true);
dpew(this, '__FEE_RATE', false, true);
dpew(this, '__TX_BUF_CACHE', false, true); dpew(this, '__TX_BUF_CACHE', false, true);
dpew(this, '__NON_WITNESS_UTXO_TX_CACHE', false, true);
dpew(this, '__NON_WITNESS_UTXO_BUF_CACHE', false, true);
dpew(this, 'opts', false, true); dpew(this, 'opts', false, true);
} }
setMaximumFeeRate(satoshiPerByte: number): void {
check32Bit(satoshiPerByte); // 42.9 BTC per byte IS excessive... so throw
this.opts.maximumFeeRate = satoshiPerByte;
}
setVersion(version: number): this { setVersion(version: number): this {
check32Bit(version); check32Bit(version);
checkInputsForPartialSig(this.inputs, 'setVersion'); checkInputsForPartialSig(this.inputs, 'setVersion');
this.__TX.version = version; this.__TX.version = version;
this.__TX_BUF_CACHE = undefined; this.__TX_BUF_CACHE = undefined;
this.__EXTRACTED_TX = undefined;
return this; return this;
} }
@ -120,6 +135,7 @@ export class Psbt extends PsbtBase {
checkInputsForPartialSig(this.inputs, 'setLocktime'); checkInputsForPartialSig(this.inputs, 'setLocktime');
this.__TX.locktime = locktime; this.__TX.locktime = locktime;
this.__TX_BUF_CACHE = undefined; this.__TX_BUF_CACHE = undefined;
this.__EXTRACTED_TX = undefined;
return this; return this;
} }
@ -131,6 +147,7 @@ export class Psbt extends PsbtBase {
} }
this.__TX.ins[inputIndex].sequence = sequence; this.__TX.ins[inputIndex].sequence = sequence;
this.__TX_BUF_CACHE = undefined; this.__TX_BUF_CACHE = undefined;
this.__EXTRACTED_TX = undefined;
return this; return this;
} }
@ -197,8 +214,33 @@ export class Psbt extends PsbtBase {
return super.addOutput(outputData, true, outputAdder); return super.addOutput(outputData, true, outputAdder);
} }
extractTransaction(): Transaction { addNonWitnessUtxoToInput(
inputIndex: number,
nonWitnessUtxo: NonWitnessUtxo,
): this {
super.addNonWitnessUtxoToInput(inputIndex, nonWitnessUtxo);
const input = this.inputs[inputIndex];
addNonWitnessTxCache(this, input, inputIndex);
return this;
}
extractTransaction(disableFeeCheck?: boolean): Transaction {
if (!this.inputs.every(isFinalized)) throw new Error('Not finalized'); if (!this.inputs.every(isFinalized)) throw new Error('Not finalized');
if (!disableFeeCheck) {
const feeRate = this.__FEE_RATE || this.getFeeRate();
const vsize = this.__EXTRACTED_TX!.virtualSize();
const satoshis = feeRate * vsize;
if (feeRate >= this.opts.maximumFeeRate) {
throw new Error(
`Warning: You are paying around ${satoshis / 1e8} in fees, which ` +
`is ${feeRate} satoshi per byte for a transaction with a VSize of ` +
`${vsize} bytes (segwit counted as 0.25 byte per byte)\n` +
`Use setMaximumFeeRate method to raise your threshold, or pass ` +
`true to the first arg of extractTransaction.`,
);
}
}
if (this.__EXTRACTED_TX) return this.__EXTRACTED_TX;
const tx = this.__TX.clone(); const tx = this.__TX.clone();
this.inputs.forEach((input, idx) => { this.inputs.forEach((input, idx) => {
if (input.finalScriptSig) tx.ins[idx].script = input.finalScriptSig; if (input.finalScriptSig) tx.ins[idx].script = input.finalScriptSig;
@ -208,9 +250,56 @@ export class Psbt extends PsbtBase {
); );
} }
}); });
this.__EXTRACTED_TX = tx;
return tx; return tx;
} }
getFeeRate(): number {
if (!this.inputs.every(isFinalized))
throw new Error('PSBT must be finalized to calculate fee rate');
if (this.__FEE_RATE) return this.__FEE_RATE;
let tx: Transaction;
let inputAmount = 0;
let mustFinalize = true;
if (this.__EXTRACTED_TX) {
tx = this.__EXTRACTED_TX;
mustFinalize = false;
} else {
tx = this.__TX.clone();
}
this.inputs.forEach((input, idx) => {
if (mustFinalize && input.finalScriptSig)
tx.ins[idx].script = input.finalScriptSig;
if (mustFinalize && input.finalScriptWitness) {
tx.ins[idx].witness = scriptWitnessToWitnessStack(
input.finalScriptWitness,
);
}
if (input.witnessUtxo) {
inputAmount += input.witnessUtxo.value;
} else if (input.nonWitnessUtxo) {
// @ts-ignore
if (!this.__NON_WITNESS_UTXO_TX_CACHE[idx]) {
addNonWitnessTxCache(this, input, idx);
}
const vout = this.__TX.ins[idx].index;
const out = this.__NON_WITNESS_UTXO_TX_CACHE[idx].outs[vout] as Output;
inputAmount += out.value;
} else {
throw new Error('Missing input value: index #' + idx);
}
});
this.__EXTRACTED_TX = tx;
const outputAmount = (tx.outs as Output[]).reduce(
(total, o) => total + o.value,
0,
);
const fee = inputAmount - outputAmount;
const bytes = tx.virtualSize();
this.__FEE_RATE = Math.floor(fee / bytes);
return this.__FEE_RATE;
}
finalizeAllInputs(): { finalizeAllInputs(): {
result: boolean; result: boolean;
inputResults: boolean[]; inputResults: boolean[];
@ -231,6 +320,7 @@ export class Psbt extends PsbtBase {
inputIndex, inputIndex,
input, input,
this.__TX, this.__TX,
this,
); );
if (!script) return false; if (!script) return false;
@ -264,6 +354,7 @@ export class Psbt extends PsbtBase {
inputIndex, inputIndex,
keyPair.publicKey, keyPair.publicKey,
this.__TX, this.__TX,
this,
); );
const partialSig = { const partialSig = {
@ -284,6 +375,7 @@ export class Psbt extends PsbtBase {
inputIndex, inputIndex,
keyPair.publicKey, keyPair.publicKey,
this.__TX, this.__TX,
this,
); );
Promise.resolve(keyPair.sign(hash)).then(signature => { Promise.resolve(keyPair.sign(hash)).then(signature => {
@ -312,16 +404,58 @@ export class Psbt extends PsbtBase {
interface PsbtOptsOptional { interface PsbtOptsOptional {
network?: Network; network?: Network;
maximumFeeRate?: number;
} }
interface PsbtOpts { interface PsbtOpts {
network: Network; network: Network;
maximumFeeRate: number;
} }
const DEFAULT_OPTS = { const DEFAULT_OPTS = {
network: btcNetwork, network: btcNetwork,
maximumFeeRate: 5000, // satoshi per byte
}; };
function addNonWitnessTxCache(
psbt: Psbt,
input: PsbtInput,
inputIndex: number,
): void {
// @ts-ignore
psbt.__NON_WITNESS_UTXO_BUF_CACHE[inputIndex] = input.nonWitnessUtxo!;
const tx = Transaction.fromBuffer(input.nonWitnessUtxo!);
// @ts-ignore
psbt.__NON_WITNESS_UTXO_TX_CACHE[inputIndex] = tx;
const self = psbt;
const selfIndex = inputIndex;
delete input.nonWitnessUtxo;
Object.defineProperty(input, 'nonWitnessUtxo', {
enumerable: true,
get(): Buffer {
// @ts-ignore
if (self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] !== undefined) {
// @ts-ignore
return self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex];
} else {
// @ts-ignore
self.__NON_WITNESS_UTXO_BUF_CACHE[
selfIndex
// @ts-ignore
] = self.__NON_WITNESS_UTXO_TX_CACHE[selfIndex].toBuffer();
// @ts-ignore
return self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex];
}
},
set(data: Buffer): void {
// @ts-ignore
self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] = data;
},
});
}
function isFinalized(input: PsbtInput): boolean { function isFinalized(input: PsbtInput): boolean {
return !!input.finalScriptSig || !!input.finalScriptWitness; return !!input.finalScriptSig || !!input.finalScriptWitness;
} }
@ -331,6 +465,7 @@ function getHashAndSighashType(
inputIndex: number, inputIndex: number,
pubkey: Buffer, pubkey: Buffer,
unsignedTx: Transaction, unsignedTx: Transaction,
psbt: Psbt,
): { ): {
hash: Buffer; hash: Buffer;
sighashType: number; sighashType: number;
@ -340,6 +475,7 @@ function getHashAndSighashType(
inputIndex, inputIndex,
input, input,
unsignedTx, unsignedTx,
psbt,
); );
checkScriptForPubkey(pubkey, script); checkScriptForPubkey(pubkey, script);
return { return {
@ -490,13 +626,19 @@ const getHashForSig = (
inputIndex: number, inputIndex: number,
input: PsbtInput, input: PsbtInput,
unsignedTx: Transaction, unsignedTx: Transaction,
psbt: Psbt,
): HashForSigData => { ): HashForSigData => {
const sighashType = input.sighashType || Transaction.SIGHASH_ALL; const sighashType = input.sighashType || Transaction.SIGHASH_ALL;
let hash: Buffer; let hash: Buffer;
let script: Buffer; let script: Buffer;
if (input.nonWitnessUtxo) { if (input.nonWitnessUtxo) {
const nonWitnessUtxoTx = Transaction.fromBuffer(input.nonWitnessUtxo); // @ts-ignore
if (!psbt.__NON_WITNESS_UTXO_TX_CACHE[inputIndex]) {
addNonWitnessTxCache(psbt, input, inputIndex);
}
// @ts-ignore
const nonWitnessUtxoTx = psbt.__NON_WITNESS_UTXO_TX_CACHE[inputIndex];
const prevoutHash = unsignedTx.ins[inputIndex].hash; const prevoutHash = unsignedTx.ins[inputIndex].hash;
const utxoHash = nonWitnessUtxoTx.getHash(); const utxoHash = nonWitnessUtxoTx.getHash();
@ -636,6 +778,7 @@ function getScriptFromInput(
inputIndex: number, inputIndex: number,
input: PsbtInput, input: PsbtInput,
unsignedTx: Transaction, unsignedTx: Transaction,
psbt: Psbt,
): GetScriptReturn { ): GetScriptReturn {
const res: GetScriptReturn = { const res: GetScriptReturn = {
script: null, script: null,
@ -648,7 +791,12 @@ function getScriptFromInput(
res.isP2SH = true; res.isP2SH = true;
res.script = input.redeemScript; res.script = input.redeemScript;
} else { } else {
const nonWitnessUtxoTx = Transaction.fromBuffer(input.nonWitnessUtxo); // @ts-ignore
if (!psbt.__NON_WITNESS_UTXO_TX_CACHE[inputIndex]) {
addNonWitnessTxCache(psbt, input, inputIndex);
}
// @ts-ignore
const nonWitnessUtxoTx = psbt.__NON_WITNESS_UTXO_TX_CACHE[inputIndex];
const prevoutIndex = unsignedTx.ins[inputIndex].index; const prevoutIndex = unsignedTx.ins[inputIndex].index;
res.script = nonWitnessUtxoTx.outs[prevoutIndex].script; res.script = nonWitnessUtxoTx.outs[prevoutIndex].script;
} }

12
types/psbt.d.ts vendored
View file

@ -1,6 +1,6 @@
/// <reference types="node" /> /// <reference types="node" />
import { Psbt as PsbtBase } from 'bip174'; import { Psbt as PsbtBase } from 'bip174';
import { TransactionInput, TransactionOutput } from 'bip174/src/lib/interfaces'; import { NonWitnessUtxo, TransactionInput, TransactionOutput } from 'bip174/src/lib/interfaces';
import { Signer, SignerAsync } from './ecpair'; import { Signer, SignerAsync } from './ecpair';
import { Network } from './networks'; import { Network } from './networks';
import { Transaction } from './transaction'; import { Transaction } from './transaction';
@ -9,14 +9,21 @@ export declare class Psbt extends PsbtBase {
static fromBuffer<T extends typeof PsbtBase>(this: T, buffer: Buffer): InstanceType<T>; static fromBuffer<T extends typeof PsbtBase>(this: T, buffer: Buffer): InstanceType<T>;
private __TX; private __TX;
private __TX_BUF_CACHE?; private __TX_BUF_CACHE?;
private __FEE_RATE?;
private __EXTRACTED_TX?;
private __NON_WITNESS_UTXO_TX_CACHE;
private __NON_WITNESS_UTXO_BUF_CACHE;
private opts; private opts;
constructor(opts?: PsbtOptsOptional); constructor(opts?: PsbtOptsOptional);
setMaximumFeeRate(satoshiPerByte: number): void;
setVersion(version: number): this; setVersion(version: number): this;
setLocktime(locktime: number): this; setLocktime(locktime: number): this;
setSequence(inputIndex: number, sequence: number): this; setSequence(inputIndex: number, sequence: number): this;
addInput(inputData: TransactionInput): this; addInput(inputData: TransactionInput): this;
addOutput(outputData: TransactionOutput): this; addOutput(outputData: TransactionOutput): this;
extractTransaction(): Transaction; addNonWitnessUtxoToInput(inputIndex: number, nonWitnessUtxo: NonWitnessUtxo): this;
extractTransaction(disableFeeCheck?: boolean): Transaction;
getFeeRate(): number;
finalizeAllInputs(): { finalizeAllInputs(): {
result: boolean; result: boolean;
inputResults: boolean[]; inputResults: boolean[];
@ -27,5 +34,6 @@ export declare class Psbt extends PsbtBase {
} }
interface PsbtOptsOptional { interface PsbtOptsOptional {
network?: Network; network?: Network;
maximumFeeRate?: number;
} }
export {}; export {};