Repository: bitcoinjs/bitcoinjs-lib
Pull request #637 (2016-09-27 14:34:09)

TransactionBuilder: major internal refactor
This PR restructures `TransactionBuilder`s use of `extractInput` (now named `expandInput`) and `extractFromOutputScript` such they are used consistently,  by value,  and without duplicate handling.


Patch:
diff --git a/src/address.js b/src/address.js
index d7e5c6d7..656ec371 100644
--- a/src/address.js
+++ b/src/address.js
@@ -15,15 +15,6 @@ function fromBase58Check (address) {
   return { hash: hash, version: version }
 }
 
-function fromOutputScript (scriptPubKey, network) {
-  network = network || networks.bitcoin
-
-  if (bscript.isPubKeyHashOutput(scriptPubKey)) return toBase58Check(bscript.compile(scriptPubKey).slice(3, 23), network.pubKeyHash)
-  if (bscript.isScriptHashOutput(scriptPubKey)) return toBase58Check(bscript.compile(scriptPubKey).slice(2, 22), network.scriptHash)
-
-  throw new Error(bscript.toASM(scriptPubKey) + ' has no matching Address')
-}
-
 function toBase58Check (hash, version) {
   typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments)
 
@@ -34,6 +25,15 @@ function toBase58Check (hash, version) {
   return bs58check.encode(payload)
 }
 
