From b6ae10b2f1b35ec33493f23e03b6a5086d5b695d Mon Sep 17 00:00:00 2001 From: Thomas Kerin Date: Thu, 22 Dec 2016 13:18:45 +0100 Subject: [PATCH] buildStack, and make tests pass --- src/transaction_builder.js | 124 ++++++++++++++++++++----------------- test/templates.js | 5 +- 2 files changed, 70 insertions(+), 59 deletions(-) diff --git a/src/transaction_builder.js b/src/transaction_builder.js index cdaecc3..120791a 100644 --- a/src/transaction_builder.js +++ b/src/transaction_builder.js @@ -6,6 +6,9 @@ var ops = require('bitcoin-ops') var typeforce = require('typeforce') var types = require('./types') var scriptTypes = bscript.types +var SIGNABLE = [bscript.types.P2PKH, bscript.types.P2PK, bscript.types.MULTISIG] +var P2SH = SIGNABLE.concat([bscript.types.P2WPKH, bscript.types.P2WSH]) +var EMPTY_SCRIPT = new Buffer(0) var ECPair = require('./ecpair') var ECSignature = require('./ecsignature') @@ -223,72 +226,75 @@ function prepareInput (input, kpPubKey, redeemScript) { } } -var EMPTY_SCRIPT = new Buffer(0) - -function buildInput (input, allowIncomplete) { - var signatures = input.signatures - var scriptType = input.redeemScriptType || input.prevOutType - var stack - - switch (scriptType) { - case scriptTypes.P2WPKH: - if (signatures.length < 1 || !signatures[0]) throw new Error('Not enough signatures provided') - stack = bscript.witnessPubKeyHash.input.encodeStack(signatures[0], input.pubKeys[0]) - break - - case scriptTypes.P2PKH: - if (signatures.length < 1 || !signatures[0]) throw new Error('Not enough signatures provided') - stack = bscript.pubKeyHash.input.encodeStack(signatures[0], input.pubKeys[0]) - break - - case scriptTypes.P2PK: - if (signatures.length < 1 || !signatures[0]) throw new Error('Not enough signatures provided') - stack = bscript.pubKey.input.encodeStack(signatures[0]) - break - - // ref https://github.com/bitcoin/bitcoin/blob/d612837814020ae832499d18e6ee5eb919a87907/src/script/sign.cpp#L232 - case scriptTypes.MULTISIG: - signatures = signatures.map(function (signature) { - return signature || ops.OP_0 - }) - - if (!allowIncomplete) { - // remove blank signatures - signatures = signatures.filter(function (x) { return x !== ops.OP_0 }) - } +function buildStack (type, signatures, pubKeys, allowIncomplete) { + if (type === scriptTypes.P2PKH) { + if (signatures.length < 1 || !signatures[0]) throw new Error('Not enough signatures provided') + return bscript.pubKeyHash.input.encodeStack(signatures[0], pubKeys[0]) + } else if (type === scriptTypes.P2PK) { + if (signatures.length < 1 || !signatures[0]) throw new Error('Not enough signatures provided') + return bscript.pubKey.input.encodeStack(signatures[0]) + } else { + signatures = signatures.map(function (signature) { + return signature || ops.OP_0 + }) - stack = bscript.multisig.input.encodeStack(signatures, allowIncomplete ? undefined : input.redeemScript) - break + if (!allowIncomplete) { + // remove blank signatures + signatures = signatures.filter(function (x) { return x !== ops.OP_0 }) + } - default: return + return bscript.multisig.input.encodeStack(signatures /* see if it's necessary first */) } +} - var script, witness - - // encode witness for P2WPKH if necessary - if (input.prevOutType === scriptTypes.P2WPKH || - input.redeemScriptType === scriptTypes.P2WPKH) { - witness = stack - script = EMPTY_SCRIPT +function buildInput (input, allowIncomplete) { + var scriptType = input.prevOutType + var sig = [] + var witness = [] + if (SIGNABLE.indexOf(scriptType) !== -1) { + sig = buildStack(scriptType, input.signatures, input.pubKeys, input.script, allowIncomplete) } - // no witness? plain old script! - if (witness === undefined) { - script = bscript.compilePushOnly(stack) + var p2sh = false + if (scriptType === bscript.types.P2SH) { + // We can remove this error later when we have a guarantee prepareInput + // rejects unsignabale scripts - it MUST be signable at this point. + if (P2SH.indexOf(input.redeemScriptType) === -1) { + throw new Error('Impossible to sign this type') + } + p2sh = true + if (SIGNABLE.indexOf(input.redeemScriptType) !== -1) { + sig = buildStack(input.redeemScriptType, input.signatures, input.pubKeys, allowIncomplete) + } + // If it wasn't SIGNABLE, it's witness, defer to that + scriptType = input.redeemScriptType } - // wrap as scriptHash if necessary - if (input.prevOutType === scriptTypes.P2SH) { - script = bscript.scriptHash.input.encode(script, input.redeemScript) + if (scriptType === bscript.types.P2WPKH) { + // P2WPKH is a special case of P2PKH + witness = buildStack(bscript.types.P2PKH, input.signatures, input.pubKeys, allowIncomplete) + } else if (scriptType === bscript.types.P2WSH) { + // We can remove this check later + if (SIGNABLE.indexOf(input.witnessScriptType) !== -1) { + witness = buildStack(input.witnessScriptType, input.signatures, input.pubKeys, allowIncomplete) + witness.push(input.witnessScript) + } else { + // We can remove this error later when we have a guarantee prepareInput + // rejects unsignble scripts - it MUST be signable at this point. + throw new Error() + } + + scriptType = input.witnessScriptType } - // falsy is easier - if (script === EMPTY_SCRIPT) { - script = undefined + // append redeemScript if necessary + if (p2sh) { + sig.push(input.redeemScript) } return { - script: script, + type: scriptType, + script: bscript.compile(sig), witness: witness } } @@ -461,15 +467,17 @@ TransactionBuilder.prototype.__build = function (allowIncomplete) { this.inputs.forEach(function (input, i) { var scriptType = input.redeemScriptType || input.prevOutType if (!scriptType && !allowIncomplete) throw new Error('Transaction is not complete') - var result = buildInput(input, allowIncomplete) // skip if no result - if (!result && allowIncomplete) return - if (result && result.script) return tx.setInputScript(i, result.script) - if (result && result.witness) return tx.setWitness(i, result.witness) + if (!allowIncomplete) { + if (SIGNABLE.indexOf(result.type) === -1 && result.type !== bscript.types.P2WPKH) { + throw new Error(result.type + ' not supported') + } + } - throw new Error(scriptType + ' not supported') + tx.setInputScript(i, result.script) + tx.setWitness(i, result.witness) }) if (!allowIncomplete) { diff --git a/test/templates.js b/test/templates.js index 6e9a5b1..b06a21e 100644 --- a/test/templates.js +++ b/test/templates.js @@ -61,7 +61,10 @@ describe('script-templates', function () { describe(name + '.input.check', function () { fixtures.valid.forEach(function (f) { - var expected = name.toLowerCase() === f.type + // Temporary - while we don't have witnessKeyHash.input, etc. + if (name.toLowerCase() === bscript.types.P2WPKH) return + if (name.toLowerCase() === bscript.types.P2WSH) return + var expected = name.toLowerCase() === f.type.toLowerCase() if (inputType && f.input) { var input = bscript.fromASM(f.input)