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. 131
      src/transaction_builder.js

131
src/transaction_builder.js

@ -44,37 +44,36 @@ 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 }
var processScript = function (scriptType, scriptSigChunks, redeemScriptChunks) {
// ensure chunks are decompiled
scriptSigChunks = bscript.decompile(scriptSigChunks)
redeemScriptChunks = redeemScriptChunks ? bscript.decompile(redeemScriptChunks) : undefined
var hashType, pubKeys, signatures, prevOutScript, redeemScript, redeemScriptType, result, parsed
// Re-classify if scriptHash switch (scriptType) {
if (prevOutType === 'scripthash') { case 'scripthash':
redeemScript = scriptSigChunks.slice(-1)[0] redeemScript = scriptSigChunks.slice(-1)[0]
prevOutScript = bscript.scriptHashOutput(bcrypto.hash160(redeemScript)) scriptSigChunks = bscript.compile(scriptSigChunks.slice(0, -1))
scriptSig = bscript.compile(scriptSigChunks.slice(0, -1)) redeemScriptType = bscript.classifyInput(scriptSigChunks, true)
scriptSigChunks = scriptSigChunks.slice(0, -1) prevOutScript = bscript.scriptHashOutput(bcrypto.hash160(redeemScript))
scriptType = bscript.classifyInput(scriptSig, true) result = processScript(redeemScriptType, scriptSigChunks, bscript.decompile(redeemScript))
} else {
scriptType = prevOutType
}
// pre-empt redeemScript decompilation result.prevOutScript = prevOutScript
var redeemScriptChunks result.redeemScript = redeemScript
if (redeemScript) { result.redeemScriptType = redeemScriptType
redeemScriptChunks = bscript.decompile(redeemScript)
}
// Extract hashType, pubKeys and signatures return result
var hashType, parsed, pubKeys, signatures
switch (scriptType) {
case 'pubkeyhash': case 'pubkeyhash':
parsed = ECSignature.parseScriptSignature(scriptSigChunks[0]) parsed = ECSignature.parseScriptSignature(scriptSigChunks[0])
hashType = parsed.hashType hashType = parsed.hashType
@ -89,7 +88,7 @@ function extractInput (transaction, txIn, vin) {
hashType = parsed.hashType hashType = parsed.hashType
signatures = [parsed.signature] signatures = [parsed.signature]
if (redeemScript) { if (redeemScriptChunks) {
pubKeys = redeemScriptChunks.slice(0, 1) pubKeys = redeemScriptChunks.slice(0, 1)
} }
@ -99,17 +98,17 @@ function extractInput (transaction, txIn, vin) {
signatures = scriptSigChunks.slice(1).map(function (chunk) { signatures = scriptSigChunks.slice(1).map(function (chunk) {
if (chunk === ops.OP_0) return undefined if (chunk === ops.OP_0) return undefined
var parsed = ECSignature.parseScriptSignature(chunk) parsed = ECSignature.parseScriptSignature(chunk)
hashType = parsed.hashType 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)
} }
} }
@ -118,12 +117,25 @@ function extractInput (transaction, txIn, vin) {
return { return {
hashType: hashType, hashType: hashType,
prevOutScript: prevOutScript,
prevOutType: prevOutType,
pubKeys: pubKeys, pubKeys: pubKeys,
signatures: signatures,
prevOutScript: prevOutScript,
redeemScript: redeemScript, redeemScript: redeemScript,
scriptType: scriptType, redeemScriptType: redeemScriptType
signatures: signatures }
}
// Extract hashType, pubKeys, signatures and prevOutScript
var result = processScript(prevOutType, scriptSigChunks)
return {
hashType: result.hashType,
prevOutScript: result.prevOutScript,
prevOutType: prevOutType,
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') 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,9 +319,13 @@ TransactionBuilder.prototype.__build = function (allowIncomplete) {
} }
if (input.signatures) { if (input.signatures) {
var processScript = function (scriptType, parentType, redeemScript) {
var scriptSig
var pkhSignature
switch (scriptType) { switch (scriptType) {
case 'pubkeyhash': case 'pubkeyhash':
var pkhSignature = input.signatures[0].toScriptSignature(input.hashType) pkhSignature = input.signatures[0].toScriptSignature(input.hashType)
scriptSig = bscript.pubKeyHashInput(pkhSignature, input.pubKeys[0]) scriptSig = bscript.pubKeyHashInput(pkhSignature, input.pubKeys[0])
break break
@ -332,8 +345,8 @@ TransactionBuilder.prototype.__build = function (allowIncomplete) {
msSignatures = msSignatures.filter(function (x) { return x }) msSignatures = msSignatures.filter(function (x) { return x })
} }
var redeemScript = allowIncomplete ? undefined : input.redeemScript scriptSig = bscript.multisigInput(msSignatures, allowIncomplete ? undefined : redeemScript)
scriptSig = bscript.multisigInput(msSignatures, redeemScript)
break break
case 'pubkey': case 'pubkey':
@ -341,15 +354,20 @@ TransactionBuilder.prototype.__build = function (allowIncomplete) {
scriptSig = bscript.pubKeyInput(pkSignature) scriptSig = bscript.pubKeyInput(pkSignature)
break break
} }
}
// did we build a scriptSig?
if (scriptSig) {
// wrap as scriptHash if necessary // wrap as scriptHash if necessary
if (input.prevOutType === 'scripthash') { if (parentType === 'scripthash') {
scriptSig = bscript.scriptHashInput(scriptSig, input.redeemScript) scriptSig = bscript.scriptHashInput(scriptSig, redeemScript)
}
return scriptSig
} }
scriptSig = processScript(scriptType, input.prevOutType, input.redeemScript)
}
// did we build a scriptSig? Buffer('') is allowed
if (scriptSig) {
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,9 +414,13 @@ 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 pubKeys, pkh1, pkh2
var redeemScriptType
var processScript = function (redeemScript) {
var scriptType = bscript.classifyOutput(redeemScript) var scriptType = bscript.classifyOutput(redeemScript)
var redeemScriptChunks = bscript.decompile(redeemScript) var redeemScriptChunks = bscript.decompile(redeemScript)
var pubKeys
switch (scriptType) { switch (scriptType) {
case 'multisig': case 'multisig':
@ -405,8 +429,8 @@ TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hash
break break
case 'pubkeyhash': case 'pubkeyhash':
var pkh1 = redeemScriptChunks[2] pkh1 = redeemScriptChunks[2]
var pkh2 = bcrypto.hash160(keyPair.getPublicKeyBuffer()) pkh2 = bcrypto.hash160(keyPair.getPublicKeyBuffer())
if (!bufferEquals(pkh1, pkh2)) throw new Error('privateKey cannot sign for this input') if (!bufferEquals(pkh1, pkh2)) throw new Error('privateKey cannot sign for this input')
pubKeys = [kpPubKey] pubKeys = [kpPubKey]
@ -422,6 +446,11 @@ TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hash
throw new Error('RedeemScript not supported (' + scriptType + ')') 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