diff --git a/package-lock.json b/package-lock.json index fce68c4..fabd361 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 03e6d5f..a32021a 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/psbt.js b/src/psbt.js index 752714f..578e17c 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -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) { diff --git a/test/psbt.js b/test/psbt.js index 99bd9c1..1804e67 100644 --- a/test/psbt.js +++ b/test/psbt.js @@ -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)) }) }) }) diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index 928ca04..5a73031 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -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( - this: T, - txBuf: Buffer, - ): InstanceType { +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; + return psbt; } - static fromBuffer( - this: T, - buffer: Buffer, - ): InstanceType { + 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; + 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> = []; - 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 { diff --git a/types/psbt.d.ts b/types/psbt.d.ts index 95f6624..533b8fd 100644 --- a/types/psbt.d.ts +++ b/types/psbt.d.ts @@ -1,16 +1,20 @@ /// 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(this: T, txBuf: Buffer): InstanceType; - static fromBuffer(this: T, buffer: Buffer): InstanceType; +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; signInput(inputIndex: number, keyPair: Signer, sighashTypes?: number[]): this; signInputAsync(inputIndex: number, keyPair: SignerAsync, sighashTypes?: number[]): Promise; + 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;