Merge pull request #540 from blocktrail/segwit-prep

Refactoring `extractInput` and `__build` to enable nested scripts
This commit is contained in:
Daniel Cousens 2016-02-25 13:59:47 +11:00
commit 285bbd6478

View file

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