diff --git a/package-lock.json b/package-lock.json index fabd361..3a7f8be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -200,9 +200,8 @@ } }, "bip174": { - "version": "0.0.15", - "resolved": "https://registry.npmjs.org/bip174/-/bip174-0.0.15.tgz", - "integrity": "sha512-mK/s9p7i+PG7W2s2cAedNVk1NDZQn9wAoq1DlsS2+1zz5TXR3TRTzqRqm9BQtOXwbkxkhfLwlmsOjuiRdAYkdg==" + "version": "git+https://github.com/bitcoinjs/bip174.git#5137e367c7a3a4e281ee01574f88977cdd4be896", + "from": "git+https://github.com/bitcoinjs/bip174.git#interface" }, "bip32": { "version": "2.0.3", diff --git a/package.json b/package.json index a32021a..0b91e4e 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "dependencies": { "@types/node": "10.12.18", "bech32": "^1.1.2", - "bip174": "0.0.15", + "bip174": "git+https://github.com/bitcoinjs/bip174.git#interface", "bip32": "^2.0.3", "bip66": "^1.1.0", "bitcoin-ops": "^1.4.0", diff --git a/src/psbt.js b/src/psbt.js index 578e17c..97e9e9d 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -16,7 +16,7 @@ const DEFAULT_OPTS = { maximumFeeRate: 5000, }; class Psbt { - constructor(opts = {}, data = new bip174_1.Psbt()) { + constructor(opts = {}, data = new bip174_1.Psbt(new PsbtTransaction())) { this.data = data; this.__CACHE = { __NON_WITNESS_UTXO_TX_CACHE: [], @@ -27,11 +27,11 @@ class Psbt { // set defaults this.opts = Object.assign({}, DEFAULT_OPTS, opts); const c = this.__CACHE; - c.__TX = transaction_1.Transaction.fromBuffer(data.globalMap.unsignedTx); + c.__TX = this.data.globalMap.unsignedTx.tx; if (this.data.inputs.length === 0) this.setVersion(2); // set cache - delete data.globalMap.unsignedTx; - Object.defineProperty(data.globalMap, 'unsignedTx', { + this.unsignedTx = Buffer.from([]); + Object.defineProperty(this, 'unsignedTx', { enumerable: true, get() { const buf = c.__TX_BUF_CACHE; @@ -56,24 +56,20 @@ class Psbt { dpew(this, 'opts', false, true); } static fromTransaction(txBuf, opts = {}) { - const tx = transaction_1.Transaction.fromBuffer(txBuf); - checkTxEmpty(tx); - const psbtBase = new bip174_1.Psbt(); + const tx = new PsbtTransaction(txBuf); + checkTxEmpty(tx.tx); + const psbtBase = new bip174_1.Psbt(tx); const psbt = new Psbt(opts, psbtBase); - psbt.__CACHE.__TX = tx; - checkTxForDupeIns(tx, psbt.__CACHE); - let inputCount = tx.ins.length; - let outputCount = tx.outs.length; + psbt.__CACHE.__TX = tx.tx; + checkTxForDupeIns(tx.tx, psbt.__CACHE); + let inputCount = tx.tx.ins.length; + let outputCount = tx.tx.outs.length; while (inputCount > 0) { - psbtBase.inputs.push({ - unknownKeyVals: [], - }); + psbtBase.inputs.push({}); inputCount--; } while (outputCount > 0) { - psbtBase.outputs.push({ - unknownKeyVals: [], - }); + psbtBase.outputs.push({}); outputCount--; } return psbt; @@ -87,16 +83,8 @@ class Psbt { return this.fromBuffer(buffer, opts); } static fromBuffer(buffer, opts = {}) { - let tx; - const txCountGetter = txBuf => { - tx = transaction_1.Transaction.fromBuffer(txBuf); - checkTxEmpty(tx); - return { - inputCount: tx.ins.length, - outputCount: tx.outs.length, - }; - }; - const psbtBase = bip174_1.Psbt.fromBuffer(buffer, txCountGetter); + const psbtBase = bip174_1.Psbt.fromBuffer(buffer, transactionFromBuffer); + const tx = psbtBase.globalMap.unsignedTx.tx; const psbt = new Psbt(opts, psbtBase); psbt.__CACHE.__TX = tx; checkTxForDupeIns(tx, psbt.__CACHE); @@ -156,8 +144,9 @@ class Psbt { addInput(inputData) { checkInputsForPartialSig(this.data.inputs, 'addInput'); const c = this.__CACHE; - const inputAdder = getInputAdder(c); - this.data.addInput(inputData, inputAdder); + this.data.addInput(inputData); + const txIn = c.__TX.ins[c.__TX.ins.length - 1]; + checkTxInputCache(c, txIn); const inputIndex = this.data.inputs.length - 1; const input = this.data.inputs[inputIndex]; if (input.nonWitnessUtxo) { @@ -180,8 +169,7 @@ class Psbt { outputData = Object.assign(outputData, { script }); } const c = this.__CACHE; - const outputAdder = getOutputAdder(c); - this.data.addOutput(outputData, outputAdder, true); + this.data.addOutput(outputData); c.__FEE_RATE = undefined; c.__EXTRACTED_TX = undefined; return this; @@ -238,10 +226,9 @@ class Psbt { isP2SH, isP2WSH, ); - if (finalScriptSig) - this.data.addFinalScriptSigToInput(inputIndex, finalScriptSig); + if (finalScriptSig) this.data.updateInput(inputIndex, { finalScriptSig }); if (finalScriptWitness) - this.data.addFinalScriptWitnessToInput(inputIndex, finalScriptWitness); + this.data.updateInput(inputIndex, { finalScriptWitness }); if (!finalScriptSig && !finalScriptWitness) throw new Error(`Unknown error finalizing input #${inputIndex}`); this.data.clearFinalizedInput(inputIndex); @@ -349,11 +336,13 @@ class Psbt { this.__CACHE, sighashTypes, ); - const partialSig = { - pubkey: keyPair.publicKey, - signature: bscript.signature.encode(keyPair.sign(hash), sighashType), - }; - this.data.addPartialSigToInput(inputIndex, partialSig); + const partialSig = [ + { + pubkey: keyPair.publicKey, + signature: bscript.signature.encode(keyPair.sign(hash), sighashType), + }, + ]; + this.data.updateInput(inputIndex, { partialSig }); return this; } signInputAsync( @@ -372,11 +361,13 @@ class Psbt { sighashTypes, ); Promise.resolve(keyPair.sign(hash)).then(signature => { - const partialSig = { - pubkey: keyPair.publicKey, - signature: bscript.signature.encode(signature, sighashType), - }; - this.data.addPartialSigToInput(inputIndex, partialSig); + const partialSig = [ + { + pubkey: keyPair.publicKey, + signature: bscript.signature.encode(signature, sighashType), + }, + ]; + this.data.updateInput(inputIndex, { partialSig }); resolve(); }); }); @@ -390,62 +381,23 @@ class Psbt { toBase64() { return this.data.toBase64(); } - addGlobalXpubToGlobal(globalXpub) { - this.data.addGlobalXpubToGlobal(globalXpub); + updateGlobal(updateData) { + this.data.updateGlobal(updateData); return this; } - addNonWitnessUtxoToInput(inputIndex, nonWitnessUtxo) { - this.data.addNonWitnessUtxoToInput(inputIndex, nonWitnessUtxo); - const input = this.data.inputs[inputIndex]; - addNonWitnessTxCache(this.__CACHE, input, inputIndex); + updateInput(inputIndex, updateData) { + this.data.updateInput(inputIndex, updateData); + if (updateData.nonWitnessUtxo) { + addNonWitnessTxCache( + this.__CACHE, + this.data.inputs[inputIndex], + 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); + updateOutput(outputIndex, updateData) { + this.data.updateOutput(outputIndex, updateData); return this; } addUnknownKeyValToGlobal(keyVal) { @@ -466,6 +418,54 @@ class Psbt { } } exports.Psbt = Psbt; +const transactionFromBuffer = buffer => new PsbtTransaction(buffer); +class PsbtTransaction { + constructor(buffer = Buffer.from([2, 0, 0, 0, 0, 0, 0, 0, 0, 0])) { + this.tx = transaction_1.Transaction.fromBuffer(buffer); + if (this.tx.ins.some(input => input.script.length !== 0)) { + throw new Error('Format Error: Transaction ScriptSigs are not empty'); + } + Object.defineProperty(this, 'tx', { + enumerable: false, + writable: true, + }); + } + getInputOutputCounts() { + return { + inputCount: this.tx.ins.length, + outputCount: this.tx.outs.length, + }; + } + addInput(input) { + if ( + input.hash === undefined || + input.index === undefined || + (!Buffer.isBuffer(input.hash) && typeof input.hash !== 'string') || + typeof input.index !== 'number' + ) { + throw new Error('Error adding input.'); + } + const hash = + typeof input.hash === 'string' + ? bufferutils_1.reverseBuffer(Buffer.from(input.hash, 'hex')) + : input.hash; + this.tx.addInput(hash, input.index, input.sequence); + } + addOutput(output) { + if ( + output.script === undefined || + output.value === undefined || + !Buffer.isBuffer(output.script) || + typeof output.value !== 'number' + ) { + throw new Error('Error adding output.'); + } + this.tx.addOutput(output.script, output.value); + } + toBuffer() { + return this.tx.toBuffer(); + } +} function canFinalize(input, script, scriptType) { switch (scriptType) { case 'pubkey': @@ -769,55 +769,6 @@ function getHashForSig(inputIndex, input, cache, sighashTypes) { hash, }; } -function getInputAdder(cache) { - const selfCache = cache; - return (_inputData, txBuf) => { - if ( - !txBuf || - _inputData.hash === undefined || - _inputData.index === undefined || - (!Buffer.isBuffer(_inputData.hash) && - typeof _inputData.hash !== 'string') || - typeof _inputData.index !== 'number' - ) { - throw new Error('Error adding input.'); - } - const prevHash = Buffer.isBuffer(_inputData.hash) - ? _inputData.hash - : bufferutils_1.reverseBuffer(Buffer.from(_inputData.hash, 'hex')); - // Check if input already exists in cache. - const input = { hash: prevHash, index: _inputData.index }; - checkTxInputCache(selfCache, input); - selfCache.__TX.ins.push( - Object.assign({}, input, { - script: Buffer.alloc(0), - sequence: - _inputData.sequence || transaction_1.Transaction.DEFAULT_SEQUENCE, - witness: [], - }), - ); - return selfCache.__TX.toBuffer(); - }; -} -function getOutputAdder(cache) { - const selfCache = cache; - return (_outputData, txBuf) => { - if ( - !txBuf || - _outputData.script === undefined || - _outputData.value === undefined || - !Buffer.isBuffer(_outputData.script) || - typeof _outputData.value !== 'number' - ) { - throw new Error('Error adding output.'); - } - selfCache.__TX.outs.push({ - script: _outputData.script, - value: _outputData.value, - }); - return selfCache.__TX.toBuffer(); - }; -} function getPayment(script, scriptType, partialSig) { let payment; switch (scriptType) { diff --git a/test/psbt.js b/test/psbt.js index 8ed7b51..e52fae2 100644 --- a/test/psbt.js +++ b/test/psbt.js @@ -72,20 +72,8 @@ describe(`Psbt`, () => { const fixtureData = f[`${inputOrOutput}Data`] if (fixtureData) { for (const [i, data] of fixtureData.entries()) { - const attrs = Object.keys(data) - for (const attr of attrs) { - const upperAttr = upperCaseFirstLetter(attr) - let adder = psbt[`add${upperAttr}To${upperCaseFirstLetter(inputOrOutput)}`] - if (adder !== undefined) { - adder = adder.bind(psbt) - const arg = data[attr] - if (Array.isArray(arg)) { - arg.forEach(a => adder(i, a)) - } else { - adder(i, arg) - } - } - } + const txt = upperCaseFirstLetter(inputOrOutput) + psbt[`update${txt}`](i, data) } } } @@ -309,9 +297,11 @@ describe(`Psbt`, () => { assert.throws(() => { psbt.finalizeAllInputs() }, new RegExp('No script found for input #0')) - psbt.addWitnessUtxoToInput(0, { - script: Buffer.from('0014d85c2b71d0060b09c9886aeb815e50991dda124d', 'hex'), - value: 2e5 + psbt.updateInput(0, { + witnessUtxo: { + script: Buffer.from('0014d85c2b71d0060b09c9886aeb815e50991dda124d', 'hex'), + value: 2e5 + } }) assert.throws(() => { psbt.finalizeAllInputs() @@ -438,7 +428,7 @@ describe(`Psbt`, () => { assert.strictEqual(clone.toBase64(), psbt.toBase64()) assert.strictEqual(clone.toBase64(), notAClone.toBase64()) assert.strictEqual(psbt.toBase64(), notAClone.toBase64()) - psbt.data.globalMap.unsignedTx[3] = 0xff + psbt.__CACHE.__TX.version |= 0xff0000 assert.notStrictEqual(clone.toBase64(), psbt.toBase64()) assert.notStrictEqual(clone.toBase64(), notAClone.toBase64()) assert.strictEqual(psbt.toBase64(), notAClone.toBase64()) @@ -565,7 +555,7 @@ describe(`Psbt`, () => { assert.strictEqual(psbt.__CACHE.__NON_WITNESS_UTXO_BUF_CACHE[index], undefined) // Cache is populated - psbt.addNonWitnessUtxoToInput(index, f.nonWitnessUtxo) + psbt.updateInput(index, { nonWitnessUtxo: f.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)) diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index 5a73031..0c20bc2 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -1,21 +1,16 @@ import { Psbt as PsbtBase } from 'bip174'; import * as varuint from 'bip174/src/lib/converter/varint'; import { - Bip32Derivation, - FinalScriptSig, - FinalScriptWitness, - GlobalXpub, KeyValue, - NonWitnessUtxo, PartialSig, - PorCommitment, + PsbtGlobalUpdate, PsbtInput, - RedeemScript, - SighashType, + PsbtInputUpdate, + PsbtOutputUpdate, + Transaction as ITransaction, + TransactionFromBuffer, TransactionInput, TransactionOutput, - WitnessScript, - WitnessUtxo, } from 'bip174/src/lib/interfaces'; import { checkForInput } from 'bip174/src/lib/utils'; import { toOutputScript } from './address'; @@ -38,24 +33,20 @@ const DEFAULT_OPTS: PsbtOpts = { export class Psbt { static fromTransaction(txBuf: Buffer, opts: PsbtOptsOptional = {}): Psbt { - const tx = Transaction.fromBuffer(txBuf); - checkTxEmpty(tx); - const psbtBase = new PsbtBase(); + const tx = new PsbtTransaction(txBuf); + checkTxEmpty(tx.tx); + const psbtBase = new PsbtBase(tx); const psbt = new Psbt(opts, psbtBase); - psbt.__CACHE.__TX = tx; - checkTxForDupeIns(tx, psbt.__CACHE); - let inputCount = tx.ins.length; - let outputCount = tx.outs.length; + psbt.__CACHE.__TX = tx.tx; + checkTxForDupeIns(tx.tx, psbt.__CACHE); + let inputCount = tx.tx.ins.length; + let outputCount = tx.tx.outs.length; while (inputCount > 0) { - psbtBase.inputs.push({ - unknownKeyVals: [], - }); + psbtBase.inputs.push({}); inputCount--; } while (outputCount > 0) { - psbtBase.outputs.push({ - unknownKeyVals: [], - }); + psbtBase.outputs.push({}); outputCount--; } return psbt; @@ -72,27 +63,16 @@ export class Psbt { } static fromBuffer(buffer: Buffer, opts: PsbtOptsOptional = {}): Psbt { - let tx: Transaction | undefined; - const txCountGetter = ( - txBuf: Buffer, - ): { - inputCount: number; - outputCount: number; - } => { - tx = Transaction.fromBuffer(txBuf); - checkTxEmpty(tx); - return { - inputCount: tx.ins.length, - outputCount: tx.outs.length, - }; - }; - const psbtBase = PsbtBase.fromBuffer(buffer, txCountGetter); + const psbtBase = PsbtBase.fromBuffer(buffer, transactionFromBuffer); + const tx: Transaction = (psbtBase.globalMap.unsignedTx as PsbtTransaction) + .tx; const psbt = new Psbt(opts, psbtBase); - psbt.__CACHE.__TX = tx!; - checkTxForDupeIns(tx!, psbt.__CACHE); + psbt.__CACHE.__TX = tx; + checkTxForDupeIns(tx, psbt.__CACHE); return psbt; } + unsignedTx: Buffer; private __CACHE: PsbtCache = { __NON_WITNESS_UTXO_TX_CACHE: [], __NON_WITNESS_UTXO_BUF_CACHE: [], @@ -103,17 +83,17 @@ export class Psbt { constructor( opts: PsbtOptsOptional = {}, - readonly data: PsbtBase = new PsbtBase(), + readonly data: PsbtBase = new PsbtBase(new PsbtTransaction()), ) { // set defaults this.opts = Object.assign({}, DEFAULT_OPTS, opts); const c = this.__CACHE; - c.__TX = Transaction.fromBuffer(data.globalMap.unsignedTx!); + c.__TX = (this.data.globalMap.unsignedTx as PsbtTransaction).tx; if (this.data.inputs.length === 0) this.setVersion(2); // set cache - delete data.globalMap.unsignedTx; - Object.defineProperty(data.globalMap, 'unsignedTx', { + this.unsignedTx = Buffer.from([]); + Object.defineProperty(this, 'unsignedTx', { enumerable: true, get(): Buffer { const buf = c.__TX_BUF_CACHE; @@ -206,8 +186,9 @@ export class Psbt { addInput(inputData: TransactionInput): this { checkInputsForPartialSig(this.data.inputs, 'addInput'); const c = this.__CACHE; - const inputAdder = getInputAdder(c); - this.data.addInput(inputData, inputAdder); + this.data.addInput(inputData); + const txIn = c.__TX.ins[c.__TX.ins.length - 1]; + checkTxInputCache(c, txIn); const inputIndex = this.data.inputs.length - 1; const input = this.data.inputs[inputIndex]; @@ -233,8 +214,7 @@ export class Psbt { outputData = Object.assign(outputData, { script }); } const c = this.__CACHE; - const outputAdder = getOutputAdder(c); - this.data.addOutput(outputData, outputAdder, true); + this.data.addOutput(outputData); c.__FEE_RATE = undefined; c.__EXTRACTED_TX = undefined; return this; @@ -299,10 +279,9 @@ export class Psbt { isP2WSH, ); - if (finalScriptSig) - this.data.addFinalScriptSigToInput(inputIndex, finalScriptSig); + if (finalScriptSig) this.data.updateInput(inputIndex, { finalScriptSig }); if (finalScriptWitness) - this.data.addFinalScriptWitnessToInput(inputIndex, finalScriptWitness); + this.data.updateInput(inputIndex, { finalScriptWitness }); if (!finalScriptSig && !finalScriptWitness) throw new Error(`Unknown error finalizing input #${inputIndex}`); @@ -427,12 +406,14 @@ export class Psbt { sighashTypes, ); - const partialSig = { - pubkey: keyPair.publicKey, - signature: bscript.signature.encode(keyPair.sign(hash), sighashType), - }; + const partialSig = [ + { + pubkey: keyPair.publicKey, + signature: bscript.signature.encode(keyPair.sign(hash), sighashType), + }, + ]; - this.data.addPartialSigToInput(inputIndex, partialSig); + this.data.updateInput(inputIndex, { partialSig }); return this; } @@ -454,12 +435,14 @@ export class Psbt { ); Promise.resolve(keyPair.sign(hash)).then(signature => { - const partialSig = { - pubkey: keyPair.publicKey, - signature: bscript.signature.encode(signature, sighashType), - }; + const partialSig = [ + { + pubkey: keyPair.publicKey, + signature: bscript.signature.encode(signature, sighashType), + }, + ]; - this.data.addPartialSigToInput(inputIndex, partialSig); + this.data.updateInput(inputIndex, { partialSig }); resolve(); }); }, @@ -478,102 +461,25 @@ export class Psbt { return this.data.toBase64(); } - addGlobalXpubToGlobal(globalXpub: GlobalXpub): this { - this.data.addGlobalXpubToGlobal(globalXpub); + updateGlobal(updateData: PsbtGlobalUpdate): this { + this.data.updateGlobal(updateData); return this; } - addNonWitnessUtxoToInput( - inputIndex: number, - nonWitnessUtxo: NonWitnessUtxo, - ): this { - this.data.addNonWitnessUtxoToInput(inputIndex, nonWitnessUtxo); - const input = this.data.inputs[inputIndex]; - addNonWitnessTxCache(this.__CACHE, input, inputIndex); + updateInput(inputIndex: number, updateData: PsbtInputUpdate): this { + this.data.updateInput(inputIndex, updateData); + if (updateData.nonWitnessUtxo) { + addNonWitnessTxCache( + this.__CACHE, + this.data.inputs[inputIndex], + 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); + updateOutput(outputIndex: number, updateData: PsbtOutputUpdate): this { + this.data.updateOutput(outputIndex, updateData); return this; } @@ -618,6 +524,67 @@ interface PsbtOpts { maximumFeeRate: number; } +const transactionFromBuffer: TransactionFromBuffer = ( + buffer: Buffer, +): ITransaction => new PsbtTransaction(buffer); + +class PsbtTransaction implements ITransaction { + tx: Transaction; + constructor(buffer: Buffer = Buffer.from([2, 0, 0, 0, 0, 0, 0, 0, 0, 0])) { + this.tx = Transaction.fromBuffer(buffer); + if (this.tx.ins.some(input => input.script.length !== 0)) { + throw new Error('Format Error: Transaction ScriptSigs are not empty'); + } + Object.defineProperty(this, 'tx', { + enumerable: false, + writable: true, + }); + } + + getInputOutputCounts(): { + inputCount: number; + outputCount: number; + } { + return { + inputCount: this.tx.ins.length, + outputCount: this.tx.outs.length, + }; + } + + addInput(input: any): void { + if ( + (input as any).hash === undefined || + (input as any).index === undefined || + (!Buffer.isBuffer((input as any).hash) && + typeof (input as any).hash !== 'string') || + typeof (input as any).index !== 'number' + ) { + throw new Error('Error adding input.'); + } + const hash = + typeof input.hash === 'string' + ? reverseBuffer(Buffer.from(input.hash, 'hex')) + : input.hash; + this.tx.addInput(hash, input.index, input.sequence); + } + + addOutput(output: any): void { + if ( + (output as any).script === undefined || + (output as any).value === undefined || + !Buffer.isBuffer((output as any).script) || + typeof (output as any).value !== 'number' + ) { + throw new Error('Error adding output.'); + } + this.tx.addOutput(output.script, output.value); + } + + toBuffer(): Buffer { + return this.tx.toBuffer(); + } +} + function canFinalize( input: PsbtInput, script: Buffer, @@ -979,61 +946,6 @@ function getHashForSig( }; } -function getInputAdder( - cache: PsbtCache, -): (_inputData: TransactionInput, txBuf: Buffer) => Buffer { - const selfCache = cache; - return (_inputData: TransactionInput, txBuf: Buffer): Buffer => { - if ( - !txBuf || - (_inputData as any).hash === undefined || - (_inputData as any).index === undefined || - (!Buffer.isBuffer((_inputData as any).hash) && - typeof (_inputData as any).hash !== 'string') || - typeof (_inputData as any).index !== 'number' - ) { - throw new Error('Error adding input.'); - } - const prevHash = Buffer.isBuffer(_inputData.hash) - ? _inputData.hash - : reverseBuffer(Buffer.from(_inputData.hash, 'hex')); - - // Check if input already exists in cache. - const input = { hash: prevHash, index: _inputData.index }; - checkTxInputCache(selfCache, input); - - selfCache.__TX.ins.push({ - ...input, - script: Buffer.alloc(0), - sequence: _inputData.sequence || Transaction.DEFAULT_SEQUENCE, - witness: [], - }); - return selfCache.__TX.toBuffer(); - }; -} - -function getOutputAdder( - cache: PsbtCache, -): (_outputData: TransactionOutput, txBuf: Buffer) => Buffer { - const selfCache = cache; - return (_outputData: TransactionOutput, txBuf: Buffer): Buffer => { - if ( - !txBuf || - (_outputData as any).script === undefined || - (_outputData as any).value === undefined || - !Buffer.isBuffer((_outputData as any).script) || - typeof (_outputData as any).value !== 'number' - ) { - throw new Error('Error adding output.'); - } - selfCache.__TX.outs.push({ - script: (_outputData as any).script!, - value: _outputData.value, - }); - return selfCache.__TX.toBuffer(); - }; -} - function getPayment( script: Buffer, scriptType: string, diff --git a/types/psbt.d.ts b/types/psbt.d.ts index 533b8fd..e537656 100644 --- a/types/psbt.d.ts +++ b/types/psbt.d.ts @@ -1,6 +1,6 @@ /// import { Psbt as PsbtBase } from 'bip174'; -import { Bip32Derivation, FinalScriptSig, FinalScriptWitness, GlobalXpub, KeyValue, NonWitnessUtxo, PartialSig, PorCommitment, RedeemScript, SighashType, TransactionInput, TransactionOutput, WitnessScript, WitnessUtxo } from 'bip174/src/lib/interfaces'; +import { KeyValue, PsbtGlobalUpdate, PsbtInputUpdate, PsbtOutputUpdate, TransactionInput, TransactionOutput } from 'bip174/src/lib/interfaces'; import { Signer, SignerAsync } from './ecpair'; import { Network } from './networks'; import { Transaction } from './transaction'; @@ -10,6 +10,7 @@ export declare class Psbt { static fromBase64(data: string, opts?: PsbtOptsOptional): Psbt; static fromHex(data: string, opts?: PsbtOptsOptional): Psbt; static fromBuffer(buffer: Buffer, opts?: PsbtOptsOptional): Psbt; + unsignedTx: Buffer; private __CACHE; private opts; constructor(opts?: PsbtOptsOptional, data?: PsbtBase); @@ -37,20 +38,9 @@ export declare class Psbt { 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; + updateGlobal(updateData: PsbtGlobalUpdate): this; + updateInput(inputIndex: number, updateData: PsbtInputUpdate): this; + updateOutput(outputIndex: number, updateData: PsbtOutputUpdate): this; addUnknownKeyValToGlobal(keyVal: KeyValue): this; addUnknownKeyValToInput(inputIndex: number, keyVal: KeyValue): this; addUnknownKeyValToOutput(outputIndex: number, keyVal: KeyValue): this;