Composition over inheritance

This commit is contained in:
junderw 2019-07-11 17:17:49 +09:00
parent 2f1609b918
commit 1feef9569c
No known key found for this signature in database
GPG key ID: B256185D3A971908
6 changed files with 402 additions and 130 deletions

6
package-lock.json generated
View file

@ -200,9 +200,9 @@
}
},
"bip174": {
"version": "0.0.14",
"resolved": "https://registry.npmjs.org/bip174/-/bip174-0.0.14.tgz",
"integrity": "sha512-v9cre0W4ZpAJS1v18WUJLE9yKdSZyenGpZBg7CXiZ5n35JPXganH92d4Yk8WXpRfbFZ4SMXTqKLEgpLPX1TmcA=="
"version": "0.0.15",
"resolved": "https://registry.npmjs.org/bip174/-/bip174-0.0.15.tgz",
"integrity": "sha512-mK/s9p7i+PG7W2s2cAedNVk1NDZQn9wAoq1DlsS2+1zz5TXR3TRTzqRqm9BQtOXwbkxkhfLwlmsOjuiRdAYkdg=="
},
"bip32": {
"version": "2.0.3",

View file

@ -47,7 +47,7 @@
"dependencies": {
"@types/node": "10.12.18",
"bech32": "^1.1.2",
"bip174": "0.0.14",
"bip174": "0.0.15",
"bip32": "^2.0.3",
"bip66": "^1.1.0",
"bitcoin-ops": "^1.4.0",

View file

@ -15,9 +15,9 @@ const DEFAULT_OPTS = {
network: networks_1.bitcoin,
maximumFeeRate: 5000,
};
class Psbt extends bip174_1.Psbt {
constructor(opts = {}) {
super();
class Psbt {
constructor(opts = {}, data = new bip174_1.Psbt()) {
this.data = data;
this.__CACHE = {
__NON_WITNESS_UTXO_TX_CACHE: [],
__NON_WITNESS_UTXO_BUF_CACHE: [],
@ -27,11 +27,11 @@ class Psbt extends bip174_1.Psbt {
// set defaults
this.opts = Object.assign({}, DEFAULT_OPTS, opts);
const c = this.__CACHE;
c.__TX = transaction_1.Transaction.fromBuffer(this.globalMap.unsignedTx);
this.setVersion(2);
c.__TX = transaction_1.Transaction.fromBuffer(data.globalMap.unsignedTx);
if (this.data.inputs.length === 0) this.setVersion(2);
// set cache
delete this.globalMap.unsignedTx;
Object.defineProperty(this.globalMap, 'unsignedTx', {
delete data.globalMap.unsignedTx;
Object.defineProperty(data.globalMap, 'unsignedTx', {
enumerable: true,
get() {
const buf = c.__TX_BUF_CACHE;
@ -42,8 +42,8 @@ class Psbt extends bip174_1.Psbt {
return c.__TX_BUF_CACHE;
}
},
set(data) {
c.__TX_BUF_CACHE = data;
set(_data) {
c.__TX_BUF_CACHE = _data;
},
});
// Make data hidden when enumerating
@ -55,29 +55,38 @@ class Psbt extends bip174_1.Psbt {
dpew(this, '__CACHE', false, true);
dpew(this, 'opts', false, true);
}
static fromTransaction(txBuf) {
static fromTransaction(txBuf, opts = {}) {
const tx = transaction_1.Transaction.fromBuffer(txBuf);
checkTxEmpty(tx);
const psbt = new this();
const psbtBase = new bip174_1.Psbt();
const psbt = new Psbt(opts, psbtBase);
psbt.__CACHE.__TX = tx;
checkTxForDupeIns(tx, psbt.__CACHE);
let inputCount = tx.ins.length;
let outputCount = tx.outs.length;
while (inputCount > 0) {
psbt.inputs.push({
keyVals: [],
psbtBase.inputs.push({
unknownKeyVals: [],
});
inputCount--;
}
while (outputCount > 0) {
psbt.outputs.push({
keyVals: [],
psbtBase.outputs.push({
unknownKeyVals: [],
});
outputCount--;
}
return psbt;
}
static fromBuffer(buffer) {
static fromBase64(data, opts = {}) {
const buffer = Buffer.from(data, 'base64');
return this.fromBuffer(buffer, opts);
}
static fromHex(data, opts = {}) {
const buffer = Buffer.from(data, 'hex');
return this.fromBuffer(buffer, opts);
}
static fromBuffer(buffer, opts = {}) {
let tx;
const txCountGetter = txBuf => {
tx = transaction_1.Transaction.fromBuffer(txBuf);
@ -87,17 +96,22 @@ class Psbt extends bip174_1.Psbt {
outputCount: tx.outs.length,
};
};
const psbt = super.fromBuffer(buffer, txCountGetter);
const psbtBase = bip174_1.Psbt.fromBuffer(buffer, txCountGetter);
const psbt = new Psbt(opts, psbtBase);
psbt.__CACHE.__TX = tx;
checkTxForDupeIns(tx, psbt.__CACHE);
return psbt;
}
get inputCount() {
return this.inputs.length;
return this.data.inputs.length;
}
combine(...those) {
this.data.combine(...those.map(o => o.data));
return this;
}
clone() {
// TODO: more efficient cloning
const res = Psbt.fromBuffer(this.toBuffer());
const res = Psbt.fromBuffer(this.data.toBuffer());
res.opts = JSON.parse(JSON.stringify(this.opts));
return res;
}
@ -107,7 +121,7 @@ class Psbt extends bip174_1.Psbt {
}
setVersion(version) {
check32Bit(version);
checkInputsForPartialSig(this.inputs, 'setVersion');
checkInputsForPartialSig(this.data.inputs, 'setVersion');
const c = this.__CACHE;
c.__TX.version = version;
c.__TX_BUF_CACHE = undefined;
@ -116,7 +130,7 @@ class Psbt extends bip174_1.Psbt {
}
setLocktime(locktime) {
check32Bit(locktime);
checkInputsForPartialSig(this.inputs, 'setLocktime');
checkInputsForPartialSig(this.data.inputs, 'setLocktime');
const c = this.__CACHE;
c.__TX.locktime = locktime;
c.__TX_BUF_CACHE = undefined;
@ -125,7 +139,7 @@ class Psbt extends bip174_1.Psbt {
}
setSequence(inputIndex, sequence) {
check32Bit(sequence);
checkInputsForPartialSig(this.inputs, 'setSequence');
checkInputsForPartialSig(this.data.inputs, 'setSequence');
const c = this.__CACHE;
if (c.__TX.ins.length <= inputIndex) {
throw new Error('Input index too high');
@ -140,10 +154,15 @@ class Psbt extends bip174_1.Psbt {
return this;
}
addInput(inputData) {
checkInputsForPartialSig(this.inputs, 'addInput');
checkInputsForPartialSig(this.data.inputs, 'addInput');
const c = this.__CACHE;
const inputAdder = getInputAdder(c);
super.addInput(inputData, inputAdder);
this.data.addInput(inputData, inputAdder);
const inputIndex = this.data.inputs.length - 1;
const input = this.data.inputs[inputIndex];
if (input.nonWitnessUtxo) {
addNonWitnessTxCache(this.__CACHE, input, inputIndex);
}
c.__FEE_RATE = undefined;
c.__EXTRACTED_TX = undefined;
return this;
@ -153,7 +172,7 @@ class Psbt extends bip174_1.Psbt {
return this;
}
addOutput(outputData) {
checkInputsForPartialSig(this.inputs, 'addOutput');
checkInputsForPartialSig(this.data.inputs, 'addOutput');
const { address } = outputData;
if (typeof address === 'string') {
const { network } = this.opts;
@ -162,30 +181,24 @@ class Psbt extends bip174_1.Psbt {
}
const c = this.__CACHE;
const outputAdder = getOutputAdder(c);
super.addOutput(outputData, true, outputAdder);
this.data.addOutput(outputData, outputAdder, true);
c.__FEE_RATE = undefined;
c.__EXTRACTED_TX = undefined;
return this;
}
addNonWitnessUtxoToInput(inputIndex, nonWitnessUtxo) {
super.addNonWitnessUtxoToInput(inputIndex, nonWitnessUtxo);
const input = this.inputs[inputIndex];
addNonWitnessTxCache(this.__CACHE, input, inputIndex);
return this;
}
extractTransaction(disableFeeCheck) {
if (!this.inputs.every(isFinalized)) throw new Error('Not finalized');
if (!this.data.inputs.every(isFinalized)) throw new Error('Not finalized');
const c = this.__CACHE;
if (!disableFeeCheck) {
checkFees(this, c, this.opts);
}
if (c.__EXTRACTED_TX) return c.__EXTRACTED_TX;
const tx = c.__TX.clone();
inputFinalizeGetAmts(this.inputs, tx, c, true);
inputFinalizeGetAmts(this.data.inputs, tx, c, true);
return tx;
}
getFeeRate() {
if (!this.inputs.every(isFinalized))
if (!this.data.inputs.every(isFinalized))
throw new Error('PSBT must be finalized to calculate fee rate');
const c = this.__CACHE;
if (c.__FEE_RATE) return c.__FEE_RATE;
@ -197,16 +210,16 @@ class Psbt extends bip174_1.Psbt {
} else {
tx = c.__TX.clone();
}
inputFinalizeGetAmts(this.inputs, tx, c, mustFinalize);
inputFinalizeGetAmts(this.data.inputs, tx, c, mustFinalize);
return c.__FEE_RATE;
}
finalizeAllInputs() {
utils_1.checkForInput(this.inputs, 0); // making sure we have at least one
range(this.inputs.length).forEach(idx => this.finalizeInput(idx));
utils_1.checkForInput(this.data.inputs, 0); // making sure we have at least one
range(this.data.inputs.length).forEach(idx => this.finalizeInput(idx));
return this;
}
finalizeInput(inputIndex) {
const input = utils_1.checkForInput(this.inputs, inputIndex);
const input = utils_1.checkForInput(this.data.inputs, inputIndex);
const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput(
inputIndex,
input,
@ -226,23 +239,23 @@ class Psbt extends bip174_1.Psbt {
isP2WSH,
);
if (finalScriptSig)
this.addFinalScriptSigToInput(inputIndex, finalScriptSig);
this.data.addFinalScriptSigToInput(inputIndex, finalScriptSig);
if (finalScriptWitness)
this.addFinalScriptWitnessToInput(inputIndex, finalScriptWitness);
this.data.addFinalScriptWitnessToInput(inputIndex, finalScriptWitness);
if (!finalScriptSig && !finalScriptWitness)
throw new Error(`Unknown error finalizing input #${inputIndex}`);
this.clearFinalizedInput(inputIndex);
this.data.clearFinalizedInput(inputIndex);
return this;
}
validateAllSignatures() {
utils_1.checkForInput(this.inputs, 0); // making sure we have at least one
const results = range(this.inputs.length).map(idx =>
utils_1.checkForInput(this.data.inputs, 0); // making sure we have at least one
const results = range(this.data.inputs.length).map(idx =>
this.validateSignatures(idx),
);
return results.reduce((final, res) => res === true && final, true);
}
validateSignatures(inputIndex, pubkey) {
const input = this.inputs[inputIndex];
const input = this.data.inputs[inputIndex];
const partialSig = (input || {}).partialSig;
if (!input || !partialSig || partialSig.length < 1)
throw new Error('No signatures to validate');
@ -280,7 +293,7 @@ class Psbt extends bip174_1.Psbt {
// as input information is added, then eventually
// optimize this method.
const results = [];
for (const i of range(this.inputs.length)) {
for (const i of range(this.data.inputs.length)) {
try {
this.signInput(i, keyPair, sighashTypes);
results.push(true);
@ -302,7 +315,7 @@ class Psbt extends bip174_1.Psbt {
// optimize this method.
const results = [];
const promises = [];
for (const [i] of this.inputs.entries()) {
for (const [i] of this.data.inputs.entries()) {
promises.push(
this.signInputAsync(i, keyPair, sighashTypes).then(
() => {
@ -330,7 +343,7 @@ class Psbt extends bip174_1.Psbt {
if (!keyPair || !keyPair.publicKey)
throw new Error('Need Signer to sign input');
const { hash, sighashType } = getHashAndSighashType(
this.inputs,
this.data.inputs,
inputIndex,
keyPair.publicKey,
this.__CACHE,
@ -340,7 +353,8 @@ class Psbt extends bip174_1.Psbt {
pubkey: keyPair.publicKey,
signature: bscript.signature.encode(keyPair.sign(hash), sighashType),
};
return this.addPartialSigToInput(inputIndex, partialSig);
this.data.addPartialSigToInput(inputIndex, partialSig);
return this;
}
signInputAsync(
inputIndex,
@ -351,7 +365,7 @@ class Psbt extends bip174_1.Psbt {
if (!keyPair || !keyPair.publicKey)
return reject(new Error('Need Signer to sign input'));
const { hash, sighashType } = getHashAndSighashType(
this.inputs,
this.data.inputs,
inputIndex,
keyPair.publicKey,
this.__CACHE,
@ -362,11 +376,94 @@ class Psbt extends bip174_1.Psbt {
pubkey: keyPair.publicKey,
signature: bscript.signature.encode(signature, sighashType),
};
this.addPartialSigToInput(inputIndex, partialSig);
this.data.addPartialSigToInput(inputIndex, partialSig);
resolve();
});
});
}
toBuffer() {
return this.data.toBuffer();
}
toHex() {
return this.data.toHex();
}
toBase64() {
return this.data.toBase64();
}
addGlobalXpubToGlobal(globalXpub) {
this.data.addGlobalXpubToGlobal(globalXpub);
return this;
}
addNonWitnessUtxoToInput(inputIndex, nonWitnessUtxo) {
this.data.addNonWitnessUtxoToInput(inputIndex, nonWitnessUtxo);
const input = this.data.inputs[inputIndex];
addNonWitnessTxCache(this.__CACHE, input, inputIndex);
return this;
}
addWitnessUtxoToInput(inputIndex, witnessUtxo) {
this.data.addWitnessUtxoToInput(inputIndex, witnessUtxo);
return this;
}
addPartialSigToInput(inputIndex, partialSig) {
this.data.addPartialSigToInput(inputIndex, partialSig);
return this;
}
addSighashTypeToInput(inputIndex, sighashType) {
this.data.addSighashTypeToInput(inputIndex, sighashType);
return this;
}
addRedeemScriptToInput(inputIndex, redeemScript) {
this.data.addRedeemScriptToInput(inputIndex, redeemScript);
return this;
}
addWitnessScriptToInput(inputIndex, witnessScript) {
this.data.addWitnessScriptToInput(inputIndex, witnessScript);
return this;
}
addBip32DerivationToInput(inputIndex, bip32Derivation) {
this.data.addBip32DerivationToInput(inputIndex, bip32Derivation);
return this;
}
addFinalScriptSigToInput(inputIndex, finalScriptSig) {
this.data.addFinalScriptSigToInput(inputIndex, finalScriptSig);
return this;
}
addFinalScriptWitnessToInput(inputIndex, finalScriptWitness) {
this.data.addFinalScriptWitnessToInput(inputIndex, finalScriptWitness);
return this;
}
addPorCommitmentToInput(inputIndex, porCommitment) {
this.data.addPorCommitmentToInput(inputIndex, porCommitment);
return this;
}
addRedeemScriptToOutput(outputIndex, redeemScript) {
this.data.addRedeemScriptToOutput(outputIndex, redeemScript);
return this;
}
addWitnessScriptToOutput(outputIndex, witnessScript) {
this.data.addWitnessScriptToOutput(outputIndex, witnessScript);
return this;
}
addBip32DerivationToOutput(outputIndex, bip32Derivation) {
this.data.addBip32DerivationToOutput(outputIndex, bip32Derivation);
return this;
}
addUnknownKeyValToGlobal(keyVal) {
this.data.addUnknownKeyValToGlobal(keyVal);
return this;
}
addUnknownKeyValToInput(inputIndex, keyVal) {
this.data.addUnknownKeyValToInput(inputIndex, keyVal);
return this;
}
addUnknownKeyValToOutput(outputIndex, keyVal) {
this.data.addUnknownKeyValToOutput(outputIndex, keyVal);
return this;
}
clearFinalizedInput(inputIndex) {
this.data.clearFinalizedInput(inputIndex);
return this;
}
}
exports.Psbt = Psbt;
function canFinalize(input, script, scriptType) {

View file

@ -143,8 +143,8 @@ describe(`Psbt`, () => {
assert.strictEqual(transaction1, f.transaction)
const psbt3 = Psbt.fromBase64(f.psbt)
delete psbt3.inputs[0].finalScriptSig
delete psbt3.inputs[0].finalScriptWitness
delete psbt3.data.inputs[0].finalScriptSig
delete psbt3.data.inputs[0].finalScriptWitness
assert.throws(() => {
psbt3.extractTransaction()
}, new RegExp('Not finalized'))
@ -414,7 +414,7 @@ describe(`Psbt`, () => {
assert.strictEqual(clone.toBase64(), psbt.toBase64())
assert.strictEqual(clone.toBase64(), notAClone.toBase64())
assert.strictEqual(psbt.toBase64(), notAClone.toBase64())
psbt.globalMap.unsignedTx[3] = 0xff
psbt.data.globalMap.unsignedTx[3] = 0xff
assert.notStrictEqual(clone.toBase64(), psbt.toBase64())
assert.notStrictEqual(clone.toBase64(), notAClone.toBase64())
assert.strictEqual(psbt.toBase64(), notAClone.toBase64())
@ -542,14 +542,14 @@ describe(`Psbt`, () => {
// Cache is populated
psbt.addNonWitnessUtxoToInput(index, f.nonWitnessUtxo)
const value = psbt.inputs[index].nonWitnessUtxo
const value = psbt.data.inputs[index].nonWitnessUtxo
assert.ok(psbt.__CACHE.__NON_WITNESS_UTXO_BUF_CACHE[index].equals(value))
assert.ok(psbt.__CACHE.__NON_WITNESS_UTXO_BUF_CACHE[index].equals(f.nonWitnessUtxo))
// Cache is rebuilt from internal transaction object when cleared
psbt.inputs[index].nonWitnessUtxo = Buffer.from([1,2,3])
psbt.data.inputs[index].nonWitnessUtxo = Buffer.from([1,2,3])
psbt.__CACHE.__NON_WITNESS_UTXO_BUF_CACHE[index] = undefined
assert.ok(psbt.inputs[index].nonWitnessUtxo.equals(value))
assert.ok(psbt.data.inputs[index].nonWitnessUtxo.equals(value))
})
})
})

View file

@ -1,11 +1,21 @@
import { Psbt as PsbtBase } from 'bip174';
import * as varuint from 'bip174/src/lib/converter/varint';
import {
Bip32Derivation,
FinalScriptSig,
FinalScriptWitness,
GlobalXpub,
KeyValue,
NonWitnessUtxo,
PartialSig,
PorCommitment,
PsbtInput,
RedeemScript,
SighashType,
TransactionInput,
TransactionOutput,
WitnessScript,
WitnessUtxo,
} from 'bip174/src/lib/interfaces';
import { checkForInput } from 'bip174/src/lib/utils';
import { toOutputScript } from './address';
@ -26,37 +36,42 @@ const DEFAULT_OPTS: PsbtOpts = {
maximumFeeRate: 5000, // satoshi per byte
};
export class Psbt extends PsbtBase {
static fromTransaction<T extends typeof PsbtBase>(
this: T,
txBuf: Buffer,
): InstanceType<T> {
export class Psbt {
static fromTransaction(txBuf: Buffer, opts: PsbtOptsOptional = {}): Psbt {
const tx = Transaction.fromBuffer(txBuf);
checkTxEmpty(tx);
const psbt = new this() as Psbt;
const psbtBase = new PsbtBase();
const psbt = new Psbt(opts, psbtBase);
psbt.__CACHE.__TX = tx;
checkTxForDupeIns(tx, psbt.__CACHE);
let inputCount = tx.ins.length;
let outputCount = tx.outs.length;
while (inputCount > 0) {
psbt.inputs.push({
keyVals: [],
psbtBase.inputs.push({
unknownKeyVals: [],
});
inputCount--;
}
while (outputCount > 0) {
psbt.outputs.push({
keyVals: [],
psbtBase.outputs.push({
unknownKeyVals: [],
});
outputCount--;
}
return psbt as InstanceType<T>;
return psbt;
}
static fromBuffer<T extends typeof PsbtBase>(
this: T,
buffer: Buffer,
): InstanceType<T> {
static fromBase64(data: string, opts: PsbtOptsOptional = {}): Psbt {
const buffer = Buffer.from(data, 'base64');
return this.fromBuffer(buffer, opts);
}
static fromHex(data: string, opts: PsbtOptsOptional = {}): Psbt {
const buffer = Buffer.from(data, 'hex');
return this.fromBuffer(buffer, opts);
}
static fromBuffer(buffer: Buffer, opts: PsbtOptsOptional = {}): Psbt {
let tx: Transaction | undefined;
const txCountGetter = (
txBuf: Buffer,
@ -71,10 +86,11 @@ export class Psbt extends PsbtBase {
outputCount: tx.outs.length,
};
};
const psbt = super.fromBuffer(buffer, txCountGetter) as Psbt;
const psbtBase = PsbtBase.fromBuffer(buffer, txCountGetter);
const psbt = new Psbt(opts, psbtBase);
psbt.__CACHE.__TX = tx!;
checkTxForDupeIns(tx!, psbt.__CACHE);
return psbt as InstanceType<T>;
return psbt;
}
private __CACHE: PsbtCache = {
@ -85,17 +101,19 @@ export class Psbt extends PsbtBase {
};
private opts: PsbtOpts;
constructor(opts: PsbtOptsOptional = {}) {
super();
constructor(
opts: PsbtOptsOptional = {},
readonly data: PsbtBase = new PsbtBase(),
) {
// set defaults
this.opts = Object.assign({}, DEFAULT_OPTS, opts);
const c = this.__CACHE;
c.__TX = Transaction.fromBuffer(this.globalMap.unsignedTx!);
this.setVersion(2);
c.__TX = Transaction.fromBuffer(data.globalMap.unsignedTx!);
if (this.data.inputs.length === 0) this.setVersion(2);
// set cache
delete this.globalMap.unsignedTx;
Object.defineProperty(this.globalMap, 'unsignedTx', {
delete data.globalMap.unsignedTx;
Object.defineProperty(data.globalMap, 'unsignedTx', {
enumerable: true,
get(): Buffer {
const buf = c.__TX_BUF_CACHE;
@ -106,8 +124,8 @@ export class Psbt extends PsbtBase {
return c.__TX_BUF_CACHE;
}
},
set(data: Buffer): void {
c.__TX_BUF_CACHE = data;
set(_data: Buffer): void {
c.__TX_BUF_CACHE = _data;
},
});
@ -127,12 +145,17 @@ export class Psbt extends PsbtBase {
}
get inputCount(): number {
return this.inputs.length;
return this.data.inputs.length;
}
combine(...those: Psbt[]): this {
this.data.combine(...those.map(o => o.data));
return this;
}
clone(): Psbt {
// TODO: more efficient cloning
const res = Psbt.fromBuffer(this.toBuffer());
const res = Psbt.fromBuffer(this.data.toBuffer());
res.opts = JSON.parse(JSON.stringify(this.opts));
return res;
}
@ -144,7 +167,7 @@ export class Psbt extends PsbtBase {
setVersion(version: number): this {
check32Bit(version);
checkInputsForPartialSig(this.inputs, 'setVersion');
checkInputsForPartialSig(this.data.inputs, 'setVersion');
const c = this.__CACHE;
c.__TX.version = version;
c.__TX_BUF_CACHE = undefined;
@ -154,7 +177,7 @@ export class Psbt extends PsbtBase {
setLocktime(locktime: number): this {
check32Bit(locktime);
checkInputsForPartialSig(this.inputs, 'setLocktime');
checkInputsForPartialSig(this.data.inputs, 'setLocktime');
const c = this.__CACHE;
c.__TX.locktime = locktime;
c.__TX_BUF_CACHE = undefined;
@ -164,7 +187,7 @@ export class Psbt extends PsbtBase {
setSequence(inputIndex: number, sequence: number): this {
check32Bit(sequence);
checkInputsForPartialSig(this.inputs, 'setSequence');
checkInputsForPartialSig(this.data.inputs, 'setSequence');
const c = this.__CACHE;
if (c.__TX.ins.length <= inputIndex) {
throw new Error('Input index too high');
@ -181,10 +204,16 @@ export class Psbt extends PsbtBase {
}
addInput(inputData: TransactionInput): this {
checkInputsForPartialSig(this.inputs, 'addInput');
checkInputsForPartialSig(this.data.inputs, 'addInput');
const c = this.__CACHE;
const inputAdder = getInputAdder(c);
super.addInput(inputData, inputAdder);
this.data.addInput(inputData, inputAdder);
const inputIndex = this.data.inputs.length - 1;
const input = this.data.inputs[inputIndex];
if (input.nonWitnessUtxo) {
addNonWitnessTxCache(this.__CACHE, input, inputIndex);
}
c.__FEE_RATE = undefined;
c.__EXTRACTED_TX = undefined;
return this;
@ -196,7 +225,7 @@ export class Psbt extends PsbtBase {
}
addOutput(outputData: TransactionOutput): this {
checkInputsForPartialSig(this.inputs, 'addOutput');
checkInputsForPartialSig(this.data.inputs, 'addOutput');
const { address } = outputData as any;
if (typeof address === 'string') {
const { network } = this.opts;
@ -205,36 +234,26 @@ export class Psbt extends PsbtBase {
}
const c = this.__CACHE;
const outputAdder = getOutputAdder(c);
super.addOutput(outputData, true, outputAdder);
this.data.addOutput(outputData, outputAdder, true);
c.__FEE_RATE = undefined;
c.__EXTRACTED_TX = undefined;
return this;
}
addNonWitnessUtxoToInput(
inputIndex: number,
nonWitnessUtxo: NonWitnessUtxo,
): this {
super.addNonWitnessUtxoToInput(inputIndex, nonWitnessUtxo);
const input = this.inputs[inputIndex];
addNonWitnessTxCache(this.__CACHE, input, inputIndex);
return this;
}
extractTransaction(disableFeeCheck?: boolean): Transaction {
if (!this.inputs.every(isFinalized)) throw new Error('Not finalized');
if (!this.data.inputs.every(isFinalized)) throw new Error('Not finalized');
const c = this.__CACHE;
if (!disableFeeCheck) {
checkFees(this, c, this.opts);
}
if (c.__EXTRACTED_TX) return c.__EXTRACTED_TX;
const tx = c.__TX.clone();
inputFinalizeGetAmts(this.inputs, tx, c, true);
inputFinalizeGetAmts(this.data.inputs, tx, c, true);
return tx;
}
getFeeRate(): number {
if (!this.inputs.every(isFinalized))
if (!this.data.inputs.every(isFinalized))
throw new Error('PSBT must be finalized to calculate fee rate');
const c = this.__CACHE;
if (c.__FEE_RATE) return c.__FEE_RATE;
@ -246,18 +265,18 @@ export class Psbt extends PsbtBase {
} else {
tx = c.__TX.clone();
}
inputFinalizeGetAmts(this.inputs, tx, c, mustFinalize);
inputFinalizeGetAmts(this.data.inputs, tx, c, mustFinalize);
return c.__FEE_RATE!;
}
finalizeAllInputs(): this {
checkForInput(this.inputs, 0); // making sure we have at least one
range(this.inputs.length).forEach(idx => this.finalizeInput(idx));
checkForInput(this.data.inputs, 0); // making sure we have at least one
range(this.data.inputs.length).forEach(idx => this.finalizeInput(idx));
return this;
}
finalizeInput(inputIndex: number): this {
const input = checkForInput(this.inputs, inputIndex);
const input = checkForInput(this.data.inputs, inputIndex);
const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput(
inputIndex,
input,
@ -281,26 +300,26 @@ export class Psbt extends PsbtBase {
);
if (finalScriptSig)
this.addFinalScriptSigToInput(inputIndex, finalScriptSig);
this.data.addFinalScriptSigToInput(inputIndex, finalScriptSig);
if (finalScriptWitness)
this.addFinalScriptWitnessToInput(inputIndex, finalScriptWitness);
this.data.addFinalScriptWitnessToInput(inputIndex, finalScriptWitness);
if (!finalScriptSig && !finalScriptWitness)
throw new Error(`Unknown error finalizing input #${inputIndex}`);
this.clearFinalizedInput(inputIndex);
this.data.clearFinalizedInput(inputIndex);
return this;
}
validateAllSignatures(): boolean {
checkForInput(this.inputs, 0); // making sure we have at least one
const results = range(this.inputs.length).map(idx =>
checkForInput(this.data.inputs, 0); // making sure we have at least one
const results = range(this.data.inputs.length).map(idx =>
this.validateSignatures(idx),
);
return results.reduce((final, res) => res === true && final, true);
}
validateSignatures(inputIndex: number, pubkey?: Buffer): boolean {
const input = this.inputs[inputIndex];
const input = this.data.inputs[inputIndex];
const partialSig = (input || {}).partialSig;
if (!input || !partialSig || partialSig.length < 1)
throw new Error('No signatures to validate');
@ -343,7 +362,7 @@ export class Psbt extends PsbtBase {
// as input information is added, then eventually
// optimize this method.
const results: boolean[] = [];
for (const i of range(this.inputs.length)) {
for (const i of range(this.data.inputs.length)) {
try {
this.signInput(i, keyPair, sighashTypes);
results.push(true);
@ -371,7 +390,7 @@ export class Psbt extends PsbtBase {
// optimize this method.
const results: boolean[] = [];
const promises: Array<Promise<void>> = [];
for (const [i] of this.inputs.entries()) {
for (const [i] of this.data.inputs.entries()) {
promises.push(
this.signInputAsync(i, keyPair, sighashTypes).then(
() => {
@ -401,7 +420,7 @@ export class Psbt extends PsbtBase {
if (!keyPair || !keyPair.publicKey)
throw new Error('Need Signer to sign input');
const { hash, sighashType } = getHashAndSighashType(
this.inputs,
this.data.inputs,
inputIndex,
keyPair.publicKey,
this.__CACHE,
@ -413,7 +432,8 @@ export class Psbt extends PsbtBase {
signature: bscript.signature.encode(keyPair.sign(hash), sighashType),
};
return this.addPartialSigToInput(inputIndex, partialSig);
this.data.addPartialSigToInput(inputIndex, partialSig);
return this;
}
signInputAsync(
@ -426,7 +446,7 @@ export class Psbt extends PsbtBase {
if (!keyPair || !keyPair.publicKey)
return reject(new Error('Need Signer to sign input'));
const { hash, sighashType } = getHashAndSighashType(
this.inputs,
this.data.inputs,
inputIndex,
keyPair.publicKey,
this.__CACHE,
@ -439,12 +459,143 @@ export class Psbt extends PsbtBase {
signature: bscript.signature.encode(signature, sighashType),
};
this.addPartialSigToInput(inputIndex, partialSig);
this.data.addPartialSigToInput(inputIndex, partialSig);
resolve();
});
},
);
}
toBuffer(): Buffer {
return this.data.toBuffer();
}
toHex(): string {
return this.data.toHex();
}
toBase64(): string {
return this.data.toBase64();
}
addGlobalXpubToGlobal(globalXpub: GlobalXpub): this {
this.data.addGlobalXpubToGlobal(globalXpub);
return this;
}
addNonWitnessUtxoToInput(
inputIndex: number,
nonWitnessUtxo: NonWitnessUtxo,
): this {
this.data.addNonWitnessUtxoToInput(inputIndex, nonWitnessUtxo);
const input = this.data.inputs[inputIndex];
addNonWitnessTxCache(this.__CACHE, input, inputIndex);
return this;
}
addWitnessUtxoToInput(inputIndex: number, witnessUtxo: WitnessUtxo): this {
this.data.addWitnessUtxoToInput(inputIndex, witnessUtxo);
return this;
}
addPartialSigToInput(inputIndex: number, partialSig: PartialSig): this {
this.data.addPartialSigToInput(inputIndex, partialSig);
return this;
}
addSighashTypeToInput(inputIndex: number, sighashType: SighashType): this {
this.data.addSighashTypeToInput(inputIndex, sighashType);
return this;
}
addRedeemScriptToInput(inputIndex: number, redeemScript: RedeemScript): this {
this.data.addRedeemScriptToInput(inputIndex, redeemScript);
return this;
}
addWitnessScriptToInput(
inputIndex: number,
witnessScript: WitnessScript,
): this {
this.data.addWitnessScriptToInput(inputIndex, witnessScript);
return this;
}
addBip32DerivationToInput(
inputIndex: number,
bip32Derivation: Bip32Derivation,
): this {
this.data.addBip32DerivationToInput(inputIndex, bip32Derivation);
return this;
}
addFinalScriptSigToInput(
inputIndex: number,
finalScriptSig: FinalScriptSig,
): this {
this.data.addFinalScriptSigToInput(inputIndex, finalScriptSig);
return this;
}
addFinalScriptWitnessToInput(
inputIndex: number,
finalScriptWitness: FinalScriptWitness,
): this {
this.data.addFinalScriptWitnessToInput(inputIndex, finalScriptWitness);
return this;
}
addPorCommitmentToInput(
inputIndex: number,
porCommitment: PorCommitment,
): this {
this.data.addPorCommitmentToInput(inputIndex, porCommitment);
return this;
}
addRedeemScriptToOutput(
outputIndex: number,
redeemScript: RedeemScript,
): this {
this.data.addRedeemScriptToOutput(outputIndex, redeemScript);
return this;
}
addWitnessScriptToOutput(
outputIndex: number,
witnessScript: WitnessScript,
): this {
this.data.addWitnessScriptToOutput(outputIndex, witnessScript);
return this;
}
addBip32DerivationToOutput(
outputIndex: number,
bip32Derivation: Bip32Derivation,
): this {
this.data.addBip32DerivationToOutput(outputIndex, bip32Derivation);
return this;
}
addUnknownKeyValToGlobal(keyVal: KeyValue): this {
this.data.addUnknownKeyValToGlobal(keyVal);
return this;
}
addUnknownKeyValToInput(inputIndex: number, keyVal: KeyValue): this {
this.data.addUnknownKeyValToInput(inputIndex, keyVal);
return this;
}
addUnknownKeyValToOutput(outputIndex: number, keyVal: KeyValue): this {
this.data.addUnknownKeyValToOutput(outputIndex, keyVal);
return this;
}
clearFinalizedInput(inputIndex: number): this {
this.data.clearFinalizedInput(inputIndex);
return this;
}
}
interface PsbtCache {

36
types/psbt.d.ts vendored
View file

@ -1,16 +1,20 @@
/// <reference types="node" />
import { Psbt as PsbtBase } from 'bip174';
import { NonWitnessUtxo, TransactionInput, TransactionOutput } from 'bip174/src/lib/interfaces';
import { Bip32Derivation, FinalScriptSig, FinalScriptWitness, GlobalXpub, KeyValue, NonWitnessUtxo, PartialSig, PorCommitment, RedeemScript, SighashType, TransactionInput, TransactionOutput, WitnessScript, WitnessUtxo } from 'bip174/src/lib/interfaces';
import { Signer, SignerAsync } from './ecpair';
import { Network } from './networks';
import { Transaction } from './transaction';
export declare class Psbt extends PsbtBase {
static fromTransaction<T extends typeof PsbtBase>(this: T, txBuf: Buffer): InstanceType<T>;
static fromBuffer<T extends typeof PsbtBase>(this: T, buffer: Buffer): InstanceType<T>;
export declare class Psbt {
readonly data: PsbtBase;
static fromTransaction(txBuf: Buffer, opts?: PsbtOptsOptional): Psbt;
static fromBase64(data: string, opts?: PsbtOptsOptional): Psbt;
static fromHex(data: string, opts?: PsbtOptsOptional): Psbt;
static fromBuffer(buffer: Buffer, opts?: PsbtOptsOptional): Psbt;
private __CACHE;
private opts;
constructor(opts?: PsbtOptsOptional);
constructor(opts?: PsbtOptsOptional, data?: PsbtBase);
readonly inputCount: number;
combine(...those: Psbt[]): this;
clone(): Psbt;
setMaximumFeeRate(satoshiPerByte: number): void;
setVersion(version: number): this;
@ -20,7 +24,6 @@ export declare class Psbt extends PsbtBase {
addInput(inputData: TransactionInput): this;
addOutputs(outputDatas: TransactionOutput[]): this;
addOutput(outputData: TransactionOutput): this;
addNonWitnessUtxoToInput(inputIndex: number, nonWitnessUtxo: NonWitnessUtxo): this;
extractTransaction(disableFeeCheck?: boolean): Transaction;
getFeeRate(): number;
finalizeAllInputs(): this;
@ -31,6 +34,27 @@ export declare class Psbt extends PsbtBase {
signAsync(keyPair: SignerAsync, sighashTypes?: number[]): Promise<void>;
signInput(inputIndex: number, keyPair: Signer, sighashTypes?: number[]): this;
signInputAsync(inputIndex: number, keyPair: SignerAsync, sighashTypes?: number[]): Promise<void>;
toBuffer(): Buffer;
toHex(): string;
toBase64(): string;
addGlobalXpubToGlobal(globalXpub: GlobalXpub): this;
addNonWitnessUtxoToInput(inputIndex: number, nonWitnessUtxo: NonWitnessUtxo): this;
addWitnessUtxoToInput(inputIndex: number, witnessUtxo: WitnessUtxo): this;
addPartialSigToInput(inputIndex: number, partialSig: PartialSig): this;
addSighashTypeToInput(inputIndex: number, sighashType: SighashType): this;
addRedeemScriptToInput(inputIndex: number, redeemScript: RedeemScript): this;
addWitnessScriptToInput(inputIndex: number, witnessScript: WitnessScript): this;
addBip32DerivationToInput(inputIndex: number, bip32Derivation: Bip32Derivation): this;
addFinalScriptSigToInput(inputIndex: number, finalScriptSig: FinalScriptSig): this;
addFinalScriptWitnessToInput(inputIndex: number, finalScriptWitness: FinalScriptWitness): this;
addPorCommitmentToInput(inputIndex: number, porCommitment: PorCommitment): this;
addRedeemScriptToOutput(outputIndex: number, redeemScript: RedeemScript): this;
addWitnessScriptToOutput(outputIndex: number, witnessScript: WitnessScript): this;
addBip32DerivationToOutput(outputIndex: number, bip32Derivation: Bip32Derivation): this;
addUnknownKeyValToGlobal(keyVal: KeyValue): this;
addUnknownKeyValToInput(inputIndex: number, keyVal: KeyValue): this;
addUnknownKeyValToOutput(outputIndex: number, keyVal: KeyValue): this;
clearFinalizedInput(inputIndex: number): this;
}
interface PsbtOptsOptional {
network?: Network;