From 8e72b050f3fdf60755ebbac266abe4c6dbe5d856 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Thu, 6 Oct 2016 13:18:14 +1100 Subject: [PATCH 1/2] TransactionBuilder: if prevOutScript is defined, but not signable, try 1 last time --- src/transaction_builder.js | 65 ++++++++++++++++---------- test/fixtures/transaction_builder.json | 22 +++++++++ test/transaction_builder.js | 4 +- 3 files changed, 65 insertions(+), 26 deletions(-) diff --git a/src/transaction_builder.js b/src/transaction_builder.js index efdaae07d5..54ac23c810 100644 --- a/src/transaction_builder.js +++ b/src/transaction_builder.js @@ -88,11 +88,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 = [] @@ -103,6 +105,7 @@ function expandOutput (script, ourPubKey) { var pkh1 = scriptChunks[2] var pkh2 = bcrypto.hash160(ourPubKey) + if (bufferEquals(pkh1, pkh2)) pubKeys = [ourPubKey] break @@ -114,7 +117,7 @@ function expandOutput (script, ourPubKey) { pubKeys = scriptChunks.slice(1, -2) break - default: return + default: return { scriptType: scriptType } } return { @@ -197,8 +200,8 @@ function prepareInput (input, kpPubKey, redeemScript, hashType) { input.prevOutType = 'scripthash' } - var expanded = expandOutput(redeemScript, kpPubKey) - if (!expanded) throw new Error('RedeemScript not supported "' + bscript.toASM(redeemScript) + '"') + var expanded = expandOutput(redeemScript, undefined, kpPubKey) + if (!expanded.pubKeys) throw new Error('RedeemScript not supported "' + bscript.toASM(redeemScript) + '"') input.pubKeys = expanded.pubKeys input.redeemScript = redeemScript @@ -210,8 +213,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 { @@ -357,19 +365,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) @@ -437,29 +447,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 (!bufferEquals(input.redeemScript, redeemScript)) throw new Error('Inconsistent redeemScript') - } + // if redeemScript was provided, enforce consistency + if (input.redeemScript !== undefined && redeemScript) { + if (!bufferEquals(input.redeemScript, 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 diff --git a/test/fixtures/transaction_builder.json b/test/fixtures/transaction_builder.json index e57608e2df..24f039b485 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 534f356854..71e3f5bd84 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) { From 8a96c7c2f20da58e8555a408e6e9c8a70b5a741a Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Thu, 6 Oct 2016 20:31:44 +1100 Subject: [PATCH 2/2] TransactionBuilder: avoid input mutation until after exception possibility --- src/transaction_builder.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/transaction_builder.js b/src/transaction_builder.js index 54ac23c810..186e16f8d0 100644 --- a/src/transaction_builder.js +++ b/src/transaction_builder.js @@ -193,16 +193,17 @@ function prepareInput (input, kpPubKey, redeemScript, hashType) { 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, 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' + } + input.pubKeys = expanded.pubKeys input.redeemScript = redeemScript input.redeemScriptType = expanded.scriptType