Browse Source

Refactoring `extractInput` and `__build` to enable nested scripts (`P2SH[ P2PKH / multsig ]`).

This is preperation for the segwit support which will have `P2SH[ P2WSH[ P2PKH / multsig ] ]` scripts
hk-custom-address
Ruben de Vries 9 years ago
parent
commit
7aaae4c001
  1. 263
      src/transaction_builder.js

263
src/transaction_builder.js

@ -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 var processScript = function (scriptType, scriptSigChunks, redeemScriptChunks) {
if (prevOutType === 'scripthash') { // ensure chunks are decompiled
redeemScript = scriptSigChunks.slice(-1)[0] scriptSigChunks = bscript.decompile(scriptSigChunks)
prevOutScript = bscript.scriptHashOutput(bcrypto.hash160(redeemScript)) redeemScriptChunks = redeemScriptChunks ? bscript.decompile(redeemScriptChunks) : undefined
scriptSig = bscript.compile(scriptSigChunks.slice(0, -1)) var hashType, pubKeys, signatures, prevOutScript, redeemScript, redeemScriptType, result, parsed
scriptSigChunks = scriptSigChunks.slice(0, -1)
scriptType = bscript.classifyInput(scriptSig, true) switch (scriptType) {
} else { case 'scripthash':
scriptType = prevOutType redeemScript = scriptSigChunks.slice(-1)[0]
} scriptSigChunks = bscript.compile(scriptSigChunks.slice(0, -1))
// pre-empt redeemScript decompilation redeemScriptType = bscript.classifyInput(scriptSigChunks, true)
var redeemScriptChunks prevOutScript = bscript.scriptHashOutput(bcrypto.hash160(redeemScript))
if (redeemScript) {
redeemScriptChunks = bscript.decompile(redeemScript)
}
// Extract hashType, pubKeys and signatures result = processScript(redeemScriptType, scriptSigChunks, bscript.decompile(redeemScript))
var hashType, parsed, pubKeys, signatures
switch (scriptType) { result.prevOutScript = prevOutScript
case 'pubkeyhash': result.redeemScript = redeemScript
parsed = ECSignature.parseScriptSignature(scriptSigChunks[0]) result.redeemScriptType = redeemScriptType
hashType = parsed.hashType
pubKeys = scriptSigChunks.slice(1)
signatures = [parsed.signature]
prevOutScript = bscript.pubKeyHashOutput(bcrypto.hash160(pubKeys[0]))
break return result
case 'pubkey': case 'pubkeyhash':
parsed = ECSignature.parseScriptSignature(scriptSigChunks[0]) parsed = ECSignature.parseScriptSignature(scriptSigChunks[0])
hashType = parsed.hashType hashType = parsed.hashType
signatures = [parsed.signature] pubKeys = scriptSigChunks.slice(1)
signatures = [parsed.signature]
prevOutScript = bscript.pubKeyHashOutput(bcrypto.hash160(pubKeys[0]))
if (redeemScript) { break
pubKeys = redeemScriptChunks.slice(0, 1)
}
break case 'pubkey':
parsed = ECSignature.parseScriptSignature(scriptSigChunks[0])
hashType = parsed.hashType
signatures = [parsed.signature]
case 'multisig': if (redeemScriptChunks) {
signatures = scriptSigChunks.slice(1).map(function (chunk) { pubKeys = redeemScriptChunks.slice(0, 1)
if (chunk === ops.OP_0) return undefined }
var parsed = ECSignature.parseScriptSignature(chunk) break
hashType = parsed.hashType
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 return parsed.signature
}) })
if (redeemScript) { if (redeemScriptChunks) {
pubKeys = redeemScriptChunks.slice(1, -2) pubKeys = redeemScriptChunks.slice(1, -2)
if (pubKeys.length !== signatures.length) { if (pubKeys.length !== signatures.length) {
signatures = fixMSSignatures(transaction, vin, pubKeys, signatures, redeemScript, hashType, redeemScript) signatures = fixMSSignatures(transaction, vin, pubKeys, signatures, bscript.compile(redeemScriptChunks), hashType, redeemScript)
}
} }
}
break 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 switch (scriptType) {
case 'pubkeyhash':
case 'multisig': pkhSignature = input.signatures[0].toScriptSignature(input.hashType)
var msSignatures = input.signatures.map(function (signature) { scriptSig = bscript.pubKeyHashInput(pkhSignature, input.pubKeys[0])
return signature && signature.toScriptSignature(input.hashType) break
})
case 'multisig':
// fill in blanks with OP_0 var msSignatures = input.signatures.map(function (signature) {
if (allowIncomplete) { return signature && signature.toScriptSignature(input.hashType)
for (var i = 0; i < msSignatures.length; ++i) { })
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 case 'pubkey':
pubKeys = redeemScriptChunks.slice(0, 1)
default: break
throw new Error('RedeemScript not supported (' + scriptType + ')')
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…
Cancel
Save