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) {
|
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
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Reference in a new issue