From 71ddd656a3bc6a0416d9f4e486143aefc4110d77 Mon Sep 17 00:00:00 2001
From: junderw <junderwood@bitcoinbank.co.jp>
Date: Thu, 18 Jul 2019 11:43:24 +0900
Subject: [PATCH] Modify for new BIP174 interface system

---
 package-lock.json |   5 +-
 package.json      |   2 +-
 src/psbt.js       | 241 ++++++++++++++--------------------
 test/psbt.js      |  28 ++--
 ts_src/psbt.ts    | 324 +++++++++++++++++-----------------------------
 types/psbt.d.ts   |  20 +--
 6 files changed, 231 insertions(+), 389 deletions(-)

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 @@
 /// <reference types="node" />
 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;