diff --git a/src/transaction_builder.js b/src/transaction_builder.js index 64069f2..0ca9b0c 100644 --- a/src/transaction_builder.js +++ b/src/transaction_builder.js @@ -44,86 +44,98 @@ function fixMSSignatures (transaction, vin, pubKeys, signatures, prevOutScript, } function extractInput (transaction, txIn, vin) { - var redeemScript - var scriptSig = txIn.script - var scriptSigChunks = bscript.decompile(scriptSig) + var scriptSigChunks = bscript.decompile(txIn.script) + var prevOutType = bscript.classifyInput(scriptSigChunks, true) - var prevOutScript - var prevOutType = bscript.classifyInput(scriptSig, true) - var scriptType - - // Re-classify if scriptHash - if (prevOutType === 'scripthash') { - redeemScript = scriptSigChunks.slice(-1)[0] - prevOutScript = bscript.scriptHashOutput(bcrypto.hash160(redeemScript)) - - scriptSig = bscript.compile(scriptSigChunks.slice(0, -1)) - scriptSigChunks = scriptSigChunks.slice(0, -1) - - scriptType = bscript.classifyInput(scriptSig, true) - } else { - scriptType = prevOutType + if (txIn.script.length === 0) { + return {} } - // pre-empt redeemScript decompilation - var redeemScriptChunks - if (redeemScript) { - redeemScriptChunks = bscript.decompile(redeemScript) - } + var processScript = function (scriptType, scriptSigChunks, redeemScriptChunks) { + // ensure chunks are decompiled + scriptSigChunks = bscript.decompile(scriptSigChunks) + redeemScriptChunks = redeemScriptChunks ? bscript.decompile(redeemScriptChunks) : undefined - // Extract hashType, pubKeys and signatures - var hashType, parsed, pubKeys, signatures + var hashType, pubKeys, signatures, prevOutScript, redeemScript, redeemScriptType, result, parsed - switch (scriptType) { - case 'pubkeyhash': - parsed = ECSignature.parseScriptSignature(scriptSigChunks[0]) - hashType = parsed.hashType - pubKeys = scriptSigChunks.slice(1) - signatures = [parsed.signature] - prevOutScript = bscript.pubKeyHashOutput(bcrypto.hash160(pubKeys[0])) + switch (scriptType) { + case 'scripthash': + redeemScript = scriptSigChunks.slice(-1)[0] + scriptSigChunks = bscript.compile(scriptSigChunks.slice(0, -1)) - break + redeemScriptType = bscript.classifyInput(scriptSigChunks, true) + prevOutScript = bscript.scriptHashOutput(bcrypto.hash160(redeemScript)) - case 'pubkey': - parsed = ECSignature.parseScriptSignature(scriptSigChunks[0]) - hashType = parsed.hashType - signatures = [parsed.signature] + result = processScript(redeemScriptType, scriptSigChunks, bscript.decompile(redeemScript)) - if (redeemScript) { - pubKeys = redeemScriptChunks.slice(0, 1) - } + result.prevOutScript = prevOutScript + result.redeemScript = redeemScript + result.redeemScriptType = redeemScriptType - break + return result - case 'multisig': - signatures = scriptSigChunks.slice(1).map(function (chunk) { - if (chunk === ops.OP_0) return undefined - - var parsed = 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])) - return parsed.signature - }) + break - if (redeemScript) { - pubKeys = redeemScriptChunks.slice(1, -2) + case 'pubkey': + parsed = ECSignature.parseScriptSignature(scriptSigChunks[0]) + hashType = parsed.hashType + signatures = [parsed.signature] - if (pubKeys.length !== signatures.length) { - signatures = fixMSSignatures(transaction, vin, pubKeys, signatures, redeemScript, hashType, redeemScript) + if (redeemScriptChunks) { + pubKeys = redeemScriptChunks.slice(0, 1) } - } - break + break + + case 'multisig': + signatures = scriptSigChunks.slice(1).map(function (chunk) { + if (chunk === ops.OP_0) return undefined + + parsed = ECSignature.parseScriptSignature(chunk) + hashType = parsed.hashType + + return parsed.signature + }) + + if (redeemScriptChunks) { + pubKeys = redeemScriptChunks.slice(1, -2) + + if (pubKeys.length !== signatures.length) { + signatures = fixMSSignatures(transaction, vin, pubKeys, signatures, bscript.compile(redeemScriptChunks), hashType, redeemScript) + } + } + + break + } + + return { + hashType: hashType, + pubKeys: pubKeys, + signatures: signatures, + prevOutScript: prevOutScript, + redeemScript: redeemScript, + redeemScriptType: redeemScriptType + } } + // Extract hashType, pubKeys, signatures and prevOutScript + var result = processScript(prevOutType, scriptSigChunks) + return { - hashType: hashType, - prevOutScript: prevOutScript, + hashType: result.hashType, + prevOutScript: result.prevOutScript, prevOutType: prevOutType, - pubKeys: pubKeys, - redeemScript: redeemScript, - scriptType: scriptType, - signatures: signatures + pubKeys: result.pubKeys, + redeemScript: result.redeemScript, + redeemScriptType: result.redeemScriptType, + signatures: result.signatures } } @@ -176,9 +188,6 @@ TransactionBuilder.fromTransaction = function (transaction, network) { throw new Error('coinbase inputs not supported') } - // Ignore empty scripts - if (txIn.script.length === 0) return {} - return extractInput(transaction, txIn, vin) }) @@ -298,7 +307,7 @@ TransactionBuilder.prototype.__build = function (allowIncomplete) { // Create script signatures from inputs this.inputs.forEach(function (input, index) { - var scriptType = input.scriptType + var scriptType = input.redeemScriptType || input.prevOutType var scriptSig if (!allowIncomplete) { @@ -310,46 +319,55 @@ TransactionBuilder.prototype.__build = function (allowIncomplete) { } if (input.signatures) { - switch (scriptType) { - case 'pubkeyhash': - var pkhSignature = input.signatures[0].toScriptSignature(input.hashType) - scriptSig = bscript.pubKeyHashInput(pkhSignature, input.pubKeys[0]) - break + var processScript = function (scriptType, parentType, redeemScript) { + var scriptSig + var pkhSignature - case 'multisig': - var msSignatures = input.signatures.map(function (signature) { - return signature && signature.toScriptSignature(input.hashType) - }) + switch (scriptType) { + case 'pubkeyhash': + pkhSignature = input.signatures[0].toScriptSignature(input.hashType) + scriptSig = bscript.pubKeyHashInput(pkhSignature, input.pubKeys[0]) + break - // fill in blanks with OP_0 - if (allowIncomplete) { - for (var i = 0; i < msSignatures.length; ++i) { - msSignatures[i] = msSignatures[i] || ops.OP_0 + 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 }) } - // remove blank signatures - } else { - msSignatures = msSignatures.filter(function (x) { return x }) - } + scriptSig = bscript.multisigInput(msSignatures, allowIncomplete ? undefined : redeemScript) - var redeemScript = allowIncomplete ? undefined : input.redeemScript - scriptSig = bscript.multisigInput(msSignatures, redeemScript) - break + break - case 'pubkey': - var pkSignature = input.signatures[0].toScriptSignature(input.hashType) - scriptSig = bscript.pubKeyInput(pkSignature) - break + case 'pubkey': + var pkSignature = input.signatures[0].toScriptSignature(input.hashType) + scriptSig = bscript.pubKeyInput(pkSignature) + break + } + + // wrap as scriptHash if necessary + if (parentType === 'scripthash') { + scriptSig = bscript.scriptHashInput(scriptSig, redeemScript) + } + + return scriptSig } + + scriptSig = processScript(scriptType, input.prevOutType, input.redeemScript) } - // did we build a scriptSig? + // did we build a scriptSig? Buffer('') is allowed if (scriptSig) { - // wrap as scriptHash if necessary - if (input.prevOutType === 'scripthash') { - scriptSig = bscript.scriptHashInput(scriptSig, input.redeemScript) - } - tx.setInputScript(index, scriptSig) } }) @@ -367,11 +385,12 @@ TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hash input.prevOutScript && input.prevOutType && input.pubKeys && - input.scriptType && + input.redeemScriptType && input.signatures && input.signatures.length === input.pubKeys.length var kpPubKey = keyPair.getPublicKeyBuffer() + var signatureScript // are we ready to sign? if (canSign) { @@ -385,6 +404,7 @@ TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hash // no? prepare } else { // must be pay-to-scriptHash? + if (redeemScript) { // if we have a prevOutScript, enforce scriptHash equality to the redeemScript if (input.prevOutScript) { @@ -394,34 +414,43 @@ TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hash if (!bufferEquals(scriptHash, bcrypto.hash160(redeemScript))) throw new Error('RedeemScript does not match ' + scriptHash.toString('hex')) } - var scriptType = bscript.classifyOutput(redeemScript) - var redeemScriptChunks = bscript.decompile(redeemScript) - var pubKeys + var pubKeys, pkh1, pkh2 - switch (scriptType) { - case 'multisig': - pubKeys = redeemScriptChunks.slice(1, -2) + var redeemScriptType - break + var processScript = function (redeemScript) { + var scriptType = bscript.classifyOutput(redeemScript) + var redeemScriptChunks = bscript.decompile(redeemScript) - case 'pubkeyhash': - var pkh1 = redeemScriptChunks[2] - var pkh2 = bcrypto.hash160(keyPair.getPublicKeyBuffer()) + switch (scriptType) { + case 'multisig': + pubKeys = redeemScriptChunks.slice(1, -2) - if (!bufferEquals(pkh1, pkh2)) throw new Error('privateKey cannot sign for this input') - pubKeys = [kpPubKey] + break - break + case 'pubkeyhash': + pkh1 = redeemScriptChunks[2] + pkh2 = bcrypto.hash160(keyPair.getPublicKeyBuffer()) - case 'pubkey': - pubKeys = redeemScriptChunks.slice(0, 1) + if (!bufferEquals(pkh1, pkh2)) throw new Error('privateKey cannot sign for this input') + pubKeys = [kpPubKey] - break + break - default: - throw new Error('RedeemScript not supported (' + scriptType + ')') + case 'pubkey': + pubKeys = redeemScriptChunks.slice(0, 1) + + break + + default: + throw new Error('RedeemScript not supported (' + scriptType + ')') + } + + return scriptType } + redeemScriptType = processScript(redeemScript) + // if we don't have a prevOutScript, generate a P2SH script if (!input.prevOutScript) { input.prevOutScript = bscript.scriptHashOutput(bcrypto.hash160(redeemScript)) @@ -430,7 +459,7 @@ TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hash input.pubKeys = pubKeys input.redeemScript = redeemScript - input.scriptType = scriptType + input.redeemScriptType = redeemScriptType input.signatures = pubKeys.map(function () { return undefined }) } else { // pay-to-scriptHash is not possible without a redeemScript @@ -440,6 +469,7 @@ TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hash if (!input.scriptType) { input.prevOutScript = bscript.pubKeyHashOutput(bcrypto.hash160(keyPair.getPublicKeyBuffer())) input.prevOutType = 'pubkeyhash' + input.pubKeys = [kpPubKey] input.scriptType = input.prevOutType input.signatures = [undefined] @@ -453,7 +483,7 @@ TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hash } // ready to sign? - var signatureScript = input.redeemScript || input.prevOutScript + signatureScript = signatureScript || input.redeemScript || input.prevOutScript var signatureHash = this.tx.hashForSignature(index, signatureScript, hashType) // enforce in order signing of public keys @@ -461,8 +491,7 @@ TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hash if (!bufferEquals(kpPubKey, pubKey)) return false if (input.signatures[i]) throw new Error('Signature already exists') - var signature = keyPair.sign(signatureHash) - input.signatures[i] = signature + input.signatures[i] = keyPair.sign(signatureHash) return true })