Skip to content

Commit

Permalink
Add more mempool standard checks.
Browse files Browse the repository at this point in the history
This commit adds a few more checks to restrict what transactions are
allowed into the transaction memory pool and therefore are candidates
to be mined and relayed.

In particular, the following changes were made to what is considered
standard:

- nulldata scripts are now supported and considered standard
- multi-signature transaction are now checked to ensure they only have a
  max of 3 pubkeys and the number of signatures doesn't exceed the number
  of pubkeys
- the number of inputs to a signature script must now match the expected
  number of inputs for the script type (includes support for additional
  pay-to-script-hash inputs)
- the number of inputs pushed onto the stack by a redeeming sig script
  must match the number of inputs consumed by the referenced pk script
- there can now only be a max of one nulldata output per transaction
  • Loading branch information
davecgh committed Nov 14, 2013
1 parent e3eca75 commit 50388bc
Showing 1 changed file with 93 additions and 21 deletions.
114 changes: 93 additions & 21 deletions mempool.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ const (
// prosperity. 3*80 + 3*65 + 65 = 500
maxStandardSigScriptSize = 500

// maxStandardMultiSigs is the maximum number of signatures
// allowed in a multi-signature transaction output script for it to be
// maxStandardMultiSigKeys is the maximum number of public keys allowed
// in a multi-signature transaction output script for it to be
// considered standard.
maxStandardMultiSigs = 3
maxStandardMultiSigKeys = 3

// minTxRelayFee is the minimum fee in satoshi that is required for a
// transaction to be treated as free for relay purposes. It is also
Expand Down Expand Up @@ -157,22 +157,42 @@ func isDust(txOut *btcwire.TxOut) bool {
// checkPkScriptStandard performs a series of checks on a transaction ouput
// script (public key script) to ensure it is a "standard" public key script.
// A standard public key script is one that is a recognized form, and for
// multi-signature scripts, only contains from 1 to 3 signatures.
func checkPkScriptStandard(pkScript []byte) error {
scriptClass := btcscript.GetScriptClass(pkScript)
// multi-signature scripts, only contains from 1 to maxStandardMultiSigKeys
// public keys.
func checkPkScriptStandard(pkScript []byte, scriptClass btcscript.ScriptClass) error {
switch scriptClass {
case btcscript.MultiSigTy:
// TODO(davec): Need to get the actual number of signatures.
numSigs := 1
numPubKeys, numSigs, err := btcscript.CalcMultiSigStats(pkScript)
if err != nil {
return err
}

// A standard multi-signature public key script must contain
// from 1 to maxStandardMultiSigKeys public keys.
if numPubKeys < 1 {
str := fmt.Sprintf("multi-signature script with no " +
"pubkeys")
return TxRuleError(str)
}
if numPubKeys > maxStandardMultiSigKeys {
str := fmt.Sprintf("multi-signature script with %d "+
"public keys which is more than the allowed "+
"max of %d", numPubKeys, maxStandardMultiSigKeys)
return TxRuleError(str)
}

// A standard multi-signature public key script must have at
// least 1 signature and no more signatures than available
// public keys.
if numSigs < 1 {
str := fmt.Sprintf("multi-signature script with no " +
"signatures")
return TxRuleError(str)
}
if numSigs > maxStandardMultiSigs {
if numSigs > numPubKeys {
str := fmt.Sprintf("multi-signature script with %d "+
"signatures which is more than the allowed max "+
"of %d", numSigs, maxStandardMultiSigs)
"signatures which is more than the available "+
"%d public keys", numSigs, numPubKeys)
return TxRuleError(str)
}

Expand Down Expand Up @@ -243,31 +263,83 @@ func checkTransactionStandard(tx *btcutil.Tx, height int64) error {

// None of the output public key scripts can be a non-standard script or
// be "dust".
numNullDataOutputs := 0
for i, txOut := range msgTx.TxOut {
err := checkPkScriptStandard(txOut.PkScript)
scriptClass := btcscript.GetScriptClass(txOut.PkScript)
err := checkPkScriptStandard(txOut.PkScript, scriptClass)
if err != nil {
str := fmt.Sprintf("transaction output %d: %v", i, err)
return TxRuleError(str)
}

// Accumulate the number of outputs which only carry data.
if scriptClass == btcscript.NullDataTy {
numNullDataOutputs++
}

if isDust(txOut) {
str := fmt.Sprintf("transaction output %d: payment "+
"of %d is dust", i, txOut.Value)
return TxRuleError(str)
}
}

// A standard transaction must not have more than one output script that
// only carries data.
if numNullDataOutputs > 1 {
return TxRuleError("more than one transaction output is a " +
"nulldata script")
}

return nil
}

// checkInputsStandard performs a series of checks on a transaction's inputs
// to ensure they are "standard". A standard transaction input is one that
// that consumes the same number of outputs from the stack as the output script
// pushes. This help prevent resource exhaustion attacks by "creative" use of
// scripts that are super expensive to process like OP_DUP OP_CHECKSIG OP_DROP
// repeated a large number of times followed by a final OP_TRUE.
func checkInputsStandard(tx *btcutil.Tx) error {
// TODO(davec): Implement
// that consumes the expected number of elements from the stack and that number
// is the same as the output script pushes. This help prevent resource
// exhaustion attacks by "creative" use of scripts that are super expensive to
// process like OP_DUP OP_CHECKSIG OP_DROP repeated a large number of times
// followed by a final OP_TRUE.
func checkInputsStandard(tx *btcutil.Tx, txStore btcchain.TxStore) error {
// NOTE: The reference implementation also does a coinbase check here,
// but coinbases have already been rejected prior to calling this
// function so no need to recheck.

for i, txIn := range tx.MsgTx().TxIn {
// It is safe to elide existence and index checks here since
// they have already been checked prior to calling this
// function.
prevOut := txIn.PreviousOutpoint
originTx := txStore[prevOut.Hash].Tx.MsgTx()
originPkScript := originTx.TxOut[prevOut.Index].PkScript

// Calculate stats for the script pair.
scriptInfo, err := btcscript.CalcScriptInfo(txIn.SignatureScript,
originPkScript, true)
if err != nil {
return err
}

// A negative value for expected inputs indicates the script is
// non-standard in some way.
if scriptInfo.ExpectedInputs < 0 {
str := fmt.Sprintf("transaction input #%d expects %d "+
"inputs", i, scriptInfo.ExpectedInputs)
return TxRuleError(str)
}

// The script pair is non-standard if the number of available
// inputs does not match the number of expected inputs.
if scriptInfo.NumInputs != scriptInfo.ExpectedInputs {
str := fmt.Sprintf("transaction input #%d expects %d "+
"inputs, but referenced output script only "+
"provides %d", i, scriptInfo.ExpectedInputs,
scriptInfo.NumInputs)
return TxRuleError(str)
}
}

return nil
}

Expand Down Expand Up @@ -710,7 +782,7 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcutil.Tx, isOrphan *bool) erro
// Don't allow transactions with non-standard inputs on the main
// network.
if activeNetParams.btcnet == btcwire.MainNet {
err := checkInputsStandard(tx)
err := checkInputsStandard(tx, txStore)
if err != nil {
str := fmt.Sprintf("transaction %v has a non-standard "+
"input: %v", txHash, err)
Expand All @@ -732,8 +804,8 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcutil.Tx, isOrphan *bool) erro
}

// TODO(davec): Rate-limit 'free' transactions. That is to say
// transactions which are less than the minimum relay fee and are there
// considered free.
// transactions which are less than the minimum relay fee and are
// therefore considered free.

// Verify crypto signatures for each input and reject the transaction if
// any don't verify.
Expand Down

0 comments on commit 50388bc

Please sign in to comment.