+function fromOutputScript (scriptPubKey, network) {
+  network = network || networks.bitcoin
+
+  if (bscript.isPubKeyHashOutput(scriptPubKey)) return toBase58Check(bscript.compile(scriptPubKey).slice(3, 23), network.pubKeyHash)
+  if (bscript.isScriptHashOutput(scriptPubKey)) return toBase58Check(bscript.compile(scriptPubKey).slice(2, 22), network.scriptHash)
+
+  throw new Error(bscript.toASM(scriptPubKey) + ' has no matching Address')
+}
+
 function toOutputScript (address, network) {
   network = network || networks.bitcoin
 
diff --git a/src/crypto.js b/src/crypto.js
index 3c1cb738..1bb39f1d 100644
--- a/src/crypto.js
+++ b/src/crypto.js
@@ -1,13 +1,5 @@
 var createHash = require('create-hash')
 
-function hash160 (buffer) {
-  return ripemd160(sha256(buffer))
-}
-
-function hash256 (buffer) {
-  return sha256(sha256(buffer))
-}
-
 function ripemd160 (buffer) {
   return createHash('rmd160').update(buffer).digest()
 }
@@ -20,6 +12,14 @@ function sha256 (buffer) {
   return createHash('sha256').update(buffer).digest()
 }
 
+function hash160 (buffer) {
+  return ripemd160(sha256(buffer))
+}
+
+function hash256 (buffer) {
+  return sha256(sha256(buffer))
+}
+
 module.exports = {
   hash160: hash160,
   hash256: hash256,
diff --git a/src/script.js b/src/script.js
index 07f85511..1592a39e 100644
--- a/src/script.js
+++ b/src/script.js
@@ -15,32 +15,6 @@ var REVERSE_OPS = (function () {
 
 var OP_INT_BASE = OPS.OP_RESERVED // OP_1 - 1
 
-function toASM (chunks) {
-  if (Buffer.isBuffer(chunks)) {
-    chunks = decompile(chunks)
-  }
-
-  return chunks.map(function (chunk) {
-    // data?
-    if (Buffer.isBuffer(chunk)) return chunk.toString('hex')
-
-    // opcode!
-    return REVERSE_OPS[chunk]
-  }).join(' ')
-}
-
-function fromASM (asm) {
-  typeforce(types.String, asm)
-
-  return compile(asm.split(' ').map(function (chunkStr) {
-    // opcode?
-    if (OPS[chunkStr] !== undefined) return OPS[chunkStr]
-
-    // data!
-    return new Buffer(chunkStr, 'hex')
-  }))
-}
-
 function compile (chunks) {
   // TODO: remove me
   if (Buffer.isBuffer(chunks)) return chunks
@@ -118,6 +92,32 @@ function decompile (buffer) {
   return chunks
 }
 
+function toASM (chunks) {
+  if (Buffer.isBuffer(chunks)) {
+    chunks = decompile(chunks)
+  }
+
+  return chunks.map(function (chunk) {
+    // data?
+    if (Buffer.isBuffer(chunk)) return chunk.toString('hex')
+
+    // opcode!
+    return REVERSE_OPS[chunk]
+  }).join(' ')
+}
+
+function fromASM (asm) {
+  typeforce(types.String, asm)
+
+  return compile(asm.split(' ').map(function (chunkStr) {
+    // opcode?
+    if (OPS[chunkStr] !== undefined) return OPS[chunkStr]
+
+    // data!
+    return new Buffer(chunkStr, 'hex')
+  }))
+}
+
 function isCanonicalPubKey (buffer) {
   if (!Buffer.isBuffer(buffer)) return false
   if (buffer.length < 33) return false
@@ -133,13 +133,6 @@ function isCanonicalPubKey (buffer) {
   return false
 }
 
-function isCanonicalSignature (buffer) {
-  if (!Buffer.isBuffer(buffer)) return false
-  if (!isDefinedHashType(buffer[buffer.length - 1])) return false
-
-  return bip66.check(buffer.slice(0, -1))
-}
-
 function isDefinedHashType (hashType) {
   var hashTypeMod = hashType & ~0x80
 
@@ -147,6 +140,13 @@ function isDefinedHashType (hashType) {
   return hashTypeMod > 0x00 && hashTypeMod < 0x04
 }
 
+function isCanonicalSignature (buffer) {
+  if (!Buffer.isBuffer(buffer)) return false
+  if (!isDefinedHashType(buffer[buffer.length - 1])) return false
+
+  return bip66.check(buffer.slice(0, -1))
+}
+
 function isPubKeyHashInput (script) {
   var chunks = decompile(script)
 
diff --git a/src/transaction_builder.js b/src/transaction_builder.js
index 259fa6bd..efdaae07 100644
--- a/src/transaction_builder.js
+++ b/src/transaction_builder.js
@@ -12,163 +12,251 @@ var ECPair = require('./ecpair')
 var ECSignature = require('./ecsignature')
 var Transaction = require('./transaction')
 
-// re-orders signatures to match pubKeys, fills undefined otherwise
-function fixMSSignatures (transaction, vin, pubKeys, signatures, prevOutScript, hashType, skipPubKey) {
-  // maintain a local copy of unmatched signatures
-  var unmatched = signatures.slice()
-  var signatureHash
-
-  return pubKeys.map(function (pubKey) {
-    // skip optionally provided pubKey
-    if (skipPubKey && bufferEquals(skipPubKey, pubKey)) return undefined
-
-    var matched
-    var keyPair2 = ECPair.fromPublicKeyBuffer(pubKey)
-
-    // check for a matching signature
-    unmatched.some(function (signature, i) {
-      // skip if undefined || OP_0
-      if (!signature) return false
+// inspects a scriptSig w/ optional provided redeemScript and derives
+// all necessary input information as required by TransactionBuilder
+function expandInput (scriptSig, redeemScript) {
+  var scriptSigChunks = bscript.decompile(scriptSig)
+  var scriptSigType = bscript.classifyInput(scriptSigChunks, true)
 
-      if (!signatureHash) {
-        signatureHash = transaction.hashForSignature(vin, prevOutScript, hashType)
-      }
-      if (!keyPair2.verify(signatureHash, signature)) return false
+  var hashType, pubKeys, signatures, prevOutScript
 
-      // remove matched signature from unmatched
-      unmatched[i] = undefined
-      matched = signature
+  switch (scriptSigType) {
+    case 'scripthash':
+      // FIXME: maybe depth limit instead, how possible is this anyway?
+      if (redeemScript) throw new Error('Recursive P2SH script')
 
-      return true
-    })
-
-    return matched || undefined
-  })
-}
+      var redeemScriptSig = scriptSigChunks.slice(0, -1)
+      redeemScript = scriptSigChunks[scriptSigChunks.length - 1]
 
-function extractInput (transaction, txIn, vin) {
-  if (txIn.script.length === 0) return {}
+      var result = expandInput(redeemScriptSig, redeemScript)
+      result.redeemScript = redeemScript
+      result.redeemScriptType = result.prevOutType
+      result.prevOutScript = bscript.scriptHashOutput(bcrypto.hash160(redeemScript))
+      result.prevOutType = 'scripthash'
+      return result
 
-  var scriptSigChunks = bscript.decompile(txIn.script)
-  var prevOutType = bscript.classifyInput(scriptSigChunks, true)
+    case 'pubkeyhash':
+      // if (redeemScript) throw new Error('Nonstandard... P2SH(P2PKH)')
+      var s = ECSignature.parseScriptSignature(scriptSigChunks[0])
+      hashType = s.hashType
+      pubKeys = scriptSigChunks.slice(1)
+      signatures = [s.signature]
 
-  function processScript (scriptType, scriptSigChunks, redeemScriptChunks) {
-    // ensure chunks are decompiled
-    scriptSigChunks = bscript.decompile(scriptSigChunks)
-    redeemScriptChunks = redeemScriptChunks ? bscript.decompile(redeemScriptChunks) : undefined
+      if (redeemScript) break
 
-    var hashType, pubKeys, signatures, prevOutScript, redeemScript, redeemScriptType, result, parsed
+      prevOutScript = bscript.pubKeyHashOutput(bcrypto.hash160(pubKeys[0]))
+      break
 
-    switch (scriptType) {
-      case 'scripthash':
-        redeemScript = scriptSigChunks.slice(-1)[0]
-        scriptSigChunks = bscript.compile(scriptSigChunks.slice(0, -1))
+    case 'pubkey':
+      if (redeemScript) {
+        pubKeys = bscript.decompile(redeemScript).slice(0, 1)
+      }
 
-        redeemScriptType = bscript.classifyInput(scriptSigChunks, true)
-        prevOutScript = bscript.scriptHashOutput(bcrypto.hash160(redeemScript))
+      var ss = ECSignature.parseScriptSignature(scriptSigChunks[0])
+      hashType = ss.hashType
+      signatures = [ss.signature]
+      break
 
-        result = processScript(redeemScriptType, scriptSigChunks, bscript.decompile(redeemScript))
+    case 'multisig':
+      if (redeemScript) {
+        pubKeys = bscript.decompile(redeemScript).slice(1, -2)
+      }
 
-        result.prevOutScript = prevOutScript
-        result.redeemScript = redeemScript
-        result.redeemScriptType = redeemScriptType
+      signatures = scriptSigChunks.slice(1).map(function (chunk) {
+        if (chunk === ops.OP_0) return undefined
 
-        return result
+        var sss = ECSignature.parseScriptSignature(chunk)
 
-      case 'pubkeyhash':
-        parsed = ECSignature.parseScriptSignature(scriptSigChunks[0])
-        hashType = parsed.hashType
-        pubKeys = scriptSigChunks.slice(1)
-        signatures = [parsed.signature]
-        prevOutScript = bscript.pubKeyHashOutput(bcrypto.hash160(pubKeys[0]))
+        if (hashType !== undefined) {
+          if (sss.hashType !== hashType) throw new Error('Inconsistent hashType')
+        } else {
+          hashType = sss.hashType
+        }
 
-        break
+        return sss.signature
+      })
 
-      case 'pubkey':
-        parsed = ECSignature.parseScriptSignature(scriptSigChunks[0])
-        hashType = parsed.hashType
-        signatures = [parsed.signature]
+      break
+  }
 
-        if (redeemScriptChunks) {
-          pubKeys = redeemScriptChunks.slice(0, 1)
-        }
+  return {
+    hashType: hashType,
+    pubKeys: pubKeys,
+    signatures: signatures,
+    prevOutScript: prevOutScript,
+    prevOutType: scriptSigType
+  }
+}
 
-        break
+function expandOutput (script, ourPubKey) {
+  typeforce(types.Buffer, script)
 
-      case 'multisig':
-        signatures = scriptSigChunks.slice(1).map(function (chunk) {
-          if (chunk === ops.OP_0) return undefined
+  var scriptChunks = bscript.decompile(script)
+  var scriptType = bscript.classifyOutput(script)
 
-          parsed = ECSignature.parseScriptSignature(chunk)
-          hashType = parsed.hashType
+  var pubKeys = []
 
-          return parsed.signature
-        })
+  switch (scriptType) {
+    // does our hash160(pubKey) match the output scripts?
+    case 'pubkeyhash':
+      if (!ourPubKey) break
 
-        if (redeemScriptChunks) {
-          pubKeys = redeemScriptChunks.slice(1, -2)
+      var pkh1 = scriptChunks[2]
+      var pkh2 = bcrypto.hash160(ourPubKey)
+      if (bufferEquals(pkh1, pkh2)) pubKeys = [ourPubKey]
+      break
 
-          if (pubKeys.length !== signatures.length) {
-            signatures = fixMSSignatures(transaction, vin, pubKeys, signatures, bscript.compile(redeemScriptChunks), hashType, redeemScript)
-          }
-        }
+    case 'pubkey':
+      pubKeys = scriptChunks.slice(0, 1)
+      break
 
-        break
-    }
+    case 'multisig':
+      pubKeys = scriptChunks.slice(1, -2)
+      break
 
-    return {
-      hashType: hashType,
-      pubKeys: pubKeys,
-      signatures: signatures,
-      prevOutScript: prevOutScript,
-      redeemScript: redeemScript,
-      redeemScriptType: redeemScriptType
-    }
+    default: return
   }
 
-  // Extract hashType, pubKeys, signatures and prevOutScript
-  var result = processScript(prevOutType, scriptSigChunks)
-
   return {
-    hashType: result.hashType,
-    prevOutScript: result.prevOutScript,
-    prevOutType: prevOutType,
-    pubKeys: result.pubKeys,
-    redeemScript: result.redeemScript,
-    redeemScriptType: result.redeemScriptType,
-    signatures: result.signatures
+    pubKeys: pubKeys,
+    scriptType: scriptType,
+    signatures: pubKeys.map(function () { return undefined })
   }
 }
 
-function extractFromOutputScript (outputScript, kpPubKey) {
-  var scriptType = bscript.classifyOutput(outputScript)
-  var outputScriptChunks = bscript.decompile(outputScript)
+function buildInput (input, scriptType, allowIncomplete) {
+  var signatures = input.signatures
+  var scriptSig
 
-  var pubKeys
   switch (scriptType) {
     case 'pubkeyhash':
-      var pkh1 = outputScriptChunks[2]
-      var pkh2 = bcrypto.hash160(kpPubKey)
+      // remove blank signatures
+      signatures = signatures.filter(function (x) { return x })
 
-      if (!bufferEquals(pkh1, pkh2)) throw new Error('privateKey cannot sign for this input')
-      pubKeys = [kpPubKey]
+      if (signatures.length < 1) throw new Error('Not enough signatures provided')
+      if (signatures.length > 1) throw new Error('Too many signatures provided')
+
+      var pkhSignature = signatures[0].toScriptSignature(input.hashType)
+      scriptSig = bscript.pubKeyHashInput(pkhSignature, input.pubKeys[0])
       break
 
     case 'pubkey':
-      pubKeys = outputScriptChunks.slice(0, 1)
+      // remove blank signatures
+      signatures = signatures.filter(function (x) { return x })
+
+      if (signatures.length < 1) throw new Error('Not enough signatures provided')
+      if (signatures.length > 1) throw new Error('Too many signatures provided')
+
+      var pkSignature = signatures[0].toScriptSignature(input.hashType)
+      scriptSig = bscript.pubKeyInput(pkSignature)
       break
 
+    // ref https://github.com/bitcoin/bitcoin/blob/d612837814020ae832499d18e6ee5eb919a87907/src/script/sign.cpp#L232
     case 'multisig':
-      pubKeys = outputScriptChunks.slice(1, -2)
+      signatures = signatures.map(function (signature) {
+        return signature && signature.toScriptSignature(input.hashType)
+      })
+
+      if (allowIncomplete) {
+        // fill in blanks with OP_0
+        for (var i = 0; i < signatures.length; ++i) {
+          signatures[i] = signatures[i] || ops.OP_0
+        }
+      } else {
+        // remove blank signatures
+        signatures = signatures.filter(function (x) { return x })
+      }
+
+      scriptSig = bscript.multisigInput(signatures, allowIncomplete ? undefined : input.redeemScript)
       break
+  }
 
-    default: return
+  // wrap as scriptHash if necessary
+  if (input.prevOutType === 'scripthash') {
+    scriptSig = bscript.scriptHashInput(scriptSig, input.redeemScript)
   }
 
-  return {
-    pubKeys: pubKeys,
-    scriptType: scriptType
+  return scriptSig
+}
+
+function prepareInput (input, kpPubKey, redeemScript, hashType) {
+  if (redeemScript) {
+    var redeemScriptHash = bcrypto.hash160(redeemScript)
+
+    // if redeemScript exists, it is pay-to-scriptHash
+    // if we have a prevOutScript, enforce hash160(redeemScriptequality)  to the redeemScript
+    if (input.prevOutType) {
+      if (input.prevOutType !== 'scripthash') throw new Error('PrevOutScript must be P2SH')
+
+      var prevOutScriptScriptHash = bscript.decompile(input.prevOutScript)[1]
+      if (!bufferEquals(prevOutScriptScriptHash, redeemScriptHash)) throw new Error('Inconsistent hash160(RedeemScript)')
+
+    // or, we don't have a prevOutScript, so generate a P2SH script
+    } else {
+      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
+    input.signatures = expanded.signatures
+
+  // maybe we have some prior knowledge
+  } else if (input.prevOutType) {
+    // 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')
+
+  // no prior knowledge, assume pubKeyHash
+  } else {
+    input.prevOutScript = bscript.pubKeyHashOutput(bcrypto.hash160(kpPubKey))
+    input.prevOutType = 'pubkeyhash'
+
+    input.pubKeys = [kpPubKey]
+    input.signatures = [undefined]
   }
+
+  input.hashType = hashType
+}
+
+function fixMultisigOrder (input, transaction, vin) {
+  var hashScriptType = input.redeemScriptType || input.prevOutType
+  if (hashScriptType !== 'multisig') throw new TypeError('Expected multisig input')
+
+  var hashType = input.hashType || Transaction.SIGHASH_ALL
+  var hashScript = input.redeemScript || input.prevOutScript
+
+  // maintain a local copy of unmatched signatures
+  var unmatched = input.signatures.concat()
+  var signatureHash = transaction.hashForSignature(vin, hashScript, hashType)
+
+  input.signatures = input.pubKeys.map(function (pubKey, y) {
+    var keyPair = ECPair.fromPublicKeyBuffer(pubKey)
+    var match
+
+    // check for a signature
+    unmatched.some(function (signature, i) {
+      // skip if undefined || OP_0
+      if (!signature) return false
+
+      // skip if signature does not match pubKey
+      if (!keyPair.verify(signatureHash, signature)) return false
+
+      // remove matched signature from unmatched
+      unmatched[i] = undefined
+      match = signature
+
+      return true
+    })
+
+    return match || undefined
+  })
 }
 
 function TransactionBuilder (network) {
@@ -204,34 +292,40 @@ TransactionBuilder.prototype.setVersion = function (version) {
 TransactionBuilder.fromTransaction = function (transaction, network) {
   var txb = new TransactionBuilder(network)
 
-  // Copy other transaction fields
-  txb.tx.version = transaction.version
-  txb.tx.locktime = transaction.locktime
-
-  // Extract/add inputs
-  transaction.ins.forEach(function (txIn) {
-    txb.addInput(txIn.hash, txIn.index, txIn.sequence)
-  })
+  // Copy transaction fields
+  txb.setVersion(transaction.version)
+  txb.setLockTime(transaction.locktime)
 
-  // Extract/add outputs
+  // Copy outputs (done first to avoid signature invalidation)
   transaction.outs.forEach(function (txOut) {
     txb.addOutput(txOut.script, txOut.value)
   })
 
-  // Extract/add signatures
-  txb.inputs = transaction.ins.map(function (txIn, vin) {
-    // TODO: verify whether extractInput is sane with coinbase scripts
-    if (Transaction.isCoinbaseHash(txIn.hash)) {
-      throw new Error('coinbase inputs not supported')
-    }
+  // Copy inputs
+  transaction.ins.forEach(function (txIn) {
+    txb.__addInputUnsafe(txIn.hash, txIn.index, txIn.sequence, txIn.script)
+  })
 
-    return extractInput(transaction, txIn, vin)
+  // fix some things not possible through the public API
+  txb.inputs.forEach(function (input, i) {
+    // attempt to fix any multisig inputs if they exist
+    if ((input.redeemScriptType || input.prevOutType) === 'multisig') {
+      // pubKeys will only exist for 'multisig' if a redeemScript was found
+      if (!input.pubKeys || !input.signatures) return
+      if (input.pubKeys.length === input.signatures.length) return
+
+      fixMultisigOrder(input, transaction, i)
+    }
   })
 
   return txb
 }
 
 TransactionBuilder.prototype.addInput = function (txHash, vout, sequence, prevOutScript) {
+  if (!this.__canModifyInputs()) {
+    throw new Error('No, this would invalidate signatures')
+  }
+
   // is it a hex string?
   if (typeof txHash === 'string') {
     // transaction hashs's are displayed in reverse order, un-reverse it
@@ -243,73 +337,50 @@ TransactionBuilder.prototype.addInput = function (txHash, vout, sequence, prevOu
     txHash = txHash.getHash()
   }
 
-  var input = {}
-  if (prevOutScript) {
-    var prevOutScriptChunks = bscript.decompile(prevOutScript)
-    var prevOutType = bscript.classifyOutput(prevOutScriptChunks)
+  return this.__addInputUnsafe(txHash, vout, sequence, null, prevOutScript)
+}
 
-    // if we can, extract pubKey information
-    switch (prevOutType) {
-      case 'multisig':
-        input.pubKeys = prevOutScriptChunks.slice(1, -2)
-        input.signatures = input.pubKeys.map(function () { return undefined })
+TransactionBuilder.prototype.__addInputUnsafe = function (txHash, vout, sequence, scriptSig, prevOutScript) {
+  if (Transaction.isCoinbaseHash(txHash)) {
+    throw new Error('coinbase inputs not supported')
+  }
 
-        break
+  var prevTxOut = txHash.toString('hex') + ':' + vout
+  if (this.prevTxMap[prevTxOut]) throw new Error('Duplicate TxOut: ' + prevTxOut)
 
-      case 'pubkey':
-        input.pubKeys = prevOutScriptChunks.slice(0, 1)
-        input.signatures = [undefined]
+  var input = {}
 
-        break
-    }
+  // derive what we can from the scriptSig
+  if (scriptSig) {
+    input = expandInput(scriptSig)
+  }
 
-    if (prevOutType !== 'scripthash') {
-      input.scriptType = prevOutType
+  // derive what we can from the previous transactions output script
+  if (!input.prevOutScript && prevOutScript) {
+    var prevOutScriptChunks = bscript.decompile(prevOutScript)
+    var prevOutType = bscript.classifyOutput(prevOutScriptChunks)
+
+    if (!input.pubKeys && !input.signatures) {
+      var expanded = expandOutput(prevOutScript)
+      if (expanded) {
+        input.pubKeys = expanded.pubKeys
+        input.signatures = expanded.signatures
+      }
     }
 
     input.prevOutScript = prevOutScript
     input.prevOutType = prevOutType
   }
 
-  // if signatures exist, adding inputs is only acceptable if SIGHASH_ANYONECANPAY is used
-  // throw if any signatures *didn't* use SIGHASH_ANYONECANPAY
-  if (!this.inputs.every(function (otherInput) {
-    // no signature
-    if (otherInput.hashType === undefined) return true
-
-    return otherInput.hashType & Transaction.SIGHASH_ANYONECANPAY
-  })) {
-    throw new Error('No, this would invalidate signatures')
-  }
-
-  var prevTxOut = txHash.toString('hex') + ':' + vout
-  if (this.prevTxMap[prevTxOut]) throw new Error('Duplicate TxOut: ' + prevTxOut)
-
-  var vin = this.tx.addInput(txHash, vout, sequence)
+  var vin = this.tx.addInput(txHash, vout, sequence, scriptSig)
   this.inputs[vin] = input
-  this.prevTxMap[prevTxOut] = vin
+  this.prevTxMap[prevTxOut] = true
 
   return vin
 }
 
 TransactionBuilder.prototype.addOutput = function (scriptPubKey, value) {
-  var nOutputs = this.tx.outs.length
-
-  // if signatures exist, adding outputs is only acceptable if SIGHASH_NONE or SIGHASH_SINGLE is used
-  // throws if any signatures didn't use SIGHASH_NONE|SIGHASH_SINGLE
-  if (!this.inputs.every(function (input, index) {
-    // no signature
-    if (input.hashType === undefined) return true
-
-    var hashTypeMod = input.hashType & 0x1f
-    if (hashTypeMod === Transaction.SIGHASH_NONE) return true
-    if (hashTypeMod === Transaction.SIGHASH_SINGLE) {
-      // account for SIGHASH_SINGLE signing of a non-existing output, aka the "SIGHASH_SINGLE" bug
-      return index < nOutputs
-    }
-
-    return false
-  })) {
+  if (!this.__canModifyOutputs()) {
     throw new Error('No, this would invalidate signatures')
   }
 
@@ -334,48 +405,6 @@ var canBuildTypes = {
   'pubkeyhash': true
 }
 
-function buildFromInputData (input, scriptType, parentType, redeemScript, allowIncomplete) {
-  var scriptSig
-
-  switch (scriptType) {
-    case 'pubkeyhash':
-      var pkhSignature = input.signatures[0].toScriptSignature(input.hashType)
-      scriptSig = bscript.pubKeyHashInput(pkhSignature, input.pubKeys[0])
-      break
-
-    case 'pubkey':
-      var pkSignature = input.signatures[0].toScriptSignature(input.hashType)
-      scriptSig = bscript.pubKeyInput(pkSignature)
-      break
-
-    case 'multisig':
-      var msSignatures = input.signatures.map(function (signature) {
-        return signature && signature.toScriptSignature(input.hashType)
-      })
-
-      // fill in blanks with OP_0
-      if (allowIncomplete) {
-        for (var i = 0; i < msSignatures.length; ++i) {
-          msSignatures[i] = msSignatures[i] || ops.OP_0
-        }
-
-      // remove blank signatures
-      } else {
-        msSignatures = msSignatures.filter(function (x) { return x })
-      }
-
-      scriptSig = bscript.multisigInput(msSignatures, allowIncomplete ? undefined : redeemScript)
-      break
-  }
-
-  // wrap as scriptHash if necessary
-  if (parentType === 'scripthash') {
-    scriptSig = bscript.scriptHashInput(scriptSig, redeemScript)
-  }
-
-  return scriptSig
-}
-
 TransactionBuilder.prototype.__build = function (allowIncomplete) {
   if (!allowIncomplete) {
     if (!this.tx.ins.length) throw new Error('Transaction has no inputs')
@@ -385,48 +414,43 @@ TransactionBuilder.prototype.__build = function (allowIncomplete) {
   var tx = this.tx.clone()
 
   // Create script signatures from inputs
-  this.inputs.forEach(function (input, index) {
+  this.inputs.forEach(function (input, i) {
     var scriptType = input.redeemScriptType || input.prevOutType
-    var scriptSig
 
     if (!allowIncomplete) {
       if (!scriptType) throw new Error('Transaction is not complete')
       if (!canBuildTypes[scriptType]) throw new Error(scriptType + ' not supported')
 
-      // XXX: only relevant to types that need signatures
+      // FIXME: only relevant to types that need signatures
       if (!input.signatures) throw new Error('Transaction is missing signatures')
     }
 
-    if (input.signatures) {
-      scriptSig = buildFromInputData(input, scriptType, input.prevOutType, input.redeemScript, allowIncomplete)
-    }
+    // FIXME: only relevant to types that need signatures
+    // skip if no scriptSig exists
+    if (!input.signatures) return
 
-    // did we build a scriptSig? Buffer('') is allowed
-    if (scriptSig) {
-      tx.setInputScript(index, scriptSig)
-    }
+    // build a scriptSig
+    var scriptSig = buildInput(input, scriptType, allowIncomplete)
+    tx.setInputScript(i, scriptSig)
   })
 
   return tx
 }
 
-TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hashType) {
+TransactionBuilder.prototype.sign = function (vin, keyPair, redeemScript, hashType) {
   if (keyPair.network !== this.network) throw new Error('Inconsistent network')
-  if (!this.inputs[index]) throw new Error('No input at index: ' + index)
+  if (!this.inputs[vin]) throw new Error('No input at index: ' + vin)
   hashType = hashType || Transaction.SIGHASH_ALL
 
-  var input = this.inputs[index]
-  var canSign = input.hashType &&
-    input.prevOutScript &&
-    input.prevOutType &&
-    input.pubKeys &&
-    input.redeemScriptType &&
-    input.signatures &&
+  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()
 
-  // are we ready to sign?
   if (canSign) {
     // if redeemScript was provided, enforce consistency
     if (redeemScript) {
@@ -434,56 +458,13 @@ TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hash
     }
 
     if (input.hashType !== hashType) throw new Error('Inconsistent hashType')
-
-  // no? prepare
   } else {
-    // must be pay-to-scriptHash?
-    if (redeemScript) {
-      // if we have a prevOutScript, enforce scriptHash equality to the redeemScript
-      if (input.prevOutScript) {
-        if (input.prevOutType !== 'scripthash') throw new Error('PrevOutScript must be P2SH')
-
-        var scriptHash = bscript.decompile(input.prevOutScript)[1]
-        if (!bufferEquals(scriptHash, bcrypto.hash160(redeemScript))) throw new Error('RedeemScript does not match ' + scriptHash.toString('hex'))
-      }
-
-      var extracted = extractFromOutputScript(redeemScript, kpPubKey)
-      if (!extracted) throw new Error('RedeemScript not supported "' + bscript.toASM(redeemScript) + '"')
-
-      // if we don't have a prevOutScript, generate a P2SH script
-      if (!input.prevOutScript) {
-        input.prevOutScript = bscript.scriptHashOutput(bcrypto.hash160(redeemScript))
-        input.prevOutType = 'scripthash'
-      }
-
-      input.pubKeys = extracted.pubKeys
-      input.redeemScript = redeemScript
-      input.redeemScriptType = extracted.scriptType
-      input.signatures = extracted.pubKeys.map(function () { return undefined })
-    } else {
-      // pay-to-scriptHash is not possible without a redeemScript
-      if (input.prevOutType === 'scripthash') throw new Error('PrevOutScript is P2SH, missing redeemScript')
-
-      // if we don't have a scriptType, assume pubKeyHash otherwise
-      if (!input.scriptType) {
-        input.prevOutScript = bscript.pubKeyHashOutput(bcrypto.hash160(kpPubKey))
-        input.prevOutType = 'pubkeyhash'
-
-        input.pubKeys = [kpPubKey]
-        input.scriptType = input.prevOutType
-        input.signatures = [undefined]
-      } else {
-        // throw if we can't sign with it
-        if (!input.pubKeys || !input.signatures) throw new Error(input.scriptType + ' not supported')
-      }
-    }
-
-    input.hashType = hashType
+    prepareInput(input, kpPubKey, redeemScript, hashType)
   }
 
-  // ready to sign?
-  var signatureScript = input.redeemScript || input.prevOutScript
-  var signatureHash = this.tx.hashForSignature(index, signatureScript, hashType)
+  // ready to sign
+  var hashScript = input.redeemScript || input.prevOutScript
+  var signatureHash = this.tx.hashForSignature(vin, hashScript, hashType)
 
   // enforce in order signing of public keys
   var valid = input.pubKeys.some(function (pubKey, i) {
@@ -491,11 +472,42 @@ TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hash
     if (input.signatures[i]) throw new Error('Signature already exists')
 
     input.signatures[i] = keyPair.sign(signatureHash)
-
     return true
   })
 
   if (!valid) throw new Error('Key pair cannot sign for this input')
 }
 
+TransactionBuilder.prototype.__canModifyInputs = function () {
+  return this.inputs.every(function (otherInput) {
+    // no signature
+    if (otherInput.hashType === undefined) return true
+
+    // if SIGHASH_ANYONECANPAY is set, signatures would not
+    // be invalidated by more inputs
+    return otherInput.hashType & Transaction.SIGHASH_ANYONECANPAY
+  })
+}
+
+TransactionBuilder.prototype.__canModifyOutputs = function () {
+  var nInputs = this.tx.ins.length
+  var nOutputs = this.tx.outs.length
+
+  return this.inputs.every(function (input, i) {
+    // any signatures?
+    if (input.hashType === undefined) return true
+
+    var hashTypeMod = input.hashType & 0x1f
+    if (hashTypeMod === Transaction.SIGHASH_NONE) return true
+    if (hashTypeMod === Transaction.SIGHASH_SINGLE) {
+      // if SIGHASH_SINGLE is set, and nInputs > nOutputs
+      // some signatures would be invalidated by the addition
+      // of more outputs
+      return nInputs <= nOutputs
+    }
+
+    return false
+  })
+}
+
 module.exports = TransactionBuilder
diff --git a/src/types.js b/src/types.js
index 69ab787d..735f7702 100644
--- a/src/types.js
+++ b/src/types.js
@@ -1,7 +1,7 @@
 var typeforce = require('typeforce')
 
 function nBuffer (value, n) {
-  typeforce(types.Buffer, value)
+  typeforce(typeforce.Buffer, value)
   if (value.length !== n) throw new typeforce.TfTypeError('Expected ' + (n * 8) + '-bit Buffer, got ' + (value.length * 8) + '-bit Buffer')
 
   return true


Test patch:
diff --git a/test/fixtures/transaction_builder.json b/test/fixtures/transaction_builder.json
index f3fb5784..e57608e2 100644
--- a/test/fixtures/transaction_builder.json
+++ b/test/fixtures/transaction_builder.json
@@ -632,8 +632,8 @@
       },
       {
         "description": "Incomplete transaction w/ prevTxScript defined",
-        "exception": "Transaction is missing signatures",
-        "alwaysThrows": true,
+        "exception": "Not enough signatures provided",
+        "incomplete": true,
         "inputs": [
           {
             "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
@@ -658,6 +658,29 @@
           }
         ]
       },
+      {
+        "description": "Duplicate transaction outs",
+        "exception": "Duplicate TxOut: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff:0",
+        "incomplete": true,
+        "inputs": [
+          {
+            "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
+            "vout": 0,
+            "signs": []
+          },
+          {
+            "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
+            "vout": 0,
+            "signs": []
+          }
+        ],
+        "outputs": [
+          {
+            "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG",
+            "value": 1000
+          }
+        ]
+      },
       {
         "description": "Complete transaction w/ non-standard inputs",
         "exception": "nonstandard not supported",
diff --git a/test/integration/cltv.js b/test/integration/cltv.js
index dcd2b6e8..e1b98dda 100644
--- a/test/integration/cltv.js
+++ b/test/integration/cltv.js
@@ -11,7 +11,7 @@ var bob = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLw
 describe('bitcoinjs-lib (CLTV)', function () {
   var hashType = bitcoin.Transaction.SIGHASH_ALL
 
-  function cltvCheckSigInput (aQ, bQ, utcSeconds) {
+  function cltvCheckSigOutput (aQ, bQ, utcSeconds) {
     return bitcoin.script.compile([
       bitcoin.opcodes.OP_IF,
       bitcoin.script.number.encode(utcSeconds),
@@ -38,7 +38,7 @@ describe('bitcoinjs-lib (CLTV)', function () {
 
     // three hours ago
     var timeUtc = utcNow() - (3600 * 3)
-    var redeemScript = cltvCheckSigInput(alice, bob, timeUtc)
+    var redeemScript = cltvCheckSigOutput(alice, bob, timeUtc)
     var scriptPubKey = bitcoin.script.scriptHashOutput(bitcoin.crypto.hash160(redeemScript))
     var address = bitcoin.address.fromOutputScript(scriptPubKey, network)
 
@@ -72,7 +72,7 @@ describe('bitcoinjs-lib (CLTV)', function () {
 
     // two hours ago
     var timeUtc = utcNow() - (3600 * 2)
-    var redeemScript = cltvCheckSigInput(alice, bob, timeUtc)
+    var redeemScript = cltvCheckSigOutput(alice, bob, timeUtc)
     var scriptPubKey = bitcoin.script.scriptHashOutput(bitcoin.crypto.hash160(redeemScript))
     var address = bitcoin.address.fromOutputScript(scriptPubKey, network)
 
@@ -104,7 +104,7 @@ describe('bitcoinjs-lib (CLTV)', function () {
 
     // two hours from now
     var timeUtc = utcNow() + (3600 * 2)
-    var redeemScript = cltvCheckSigInput(alice, bob, timeUtc)
+    var redeemScript = cltvCheckSigOutput(alice, bob, timeUtc)
     var scriptPubKey = bitcoin.script.scriptHashOutput(bitcoin.crypto.hash160(redeemScript))
     var address = bitcoin.address.fromOutputScript(scriptPubKey, network)
 
diff --git a/test/transaction_builder.js b/test/transaction_builder.js
index 51cde35a..534f3568 100644
--- a/test/transaction_builder.js
+++ b/test/transaction_builder.js
@@ -62,11 +62,6 @@ describe('TransactionBuilder', function () {
     return baddress.toOutputScript(x)
   })
   var txHash = new Buffer('0e7cea811c0be9f73c0aca591034396e7264473fc25c1ca45195d7417b36cbe2', 'hex')
-  var txb
-
-  beforeEach(function () {
-    txb = new TransactionBuilder()
-  })
 
   describe('fromTransaction', function () {
     fixtures.valid.build.forEach(function (f) {
@@ -108,7 +103,7 @@ describe('TransactionBuilder', function () {
     })
 
     fixtures.invalid.fromTransaction.forEach(function (f) {
-      it('throws on ' + f.exception, function () {
+      it('throws ' + f.exception, function () {
         var tx = Transaction.fromHex(f.txHex)
 
         assert.throws(function () {
@@ -119,6 +114,11 @@ describe('TransactionBuilder', function () {
   })
 
   describe('addInput', function () {
+    var txb
+    beforeEach(function () {
+      txb = new TransactionBuilder()
+    })
+
     it('accepts a txHash, index [and sequence number]', function () {
       var vin = txb.addInput(txHash, 1, 54)
       assert.strictEqual(vin, 0)
@@ -172,6 +172,11 @@ describe('TransactionBuilder', function () {
   })
 
   describe('addOutput', function () {
+    var txb
+    beforeEach(function () {
+      txb = new TransactionBuilder()
+    })
+
     it('accepts an address string and value', function () {
       var vout = txb.addOutput(keyPair.getAddress(), 1000)
       assert.strictEqual(vout, 0)
@@ -237,6 +242,7 @@ describe('TransactionBuilder', function () {
 
   describe('setLockTime', function () {
     it('throws if if there exist any scriptSigs', function () {
+      var txb = new TransactionBuilder()
       txb.addInput(txHash, 0)
       txb.sign(0, keyPair)
 
@@ -249,7 +255,7 @@ describe('TransactionBuilder', function () {
   describe('sign', function () {
     fixtures.invalid.sign.forEach(function (f) {
       it('throws on ' + f.exception + (f.description ? ' (' + f.description + ')' : ''), function () {
-        txb = construct(f, false)
+        var txb = construct(f, false)
 
         f.inputs.forEach(function (input, index) {
           input.signs.forEach(function (sign) {
@@ -277,33 +283,55 @@ describe('TransactionBuilder', function () {
   describe('build', function () {
     fixtures.valid.build.forEach(function (f) {
       it('builds "' + f.description + '"', function () {
-        txb = construct(f)
-
+        var txb = construct(f)
         var tx = txb.build()
+
         assert.strictEqual(tx.toHex(), f.txHex)
       })
     })
 
+    // TODO: remove duplicate test code
     fixtures.invalid.build.forEach(function (f) {
       describe('for ' + (f.description || f.exception), function () {
-        beforeEach(function () {
-          if (f.txHex) {
-            txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex))
-          } else {
-            txb = construct(f)
-          }
-        })
-
-        it('throws', function () {
+        it('throws ' + f.exception, function () {
           assert.throws(function () {
+            var txb
+            if (f.txHex) {
+              txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex))
+            } else {
+              txb = construct(f)
+            }
+
             txb.build()
           }, new RegExp(f.exception))
         })
 
-        if (f.alwaysThrows) return
-        it("doesn't throw if building incomplete", function () {
-          txb.buildIncomplete()
-        })
+        // if throws on incomplete too, enforce that
+        if (f.incomplete) {
+          it('throws ' + f.exception, function () {
+            assert.throws(function () {
+              var txb
+              if (f.txHex) {
+                txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex))
+              } else {
+                txb = construct(f)
+              }
+
+              txb.buildIncomplete()
+            }, new RegExp(f.exception))
+          })
+        } else {
+          it('does not throw if buildIncomplete', function () {
+            var txb
+            if (f.txHex) {
+              txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex))
+            } else {
+              txb = construct(f)
+            }
+
+            txb.buildIncomplete()
+          })
+        }
       })
     })
   })
@@ -311,8 +339,7 @@ describe('TransactionBuilder', function () {
   describe('multisig', function () {
     fixtures.valid.multisig.forEach(function (f) {
       it(f.description, function () {
-        txb = construct(f, false)
-
+        var txb = construct(f, false)
         var tx
         var network = NETWORKS[f.network]
 
@@ -358,19 +385,26 @@ describe('TransactionBuilder', function () {
   })
 
   describe('multisig edge case', function () {
+    var network = NETWORKS.testnet
+
     it('should handle badly pre-filled OP_0s', function () {
-      var lameTx = Transaction.fromHex('0100000001cff58855426469d0ef16442ee9c644c4fb13832467bcbc3173168a7916f0714900000000fd16010000483045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d014cc952410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253aeffffffff01e8030000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000')
-      var network = NETWORKS.testnet
+      // OP_0 is used where a signature is missing
+      var redeemScripSig = bscript.fromASM('OP_0 OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae')
+      var redeemScript = bscript.fromASM('OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG')
 
-      txb = TransactionBuilder.fromTransaction(lameTx, network)
+      var tx = new Transaction()
+      tx.addInput(new Buffer('cff58855426469d0ef16442ee9c644c4fb13832467bcbc3173168a7916f07149', 'hex'), 0, undefined, redeemScripSig)
+      tx.addOutput(new Buffer('76a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac', 'hex'), 1000)
 
-      var redeemScript = bscript.fromASM('OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG')
+      // now import the Transaction
+      var txb = TransactionBuilder.fromTransaction(tx, NETWORKS.testnet)
 
       var keyPair = ECPair.fromWIF('91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgx3cTMqe', network)
       txb.sign(0, keyPair, redeemScript)
 
-      var tx = txb.build()
-      assert.equal(tx.toHex(), '0100000001cff58855426469d0ef16442ee9c644c4fb13832467bcbc3173168a7916f0714900000000fd5e0100483045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01483045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af0014cc952410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253aeffffffff01e8030000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000')
+      var tx2 = txb.build()
+      assert.equal(tx2.getId(), 'eab59618a564e361adef6d918bd792903c3d41bcf1220137364fb847880467f9')
+      assert.equal(bscript.toASM(tx2.ins[0].script), 'OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 3045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae')
     })
   })
 })