Repository: bitcoinjs/bitcoinjs-lib
Pull request #647 (2016-10-06 02:18:33)

TransactionBuilder: if prevOutScript is defined, but not signable, try again
This should have fixed https://github.com/bitcoinjs/bitcoinjs-lib/issues/644#issuecomment-251782056,  but I'm still getting issues.

None the less, we need to add some test cases for this,  as it is a valid case.

dcousens: ping @runn1ng to please review :)
runn1ng: I will try it later today.

Also thanks for the invite! :)
dcousens: ping @fanatid @rubensayshi @afk11 for merge

Patch:
diff --git a/.travis.yml b/.travis.yml
index 9b2f05d0..037e1f8b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,10 +10,11 @@ node_js:
   - "6"
 matrix:
   include:
-    - node_js: "4"
+    - node_js: "6"
       env: TEST_SUITE=standard
+    - node_js: "6"
+      env: TEST_SUITE=coverage
 env:
-  - TEST_SUITE=coverage
   - TEST_SUITE=integration
   - TEST_SUITE=unit
 script: npm run-script $TEST_SUITE
diff --git a/src/transaction_builder.js b/src/transaction_builder.js
index 5c4f58d7..add817e8 100644
--- a/src/transaction_builder.js
+++ b/src/transaction_builder.js
@@ -87,11 +87,13 @@ function expandInput (scriptSig, redeemScript) {
   }
 }
 
