Browse Source

TxBuilder: only allow OP_0's when building

hk-custom-address
Daniel Cousens 10 years ago
parent
commit
1ea5252511
  1. 126
      src/transaction_builder.js

126
src/transaction_builder.js

@ -9,7 +9,39 @@ var ECPair = require('./ecpair')
var ECSignature = require('./ecsignature') var ECSignature = require('./ecsignature')
var Transaction = require('./transaction') var Transaction = require('./transaction')
function extractInput (txIn) { // re-orders signatures to match pubKeys, fills undefined otherwise
function fixMSSignatures (transaction, vin, pubKeys, signatures, prevOutScript, hashType, skipPubKey) {
// maintain a local copy of unmatched signatures
var unmatched = signatures.slice()
var cache = {}
return pubKeys.map(function (pubKey) {
// skip optionally provided pubKey
if (skipPubKey && bufferutils.equal(skipPubKey, pubKey)) return undefined
var matched
var keyPair2 = ECPair.fromPublicKeyBuffer(pubKey)
// check for a matching signature
unmatched.some(function (signature, i) {
// skip if undefined || OP_0
if (!signature) return false
var signatureHash = cache[hashType] = cache[hashType] || transaction.hashForSignature(vin, prevOutScript, hashType)
if (!keyPair2.verify(signatureHash, signature)) return false
// remove matched signature from unmatched
unmatched[i] = undefined
matched = signature
return true
})
return matched || undefined
})
}
function extractInput (transaction, txIn, vin) {
var redeemScript var redeemScript
var scriptSig = txIn.script var scriptSig = txIn.script
var scriptSigChunks = bscript.decompile(scriptSig) var scriptSigChunks = bscript.decompile(scriptSig)
@ -63,7 +95,7 @@ function extractInput (txIn) {
case 'multisig': case 'multisig':
signatures = scriptSigChunks.slice(1).map(function (chunk) { signatures = scriptSigChunks.slice(1).map(function (chunk) {
if (chunk === ops.OP_0) return chunk if (chunk === ops.OP_0) return undefined
var parsed = ECSignature.parseScriptSignature(chunk) var parsed = ECSignature.parseScriptSignature(chunk)
hashType = parsed.hashType hashType = parsed.hashType
@ -73,6 +105,10 @@ function extractInput (txIn) {
if (redeemScript) { if (redeemScript) {
pubKeys = redeemScriptChunks.slice(1, -2) pubKeys = redeemScriptChunks.slice(1, -2)
if (pubKeys.length !== signatures.length) {
signatures = fixMSSignatures(transaction, vin, pubKeys, signatures, redeemScript, hashType, redeemScript)
}
} }
break break
@ -117,7 +153,7 @@ TransactionBuilder.fromTransaction = function (transaction, network) {
}) })
// Extract/add signatures // Extract/add signatures
txb.inputs = transaction.ins.map(function (txIn) { txb.inputs = transaction.ins.map(function (txIn, vin) {
// TODO: verify whether extractInput is sane with coinbase scripts // TODO: verify whether extractInput is sane with coinbase scripts
if (Transaction.isCoinbaseHash(txIn.hash)) { if (Transaction.isCoinbaseHash(txIn.hash)) {
throw new Error('coinbase inputs not supported') throw new Error('coinbase inputs not supported')
@ -126,7 +162,7 @@ TransactionBuilder.fromTransaction = function (transaction, network) {
// Ignore empty scripts // Ignore empty scripts
if (txIn.script.length === 0) return {} if (txIn.script.length === 0) return {}
return extractInput(txIn) return extractInput(transaction, txIn, vin)
}) })
return txb return txb
@ -154,10 +190,14 @@ TransactionBuilder.prototype.addInput = function (txHash, vout, sequence, prevOu
switch (prevOutType) { switch (prevOutType) {
case 'multisig': case 'multisig':
input.pubKeys = prevOutScriptChunks.slice(1, -2) input.pubKeys = prevOutScriptChunks.slice(1, -2)
input.signatures = input.pubKeys.map(function () { return undefined })
break break
case 'pubkey': case 'pubkey':
input.pubKeys = prevOutScriptChunks.slice(0, 1) input.pubKeys = prevOutScriptChunks.slice(0, 1)
input.signatures = [undefined]
break break
} }
@ -211,10 +251,10 @@ TransactionBuilder.prototype.buildIncomplete = function () {
return this.__build(true) return this.__build(true)
} }
var canSignTypes = { var canBuildTypes = {
'pubkeyhash': true,
'multisig': true, 'multisig': true,
'pubkey': true 'pubkey': true,
'pubkeyhash': true
} }
TransactionBuilder.prototype.__build = function (allowIncomplete) { TransactionBuilder.prototype.__build = function (allowIncomplete) {
@ -225,14 +265,16 @@ TransactionBuilder.prototype.__build = function (allowIncomplete) {
var tx = this.tx.clone() var tx = this.tx.clone()
// Create script signatures from signature meta-data // Create script signatures from inputs
this.inputs.forEach(function (input, index) { this.inputs.forEach(function (input, index) {
var scriptType = input.scriptType var scriptType = input.scriptType
var scriptSig var scriptSig
if (!allowIncomplete) { if (!allowIncomplete) {
if (!scriptType) throw new Error('Transaction is not complete') if (!scriptType) throw new Error('Transaction is not complete')
if (!canSignTypes[scriptType]) throw new Error(scriptType + ' not supported') if (!canBuildTypes[scriptType]) throw new Error(scriptType + ' not supported')
// XXX: only relevant to types that need signatures
if (!input.signatures) throw new Error('Transaction is missing signatures') if (!input.signatures) throw new Error('Transaction is missing signatures')
} }
@ -244,7 +286,6 @@ TransactionBuilder.prototype.__build = function (allowIncomplete) {
break break
case 'multisig': case 'multisig':
// Array.prototype.map is sparse-compatible
var msSignatures = input.signatures.map(function (signature) { var msSignatures = input.signatures.map(function (signature) {
return signature && signature.toScriptSignature(input.hashType) return signature && signature.toScriptSignature(input.hashType)
}) })
@ -252,12 +293,11 @@ TransactionBuilder.prototype.__build = function (allowIncomplete) {
// fill in blanks with OP_0 // fill in blanks with OP_0
if (allowIncomplete) { if (allowIncomplete) {
for (var i = 0; i < msSignatures.length; ++i) { for (var i = 0; i < msSignatures.length; ++i) {
if (msSignatures[i]) continue msSignatures[i] = msSignatures[i] || ops.OP_0
msSignatures[i] = ops.OP_0
} }
// remove blank signatures
} else { } else {
// Array.prototype.filter returns non-sparse array
msSignatures = msSignatures.filter(function (x) { return x }) msSignatures = msSignatures.filter(function (x) { return x })
} }
@ -297,11 +337,12 @@ TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hash
input.prevOutType && input.prevOutType &&
input.pubKeys && input.pubKeys &&
input.scriptType && input.scriptType &&
input.signatures input.signatures &&
input.signatures.length === input.pubKeys.length
var kpPubKey = keyPair.getPublicKeyBuffer() var kpPubKey = keyPair.getPublicKeyBuffer()
// are we almost ready to sign? // are we ready to sign?
if (canSign) { if (canSign) {
// if redeemScript was provided, enforce consistency // if redeemScript was provided, enforce consistency
if (redeemScript) { if (redeemScript) {
@ -323,13 +364,13 @@ TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hash
} }
var scriptType = bscript.classifyOutput(redeemScript) var scriptType = bscript.classifyOutput(redeemScript)
if (!canSignTypes[scriptType]) throw new Error('RedeemScript not supported (' + scriptType + ')')
var redeemScriptChunks = bscript.decompile(redeemScript) var redeemScriptChunks = bscript.decompile(redeemScript)
var pubKeys = [] var pubKeys
switch (scriptType) { switch (scriptType) {
case 'multisig': case 'multisig':
pubKeys = redeemScriptChunks.slice(1, -2) pubKeys = redeemScriptChunks.slice(1, -2)
break break
case 'pubkeyhash': case 'pubkeyhash':
@ -338,11 +379,16 @@ TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hash
if (!bufferutils.equal(pkh1, pkh2)) throw new Error('privateKey cannot sign for this input') if (!bufferutils.equal(pkh1, pkh2)) throw new Error('privateKey cannot sign for this input')
pubKeys = [kpPubKey] pubKeys = [kpPubKey]
break break
case 'pubkey': case 'pubkey':
pubKeys = redeemScriptChunks.slice(0, 1) pubKeys = redeemScriptChunks.slice(0, 1)
break break
default:
throw new Error('RedeemScript not supported (' + scriptType + ')')
} }
// if we don't have a prevOutScript, generate a P2SH script // if we don't have a prevOutScript, generate a P2SH script
@ -354,55 +400,31 @@ TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hash
input.pubKeys = pubKeys input.pubKeys = pubKeys
input.redeemScript = redeemScript input.redeemScript = redeemScript
input.scriptType = scriptType input.scriptType = scriptType
input.signatures = pubKeys.map(function () { return undefined })
// cannot be pay-to-scriptHash
} else { } else {
// pay-to-scriptHash is not possible without a redeemScript
if (input.prevOutType === 'scripthash') throw new Error('PrevOutScript is P2SH, missing redeemScript') if (input.prevOutType === 'scripthash') throw new Error('PrevOutScript is P2SH, missing redeemScript')
// can we otherwise sign this? // if we don't have a scriptType, assume pubKeyHash otherwise
if (input.scriptType) { if (!input.scriptType) {
if (!input.pubKeys) throw new Error(input.scriptType + ' not supported')
// we know nothin' Jon Snow, assume pubKeyHash
} else {
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]
} else {
// throw if we can't sign with it
if (!input.pubKeys || !input.signatures) throw new Error(input.scriptType + ' not supported')
} }
} }
input.hashType = hashType input.hashType = hashType
input.signatures = input.signatures || []
} }
// ready to sign?
var signatureScript = input.redeemScript || input.prevOutScript var signatureScript = input.redeemScript || input.prevOutScript
var signatureHash = this.tx.hashForSignature(index, signatureScript, hashType) var signatureHash = this.tx.hashForSignature(index, signatureScript, hashType)
// enforce signature order matches public keys
if (input.scriptType === 'multisig' && input.redeemScript && input.signatures.length !== input.pubKeys.length) {
// maintain a local copy of unmatched signatures
var unmatched = input.signatures.slice()
input.signatures = input.pubKeys.map(function (pubKey) {
var match
var keyPair2 = ECPair.fromPublicKeyBuffer(pubKey)
// check for any matching signatures
unmatched.some(function (signature, i) {
if (!signature || !keyPair2.verify(signatureHash, signature)) return false
match = signature
// remove matched signature from unmatched
unmatched.splice(i, 1)
return true
})
return match || undefined
})
}
// enforce in order signing of public keys // enforce in order signing of public keys
var valid = input.pubKeys.some(function (pubKey, i) { var valid = input.pubKeys.some(function (pubKey, i) {
if (!bufferutils.equal(kpPubKey, pubKey)) return false if (!bufferutils.equal(kpPubKey, pubKey)) return false

Loading…
Cancel
Save