diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index fdc2fdb9cdf..48f2a6a7446 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -163,6 +163,35 @@ bool IsStandardTx(const CTransaction& tx, const std::optional& max_dat return true; } +/** + * Check the total number of non-witness sigops across the whole transaction, as per BIP54. + */ +static bool CheckSigopsBIP54(const CTransaction& tx, const CCoinsViewCache& inputs) +{ + Assert(!tx.IsCoinBase()); + + unsigned int sigops{0}; + for (const auto& txin: tx.vin) { + const auto& prev_txo{inputs.AccessCoin(txin.prevout).out}; + + // Unlike the existing block wide sigop limit which counts sigops present in the block + // itself (including the scriptPubKey which is not executed until spending later), BIP54 + // counts sigops in the block where they are potentially executed (only). + // This means sigops in the spent scriptPubKey count toward the limit. + // `fAccurate` means correctly accounting sigops for CHECKMULTISIGs(VERIFY) with 16 pubkeys + // or fewer. This method of accounting was introduced by BIP16, and BIP54 reuses it. + // The GetSigOpCount call on the previous scriptPubKey counts both bare and P2SH sigops. + sigops += txin.scriptSig.GetSigOpCount(/*fAccurate=*/true); + sigops += prev_txo.scriptPubKey.GetSigOpCount(txin.scriptSig); + + if (sigops > MAX_TX_LEGACY_SIGOPS) { + return false; + } + } + + return true; +} + /** * Check transaction inputs. * @@ -178,6 +207,8 @@ bool IsStandardTx(const CTransaction& tx, const std::optional& max_dat * as potential new upgrade hooks. * * Note that only the non-witness portion of the transaction is checked here. + * + * We also check the total number of non-witness sigops across the whole transaction, as per BIP54. */ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) { @@ -185,6 +216,10 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) return true; // Coinbases don't use vin normally } + if (!CheckSigopsBIP54(tx, mapInputs)) { + return false; + } + for (unsigned int i = 0; i < tx.vin.size(); i++) { const CTxOut& prev = mapInputs.AccessCoin(tx.vin[i].prevout).out; diff --git a/src/policy/policy.h b/src/policy/policy.h index e62efa02e3e..f9a18561bce 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -38,6 +38,8 @@ static constexpr unsigned int MIN_STANDARD_TX_NONWITNESS_SIZE{65}; static constexpr unsigned int MAX_P2SH_SIGOPS{15}; /** The maximum number of sigops we're willing to relay/mine in a single tx */ static constexpr unsigned int MAX_STANDARD_TX_SIGOPS_COST{MAX_BLOCK_SIGOPS_COST/5}; +/** The maximum number of potentially executed legacy signature operations in a single standard tx */ +static constexpr unsigned int MAX_TX_LEGACY_SIGOPS{2'500}; /** Default for -incrementalrelayfee, which sets the minimum feerate increase for mempool limiting or replacement **/ static constexpr unsigned int DEFAULT_INCREMENTAL_RELAY_FEE{1000}; /** Default for -bytespersigop */