-function expandOutput (script, ourPubKey) {
+function expandOutput (script, scriptType, ourPubKey) {
   typeforce(types.Buffer, script)
 
   var scriptChunks = bscript.decompile(script)
-  var scriptType = bscript.classifyOutput(script)
+  if (!scriptType) {
+    scriptType = bscript.classifyOutput(scriptChunks)
+  }
 
   var pubKeys = []
 
@@ -113,7 +115,7 @@ function expandOutput (script, ourPubKey) {
       pubKeys = scriptChunks.slice(1, -2)
       break
 
-    default: return
+    default: return { scriptType: scriptType }
   }
 
   return {
@@ -189,16 +191,17 @@ function prepareInput (input, kpPubKey, redeemScript, hashType) {
 
       var prevOutScriptScriptHash = bscript.decompile(input.prevOutScript)[1]
       if (!prevOutScriptScriptHash.equals(redeemScriptHash)) throw new Error('Inconsistent hash160(RedeemScript)')
+    }
 
-    // or, we don't have a prevOutScript, so generate a P2SH script
-    } else {
+    var expanded = expandOutput(redeemScript, undefined, kpPubKey)
+    if (!expanded.pubKeys) throw new Error('RedeemScript not supported "' + bscript.toASM(redeemScript) + '"')
+
+    // if we don't have a prevOutScript, generate a P2SH script
+    if (!input.prevOutType) {
       input.prevOutScript = bscript.scriptHashOutput(redeemScriptHash)
       input.prevOutType = 'scripthash'
     }
 
-    var expanded = expandOutput(redeemScript, kpPubKey)
-    if (!expanded) throw new Error('RedeemScript not supported "' + bscript.toASM(redeemScript) + '"')
-
     input.pubKeys = expanded.pubKeys
     input.redeemScript = redeemScript
     input.redeemScriptType = expanded.scriptType
@@ -209,8 +212,13 @@ function prepareInput (input, kpPubKey, redeemScript, hashType) {
     // pay-to-scriptHash is not possible without a redeemScript
     if (input.prevOutType === 'scripthash') throw new Error('PrevOutScript is P2SH, missing redeemScript')
 
-    // throw if we can't sign with it
-    if (!input.pubKeys || !input.signatures) throw new Error(input.prevOutType + ' not supported')
+    // try to derive the missing information about the script now that we
+    // have a kpPubKey
+    expanded = expandOutput(input.prevOutScript, input.prevOutType, kpPubKey)
+    if (!expanded.pubKeys) return
+
+    input.pubKeys = expanded.pubKeys
+    input.signatures = expanded.signatures
 
   // no prior knowledge, assume pubKeyHash
   } else {
@@ -356,19 +364,21 @@ TransactionBuilder.prototype.__addInputUnsafe = function (txHash, vout, sequence
 
   // derive what we can from the previous transactions output script
   if (!input.prevOutScript && prevOutScript) {
-    var prevOutScriptChunks = bscript.decompile(prevOutScript)
-    var prevOutType = bscript.classifyOutput(prevOutScriptChunks)
+    var prevOutType
 
     if (!input.pubKeys && !input.signatures) {
       var expanded = expandOutput(prevOutScript)
-      if (expanded) {
+
+      if (expanded.pubKeys) {
         input.pubKeys = expanded.pubKeys
         input.signatures = expanded.signatures
       }
+
+      prevOutType = expanded.scriptType
     }
 
     input.prevOutScript = prevOutScript
-    input.prevOutType = prevOutType
+    input.prevOutType = prevOutType || bscript.classifyOutput(prevOutScript)
   }
 
   var vin = this.tx.addInput(txHash, vout, sequence, scriptSig)
@@ -436,29 +446,36 @@ TransactionBuilder.prototype.__build = function (allowIncomplete) {
   return tx
 }
 
+function canSign (input) {
+  return input.hashType !== undefined &&
+    input.prevOutScript !== undefined &&
+    input.pubKeys !== undefined &&
+    input.signatures !== undefined &&
+    input.signatures.length === input.pubKeys.length &&
+    input.pubKeys.length > 0
+}
+
 TransactionBuilder.prototype.sign = function (vin, keyPair, redeemScript, hashType) {
   if (keyPair.network !== this.network) throw new Error('Inconsistent network')
   if (!this.inputs[vin]) throw new Error('No input at index: ' + vin)
   hashType = hashType || Transaction.SIGHASH_ALL
 
   var input = this.inputs[vin]
-  var canSign = input.hashType !== undefined &&
-    input.prevOutScript !== undefined &&
-    input.pubKeys !== undefined &&
-    input.signatures !== undefined &&
-    input.signatures.length === input.pubKeys.length
 
-  var kpPubKey = keyPair.getPublicKeyBuffer()
-
-  if (canSign) {
-    // if redeemScript was provided, enforce consistency
-    if (redeemScript) {
-      if (!input.redeemScript.equals(redeemScript)) throw new Error('Inconsistent redeemScript')
-    }
+  // if redeemScript was provided, enforce consistency
+  if (input.redeemScript !== undefined && redeemScript) {
+    if (!input.redeemScript.equals(redeemScript)) throw new Error('Inconsistent redeemScript')
+  }
 
+  if (input.hashType !== undefined) {
     if (input.hashType !== hashType) throw new Error('Inconsistent hashType')
-  } else {
+  }
+
+  var kpPubKey = keyPair.getPublicKeyBuffer()
+  if (!canSign(input)) {
     prepareInput(input, kpPubKey, redeemScript, hashType)
+
+    if (!canSign(input)) throw Error(input.prevOutType + ' not supported')
   }
 
   // ready to sign


Test patch:
diff --git a/test/fixtures/transaction_builder.json b/test/fixtures/transaction_builder.json
index e57608e2..24f039b4 100644
--- a/test/fixtures/transaction_builder.json
+++ b/test/fixtures/transaction_builder.json
@@ -285,6 +285,28 @@
             "value": 10000
           }
         ]
+      },
+      {
+        "description": "Transaction w/ 1 pubKeyHash transaction input (Issue #644)",
+        "network": "testnet",
+        "txHex": "010000000132595835c74fccf097db4ccae9dc2de621e58e0d3f697a27b469b61c7a223b39000000006b483045022100d771395776280955561190c09a6bca731684d09db8995c53496b816b8222019302202a21c9a90d0b5de188800673ad31861183c3f4cb15ea0988b485686aed9fce1d012103f29374a4c2c218a4077db9ba0b9d674cde3719560460af4eb3190d512dd5de92ffffffff0170170000000000001976a914ff99e06c1a4ac394b4e1cb3d3a4b2b47749e339a88ac00000000",
+        "inputs": [
+          {
+            "txHex": "0100000001f7e6430096cd2790bac115aaab22c0a50fb0a1794305302e1a399e81d8d354f4020000006a47304402205793a862d193264afc32713e2e14541e1ff9ebb647dd7e7e6a0051d0faa87de302205216653741ecbbed573ea2fc053209dd6980616701c27be5b958a159fc97f45a012103e877e7deb32d19250dcfe534ea82c99ad739800295cd5429a7f69e2896c36fcdfeffffff0340420f00000000001976a9145c7b8d623fba952d2387703d051d8e931a6aa0a188ac8bda2702000000001976a9145a0ef60784137d03e7868d063b05424f2f43799f88ac40420f00000000001976a9145c7b8d623fba952d2387703d051d8e931a6aa0a188ac2fcc0e00",
+            "vout": 0,
+            "signs": [
+              {
+                "keyPair": "cQ6483mDWwoG8o4tn6nU9Jg52RKMjPUWXSY1vycAyPRXQJ1Pn2Rq"
+              }
+            ]
+          }
+        ],
+        "outputs": [
+          {
+            "script": "OP_DUP OP_HASH160 ff99e06c1a4ac394b4e1cb3d3a4b2b47749e339a OP_EQUALVERIFY OP_CHECKSIG",
+            "value": 6000
+          }
+        ]
       }
     ],
     "fromTransaction": [
diff --git a/test/transaction_builder.js b/test/transaction_builder.js
index 534f3568..71e3f5bd 100644
--- a/test/transaction_builder.js
+++ b/test/transaction_builder.js
@@ -23,11 +23,11 @@ function construct (f, sign) {
   f.inputs.forEach(function (input) {
     var prevTxScript
 
-    if (input.prevTxScript) {
+    if (!input.txHex && input.prevTxScript) {
       prevTxScript = bscript.fromASM(input.prevTxScript)
     }
 
-    txb.addInput(input.txId, input.vout, input.sequence, prevTxScript)
+    txb.addInput(input.txId || Transaction.fromHex(input.txHex), input.vout, input.sequence, prevTxScript)
   })
 
   f.outputs.forEach(function (output) {