From 361ea7c098489545253ba1824202c41a27d22596 Mon Sep 17 00:00:00 2001
From: junderw <junderwood@bitcoinbank.co.jp>
Date: Mon, 27 Apr 2020 17:10:11 +0900
Subject: [PATCH 01/15] Add inputHasPubkey and outputHasPubkey methods

---
 src/psbt.js       | 100 +++++++++++++++++++++++++++++---
 test/psbt.spec.ts | 141 +++++++++++++++++++++++++++++++++++++++++++++-
 ts_src/psbt.ts    | 127 +++++++++++++++++++++++++++++++++++++----
 types/psbt.d.ts   |   2 +
 4 files changed, 349 insertions(+), 21 deletions(-)

diff --git a/src/psbt.js b/src/psbt.js
index 134be87..48fb798 100644
--- a/src/psbt.js
+++ b/src/psbt.js
@@ -277,6 +277,14 @@ class Psbt {
     this.data.clearFinalizedInput(inputIndex);
     return this;
   }
+  inputHasPubkey(inputIndex, pubkey) {
+    const input = utils_1.checkForInput(this.data.inputs, inputIndex);
+    return pubkeyInInput(pubkey, input, inputIndex, this.__CACHE);
+  }
+  outputHasPubkey(outputIndex, pubkey) {
+    const output = utils_1.checkForOutput(this.data.outputs, outputIndex);
+    return pubkeyInOutput(pubkey, output, outputIndex, this.__CACHE);
+  }
   validateSignaturesOfAllInputs() {
     utils_1.checkForInput(this.data.inputs, 0); // making sure we have at least one
     const results = range(this.data.inputs.length).map(idx =>
@@ -653,6 +661,7 @@ const isP2PK = isPaymentFactory(payments.p2pk);
 const isP2PKH = isPaymentFactory(payments.p2pkh);
 const isP2WPKH = isPaymentFactory(payments.p2wpkh);
 const isP2WSHScript = isPaymentFactory(payments.p2wsh);
+const isP2SHScript = isPaymentFactory(payments.p2sh);
 function check32Bit(num) {
   if (
     typeof num !== 'number' ||
@@ -723,14 +732,7 @@ function checkPartialSigSighashes(input) {
   });
 }
 function checkScriptForPubkey(pubkey, script, action) {
-  const pubkeyHash = crypto_1.hash160(pubkey);
-  const decompiled = bscript.decompile(script);
-  if (decompiled === null) throw new Error('Unknown script error');
-  const hasKey = decompiled.some(element => {
-    if (typeof element === 'number') return false;
-    return element.equals(pubkey) || element.equals(pubkeyHash);
-  });
-  if (!hasKey) {
+  if (!pubkeyInScript(pubkey, script)) {
     throw new Error(
       `Can not ${action} for this input with the key ${pubkey.toString('hex')}`,
     );
@@ -1219,6 +1221,88 @@ function nonWitnessUtxoTxFromCache(cache, input, inputIndex) {
   }
   return c[inputIndex];
 }
+function pubkeyInInput(pubkey, input, inputIndex, cache) {
+  let script;
+  if (input.witnessUtxo !== undefined) {
+    script = input.witnessUtxo.script;
+  } else if (input.nonWitnessUtxo !== undefined) {
+    const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(
+      cache,
+      input,
+      inputIndex,
+    );
+    script = nonWitnessUtxoTx.outs[cache.__TX.ins[inputIndex].index].script;
+  } else {
+    throw new Error("Can't find pubkey in input without Utxo data");
+  }
+  const meaningfulScript = checkScripts(
+    script,
+    input.redeemScript,
+    input.witnessScript,
+  );
+  return pubkeyInScript(pubkey, meaningfulScript);
+}
+function pubkeyInOutput(pubkey, output, outputIndex, cache) {
+  const script = cache.__TX.outs[outputIndex].script;
+  const meaningfulScript = checkScripts(
+    script,
+    output.redeemScript,
+    output.witnessScript,
+  );
+  return pubkeyInScript(pubkey, meaningfulScript);
+}
+function checkScripts(script, redeemScript, witnessScript) {
+  let fail = false;
+  if (isP2SHScript(script)) {
+    if (redeemScript === undefined) {
+      fail = true;
+    } else if (isP2WSHScript(redeemScript)) {
+      if (witnessScript === undefined) {
+        fail = true;
+      } else {
+        fail = !payments
+          .p2sh({
+            redeem: payments.p2wsh({
+              redeem: { output: witnessScript },
+            }),
+          })
+          .output.equals(script);
+        if (!fail) return witnessScript;
+      }
+    } else {
+      fail = !payments
+        .p2sh({
+          redeem: { output: redeemScript },
+        })
+        .output.equals(script);
+      if (!fail) return redeemScript;
+    }
+  } else if (isP2WSHScript(script)) {
+    if (witnessScript === undefined) {
+      fail = true;
+    } else {
+      fail = !payments
+        .p2wsh({
+          redeem: { output: witnessScript },
+        })
+        .output.equals(script);
+      if (!fail) return witnessScript;
+    }
+  }
+  if (fail) {
+    throw new Error('Incomplete script information');
+  }
+  return script;
+}
+function pubkeyInScript(pubkey, script) {
+  const pubkeyHash = crypto_1.hash160(pubkey);
+  const decompiled = bscript.decompile(script);
+  if (decompiled === null) throw new Error('Unknown script error');
+  return decompiled.some(element => {
+    if (typeof element === 'number') return false;
+    return element.equals(pubkey) || element.equals(pubkeyHash);
+  });
+}
 function classifyScript(script) {
   if (isP2WPKH(script)) return 'witnesspubkeyhash';
   if (isP2PKH(script)) return 'pubkeyhash';
diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts
index e0eba81..9400a5e 100644
--- a/test/psbt.spec.ts
+++ b/test/psbt.spec.ts
@@ -1,7 +1,7 @@
 import * as assert from 'assert';
 import { describe, it } from 'mocha';
 
-import { bip32, ECPair, networks as NETWORKS, Psbt } from '..';
+import { bip32, ECPair, networks as NETWORKS, Psbt, payments } from '..';
 
 import * as preFixtures from './fixtures/psbt.json';
 
@@ -542,6 +542,143 @@ describe(`Psbt`, () => {
     });
   });
 
+  describe('inputHasPubkey', () => {
+    it('should throw', () => {
+      const psbt = new Psbt();
+      psbt.addInput({
+        hash:
+          '0000000000000000000000000000000000000000000000000000000000000000',
+        index: 0,
+      });
+
+      assert.throws(() => {
+        psbt.inputHasPubkey(0, Buffer.from([]));
+      }, new RegExp("Can't find pubkey in input without Utxo data"));
+
+      psbt.updateInput(0, {
+        witnessUtxo: {
+          value: 1337,
+          script: payments.p2sh({
+            redeem: { output: Buffer.from([0x51]) },
+          }).output!,
+        },
+      });
+
+      assert.throws(() => {
+        psbt.inputHasPubkey(0, Buffer.from([]));
+      }, new RegExp('Incomplete script information'));
+
+      delete psbt.data.inputs[0].witnessUtxo;
+
+      psbt.updateInput(0, {
+        witnessUtxo: {
+          value: 1337,
+          script: payments.p2wsh({
+            redeem: { output: Buffer.from([0x51]) },
+          }).output!,
+        },
+      });
+
+      assert.throws(() => {
+        psbt.inputHasPubkey(0, Buffer.from([]));
+      }, new RegExp('Incomplete script information'));
+
+      delete psbt.data.inputs[0].witnessUtxo;
+
+      psbt.updateInput(0, {
+        witnessUtxo: {
+          value: 1337,
+          script: payments.p2sh({
+            redeem: payments.p2wsh({
+              redeem: { output: Buffer.from([0x51]) },
+            }),
+          }).output!,
+        },
+        redeemScript: payments.p2wsh({
+          redeem: { output: Buffer.from([0x51]) },
+        }).output!,
+      });
+
+      assert.throws(() => {
+        psbt.inputHasPubkey(0, Buffer.from([]));
+      }, new RegExp('Incomplete script information'));
+
+      psbt.updateInput(0, {
+        witnessScript: Buffer.from([0x51]),
+      });
+
+      assert.doesNotThrow(() => {
+        psbt.inputHasPubkey(0, Buffer.from([0x51]));
+      });
+    });
+  });
+
+  describe('outputHasPubkey', () => {
+    it('should throw', () => {
+      const psbt = new Psbt();
+      psbt
+        .addInput({
+          hash:
+            '0000000000000000000000000000000000000000000000000000000000000000',
+          index: 0,
+        })
+        .addOutput({
+          script: payments.p2sh({
+            redeem: { output: Buffer.from([0x51]) },
+          }).output!,
+          value: 1337,
+        });
+
+      assert.throws(() => {
+        psbt.outputHasPubkey(0, Buffer.from([]));
+      }, new RegExp('Incomplete script information'));
+
+      (psbt as any).__CACHE.__TX.outs[0].script = payments.p2wsh({
+        redeem: { output: Buffer.from([0x51]) },
+      }).output!;
+
+      assert.throws(() => {
+        psbt.outputHasPubkey(0, Buffer.from([]));
+      }, new RegExp('Incomplete script information'));
+
+      (psbt as any).__CACHE.__TX.outs[0].script = payments.p2sh({
+        redeem: payments.p2wsh({
+          redeem: { output: Buffer.from([0x51]) },
+        }),
+      }).output!;
+
+      psbt.updateOutput(0, {
+        redeemScript: payments.p2wsh({
+          redeem: { output: Buffer.from([0x51]) },
+        }).output!,
+      });
+
+      assert.throws(() => {
+        psbt.outputHasPubkey(0, Buffer.from([]));
+      }, new RegExp('Incomplete script information'));
+
+      delete psbt.data.outputs[0].redeemScript;
+
+      psbt.updateOutput(0, {
+        witnessScript: Buffer.from([0x51]),
+      });
+
+      assert.throws(() => {
+        psbt.outputHasPubkey(0, Buffer.from([]));
+      }, new RegExp('Incomplete script information'));
+
+      psbt.updateOutput(0, {
+        redeemScript: payments.p2wsh({
+          redeem: { output: Buffer.from([0x51]) },
+        }).output!,
+      });
+
+      assert.doesNotThrow(() => {
+        psbt.outputHasPubkey(0, Buffer.from([0x51]));
+      });
+    });
+  });
+
   describe('clone', () => {
     it('Should clone a psbt exactly with no reference', () => {
       const f = fixtures.clone;
@@ -643,6 +780,8 @@ describe(`Psbt`, () => {
     assert.throws(() => {
       psbt.setVersion(3);
     }, new RegExp('Can not modify transaction, signatures exist.'));
+    assert.strictEqual(psbt.inputHasPubkey(0, alice.publicKey), true);
+    assert.strictEqual(psbt.outputHasPubkey(0, alice.publicKey), false);
     assert.strictEqual(
       psbt.extractTransaction().toHex(),
       '02000000013ebc8203037dda39d482bf41ff3be955996c50d9d4f7cfc3d2097a694a7' +
diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts
index 89491b9..301596a 100644
--- a/ts_src/psbt.ts
+++ b/ts_src/psbt.ts
@@ -13,7 +13,7 @@ import {
   TransactionInput,
   TransactionOutput,
 } from 'bip174/src/lib/interfaces';
-import { checkForInput } from 'bip174/src/lib/utils';
+import { checkForInput, checkForOutput } from 'bip174/src/lib/utils';
 import { fromOutputScript, toOutputScript } from './address';
 import { cloneBuffer, reverseBuffer } from './bufferutils';
 import { hash160 } from './crypto';
@@ -340,6 +340,16 @@ export class Psbt {
     return this;
   }
 
+  inputHasPubkey(inputIndex: number, pubkey: Buffer): boolean {
+    const input = checkForInput(this.data.inputs, inputIndex);
+    return pubkeyInInput(pubkey, input, inputIndex, this.__CACHE);
+  }
+
+  outputHasPubkey(outputIndex: number, pubkey: Buffer): boolean {
+    const output = checkForOutput(this.data.outputs, outputIndex);
+    return pubkeyInOutput(pubkey, output, outputIndex, this.__CACHE);
+  }
+
   validateSignaturesOfAllInputs(): boolean {
     checkForInput(this.data.inputs, 0); // making sure we have at least one
     const results = range(this.data.inputs.length).map(idx =>
@@ -849,6 +859,7 @@ const isP2PK = isPaymentFactory(payments.p2pk);
 const isP2PKH = isPaymentFactory(payments.p2pkh);
 const isP2WPKH = isPaymentFactory(payments.p2wpkh);
 const isP2WSHScript = isPaymentFactory(payments.p2wsh);
+const isP2SHScript = isPaymentFactory(payments.p2sh);
 
 function check32Bit(num: number): void {
   if (
@@ -927,17 +938,7 @@ function checkScriptForPubkey(
   script: Buffer,
   action: string,
 ): void {
-  const pubkeyHash = hash160(pubkey);
-
-  const decompiled = bscript.decompile(script);
-  if (decompiled === null) throw new Error('Unknown script error');
-
-  const hasKey = decompiled.some(element => {
-    if (typeof element === 'number') return false;
-    return element.equals(pubkey) || element.equals(pubkeyHash);
-  });
-
-  if (!hasKey) {
+  if (!pubkeyInScript(pubkey, script)) {
     throw new Error(
       `Can not ${action} for this input with the key ${pubkey.toString('hex')}`,
     );
@@ -1560,6 +1561,108 @@ function nonWitnessUtxoTxFromCache(
   return c[inputIndex];
 }
 
+function pubkeyInInput(
+  pubkey: Buffer,
+  input: PsbtInput,
+  inputIndex: number,
+  cache: PsbtCache,
+): boolean {
+  let script: Buffer;
+  if (input.witnessUtxo !== undefined) {
+    script = input.witnessUtxo.script;
+  } else if (input.nonWitnessUtxo !== undefined) {
+    const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(
+      cache,
+      input,
+      inputIndex,
+    );
+    script = nonWitnessUtxoTx.outs[cache.__TX.ins[inputIndex].index].script;
+  } else {
+    throw new Error("Can't find pubkey in input without Utxo data");
+  }
+  const meaningfulScript = checkScripts(
+    script,
+    input.redeemScript,
+    input.witnessScript,
+  );
+  return pubkeyInScript(pubkey, meaningfulScript);
+}
+
+function pubkeyInOutput(
+  pubkey: Buffer,
+  output: PsbtOutput,
+  outputIndex: number,
+  cache: PsbtCache,
+): boolean {
+  const script = cache.__TX.outs[outputIndex].script;
+  const meaningfulScript = checkScripts(
+    script,
+    output.redeemScript,
+    output.witnessScript,
+  );
+  return pubkeyInScript(pubkey, meaningfulScript);
+}
+
+function checkScripts(
+  script: Buffer,
+  redeemScript?: Buffer,
+  witnessScript?: Buffer,
+): Buffer {
+  let fail = false;
+  if (isP2SHScript(script)) {
+    if (redeemScript === undefined) {
+      fail = true;
+    } else if (isP2WSHScript(redeemScript)) {
+      if (witnessScript === undefined) {
+        fail = true;
+      } else {
+        fail = !payments
+          .p2sh({
+            redeem: payments.p2wsh({
+              redeem: { output: witnessScript },
+            }),
+          })
+          .output!.equals(script);
+        if (!fail) return witnessScript;
+      }
+    } else {
+      fail = !payments
+        .p2sh({
+          redeem: { output: redeemScript },
+        })
+        .output!.equals(script);
+      if (!fail) return redeemScript;
+    }
+  } else if (isP2WSHScript(script)) {
+    if (witnessScript === undefined) {
+      fail = true;
+    } else {
+      fail = !payments
+        .p2wsh({
+          redeem: { output: witnessScript },
+        })
+        .output!.equals(script);
+      if (!fail) return witnessScript;
+    }
+  }
+  if (fail) {
+    throw new Error('Incomplete script information');
+  }
+  return script;
+}
+
+function pubkeyInScript(pubkey: Buffer, script: Buffer): boolean {
+  const pubkeyHash = hash160(pubkey);
+
+  const decompiled = bscript.decompile(script);
+  if (decompiled === null) throw new Error('Unknown script error');
+
+  return decompiled.some(element => {
+    if (typeof element === 'number') return false;
+    return element.equals(pubkey) || element.equals(pubkeyHash);
+  });
+}
+
 function classifyScript(script: Buffer): string {
   if (isP2WPKH(script)) return 'witnesspubkeyhash';
   if (isP2PKH(script)) return 'pubkeyhash';
diff --git a/types/psbt.d.ts b/types/psbt.d.ts
index 0a898d8..c47ce74 100644
--- a/types/psbt.d.ts
+++ b/types/psbt.d.ts
@@ -63,6 +63,8 @@ export declare class Psbt {
     getFee(): number;
     finalizeAllInputs(): this;
     finalizeInput(inputIndex: number, finalScriptsFunc?: FinalScriptsFunc): this;
+    inputHasPubkey(inputIndex: number, pubkey: Buffer): boolean;
+    outputHasPubkey(outputIndex: number, pubkey: Buffer): boolean;
     validateSignaturesOfAllInputs(): boolean;
     validateSignaturesOfInput(inputIndex: number, pubkey?: Buffer): boolean;
     signAllInputsHD(hdKeyPair: HDSigner, sighashTypes?: number[]): this;

From de0bbf51e59e74dad62d4a15f4913e6bd6a9f3c6 Mon Sep 17 00:00:00 2001
From: Luke Childs <lukechilds123@gmail.com>
Date: Mon, 27 Apr 2020 16:46:07 +0700
Subject: [PATCH 02/15] Export PSBT getter types

---
 ts_src/index.ts  |  2 +-
 ts_src/psbt.ts   | 13 ++++++++++---
 types/index.d.ts |  2 +-
 types/psbt.d.ts  | 14 ++++++++++----
 4 files changed, 22 insertions(+), 9 deletions(-)

diff --git a/ts_src/index.ts b/ts_src/index.ts
index 505407f..b9aa49c 100644
--- a/ts_src/index.ts
+++ b/ts_src/index.ts
@@ -9,7 +9,7 @@ import * as script from './script';
 export { ECPair, address, bip32, crypto, networks, payments, script };
 
 export { Block } from './block';
-export { Psbt } from './psbt';
+export { Psbt, PsbtTxInput, PsbtTxOutput } from './psbt';
 export { OPS as opcodes } from './script';
 export { Transaction } from './transaction';
 export { TransactionBuilder } from './transaction_builder';
diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts
index 301596a..31d15c7 100644
--- a/ts_src/psbt.ts
+++ b/ts_src/psbt.ts
@@ -11,7 +11,6 @@ import {
   Transaction as ITransaction,
   TransactionFromBuffer,
   TransactionInput,
-  TransactionOutput,
 } from 'bip174/src/lib/interfaces';
 import { checkForInput, checkForOutput } from 'bip174/src/lib/utils';
 import { fromOutputScript, toOutputScript } from './address';
@@ -27,6 +26,14 @@ import * as payments from './payments';
 import * as bscript from './script';
 import { Output, Transaction } from './transaction';
 
+export interface PsbtTxInput extends TransactionInput {
+  hash: Buffer;
+}
+
+export interface PsbtTxOutput extends Output {
+  address: string;
+}
+
 /**
  * These are the default arguments for a Psbt instance.
  */
@@ -146,7 +153,7 @@ export class Psbt {
     this.setLocktime(locktime);
   }
 
-  get txInputs(): TransactionInput[] {
+  get txInputs(): PsbtTxInput[] {
     return this.__CACHE.__TX.ins.map(input => ({
       hash: cloneBuffer(input.hash),
       index: input.index,
@@ -154,7 +161,7 @@ export class Psbt {
     }));
   }
 
-  get txOutputs(): TransactionOutput[] {
+  get txOutputs(): PsbtTxOutput[] {
     return this.__CACHE.__TX.outs.map(output => ({
       script: cloneBuffer(output.script),
       value: output.value,
diff --git a/types/index.d.ts b/types/index.d.ts
index 68da119..c8f2a00 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -7,7 +7,7 @@ import * as payments from './payments';
 import * as script from './script';
 export { ECPair, address, bip32, crypto, networks, payments, script };
 export { Block } from './block';
-export { Psbt } from './psbt';
+export { Psbt, PsbtTxInput, PsbtTxOutput } from './psbt';
 export { OPS as opcodes } from './script';
 export { Transaction } from './transaction';
 export { TransactionBuilder } from './transaction_builder';
diff --git a/types/psbt.d.ts b/types/psbt.d.ts
index c47ce74..127ef0f 100644
--- a/types/psbt.d.ts
+++ b/types/psbt.d.ts
@@ -1,8 +1,14 @@
 import { Psbt as PsbtBase } from 'bip174';
-import { KeyValue, PsbtGlobalUpdate, PsbtInput, PsbtInputUpdate, PsbtOutput, PsbtOutputUpdate, TransactionInput, TransactionOutput } from 'bip174/src/lib/interfaces';
+import { KeyValue, PsbtGlobalUpdate, PsbtInput, PsbtInputUpdate, PsbtOutput, PsbtOutputUpdate, TransactionInput } from 'bip174/src/lib/interfaces';
 import { Signer, SignerAsync } from './ecpair';
 import { Network } from './networks';
-import { Transaction } from './transaction';
+import { Output, Transaction } from './transaction';
+export interface PsbtTxInput extends TransactionInput {
+    hash: Buffer;
+}
+export interface PsbtTxOutput extends Output {
+    address: string;
+}
 /**
  * Psbt class can parse and generate a PSBT binary based off of the BIP174.
  * There are 6 roles that this class fulfills. (Explained in BIP174)
@@ -46,8 +52,8 @@ export declare class Psbt {
     readonly inputCount: number;
     version: number;
     locktime: number;
-    readonly txInputs: TransactionInput[];
-    readonly txOutputs: TransactionOutput[];
+    readonly txInputs: PsbtTxInput[];
+    readonly txOutputs: PsbtTxOutput[];
     combine(...those: Psbt[]): this;
     clone(): Psbt;
     setMaximumFeeRate(satoshiPerByte: number): void;

From 9fd13f3a43c99273644c0ba205794864d2749f71 Mon Sep 17 00:00:00 2001
From: Luke Childs <lukechilds123@gmail.com>
Date: Mon, 27 Apr 2020 17:18:05 +0700
Subject: [PATCH 03/15] Fix lint error

---
 test/psbt.spec.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts
index 9400a5e..f755ba7 100644
--- a/test/psbt.spec.ts
+++ b/test/psbt.spec.ts
@@ -1,7 +1,7 @@
 import * as assert from 'assert';
 import { describe, it } from 'mocha';
 
-import { bip32, ECPair, networks as NETWORKS, Psbt, payments } from '..';
+import { bip32, ECPair, networks as NETWORKS, payments, Psbt } from '..';
 
 import * as preFixtures from './fixtures/psbt.json';
 

From e9382ebea26875dea19ce3888cd2aaf37e5fc783 Mon Sep 17 00:00:00 2001
From: Luke Childs <lukechilds123@gmail.com>
Date: Mon, 27 Apr 2020 20:37:32 +0700
Subject: [PATCH 04/15] Fix horrific bug!

---
 src/bufferutils.js    | 2 +-
 ts_src/bufferutils.ts | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/bufferutils.js b/src/bufferutils.js
index 87645c6..2ee3542 100644
--- a/src/bufferutils.js
+++ b/src/bufferutils.js
@@ -44,7 +44,7 @@ exports.reverseBuffer = reverseBuffer;
 function cloneBuffer(buffer) {
   const clone = Buffer.alloc(buffer.length);
   buffer.copy(clone);
-  return buffer;
+  return clone;
 }
 exports.cloneBuffer = cloneBuffer;
 /**
diff --git a/ts_src/bufferutils.ts b/ts_src/bufferutils.ts
index 087162f..2025f88 100644
--- a/ts_src/bufferutils.ts
+++ b/ts_src/bufferutils.ts
@@ -51,7 +51,7 @@ export function reverseBuffer(buffer: Buffer): Buffer {
 export function cloneBuffer(buffer: Buffer): Buffer {
   const clone = Buffer.alloc(buffer.length);
   buffer.copy(clone);
-  return buffer;
+  return clone;
 }
 
 /**

From e3bf997d64dea29efcdd24f9d0441dfb844d6285 Mon Sep 17 00:00:00 2001
From: Luke Childs <lukechilds123@gmail.com>
Date: Mon, 27 Apr 2020 20:38:04 +0700
Subject: [PATCH 05/15] Improve test coverage

---
 test/psbt.spec.ts | 67 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 67 insertions(+)

diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts
index f755ba7..de83fe3 100644
--- a/test/psbt.spec.ts
+++ b/test/psbt.spec.ts
@@ -846,4 +846,71 @@ describe(`Psbt`, () => {
       assert.ok((psbt as any).data.inputs[index].nonWitnessUtxo.equals(value));
     });
   });
+
+  describe('Transaction properties', () => {
+    it('.version is exposed and is settable', () => {
+      const psbt = new Psbt();
+
+      assert.strictEqual(psbt.version, 2);
+      assert.strictEqual(psbt.version, (psbt as any).__CACHE.__TX.version);
+
+      psbt.version = 1;
+      assert.strictEqual(psbt.version, 1);
+      assert.strictEqual(psbt.version, (psbt as any).__CACHE.__TX.version);
+    });
+
+    it('.locktime is exposed and is settable', () => {
+      const psbt = new Psbt();
+
+      assert.strictEqual(psbt.locktime, 0);
+      assert.strictEqual(psbt.locktime, (psbt as any).__CACHE.__TX.locktime);
+
+      psbt.locktime = 123;
+      assert.strictEqual(psbt.locktime, 123);
+      assert.strictEqual(psbt.locktime, (psbt as any).__CACHE.__TX.locktime);
+    });
+
+    it('.txInputs is exposed as a readonly clone', () => {
+      const psbt = new Psbt();
+      const hash = Buffer.alloc(32);
+      const index = 0;
+      psbt.addInput({ hash, index });
+
+      const input = psbt.txInputs[0];
+      const internalInput = (psbt as any).__CACHE.__TX.ins[0];
+
+      assert.ok(input.hash.equals(internalInput.hash));
+      assert.strictEqual(input.index, internalInput.index);
+      assert.strictEqual(input.sequence, internalInput.sequence);
+
+      input.hash[0] = 123;
+      input.index = 123;
+      input.sequence = 123;
+
+      assert.ok(!input.hash.equals(internalInput.hash));
+      assert.notEqual(input.index, internalInput.index);
+      assert.notEqual(input.sequence, internalInput.sequence);
+    });
+
+    it('.txOutputs is exposed as a readonly clone', () => {
+      const psbt = new Psbt();
+      const address = '1LukeQU5jwebXbMLDVydeH4vFSobRV9rkj';
+      const value = 100000;
+      psbt.addOutput({ address, value });
+
+      const output = psbt.txOutputs[0];
+      const internalInput = (psbt as any).__CACHE.__TX.outs[0];
+
+      assert.strictEqual(output.address, address);
+
+      assert.ok(output.script.equals(internalInput.script));
+      assert.strictEqual(output.value, internalInput.value);
+
+      output.script[0] = 123;
+      output.value = 123;
+
+      assert.ok(!output.script.equals(internalInput.script));
+      assert.notEqual(output.value, internalInput.value);
+    });
+  });
 });

From 97074f8a649e9b2733d6c24ac9e18b9b09517211 Mon Sep 17 00:00:00 2001
From: junderw <junderwood@bitcoinbank.co.jp>
Date: Tue, 28 Apr 2020 14:41:48 +0900
Subject: [PATCH 06/15] Refactor getMeaningfulScript

---
 src/psbt.js       | 80 +++++++++++++++++++++------------------------
 test/psbt.spec.ts | 14 ++++----
 ts_src/psbt.ts    | 83 +++++++++++++++++++++++------------------------
 3 files changed, 84 insertions(+), 93 deletions(-)

diff --git a/src/psbt.js b/src/psbt.js
index 48fb798..91e0438 100644
--- a/src/psbt.js
+++ b/src/psbt.js
@@ -1235,7 +1235,7 @@ function pubkeyInInput(pubkey, input, inputIndex, cache) {
   } else {
     throw new Error("Can't find pubkey in input without Utxo data");
   }
-  const meaningfulScript = checkScripts(
+  const meaningfulScript = getMeaningfulScript(
     script,
     input.redeemScript,
     input.witnessScript,
@@ -1244,55 +1244,49 @@ function pubkeyInInput(pubkey, input, inputIndex, cache) {
 }
 function pubkeyInOutput(pubkey, output, outputIndex, cache) {
   const script = cache.__TX.outs[outputIndex].script;
-  const meaningfulScript = checkScripts(
+  const meaningfulScript = getMeaningfulScript(
     script,
     output.redeemScript,
     output.witnessScript,
   );
   return pubkeyInScript(pubkey, meaningfulScript);
 }
-function checkScripts(script, redeemScript, witnessScript) {
-  let fail = false;
-  if (isP2SHScript(script)) {
-    if (redeemScript === undefined) {
-      fail = true;
-    } else if (isP2WSHScript(redeemScript)) {
-      if (witnessScript === undefined) {
-        fail = true;
-      } else {
-        fail = !payments
-          .p2sh({
-            redeem: payments.p2wsh({
-              redeem: { output: witnessScript },
-            }),
-          })
-          .output.equals(script);
-        if (!fail) return witnessScript;
-      }
-    } else {
-      fail = !payments
-        .p2sh({
-          redeem: { output: redeemScript },
-        })
-        .output.equals(script);
-      if (!fail) return redeemScript;
-    }
-  } else if (isP2WSHScript(script)) {
-    if (witnessScript === undefined) {
-      fail = true;
-    } else {
-      fail = !payments
-        .p2wsh({
-          redeem: { output: witnessScript },
-        })
-        .output.equals(script);
-      if (!fail) return witnessScript;
-    }
+function getMeaningfulScript(script, redeemScript, witnessScript) {
+  const { p2sh, p2wsh } = payments;
+  const isP2SH = isP2SHScript(script);
+  const isP2SHP2WSH = isP2SH && redeemScript && isP2WSHScript(redeemScript);
+  const isP2WSH = isP2WSHScript(script);
+  if (isP2SH && redeemScript === undefined)
+    throw new Error('scriptPubkey is P2SH but redeemScript missing');
+  if ((isP2WSH || isP2SHP2WSH) && witnessScript === undefined)
+    throw new Error(
+      'scriptPubkey or redeemScript is P2WSH but witnessScript missing',
+    );
+  let payment;
+  let meaningfulScript;
+  if (isP2SHP2WSH) {
+    meaningfulScript = witnessScript;
+    payment = p2sh({ redeem: p2wsh({ redeem: { output: meaningfulScript } }) });
+    if (!payment.redeem.output.equals(redeemScript))
+      throw new Error('P2SHP2WSH witnessScript and redeemScript do not match');
+    if (!payment.output.equals(script))
+      throw new Error(
+        'P2SHP2WSH witnessScript+redeemScript and scriptPubkey do not match',
+      );
+  } else if (isP2WSH) {
+    meaningfulScript = witnessScript;
+    payment = p2wsh({ redeem: { output: meaningfulScript } });
+    if (!payment.output.equals(script))
+      throw new Error('P2WSH witnessScript and scriptPubkey do not match');
+  } else if (isP2SH) {
+    meaningfulScript = redeemScript;
+    payment = p2sh({ redeem: { output: meaningfulScript } });
+    if (!payment.output.equals(script))
+      throw new Error('P2SH redeemScript and scriptPubkey do not match');
+  } else {
+    meaningfulScript = script;
   }
-  if (fail) {
-    throw new Error('Incomplete script information');
-  }
-  return script;
+  return meaningfulScript;
 }
 function pubkeyInScript(pubkey, script) {
   const pubkeyHash = crypto_1.hash160(pubkey);
diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts
index de83fe3..ff2131b 100644
--- a/test/psbt.spec.ts
+++ b/test/psbt.spec.ts
@@ -566,7 +566,7 @@ describe(`Psbt`, () => {
 
       assert.throws(() => {
         psbt.inputHasPubkey(0, Buffer.from([]));
-      }, new RegExp('Incomplete script information'));
+      }, new RegExp('scriptPubkey is P2SH but redeemScript missing'));
 
       delete psbt.data.inputs[0].witnessUtxo;
 
@@ -581,7 +581,7 @@ describe(`Psbt`, () => {
 
       assert.throws(() => {
         psbt.inputHasPubkey(0, Buffer.from([]));
-      }, new RegExp('Incomplete script information'));
+      }, new RegExp('scriptPubkey or redeemScript is P2WSH but witnessScript missing'));
 
       delete psbt.data.inputs[0].witnessUtxo;
 
@@ -601,7 +601,7 @@ describe(`Psbt`, () => {
 
       assert.throws(() => {
         psbt.inputHasPubkey(0, Buffer.from([]));
-      }, new RegExp('Incomplete script information'));
+      }, new RegExp('scriptPubkey or redeemScript is P2WSH but witnessScript missing'));
 
       psbt.updateInput(0, {
         witnessScript: Buffer.from([0x51]),
@@ -631,7 +631,7 @@ describe(`Psbt`, () => {
 
       assert.throws(() => {
         psbt.outputHasPubkey(0, Buffer.from([]));
-      }, new RegExp('Incomplete script information'));
+      }, new RegExp('scriptPubkey is P2SH but redeemScript missing'));
 
       (psbt as any).__CACHE.__TX.outs[0].script = payments.p2wsh({
         redeem: { output: Buffer.from([0x51]) },
@@ -639,7 +639,7 @@ describe(`Psbt`, () => {
 
       assert.throws(() => {
         psbt.outputHasPubkey(0, Buffer.from([]));
-      }, new RegExp('Incomplete script information'));
+      }, new RegExp('scriptPubkey or redeemScript is P2WSH but witnessScript missing'));
 
       (psbt as any).__CACHE.__TX.outs[0].script = payments.p2sh({
         redeem: payments.p2wsh({
@@ -655,7 +655,7 @@ describe(`Psbt`, () => {
 
       assert.throws(() => {
         psbt.outputHasPubkey(0, Buffer.from([]));
-      }, new RegExp('Incomplete script information'));
+      }, new RegExp('scriptPubkey or redeemScript is P2WSH but witnessScript missing'));
 
       delete psbt.data.outputs[0].redeemScript;
 
@@ -665,7 +665,7 @@ describe(`Psbt`, () => {
 
       assert.throws(() => {
         psbt.outputHasPubkey(0, Buffer.from([]));
-      }, new RegExp('Incomplete script information'));
+      }, new RegExp('scriptPubkey is P2SH but redeemScript missing'));
 
       psbt.updateOutput(0, {
         redeemScript: payments.p2wsh({
diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts
index 31d15c7..7749827 100644
--- a/ts_src/psbt.ts
+++ b/ts_src/psbt.ts
@@ -1587,7 +1587,7 @@ function pubkeyInInput(
   } else {
     throw new Error("Can't find pubkey in input without Utxo data");
   }
-  const meaningfulScript = checkScripts(
+  const meaningfulScript = getMeaningfulScript(
     script,
     input.redeemScript,
     input.witnessScript,
@@ -1602,7 +1602,7 @@ function pubkeyInOutput(
   cache: PsbtCache,
 ): boolean {
   const script = cache.__TX.outs[outputIndex].script;
-  const meaningfulScript = checkScripts(
+  const meaningfulScript = getMeaningfulScript(
     script,
     output.redeemScript,
     output.witnessScript,
@@ -1610,52 +1610,49 @@ function pubkeyInOutput(
   return pubkeyInScript(pubkey, meaningfulScript);
 }
 
-function checkScripts(
+function getMeaningfulScript(
   script: Buffer,
   redeemScript?: Buffer,
   witnessScript?: Buffer,
 ): Buffer {
-  let fail = false;
-  if (isP2SHScript(script)) {
-    if (redeemScript === undefined) {
-      fail = true;
-    } else if (isP2WSHScript(redeemScript)) {
-      if (witnessScript === undefined) {
-        fail = true;
-      } else {
-        fail = !payments
-          .p2sh({
-            redeem: payments.p2wsh({
-              redeem: { output: witnessScript },
-            }),
-          })
-          .output!.equals(script);
-        if (!fail) return witnessScript;
-      }
-    } else {
-      fail = !payments
-        .p2sh({
-          redeem: { output: redeemScript },
-        })
-        .output!.equals(script);
-      if (!fail) return redeemScript;
-    }
-  } else if (isP2WSHScript(script)) {
-    if (witnessScript === undefined) {
-      fail = true;
-    } else {
-      fail = !payments
-        .p2wsh({
-          redeem: { output: witnessScript },
-        })
-        .output!.equals(script);
-      if (!fail) return witnessScript;
-    }
+  const { p2sh, p2wsh } = payments;
+  const isP2SH = isP2SHScript(script);
+  const isP2SHP2WSH = isP2SH && redeemScript && isP2WSHScript(redeemScript);
+  const isP2WSH = isP2WSHScript(script);
+
+  if (isP2SH && redeemScript === undefined)
+    throw new Error('scriptPubkey is P2SH but redeemScript missing');
+  if ((isP2WSH || isP2SHP2WSH) && witnessScript === undefined)
+    throw new Error(
+      'scriptPubkey or redeemScript is P2WSH but witnessScript missing',
+    );
+
+  let payment: payments.Payment;
+  let meaningfulScript: Buffer;
+
+  if (isP2SHP2WSH) {
+    meaningfulScript = witnessScript!;
+    payment = p2sh({ redeem: p2wsh({ redeem: { output: meaningfulScript } }) });
+    if (!payment.redeem!.output!.equals(redeemScript!))
+      throw new Error('P2SHP2WSH witnessScript and redeemScript do not match');
+    if (!payment.output!.equals(script!))
+      throw new Error(
+        'P2SHP2WSH witnessScript+redeemScript and scriptPubkey do not match',
+      );
+  } else if (isP2WSH) {
+    meaningfulScript = witnessScript!;
+    payment = p2wsh({ redeem: { output: meaningfulScript } });
+    if (!payment.output!.equals(script!))
+      throw new Error('P2WSH witnessScript and scriptPubkey do not match');
+  } else if (isP2SH) {
+    meaningfulScript = redeemScript!;
+    payment = p2sh({ redeem: { output: meaningfulScript } });
+    if (!payment.output!.equals(script!))
+      throw new Error('P2SH redeemScript and scriptPubkey do not match');
+  } else {
+    meaningfulScript = script;
   }
-  if (fail) {
-    throw new Error('Incomplete script information');
-  }
-  return script;
+  return meaningfulScript;
 }
 
 function pubkeyInScript(pubkey: Buffer, script: Buffer): boolean {

From 7d09fe5dcb8c8835570ff9341bc48c21d975307a Mon Sep 17 00:00:00 2001
From: junderw <junderwood@bitcoinbank.co.jp>
Date: Tue, 28 Apr 2020 18:50:00 +0900
Subject: [PATCH 07/15] Refactor Psbt logic

---
 src/psbt.js    | 164 ++++++++++++++++++++---------------------------
 ts_src/psbt.ts | 169 +++++++++++++++++++++----------------------------
 2 files changed, 143 insertions(+), 190 deletions(-)

diff --git a/src/psbt.js b/src/psbt.js
index 91e0438..d240350 100644
--- a/src/psbt.js
+++ b/src/psbt.js
@@ -764,13 +764,13 @@ function checkTxInputCache(cache, input) {
   cache.__TX_IN_CACHE[key] = 1;
 }
 function scriptCheckerFactory(payment, paymentScriptName) {
-  return (inputIndex, scriptPubKey, redeemScript) => {
+  return (inputIndex, scriptPubKey, redeemScript, ioType) => {
     const redeemScriptOutput = payment({
       redeem: { output: redeemScript },
     }).output;
     if (!scriptPubKey.equals(redeemScriptOutput)) {
       throw new Error(
-        `${paymentScriptName} for input #${inputIndex} doesn't match the scriptPubKey in the prevout`,
+        `${paymentScriptName} for ${ioType} #${inputIndex} doesn't match the scriptPubKey in the prevout`,
       );
     }
   };
@@ -877,7 +877,7 @@ function getHashForSig(inputIndex, input, cache, sighashTypes) {
     );
   }
   let hash;
-  let script;
+  let prevout;
   if (input.nonWitnessUtxo) {
     const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(
       cache,
@@ -893,83 +893,51 @@ function getHashForSig(inputIndex, input, cache, sighashTypes) {
       );
     }
     const prevoutIndex = unsignedTx.ins[inputIndex].index;
-    const prevout = nonWitnessUtxoTx.outs[prevoutIndex];
-    if (input.redeemScript) {
-      // If a redeemScript is provided, the scriptPubKey must be for that redeemScript
-      checkRedeemScript(inputIndex, prevout.script, input.redeemScript);
-      script = input.redeemScript;
-    } else {
-      script = prevout.script;
-    }
-    if (isP2WSHScript(script)) {
-      if (!input.witnessScript)
-        throw new Error('Segwit input needs witnessScript if not P2WPKH');
-      checkWitnessScript(inputIndex, script, input.witnessScript);
-      hash = unsignedTx.hashForWitnessV0(
-        inputIndex,
-        input.witnessScript,
-        prevout.value,
-        sighashType,
-      );
-      script = input.witnessScript;
-    } else if (isP2WPKH(script)) {
-      // P2WPKH uses the P2PKH template for prevoutScript when signing
-      const signingScript = payments.p2pkh({ hash: script.slice(2) }).output;
-      hash = unsignedTx.hashForWitnessV0(
-        inputIndex,
-        signingScript,
-        prevout.value,
-        sighashType,
-      );
-    } else {
-      hash = unsignedTx.hashForSignature(inputIndex, script, sighashType);
-    }
+    prevout = nonWitnessUtxoTx.outs[prevoutIndex];
   } else if (input.witnessUtxo) {
-    let _script; // so we don't shadow the `let script` above
-    if (input.redeemScript) {
-      // If a redeemScript is provided, the scriptPubKey must be for that redeemScript
-      checkRedeemScript(
-        inputIndex,
-        input.witnessUtxo.script,
-        input.redeemScript,
-      );
-      _script = input.redeemScript;
-    } else {
-      _script = input.witnessUtxo.script;
-    }
-    if (isP2WPKH(_script)) {
-      // P2WPKH uses the P2PKH template for prevoutScript when signing
-      const signingScript = payments.p2pkh({ hash: _script.slice(2) }).output;
-      hash = unsignedTx.hashForWitnessV0(
-        inputIndex,
-        signingScript,
-        input.witnessUtxo.value,
-        sighashType,
-      );
-      script = _script;
-    } else if (isP2WSHScript(_script)) {
-      if (!input.witnessScript)
-        throw new Error('Segwit input needs witnessScript if not P2WPKH');
-      checkWitnessScript(inputIndex, _script, input.witnessScript);
-      hash = unsignedTx.hashForWitnessV0(
-        inputIndex,
-        input.witnessScript,
-        input.witnessUtxo.value,
-        sighashType,
-      );
-      // want to make sure the script we return is the actual meaningful script
-      script = input.witnessScript;
-    } else {
-      throw new Error(
-        `Input #${inputIndex} has witnessUtxo but non-segwit script: ` +
-          `${_script.toString('hex')}`,
-      );
-    }
+    prevout = input.witnessUtxo;
   } else {
     throw new Error('Need a Utxo input item for signing');
   }
+  const { meaningfulScript, type } = getMeaningfulScript(
+    prevout.script,
+    inputIndex,
+    'input',
+    input.redeemScript,
+    input.witnessScript,
+  );
+  if (['p2shp2wsh', 'p2wsh'].indexOf(type) >= 0) {
+    hash = unsignedTx.hashForWitnessV0(
+      inputIndex,
+      meaningfulScript,
+      prevout.value,
+      sighashType,
+    );
+  } else if (isP2WPKH(meaningfulScript)) {
+    // P2WPKH uses the P2PKH template for prevoutScript when signing
+    const signingScript = payments.p2pkh({ hash: meaningfulScript.slice(2) })
+      .output;
+    hash = unsignedTx.hashForWitnessV0(
+      inputIndex,
+      signingScript,
+      prevout.value,
+      sighashType,
+    );
+  } else {
+    // non-segwit
+    if (input.nonWitnessUtxo === undefined)
+      throw new Error(
+        `Input #${inputIndex} has witnessUtxo but non-segwit script: ` +
+          `${meaningfulScript.toString('hex')}`,
+      );
+    hash = unsignedTx.hashForSignature(
+      inputIndex,
+      meaningfulScript,
+      sighashType,
+    );
+  }
   return {
-    script,
+    script: meaningfulScript,
     sighashType,
     hash,
   };
@@ -1235,8 +1203,10 @@ function pubkeyInInput(pubkey, input, inputIndex, cache) {
   } else {
     throw new Error("Can't find pubkey in input without Utxo data");
   }
-  const meaningfulScript = getMeaningfulScript(
+  const { meaningfulScript } = getMeaningfulScript(
     script,
+    inputIndex,
+    'input',
     input.redeemScript,
     input.witnessScript,
   );
@@ -1244,15 +1214,22 @@ function pubkeyInInput(pubkey, input, inputIndex, cache) {
 }
 function pubkeyInOutput(pubkey, output, outputIndex, cache) {
   const script = cache.__TX.outs[outputIndex].script;
-  const meaningfulScript = getMeaningfulScript(
+  const { meaningfulScript } = getMeaningfulScript(
     script,
+    outputIndex,
+    'output',
     output.redeemScript,
     output.witnessScript,
   );
   return pubkeyInScript(pubkey, meaningfulScript);
 }
-function getMeaningfulScript(script, redeemScript, witnessScript) {
-  const { p2sh, p2wsh } = payments;
+function getMeaningfulScript(
+  script,
+  index,
+  ioType,
+  redeemScript,
+  witnessScript,
+) {
   const isP2SH = isP2SHScript(script);
   const isP2SHP2WSH = isP2SH && redeemScript && isP2WSHScript(redeemScript);
   const isP2WSH = isP2WSHScript(script);
@@ -1262,31 +1239,30 @@ function getMeaningfulScript(script, redeemScript, witnessScript) {
     throw new Error(
       'scriptPubkey or redeemScript is P2WSH but witnessScript missing',
     );
-  let payment;
   let meaningfulScript;
   if (isP2SHP2WSH) {
     meaningfulScript = witnessScript;
-    payment = p2sh({ redeem: p2wsh({ redeem: { output: meaningfulScript } }) });
-    if (!payment.redeem.output.equals(redeemScript))
-      throw new Error('P2SHP2WSH witnessScript and redeemScript do not match');
-    if (!payment.output.equals(script))
-      throw new Error(
-        'P2SHP2WSH witnessScript+redeemScript and scriptPubkey do not match',
-      );
+    checkRedeemScript(index, script, redeemScript, ioType);
+    checkWitnessScript(index, redeemScript, witnessScript, ioType);
   } else if (isP2WSH) {
     meaningfulScript = witnessScript;
-    payment = p2wsh({ redeem: { output: meaningfulScript } });
-    if (!payment.output.equals(script))
-      throw new Error('P2WSH witnessScript and scriptPubkey do not match');
+    checkWitnessScript(index, script, witnessScript, ioType);
   } else if (isP2SH) {
     meaningfulScript = redeemScript;
-    payment = p2sh({ redeem: { output: meaningfulScript } });
-    if (!payment.output.equals(script))
-      throw new Error('P2SH redeemScript and scriptPubkey do not match');
+    checkRedeemScript(index, script, redeemScript, ioType);
   } else {
     meaningfulScript = script;
   }
-  return meaningfulScript;
+  return {
+    meaningfulScript,
+    type: isP2SHP2WSH
+      ? 'p2shp2wsh'
+      : isP2SH
+      ? 'p2sh'
+      : isP2WSH
+      ? 'p2wsh'
+      : 'raw',
+  };
 }
 function pubkeyInScript(pubkey, script) {
   const pubkeyHash = crypto_1.hash160(pubkey);
diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts
index 7749827..13e1286 100644
--- a/ts_src/psbt.ts
+++ b/ts_src/psbt.ts
@@ -984,11 +984,12 @@ function checkTxInputCache(
 function scriptCheckerFactory(
   payment: any,
   paymentScriptName: string,
-): (idx: number, spk: Buffer, rs: Buffer) => void {
+): (idx: number, spk: Buffer, rs: Buffer, ioType: 'input' | 'output') => void {
   return (
     inputIndex: number,
     scriptPubKey: Buffer,
     redeemScript: Buffer,
+    ioType: 'input' | 'output',
   ): void => {
     const redeemScriptOutput = payment({
       redeem: { output: redeemScript },
@@ -996,7 +997,7 @@ function scriptCheckerFactory(
 
     if (!scriptPubKey.equals(redeemScriptOutput)) {
       throw new Error(
-        `${paymentScriptName} for input #${inputIndex} doesn't match the scriptPubKey in the prevout`,
+        `${paymentScriptName} for ${ioType} #${inputIndex} doesn't match the scriptPubKey in the prevout`,
       );
     }
   };
@@ -1158,7 +1159,7 @@ function getHashForSig(
     );
   }
   let hash: Buffer;
-  let script: Buffer;
+  let prevout: Output;
 
   if (input.nonWitnessUtxo) {
     const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(
@@ -1178,85 +1179,54 @@ function getHashForSig(
     }
 
     const prevoutIndex = unsignedTx.ins[inputIndex].index;
-    const prevout = nonWitnessUtxoTx.outs[prevoutIndex] as Output;
-
-    if (input.redeemScript) {
-      // If a redeemScript is provided, the scriptPubKey must be for that redeemScript
-      checkRedeemScript(inputIndex, prevout.script, input.redeemScript);
-      script = input.redeemScript;
-    } else {
-      script = prevout.script;
-    }
-
-    if (isP2WSHScript(script)) {
-      if (!input.witnessScript)
-        throw new Error('Segwit input needs witnessScript if not P2WPKH');
-      checkWitnessScript(inputIndex, script, input.witnessScript);
-      hash = unsignedTx.hashForWitnessV0(
-        inputIndex,
-        input.witnessScript,
-        prevout.value,
-        sighashType,
-      );
-      script = input.witnessScript;
-    } else if (isP2WPKH(script)) {
-      // P2WPKH uses the P2PKH template for prevoutScript when signing
-      const signingScript = payments.p2pkh({ hash: script.slice(2) }).output!;
-      hash = unsignedTx.hashForWitnessV0(
-        inputIndex,
-        signingScript,
-        prevout.value,
-        sighashType,
-      );
-    } else {
-      hash = unsignedTx.hashForSignature(inputIndex, script, sighashType);
-    }
+    prevout = nonWitnessUtxoTx.outs[prevoutIndex] as Output;
   } else if (input.witnessUtxo) {
-    let _script: Buffer; // so we don't shadow the `let script` above
-    if (input.redeemScript) {
-      // If a redeemScript is provided, the scriptPubKey must be for that redeemScript
-      checkRedeemScript(
-        inputIndex,
-        input.witnessUtxo.script,
-        input.redeemScript,
-      );
-      _script = input.redeemScript;
-    } else {
-      _script = input.witnessUtxo.script;
-    }
-    if (isP2WPKH(_script)) {
-      // P2WPKH uses the P2PKH template for prevoutScript when signing
-      const signingScript = payments.p2pkh({ hash: _script.slice(2) }).output!;
-      hash = unsignedTx.hashForWitnessV0(
-        inputIndex,
-        signingScript,
-        input.witnessUtxo.value,
-        sighashType,
-      );
-      script = _script;
-    } else if (isP2WSHScript(_script)) {
-      if (!input.witnessScript)
-        throw new Error('Segwit input needs witnessScript if not P2WPKH');
-      checkWitnessScript(inputIndex, _script, input.witnessScript);
-      hash = unsignedTx.hashForWitnessV0(
-        inputIndex,
-        input.witnessScript,
-        input.witnessUtxo.value,
-        sighashType,
-      );
-      // want to make sure the script we return is the actual meaningful script
-      script = input.witnessScript;
-    } else {
-      throw new Error(
-        `Input #${inputIndex} has witnessUtxo but non-segwit script: ` +
-          `${_script.toString('hex')}`,
-      );
-    }
+    prevout = input.witnessUtxo;
   } else {
     throw new Error('Need a Utxo input item for signing');
   }
+
+  const { meaningfulScript, type } = getMeaningfulScript(
+    prevout.script,
+    inputIndex,
+    'input',
+    input.redeemScript,
+    input.witnessScript,
+  );
+
+  if (['p2shp2wsh', 'p2wsh'].indexOf(type) >= 0) {
+    hash = unsignedTx.hashForWitnessV0(
+      inputIndex,
+      meaningfulScript,
+      prevout.value,
+      sighashType,
+    );
+  } else if (isP2WPKH(meaningfulScript)) {
+    // P2WPKH uses the P2PKH template for prevoutScript when signing
+    const signingScript = payments.p2pkh({ hash: meaningfulScript.slice(2) })
+      .output!;
+    hash = unsignedTx.hashForWitnessV0(
+      inputIndex,
+      signingScript,
+      prevout.value,
+      sighashType,
+    );
+  } else {
+    // non-segwit
+    if (input.nonWitnessUtxo === undefined)
+      throw new Error(
+        `Input #${inputIndex} has witnessUtxo but non-segwit script: ` +
+          `${meaningfulScript.toString('hex')}`,
+      );
+    hash = unsignedTx.hashForSignature(
+      inputIndex,
+      meaningfulScript,
+      sighashType,
+    );
+  }
+
   return {
-    script,
+    script: meaningfulScript,
     sighashType,
     hash,
   };
@@ -1587,8 +1557,10 @@ function pubkeyInInput(
   } else {
     throw new Error("Can't find pubkey in input without Utxo data");
   }
-  const meaningfulScript = getMeaningfulScript(
+  const { meaningfulScript } = getMeaningfulScript(
     script,
+    inputIndex,
+    'input',
     input.redeemScript,
     input.witnessScript,
   );
@@ -1602,8 +1574,10 @@ function pubkeyInOutput(
   cache: PsbtCache,
 ): boolean {
   const script = cache.__TX.outs[outputIndex].script;
-  const meaningfulScript = getMeaningfulScript(
+  const { meaningfulScript } = getMeaningfulScript(
     script,
+    outputIndex,
+    'output',
     output.redeemScript,
     output.witnessScript,
   );
@@ -1612,10 +1586,14 @@ function pubkeyInOutput(
 
 function getMeaningfulScript(
   script: Buffer,
+  index: number,
+  ioType: 'input' | 'output',
   redeemScript?: Buffer,
   witnessScript?: Buffer,
-): Buffer {
-  const { p2sh, p2wsh } = payments;
+): {
+  meaningfulScript: Buffer;
+  type: 'p2sh' | 'p2wsh' | 'p2shp2wsh' | 'raw';
+} {
   const isP2SH = isP2SHScript(script);
   const isP2SHP2WSH = isP2SH && redeemScript && isP2WSHScript(redeemScript);
   const isP2WSH = isP2WSHScript(script);
@@ -1627,32 +1605,31 @@ function getMeaningfulScript(
       'scriptPubkey or redeemScript is P2WSH but witnessScript missing',
     );
 
-  let payment: payments.Payment;
   let meaningfulScript: Buffer;
 
   if (isP2SHP2WSH) {
     meaningfulScript = witnessScript!;
-    payment = p2sh({ redeem: p2wsh({ redeem: { output: meaningfulScript } }) });
-    if (!payment.redeem!.output!.equals(redeemScript!))
-      throw new Error('P2SHP2WSH witnessScript and redeemScript do not match');
-    if (!payment.output!.equals(script!))
-      throw new Error(
-        'P2SHP2WSH witnessScript+redeemScript and scriptPubkey do not match',
-      );
+    checkRedeemScript(index, script, redeemScript!, ioType);
+    checkWitnessScript(index, redeemScript!, witnessScript!, ioType);
   } else if (isP2WSH) {
     meaningfulScript = witnessScript!;
-    payment = p2wsh({ redeem: { output: meaningfulScript } });
-    if (!payment.output!.equals(script!))
-      throw new Error('P2WSH witnessScript and scriptPubkey do not match');
+    checkWitnessScript(index, script, witnessScript!, ioType);
   } else if (isP2SH) {
     meaningfulScript = redeemScript!;
-    payment = p2sh({ redeem: { output: meaningfulScript } });
-    if (!payment.output!.equals(script!))
-      throw new Error('P2SH redeemScript and scriptPubkey do not match');
+    checkRedeemScript(index, script, redeemScript!, ioType);
   } else {
     meaningfulScript = script;
   }
-  return meaningfulScript;
+  return {
+    meaningfulScript,
+    type: isP2SHP2WSH
+      ? 'p2shp2wsh'
+      : isP2SH
+      ? 'p2sh'
+      : isP2WSH
+      ? 'p2wsh'
+      : 'raw',
+  };
 }
 
 function pubkeyInScript(pubkey: Buffer, script: Buffer): boolean {

From 0c52803ba1fe5ae8dd3b84ad50bd84b0878d17de Mon Sep 17 00:00:00 2001
From: junderw <junderwood@bitcoinbank.co.jp>
Date: Tue, 28 Apr 2020 18:52:43 +0900
Subject: [PATCH 08/15] Add discouraged unsafe nonsegwit signing

---
 src/psbt.js    | 35 +++++++++++++++++++++++++++++++++--
 ts_src/psbt.ts | 36 +++++++++++++++++++++++++++++++++++-
 2 files changed, 68 insertions(+), 3 deletions(-)

diff --git a/src/psbt.js b/src/psbt.js
index d240350..5958b1e 100644
--- a/src/psbt.js
+++ b/src/psbt.js
@@ -69,6 +69,14 @@ class Psbt {
       __NON_WITNESS_UTXO_BUF_CACHE: [],
       __TX_IN_CACHE: {},
       __TX: this.data.globalMap.unsignedTx.tx,
+      // Old TransactionBuilder behavior was to not confirm input values
+      // before signing. Even though we highly encourage people to get
+      // the full parent transaction to verify values, the ability to
+      // sign non-segwit inputs without the full transaction was often
+      // requested. So the only way to activate is to use @ts-ignore.
+      // We will disable exporting the Psbt when unsafe sign is active.
+      // because it is not BIP174 compliant.
+      __UNSAFE_SIGN_NONSEGWIT: false,
     };
     if (this.data.inputs.length === 0) this.setVersion(2);
     // Make data hidden when enumerating
@@ -313,6 +321,7 @@ class Psbt {
               inputIndex,
               Object.assign({}, input, { sighashType: sig.hashType }),
               this.__CACHE,
+              true,
             )
           : { hash: hashCache, script: scriptCache };
       sighashCache = sig.hashType;
@@ -513,12 +522,15 @@ class Psbt {
     });
   }
   toBuffer() {
+    checkCache(this.__CACHE);
     return this.data.toBuffer();
   }
   toHex() {
+    checkCache(this.__CACHE);
     return this.data.toHex();
   }
   toBase64() {
+    checkCache(this.__CACHE);
     return this.data.toBase64();
   }
   updateGlobal(updateData) {
@@ -626,6 +638,11 @@ function canFinalize(input, script, scriptType) {
       return false;
   }
 }
+function checkCache(cache) {
+  if (cache.__UNSAFE_SIGN_NONSEGWIT !== false) {
+    throw new Error('Not BIP174 compliant, can not export');
+  }
+}
 function hasSigs(neededSigs, partialSig, pubkeys) {
   if (!partialSig) return false;
   let sigs;
@@ -857,6 +874,7 @@ function getHashAndSighashType(
     inputIndex,
     input,
     cache,
+    false,
     sighashTypes,
   );
   checkScriptForPubkey(pubkey, script, 'sign');
@@ -865,7 +883,7 @@ function getHashAndSighashType(
     sighashType,
   };
 }
-function getHashForSig(inputIndex, input, cache, sighashTypes) {
+function getHashForSig(inputIndex, input, cache, forValidate, sighashTypes) {
   const unsignedTx = cache.__TX;
   const sighashType =
     input.sighashType || transaction_1.Transaction.SIGHASH_ALL;
@@ -925,11 +943,24 @@ function getHashForSig(inputIndex, input, cache, sighashTypes) {
     );
   } else {
     // non-segwit
-    if (input.nonWitnessUtxo === undefined)
+    if (
+      input.nonWitnessUtxo === undefined &&
+      cache.__UNSAFE_SIGN_NONSEGWIT === false
+    )
       throw new Error(
         `Input #${inputIndex} has witnessUtxo but non-segwit script: ` +
           `${meaningfulScript.toString('hex')}`,
       );
+    if (!forValidate && cache.__UNSAFE_SIGN_NONSEGWIT !== false)
+      console.warn(
+        'Warning: Signing non-segwit inputs without the full parent transaction ' +
+          'means there is a chance that a miner could feed you incorrect information ' +
+          'to trick you into paying large fees. This behavior is the same as the old ' +
+          'TransactionBuilder class when signing non-segwit scripts. You are not ' +
+          'able to export this Psbt with toBuffer|toBase64|toHex since it is not ' +
+          'BIP174 compliant.\n*********************\nPROCEED WITH CAUTION!\n' +
+          '*********************',
+      );
     hash = unsignedTx.hashForSignature(
       inputIndex,
       meaningfulScript,
diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts
index 13e1286..23bdc1d 100644
--- a/ts_src/psbt.ts
+++ b/ts_src/psbt.ts
@@ -115,6 +115,14 @@ export class Psbt {
       __NON_WITNESS_UTXO_BUF_CACHE: [],
       __TX_IN_CACHE: {},
       __TX: (this.data.globalMap.unsignedTx as PsbtTransaction).tx,
+      // Old TransactionBuilder behavior was to not confirm input values
+      // before signing. Even though we highly encourage people to get
+      // the full parent transaction to verify values, the ability to
+      // sign non-segwit inputs without the full transaction was often
+      // requested. So the only way to activate is to use @ts-ignore.
+      // We will disable exporting the Psbt when unsafe sign is active.
+      // because it is not BIP174 compliant.
+      __UNSAFE_SIGN_NONSEGWIT: false,
     };
     if (this.data.inputs.length === 0) this.setVersion(2);
 
@@ -386,6 +394,7 @@ export class Psbt {
               inputIndex,
               Object.assign({}, input, { sighashType: sig.hashType }),
               this.__CACHE,
+              true,
             )
           : { hash: hashCache!, script: scriptCache! };
       sighashCache = sig.hashType;
@@ -619,14 +628,17 @@ export class Psbt {
   }
 
   toBuffer(): Buffer {
+    checkCache(this.__CACHE);
     return this.data.toBuffer();
   }
 
   toHex(): string {
+    checkCache(this.__CACHE);
     return this.data.toHex();
   }
 
   toBase64(): string {
+    checkCache(this.__CACHE);
     return this.data.toBase64();
   }
 
@@ -681,6 +693,7 @@ interface PsbtCache {
   __FEE_RATE?: number;
   __FEE?: number;
   __EXTRACTED_TX?: Transaction;
+  __UNSAFE_SIGN_NONSEGWIT: boolean;
 }
 
 interface PsbtOptsOptional {
@@ -825,6 +838,12 @@ function canFinalize(
   }
 }
 
+function checkCache(cache: PsbtCache): void {
+  if (cache.__UNSAFE_SIGN_NONSEGWIT !== false) {
+    throw new Error('Not BIP174 compliant, can not export');
+  }
+}
+
 function hasSigs(
   neededSigs: number,
   partialSig?: any[],
@@ -1130,6 +1149,7 @@ function getHashAndSighashType(
     inputIndex,
     input,
     cache,
+    false,
     sighashTypes,
   );
   checkScriptForPubkey(pubkey, script, 'sign');
@@ -1143,6 +1163,7 @@ function getHashForSig(
   inputIndex: number,
   input: PsbtInput,
   cache: PsbtCache,
+  forValidate: boolean,
   sighashTypes?: number[],
 ): {
   script: Buffer;
@@ -1213,11 +1234,24 @@ function getHashForSig(
     );
   } else {
     // non-segwit
-    if (input.nonWitnessUtxo === undefined)
+    if (
+      input.nonWitnessUtxo === undefined &&
+      cache.__UNSAFE_SIGN_NONSEGWIT === false
+    )
       throw new Error(
         `Input #${inputIndex} has witnessUtxo but non-segwit script: ` +
           `${meaningfulScript.toString('hex')}`,
       );
+    if (!forValidate && cache.__UNSAFE_SIGN_NONSEGWIT !== false)
+      console.warn(
+        'Warning: Signing non-segwit inputs without the full parent transaction ' +
+          'means there is a chance that a miner could feed you incorrect information ' +
+          'to trick you into paying large fees. This behavior is the same as the old ' +
+          'TransactionBuilder class when signing non-segwit scripts. You are not ' +
+          'able to export this Psbt with toBuffer|toBase64|toHex since it is not ' +
+          'BIP174 compliant.\n*********************\nPROCEED WITH CAUTION!\n' +
+          '*********************',
+      );
     hash = unsignedTx.hashForSignature(
       inputIndex,
       meaningfulScript,

From d02483473b26f9fd9f24fe11bd1c31d1bb9bc90d Mon Sep 17 00:00:00 2001
From: Luke Childs <lukechilds123@gmail.com>
Date: Tue, 28 Apr 2020 19:58:05 +0700
Subject: [PATCH 09/15] allocUnsafe for faster buffer cloning

It's safe to do this because we immediately overwrite the entire buffer. No need to zero out first.
---
 src/bufferutils.js    | 2 +-
 ts_src/bufferutils.ts | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/bufferutils.js b/src/bufferutils.js
index 2ee3542..a68fd31 100644
--- a/src/bufferutils.js
+++ b/src/bufferutils.js
@@ -42,7 +42,7 @@ function reverseBuffer(buffer) {
 }
 exports.reverseBuffer = reverseBuffer;
 function cloneBuffer(buffer) {
-  const clone = Buffer.alloc(buffer.length);
+  const clone = Buffer.allocUnsafe(buffer.length);
   buffer.copy(clone);
   return clone;
 }
diff --git a/ts_src/bufferutils.ts b/ts_src/bufferutils.ts
index 2025f88..9005f2a 100644
--- a/ts_src/bufferutils.ts
+++ b/ts_src/bufferutils.ts
@@ -49,7 +49,7 @@ export function reverseBuffer(buffer: Buffer): Buffer {
 }
 
 export function cloneBuffer(buffer: Buffer): Buffer {
-  const clone = Buffer.alloc(buffer.length);
+  const clone = Buffer.allocUnsafe(buffer.length);
   buffer.copy(clone);
   return clone;
 }

From c9f399e509c5375580e9c196e3c2c5eeb59aa053 Mon Sep 17 00:00:00 2001
From: junderw <junderwood@bitcoinbank.co.jp>
Date: Wed, 29 Apr 2020 11:05:33 +0900
Subject: [PATCH 10/15] Add getInputType

---
 src/psbt.js             | 37 ++++++++++++++---
 test/fixtures/psbt.json | 18 +++++++++
 test/psbt.spec.ts       | 89 +++++++++++++++++++++++++++++++++++++++++
 ts_src/psbt.ts          | 79 +++++++++++++++++++++++++++++++-----
 types/psbt.d.ts         |  2 +
 5 files changed, 208 insertions(+), 17 deletions(-)

diff --git a/src/psbt.js b/src/psbt.js
index 5958b1e..7cab795 100644
--- a/src/psbt.js
+++ b/src/psbt.js
@@ -189,6 +189,7 @@ class Psbt {
       );
     }
     checkInputsForPartialSig(this.data.inputs, 'addInput');
+    if (inputData.witnessScript) checkInvalidP2WSH(inputData.witnessScript);
     const c = this.__CACHE;
     this.data.addInput(inputData);
     const txIn = c.__TX.ins[c.__TX.ins.length - 1];
@@ -285,6 +286,20 @@ class Psbt {
     this.data.clearFinalizedInput(inputIndex);
     return this;
   }
+  getInputType(inputIndex) {
+    const input = utils_1.checkForInput(this.data.inputs, inputIndex);
+    const script = getScriptFromUtxo(inputIndex, input, this.__CACHE);
+    const result = getMeaningfulScript(
+      script,
+      inputIndex,
+      'input',
+      input.redeemScript,
+      input.witnessScript,
+    );
+    const type = result.type === 'raw' ? '' : result.type + '-';
+    const mainType = classifyScript(result.meaningfulScript);
+    return type + mainType;
+  }
   inputHasPubkey(inputIndex, pubkey) {
     const input = utils_1.checkForInput(this.data.inputs, inputIndex);
     return pubkeyInInput(pubkey, input, inputIndex, this.__CACHE);
@@ -538,6 +553,7 @@ class Psbt {
     return this;
   }
   updateInput(inputIndex, updateData) {
+    if (updateData.witnessScript) checkInvalidP2WSH(updateData.witnessScript);
     this.data.updateInput(inputIndex, updateData);
     if (updateData.nonWitnessUtxo) {
       addNonWitnessTxCache(
@@ -924,7 +940,7 @@ function getHashForSig(inputIndex, input, cache, forValidate, sighashTypes) {
     input.redeemScript,
     input.witnessScript,
   );
-  if (['p2shp2wsh', 'p2wsh'].indexOf(type) >= 0) {
+  if (['p2sh-p2wsh', 'p2wsh'].indexOf(type) >= 0) {
     hash = unsignedTx.hashForWitnessV0(
       inputIndex,
       meaningfulScript,
@@ -1220,20 +1236,22 @@ function nonWitnessUtxoTxFromCache(cache, input, inputIndex) {
   }
   return c[inputIndex];
 }
-function pubkeyInInput(pubkey, input, inputIndex, cache) {
-  let script;
+function getScriptFromUtxo(inputIndex, input, cache) {
   if (input.witnessUtxo !== undefined) {
-    script = input.witnessUtxo.script;
+    return input.witnessUtxo.script;
   } else if (input.nonWitnessUtxo !== undefined) {
     const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(
       cache,
       input,
       inputIndex,
     );
-    script = nonWitnessUtxoTx.outs[cache.__TX.ins[inputIndex].index].script;
+    return nonWitnessUtxoTx.outs[cache.__TX.ins[inputIndex].index].script;
   } else {
     throw new Error("Can't find pubkey in input without Utxo data");
   }
+}
+function pubkeyInInput(pubkey, input, inputIndex, cache) {
+  const script = getScriptFromUtxo(inputIndex, input, cache);
   const { meaningfulScript } = getMeaningfulScript(
     script,
     inputIndex,
@@ -1275,9 +1293,11 @@ function getMeaningfulScript(
     meaningfulScript = witnessScript;
     checkRedeemScript(index, script, redeemScript, ioType);
     checkWitnessScript(index, redeemScript, witnessScript, ioType);
+    checkInvalidP2WSH(meaningfulScript);
   } else if (isP2WSH) {
     meaningfulScript = witnessScript;
     checkWitnessScript(index, script, witnessScript, ioType);
+    checkInvalidP2WSH(meaningfulScript);
   } else if (isP2SH) {
     meaningfulScript = redeemScript;
     checkRedeemScript(index, script, redeemScript, ioType);
@@ -1287,7 +1307,7 @@ function getMeaningfulScript(
   return {
     meaningfulScript,
     type: isP2SHP2WSH
-      ? 'p2shp2wsh'
+      ? 'p2sh-p2wsh'
       : isP2SH
       ? 'p2sh'
       : isP2WSH
@@ -1295,6 +1315,11 @@ function getMeaningfulScript(
       : 'raw',
   };
 }
+function checkInvalidP2WSH(script) {
+  if (isP2WPKH(script) || isP2SHScript(script)) {
+    throw new Error('P2WPKH or P2SH can not be contained within P2WSH');
+  }
+}
 function pubkeyInScript(pubkey, script) {
   const pubkeyHash = crypto_1.hash160(pubkey);
   const decompiled = bscript.decompile(script);
diff --git a/test/fixtures/psbt.json b/test/fixtures/psbt.json
index e3062e8..0e51d57 100644
--- a/test/fixtures/psbt.json
+++ b/test/fixtures/psbt.json
@@ -313,6 +313,24 @@
         },
         "exception": "Invalid arguments for Psbt\\.addInput\\. Requires single object with at least \\[hash\\] and \\[index\\]"
       },
+      {
+        "description": "checks for invalid p2wsh witnessScript",
+        "inputData": {
+          "hash": "Buffer.from('000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f', 'hex')",
+          "index": 0,
+          "witnessScript": "Buffer.from('0014000102030405060708090a0b0c0d0e0f00010203', 'hex')"
+        },
+        "exception": "P2WPKH or P2SH can not be contained within P2WSH"
+      },
+      {
+        "description": "checks for invalid p2wsh witnessScript",
+        "inputData": {
+          "hash": "Buffer.from('000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f', 'hex')",
+          "index": 0,
+          "witnessScript": "Buffer.from('a914000102030405060708090a0b0c0d0e0f0001020387', 'hex')"
+        },
+        "exception": "P2WPKH or P2SH can not be contained within P2WSH"
+      },
       {
         "description": "should be equal",
         "inputData": {
diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts
index ff2131b..5e88fe0 100644
--- a/test/psbt.spec.ts
+++ b/test/psbt.spec.ts
@@ -542,6 +542,95 @@ describe(`Psbt`, () => {
     });
   });
 
+  describe('getInputType', () => {
+    const { publicKey } = ECPair.makeRandom();
+    const p2wpkhPub = (pubkey: Buffer): Buffer =>
+      payments.p2wpkh({
+        pubkey,
+      }).output!;
+    const p2pkhPub = (pubkey: Buffer): Buffer =>
+      payments.p2pkh({
+        pubkey,
+      }).output!;
+    const p2shOut = (output: Buffer): Buffer =>
+      payments.p2sh({
+        redeem: { output },
+      }).output!;
+    const p2wshOut = (output: Buffer): Buffer =>
+      payments.p2wsh({
+        redeem: { output },
+      }).output!;
+    const p2shp2wshOut = (output: Buffer): Buffer => p2shOut(p2wshOut(output));
+    const noOuter = (output: Buffer): Buffer => output;
+
+    function getInputTypeTest({
+      innerScript,
+      outerScript,
+      redeemGetter,
+      witnessGetter,
+      expectedType,
+    }: any): void {
+      const psbt = new Psbt();
+      psbt.addInput({
+        hash:
+          '0000000000000000000000000000000000000000000000000000000000000000',
+        index: 0,
+        witnessUtxo: {
+          script: outerScript(innerScript(publicKey)),
+          value: 2e3,
+        },
+        ...(redeemGetter ? { redeemScript: redeemGetter(publicKey) } : {}),
+        ...(witnessGetter ? { witnessScript: witnessGetter(publicKey) } : {}),
+      });
+      const type = psbt.getInputType(0);
+      assert.strictEqual(type, expectedType, 'incorrect input type');
+    }
+    [
+      {
+        innerScript: p2pkhPub,
+        outerScript: noOuter,
+        redeemGetter: null,
+        witnessGetter: null,
+        expectedType: 'pubkeyhash',
+      },
+      {
+        innerScript: p2wpkhPub,
+        outerScript: noOuter,
+        redeemGetter: null,
+        witnessGetter: null,
+        expectedType: 'witnesspubkeyhash',
+      },
+      {
+        innerScript: p2pkhPub,
+        outerScript: p2shOut,
+        redeemGetter: p2pkhPub,
+        witnessGetter: null,
+        expectedType: 'p2sh-pubkeyhash',
+      },
+      {
+        innerScript: p2wpkhPub,
+        outerScript: p2shOut,
+        redeemGetter: p2wpkhPub,
+        witnessGetter: null,
+        expectedType: 'p2sh-witnesspubkeyhash',
+      },
+      {
+        innerScript: p2pkhPub,
+        outerScript: p2wshOut,
+        redeemGetter: null,
+        witnessGetter: p2pkhPub,
+        expectedType: 'p2wsh-pubkeyhash',
+      },
+      {
+        innerScript: p2pkhPub,
+        outerScript: p2shp2wshOut,
+        redeemGetter: (pk: Buffer): Buffer => p2wshOut(p2pkhPub(pk)),
+        witnessGetter: p2pkhPub,
+        expectedType: 'p2sh-p2wsh-pubkeyhash',
+      },
+    ].forEach(getInputTypeTest);
+  });
+
   describe('inputHasPubkey', () => {
     it('should throw', () => {
       const psbt = new Psbt();
diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts
index 23bdc1d..cb14fc5 100644
--- a/ts_src/psbt.ts
+++ b/ts_src/psbt.ts
@@ -242,6 +242,7 @@ export class Psbt {
       );
     }
     checkInputsForPartialSig(this.data.inputs, 'addInput');
+    if (inputData.witnessScript) checkInvalidP2WSH(inputData.witnessScript);
     const c = this.__CACHE;
     this.data.addInput(inputData);
     const txIn = c.__TX.ins[c.__TX.ins.length - 1];
@@ -355,6 +356,21 @@ export class Psbt {
     return this;
   }
 
+  getInputType(inputIndex: number): AllScriptType {
+    const input = checkForInput(this.data.inputs, inputIndex);
+    const script = getScriptFromUtxo(inputIndex, input, this.__CACHE);
+    const result = getMeaningfulScript(
+      script,
+      inputIndex,
+      'input',
+      input.redeemScript,
+      input.witnessScript,
+    );
+    const type = result.type === 'raw' ? '' : result.type + '-';
+    const mainType = classifyScript(result.meaningfulScript);
+    return (type + mainType) as AllScriptType;
+  }
+
   inputHasPubkey(inputIndex: number, pubkey: Buffer): boolean {
     const input = checkForInput(this.data.inputs, inputIndex);
     return pubkeyInInput(pubkey, input, inputIndex, this.__CACHE);
@@ -648,6 +664,7 @@ export class Psbt {
   }
 
   updateInput(inputIndex: number, updateData: PsbtInputUpdate): this {
+    if (updateData.witnessScript) checkInvalidP2WSH(updateData.witnessScript);
     this.data.updateInput(inputIndex, updateData);
     if (updateData.nonWitnessUtxo) {
       addNonWitnessTxCache(
@@ -1215,7 +1232,7 @@ function getHashForSig(
     input.witnessScript,
   );
 
-  if (['p2shp2wsh', 'p2wsh'].indexOf(type) >= 0) {
+  if (['p2sh-p2wsh', 'p2wsh'].indexOf(type) >= 0) {
     hash = unsignedTx.hashForWitnessV0(
       inputIndex,
       meaningfulScript,
@@ -1572,25 +1589,32 @@ function nonWitnessUtxoTxFromCache(
   return c[inputIndex];
 }
 
-function pubkeyInInput(
-  pubkey: Buffer,
-  input: PsbtInput,
+function getScriptFromUtxo(
   inputIndex: number,
+  input: PsbtInput,
   cache: PsbtCache,
-): boolean {
-  let script: Buffer;
+): Buffer {
   if (input.witnessUtxo !== undefined) {
-    script = input.witnessUtxo.script;
+    return input.witnessUtxo.script;
   } else if (input.nonWitnessUtxo !== undefined) {
     const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(
       cache,
       input,
       inputIndex,
     );
-    script = nonWitnessUtxoTx.outs[cache.__TX.ins[inputIndex].index].script;
+    return nonWitnessUtxoTx.outs[cache.__TX.ins[inputIndex].index].script;
   } else {
     throw new Error("Can't find pubkey in input without Utxo data");
   }
+}
+
+function pubkeyInInput(
+  pubkey: Buffer,
+  input: PsbtInput,
+  inputIndex: number,
+  cache: PsbtCache,
+): boolean {
+  const script = getScriptFromUtxo(inputIndex, input, cache);
   const { meaningfulScript } = getMeaningfulScript(
     script,
     inputIndex,
@@ -1626,7 +1650,7 @@ function getMeaningfulScript(
   witnessScript?: Buffer,
 ): {
   meaningfulScript: Buffer;
-  type: 'p2sh' | 'p2wsh' | 'p2shp2wsh' | 'raw';
+  type: 'p2sh' | 'p2wsh' | 'p2sh-p2wsh' | 'raw';
 } {
   const isP2SH = isP2SHScript(script);
   const isP2SHP2WSH = isP2SH && redeemScript && isP2WSHScript(redeemScript);
@@ -1645,9 +1669,11 @@ function getMeaningfulScript(
     meaningfulScript = witnessScript!;
     checkRedeemScript(index, script, redeemScript!, ioType);
     checkWitnessScript(index, redeemScript!, witnessScript!, ioType);
+    checkInvalidP2WSH(meaningfulScript);
   } else if (isP2WSH) {
     meaningfulScript = witnessScript!;
     checkWitnessScript(index, script, witnessScript!, ioType);
+    checkInvalidP2WSH(meaningfulScript);
   } else if (isP2SH) {
     meaningfulScript = redeemScript!;
     checkRedeemScript(index, script, redeemScript!, ioType);
@@ -1657,7 +1683,7 @@ function getMeaningfulScript(
   return {
     meaningfulScript,
     type: isP2SHP2WSH
-      ? 'p2shp2wsh'
+      ? 'p2sh-p2wsh'
       : isP2SH
       ? 'p2sh'
       : isP2WSH
@@ -1666,6 +1692,12 @@ function getMeaningfulScript(
   };
 }
 
+function checkInvalidP2WSH(script: Buffer): void {
+  if (isP2WPKH(script) || isP2SHScript(script)) {
+    throw new Error('P2WPKH or P2SH can not be contained within P2WSH');
+  }
+}
+
 function pubkeyInScript(pubkey: Buffer, script: Buffer): boolean {
   const pubkeyHash = hash160(pubkey);
 
@@ -1678,7 +1710,32 @@ function pubkeyInScript(pubkey: Buffer, script: Buffer): boolean {
   });
 }
 
-function classifyScript(script: Buffer): string {
+type AllScriptType =
+  | 'witnesspubkeyhash'
+  | 'pubkeyhash'
+  | 'multisig'
+  | 'pubkey'
+  | 'nonstandard'
+  | 'p2sh-witnesspubkeyhash'
+  | 'p2sh-pubkeyhash'
+  | 'p2sh-multisig'
+  | 'p2sh-pubkey'
+  | 'p2sh-nonstandard'
+  | 'p2wsh-pubkeyhash'
+  | 'p2wsh-multisig'
+  | 'p2wsh-pubkey'
+  | 'p2wsh-nonstandard'
+  | 'p2sh-p2wsh-pubkeyhash'
+  | 'p2sh-p2wsh-multisig'
+  | 'p2sh-p2wsh-pubkey'
+  | 'p2sh-p2wsh-nonstandard';
+type ScriptType =
+  | 'witnesspubkeyhash'
+  | 'pubkeyhash'
+  | 'multisig'
+  | 'pubkey'
+  | 'nonstandard';
+function classifyScript(script: Buffer): ScriptType {
   if (isP2WPKH(script)) return 'witnesspubkeyhash';
   if (isP2PKH(script)) return 'pubkeyhash';
   if (isP2MS(script)) return 'multisig';
diff --git a/types/psbt.d.ts b/types/psbt.d.ts
index 127ef0f..4d1c099 100644
--- a/types/psbt.d.ts
+++ b/types/psbt.d.ts
@@ -69,6 +69,7 @@ export declare class Psbt {
     getFee(): number;
     finalizeAllInputs(): this;
     finalizeInput(inputIndex: number, finalScriptsFunc?: FinalScriptsFunc): this;
+    getInputType(inputIndex: number): AllScriptType;
     inputHasPubkey(inputIndex: number, pubkey: Buffer): boolean;
     outputHasPubkey(outputIndex: number, pubkey: Buffer): boolean;
     validateSignaturesOfAllInputs(): boolean;
@@ -151,4 +152,5 @@ isP2WSH: boolean) => {
     finalScriptSig: Buffer | undefined;
     finalScriptWitness: Buffer | undefined;
 };
+declare type AllScriptType = 'witnesspubkeyhash' | 'pubkeyhash' | 'multisig' | 'pubkey' | 'nonstandard' | 'p2sh-witnesspubkeyhash' | 'p2sh-pubkeyhash' | 'p2sh-multisig' | 'p2sh-pubkey' | 'p2sh-nonstandard' | 'p2wsh-pubkeyhash' | 'p2wsh-multisig' | 'p2wsh-pubkey' | 'p2wsh-nonstandard' | 'p2sh-p2wsh-pubkeyhash' | 'p2sh-p2wsh-multisig' | 'p2sh-p2wsh-pubkey' | 'p2sh-p2wsh-nonstandard';
 export {};

From 5d19abfb85641887060804a2a396eacd6fea9117 Mon Sep 17 00:00:00 2001
From: junderw <junderwood@bitcoinbank.co.jp>
Date: Wed, 29 Apr 2020 13:32:57 +0900
Subject: [PATCH 11/15] Add ability to get redeemScript|witnessScript from
 finalized scripts

---
 src/psbt.js       | 35 +++++++++++++++++++++++++++++++++--
 test/psbt.spec.ts | 34 ++++++++++++++++++++++------------
 ts_src/psbt.ts    | 43 +++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 96 insertions(+), 16 deletions(-)

diff --git a/src/psbt.js b/src/psbt.js
index 7cab795..b80951f 100644
--- a/src/psbt.js
+++ b/src/psbt.js
@@ -293,8 +293,9 @@ class Psbt {
       script,
       inputIndex,
       'input',
-      input.redeemScript,
-      input.witnessScript,
+      input.redeemScript || redeemFromFinalScriptSig(input.finalScriptSig),
+      input.witnessScript ||
+        redeemFromFinalWitnessScript(input.finalScriptWitness),
     );
     const type = result.type === 'raw' ? '' : result.type + '-';
     const mainType = classifyScript(result.meaningfulScript);
@@ -1272,6 +1273,36 @@ function pubkeyInOutput(pubkey, output, outputIndex, cache) {
   );
   return pubkeyInScript(pubkey, meaningfulScript);
 }
+function redeemFromFinalScriptSig(finalScript) {
+  if (!finalScript) return;
+  const decomp = bscript.decompile(finalScript);
+  if (!decomp) return;
+  const lastItem = decomp[decomp.length - 1];
+  if (
+    !Buffer.isBuffer(lastItem) ||
+    isPubkeyLike(lastItem) ||
+    isSigLike(lastItem)
+  )
+    return;
+  const sDecomp = bscript.decompile(lastItem);
+  if (!sDecomp) return;
+  return lastItem;
+}
+function redeemFromFinalWitnessScript(finalScript) {
+  if (!finalScript) return;
+  const decomp = scriptWitnessToWitnessStack(finalScript);
+  const lastItem = decomp[decomp.length - 1];
+  if (isPubkeyLike(lastItem)) return;
+  const sDecomp = bscript.decompile(lastItem);
+  if (!sDecomp) return;
+  return lastItem;
+}
+function isPubkeyLike(buf) {
+  return buf.length === 33 && bscript.isCanonicalPubKey(buf);
+}
+function isSigLike(buf) {
+  return bscript.isCanonicalScriptSignature(buf);
+}
 function getMeaningfulScript(
   script,
   index,
diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts
index 5e88fe0..a5a2214 100644
--- a/test/psbt.spec.ts
+++ b/test/psbt.spec.ts
@@ -543,7 +543,8 @@ describe(`Psbt`, () => {
   });
 
   describe('getInputType', () => {
-    const { publicKey } = ECPair.makeRandom();
+    const key = ECPair.makeRandom();
+    const { publicKey } = key;
     const p2wpkhPub = (pubkey: Buffer): Buffer =>
       payments.p2wpkh({
         pubkey,
@@ -569,19 +570,26 @@ describe(`Psbt`, () => {
       redeemGetter,
       witnessGetter,
       expectedType,
+      finalize,
     }: any): void {
       const psbt = new Psbt();
-      psbt.addInput({
-        hash:
-          '0000000000000000000000000000000000000000000000000000000000000000',
-        index: 0,
-        witnessUtxo: {
-          script: outerScript(innerScript(publicKey)),
-          value: 2e3,
-        },
-        ...(redeemGetter ? { redeemScript: redeemGetter(publicKey) } : {}),
-        ...(witnessGetter ? { witnessScript: witnessGetter(publicKey) } : {}),
-      });
+      psbt
+        .addInput({
+          hash:
+            '0000000000000000000000000000000000000000000000000000000000000000',
+          index: 0,
+          witnessUtxo: {
+            script: outerScript(innerScript(publicKey)),
+            value: 2e3,
+          },
+          ...(redeemGetter ? { redeemScript: redeemGetter(publicKey) } : {}),
+          ...(witnessGetter ? { witnessScript: witnessGetter(publicKey) } : {}),
+        })
+        .addOutput({
+          script: Buffer.from('0014d85c2b71d0060b09c9886aeb815e50991dda124d'),
+          value: 1800,
+        });
+      if (finalize) psbt.signInput(0, key).finalizeInput(0);
       const type = psbt.getInputType(0);
       assert.strictEqual(type, expectedType, 'incorrect input type');
     }
@@ -613,6 +621,7 @@ describe(`Psbt`, () => {
         redeemGetter: p2wpkhPub,
         witnessGetter: null,
         expectedType: 'p2sh-witnesspubkeyhash',
+        finalize: true,
       },
       {
         innerScript: p2pkhPub,
@@ -620,6 +629,7 @@ describe(`Psbt`, () => {
         redeemGetter: null,
         witnessGetter: p2pkhPub,
         expectedType: 'p2wsh-pubkeyhash',
+        finalize: true,
       },
       {
         innerScript: p2pkhPub,
diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts
index cb14fc5..39d3a4c 100644
--- a/ts_src/psbt.ts
+++ b/ts_src/psbt.ts
@@ -363,8 +363,9 @@ export class Psbt {
       script,
       inputIndex,
       'input',
-      input.redeemScript,
-      input.witnessScript,
+      input.redeemScript || redeemFromFinalScriptSig(input.finalScriptSig),
+      input.witnessScript ||
+        redeemFromFinalWitnessScript(input.finalScriptWitness),
     );
     const type = result.type === 'raw' ? '' : result.type + '-';
     const mainType = classifyScript(result.meaningfulScript);
@@ -1642,6 +1643,44 @@ function pubkeyInOutput(
   return pubkeyInScript(pubkey, meaningfulScript);
 }
 
+function redeemFromFinalScriptSig(
+  finalScript: Buffer | undefined,
+): Buffer | undefined {
+  if (!finalScript) return;
+  const decomp = bscript.decompile(finalScript);
+  if (!decomp) return;
+  const lastItem = decomp[decomp.length - 1];
+  if (
+    !Buffer.isBuffer(lastItem) ||
+    isPubkeyLike(lastItem) ||
+    isSigLike(lastItem)
+  )
+    return;
+  const sDecomp = bscript.decompile(lastItem);
+  if (!sDecomp) return;
+  return lastItem;
+}
+
+function redeemFromFinalWitnessScript(
+  finalScript: Buffer | undefined,
+): Buffer | undefined {
+  if (!finalScript) return;
+  const decomp = scriptWitnessToWitnessStack(finalScript);
+  const lastItem = decomp[decomp.length - 1];
+  if (isPubkeyLike(lastItem)) return;
+  const sDecomp = bscript.decompile(lastItem);
+  if (!sDecomp) return;
+  return lastItem;
+}
+
+function isPubkeyLike(buf: Buffer): boolean {
+  return buf.length === 33 && bscript.isCanonicalPubKey(buf);
+}
+
+function isSigLike(buf: Buffer): boolean {
+  return bscript.isCanonicalScriptSignature(buf);
+}
+
 function getMeaningfulScript(
   script: Buffer,
   index: number,

From f87a20caa7828f6f8c29049c73efd369ff81c57b Mon Sep 17 00:00:00 2001
From: junderw <junderwood@bitcoinbank.co.jp>
Date: Wed, 29 Apr 2020 14:39:50 +0900
Subject: [PATCH 12/15] Add hasHDKey

---
 src/psbt.js       | 21 ++++++++++++++++++
 test/psbt.spec.ts | 55 +++++++++++++++++++++++++++++++++++++++++++++++
 ts_src/psbt.ts    | 27 +++++++++++++++++++++++
 types/psbt.d.ts   |  2 ++
 4 files changed, 105 insertions(+)

diff --git a/src/psbt.js b/src/psbt.js
index b80951f..693bfc3 100644
--- a/src/psbt.js
+++ b/src/psbt.js
@@ -305,10 +305,24 @@ class Psbt {
     const input = utils_1.checkForInput(this.data.inputs, inputIndex);
     return pubkeyInInput(pubkey, input, inputIndex, this.__CACHE);
   }
+  inputHasHDKey(inputIndex, root) {
+    const input = utils_1.checkForInput(this.data.inputs, inputIndex);
+    const derivationIsMine = bip32DerivationIsMine(root);
+    return (
+      !!input.bip32Derivation && input.bip32Derivation.some(derivationIsMine)
+    );
+  }
   outputHasPubkey(outputIndex, pubkey) {
     const output = utils_1.checkForOutput(this.data.outputs, outputIndex);
     return pubkeyInOutput(pubkey, output, outputIndex, this.__CACHE);
   }
+  outputHasHDKey(outputIndex, root) {
+    const output = utils_1.checkForOutput(this.data.outputs, outputIndex);
+    const derivationIsMine = bip32DerivationIsMine(root);
+    return (
+      !!output.bip32Derivation && output.bip32Derivation.some(derivationIsMine)
+    );
+  }
   validateSignaturesOfAllInputs() {
     utils_1.checkForInput(this.data.inputs, 0); // making sure we have at least one
     const results = range(this.data.inputs.length).map(idx =>
@@ -696,6 +710,13 @@ const isP2PKH = isPaymentFactory(payments.p2pkh);
 const isP2WPKH = isPaymentFactory(payments.p2wpkh);
 const isP2WSHScript = isPaymentFactory(payments.p2wsh);
 const isP2SHScript = isPaymentFactory(payments.p2sh);
+function bip32DerivationIsMine(root) {
+  return d => {
+    if (!d.masterFingerprint.equals(root.fingerprint)) return false;
+    if (!root.derivePath(d.path).publicKey.equals(d.pubkey)) return false;
+    return true;
+  };
+}
 function check32Bit(num) {
   if (
     typeof num !== 'number' ||
diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts
index a5a2214..c33e9cf 100644
--- a/test/psbt.spec.ts
+++ b/test/psbt.spec.ts
@@ -1,4 +1,5 @@
 import * as assert from 'assert';
+import * as crypto from 'crypto';
 import { describe, it } from 'mocha';
 
 import { bip32, ECPair, networks as NETWORKS, payments, Psbt } from '..';
@@ -641,6 +642,29 @@ describe(`Psbt`, () => {
     ].forEach(getInputTypeTest);
   });
 
+  describe('inputHasHDKey', () => {
+    it('should return true if HD key is present', () => {
+      const root = bip32.fromSeed(crypto.randomBytes(32));
+      const root2 = bip32.fromSeed(crypto.randomBytes(32));
+      const path = "m/0'/0";
+      const psbt = new Psbt();
+      psbt.addInput({
+        hash:
+          '0000000000000000000000000000000000000000000000000000000000000000',
+        index: 0,
+        bip32Derivation: [
+          {
+            masterFingerprint: root.fingerprint,
+            path,
+            pubkey: root.derivePath(path).publicKey,
+          },
+        ],
+      });
+      assert.strictEqual(psbt.inputHasHDKey(0, root), true);
+      assert.strictEqual(psbt.inputHasHDKey(0, root2), false);
+    });
+  });
+
   describe('inputHasPubkey', () => {
     it('should throw', () => {
       const psbt = new Psbt();
@@ -712,6 +736,37 @@ describe(`Psbt`, () => {
     });
   });
 
+  describe('outputHasHDKey', () => {
+    it('should return true if HD key is present', () => {
+      const root = bip32.fromSeed(crypto.randomBytes(32));
+      const root2 = bip32.fromSeed(crypto.randomBytes(32));
+      const path = "m/0'/0";
+      const psbt = new Psbt();
+      psbt
+        .addInput({
+          hash:
+            '0000000000000000000000000000000000000000000000000000000000000000',
+          index: 0,
+        })
+        .addOutput({
+          script: Buffer.from(
+            '0014000102030405060708090a0b0c0d0e0f00010203',
+            'hex',
+          ),
+          value: 2000,
+          bip32Derivation: [
+            {
+              masterFingerprint: root.fingerprint,
+              path,
+              pubkey: root.derivePath(path).publicKey,
+            },
+          ],
+        });
+      assert.strictEqual(psbt.outputHasHDKey(0, root), true);
+      assert.strictEqual(psbt.outputHasHDKey(0, root2), false);
+    });
+  });
+
   describe('outputHasPubkey', () => {
     it('should throw', () => {
       const psbt = new Psbt();
diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts
index 39d3a4c..8f06e15 100644
--- a/ts_src/psbt.ts
+++ b/ts_src/psbt.ts
@@ -1,6 +1,7 @@
 import { Psbt as PsbtBase } from 'bip174';
 import * as varuint from 'bip174/src/lib/converter/varint';
 import {
+  Bip32Derivation,
   KeyValue,
   PartialSig,
   PsbtGlobalUpdate,
@@ -377,11 +378,27 @@ export class Psbt {
     return pubkeyInInput(pubkey, input, inputIndex, this.__CACHE);
   }
 
+  inputHasHDKey(inputIndex: number, root: HDSigner): boolean {
+    const input = checkForInput(this.data.inputs, inputIndex);
+    const derivationIsMine = bip32DerivationIsMine(root);
+    return (
+      !!input.bip32Derivation && input.bip32Derivation.some(derivationIsMine)
+    );
+  }
+
   outputHasPubkey(outputIndex: number, pubkey: Buffer): boolean {
     const output = checkForOutput(this.data.outputs, outputIndex);
     return pubkeyInOutput(pubkey, output, outputIndex, this.__CACHE);
   }
 
+  outputHasHDKey(outputIndex: number, root: HDSigner): boolean {
+    const output = checkForOutput(this.data.outputs, outputIndex);
+    const derivationIsMine = bip32DerivationIsMine(root);
+    return (
+      !!output.bip32Derivation && output.bip32Derivation.some(derivationIsMine)
+    );
+  }
+
   validateSignaturesOfAllInputs(): boolean {
     checkForInput(this.data.inputs, 0); // making sure we have at least one
     const results = range(this.data.inputs.length).map(idx =>
@@ -905,6 +922,16 @@ const isP2WPKH = isPaymentFactory(payments.p2wpkh);
 const isP2WSHScript = isPaymentFactory(payments.p2wsh);
 const isP2SHScript = isPaymentFactory(payments.p2sh);
 
+function bip32DerivationIsMine(
+  root: HDSigner,
+): (d: Bip32Derivation) => boolean {
+  return (d: Bip32Derivation): boolean => {
+    if (!d.masterFingerprint.equals(root.fingerprint)) return false;
+    if (!root.derivePath(d.path).publicKey.equals(d.pubkey)) return false;
+    return true;
+  };
+}
+
 function check32Bit(num: number): void {
   if (
     typeof num !== 'number' ||
diff --git a/types/psbt.d.ts b/types/psbt.d.ts
index 4d1c099..eb239dc 100644
--- a/types/psbt.d.ts
+++ b/types/psbt.d.ts
@@ -71,7 +71,9 @@ export declare class Psbt {
     finalizeInput(inputIndex: number, finalScriptsFunc?: FinalScriptsFunc): this;
     getInputType(inputIndex: number): AllScriptType;
     inputHasPubkey(inputIndex: number, pubkey: Buffer): boolean;
+    inputHasHDKey(inputIndex: number, root: HDSigner): boolean;
     outputHasPubkey(outputIndex: number, pubkey: Buffer): boolean;
+    outputHasHDKey(outputIndex: number, root: HDSigner): boolean;
     validateSignaturesOfAllInputs(): boolean;
     validateSignaturesOfInput(inputIndex: number, pubkey?: Buffer): boolean;
     signAllInputsHD(hdKeyPair: HDSigner, sighashTypes?: number[]): this;

From 3a54c738176a04ad8cc5e08944cb07bb171a87de Mon Sep 17 00:00:00 2001
From: junderw <junderwood@bitcoinbank.co.jp>
Date: Sat, 12 Sep 2020 00:19:21 +0900
Subject: [PATCH 13/15] Update bip174 dep

---
 package-lock.json | 9 +++++----
 package.json      | 2 +-
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index d9a67fe..5b53bb7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -367,9 +367,9 @@
       }
     },
     "bip174": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/bip174/-/bip174-1.0.1.tgz",
-      "integrity": "sha512-Mq2aFs1TdMfxBpYPg7uzjhsiXbAtoVq44TNjEWtvuZBiBgc3m7+n55orYMtTAxdg7jWbL4DtH0MKocJER4xERQ=="
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/bip174/-/bip174-2.0.1.tgz",
+      "integrity": "sha512-i3X26uKJOkDTAalYAp0Er+qGMDhrbbh2o93/xiPyAN2s25KrClSpe3VXo/7mNJoqA5qfko8rLS2l3RWZgYmjKQ=="
     },
     "bip32": {
       "version": "2.0.4",
@@ -1344,7 +1344,8 @@
     "lodash": {
       "version": "4.17.19",
       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
-      "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
+      "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
+      "dev": true
     },
     "lodash.flattendeep": {
       "version": "4.4.0",
diff --git a/package.json b/package.json
index 5b765c6..b45735b 100644
--- a/package.json
+++ b/package.json
@@ -50,7 +50,7 @@
   ],
   "dependencies": {
     "bech32": "^1.1.2",
-    "bip174": "^1.0.1",
+    "bip174": "^2.0.1",
     "bip32": "^2.0.4",
     "bip66": "^1.1.0",
     "bitcoin-ops": "^1.4.0",

From 5e3442b74be523f511bcee362c184d91d5f44331 Mon Sep 17 00:00:00 2001
From: junderw <junderwood@bitcoinbank.co.jp>
Date: Sat, 12 Sep 2020 00:35:57 +0900
Subject: [PATCH 14/15] Fix txOutputs

---
 ts_src/psbt.ts  | 6 +++---
 types/psbt.d.ts | 8 ++++----
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts
index d4d886f..c55e6bc 100644
--- a/ts_src/psbt.ts
+++ b/ts_src/psbt.ts
@@ -32,8 +32,8 @@ export interface PsbtTxInput extends TransactionInput {
   hash: Buffer;
 }
 
-export interface PsbtTxOutput extends Output {
-  address: string;
+export interface PsbtTxOutput extends TransactionOutput {
+  address: string | undefined;
 }
 
 /**
@@ -171,7 +171,7 @@ export class Psbt {
     }));
   }
 
-  get txOutputs(): TransactionOutput[] {
+  get txOutputs(): PsbtTxOutput[] {
     return this.__CACHE.__TX.outs.map(output => {
       let address;
       try {
diff --git a/types/psbt.d.ts b/types/psbt.d.ts
index eb239dc..022a95d 100644
--- a/types/psbt.d.ts
+++ b/types/psbt.d.ts
@@ -1,13 +1,13 @@
 import { Psbt as PsbtBase } from 'bip174';
-import { KeyValue, PsbtGlobalUpdate, PsbtInput, PsbtInputUpdate, PsbtOutput, PsbtOutputUpdate, TransactionInput } from 'bip174/src/lib/interfaces';
+import { KeyValue, PsbtGlobalUpdate, PsbtInput, PsbtInputUpdate, PsbtOutput, PsbtOutputUpdate, TransactionInput, TransactionOutput } from 'bip174/src/lib/interfaces';
 import { Signer, SignerAsync } from './ecpair';
 import { Network } from './networks';
-import { Output, Transaction } from './transaction';
+import { Transaction } from './transaction';
 export interface PsbtTxInput extends TransactionInput {
     hash: Buffer;
 }
-export interface PsbtTxOutput extends Output {
-    address: string;
+export interface PsbtTxOutput extends TransactionOutput {
+    address: string | undefined;
 }
 /**
  * Psbt class can parse and generate a PSBT binary based off of the BIP174.

From 7aaef308e0b1c9dddd7ba8049780e59ecb4f6f8c Mon Sep 17 00:00:00 2001
From: junderw <junderwood@bitcoinbank.co.jp>
Date: Sat, 12 Sep 2020 00:49:05 +0900
Subject: [PATCH 15/15] 5.2.0

---
 CHANGELOG.md      | 7 +++++++
 package-lock.json | 2 +-
 package.json      | 2 +-
 3 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2e9eae7..a4e1eb7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+# 5.2.0
+__changed__
+- Updated PSBT to allow for witnessUtxo and nonWitnessUtxo simultaneously (Re: segwit psbt bug) (#1563)
+
+__added__
+- PSBT methods `getInputType`, `inputHasPubkey`, `inputHasHDKey`, `outputHasPubkey`, `outputHasHDKey` (#1563)
+
 # 5.1.10
 __fixed__
 - Fixed psbt.signInputAsync (and consequentially all Async signing methods) not handling rejection of keypair.sign properly (#1582)
diff --git a/package-lock.json b/package-lock.json
index 5b53bb7..7278818 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "bitcoinjs-lib",
-  "version": "5.1.10",
+  "version": "5.2.0",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
diff --git a/package.json b/package.json
index b45735b..0f92b75 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "bitcoinjs-lib",
-  "version": "5.1.10",
+  "version": "5.2.0",
   "description": "Client-side Bitcoin JavaScript library",
   "main": "./src/index.js",
   "types": "./types/index.d.ts",