Merge pull request #540 from blocktrail/segwit-prep
Refactoring `extractInput` and `__build` to enable nested scripts
This commit is contained in:
commit
285bbd6478
1 changed files with 146 additions and 117 deletions
|
@ -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
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue