Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0ee6f63
chainparams: add versionbits deployment for BIP 54
darosior Apr 30, 2026
a16b14a
scripted-diff: rename BIP54-sigops constants to MAX_TX_BIP54_SIGOPS
darosior Jan 20, 2026
e2d02a2
moveonly: move CheckSigopsBIP54 from policy to consensus
darosior Sep 4, 2025
c1945dd
validation: make BIP54 sigops check consensus-critical
darosior Oct 14, 2025
b8ffa1e
qa: add to utilities a version of SignSignature for Taproot inputs
darosior Sep 16, 2025
85a4893
qa: extensive unit tests for BIP54 legacy sigops limit
darosior Sep 22, 2025
7039a44
fuzz: add a fuzz target for the BIP54 sigops check
darosior Sep 17, 2025
7e915b0
scripted-diff: rename testnet4 timewarp constant
darosior Jan 31, 2025
67a69f6
miner: update a timewarp comment to refer specifically to BIP 54
darosior Feb 4, 2026
2814945
validation: prevent timewarp attacks with a 2h grace period
darosior Sep 5, 2025
2835d88
validation: prevent negative difficulty adjustment intervals
darosior Jan 31, 2025
266def8
qa: BIP54 test vectors for timewarp and Murch-Zawy
darosior Sep 29, 2025
4c024f6
validation: enforce that coinbase transactions are timelocked to bloc…
darosior Mar 10, 2025
b4836f3
qa: BIP54 test vectors for restrictions on coinbase transactions
darosior Oct 1, 2025
7528984
Avoid creating <= 64-byte transactions in most functional tests.
TheBlueMatt Feb 25, 2019
6552fb9
[test] Separate 64B and 63B tx size tests
ajtowns Mar 28, 2023
c102d03
validation: make 64-byte transactions invalid
darosior Mar 11, 2025
2e66e1e
qa: unit tests for BIP54 rule on 64-byte transactions (with JSON test…
darosior Sep 22, 2025
a0eedb2
qa: end-to-end test all BIP54 mitigations
darosior Oct 16, 2025
ce1fc81
doc: add a BIP 54 entry to bips.md
darosior Jul 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/bips.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ BIPs that are implemented by Bitcoin Core:
* [`BIP 43`](https://github.com/bitcoin/bips/blob/master/bip-0043.mediawiki): The experimental descriptor wallets introduced in **v0.21.0** by default use the Hierarchical Deterministic Wallet derivation proposed by BIP 43. ([PR #16528](https://github.com/bitcoin/bitcoin/pull/16528))
* [`BIP 44`](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki): The experimental descriptor wallets introduced in **v0.21.0** by default use the Hierarchical Deterministic Wallet derivation proposed by BIP 44. ([PR #16528](https://github.com/bitcoin/bitcoin/pull/16528))
* [`BIP 49`](https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki): The experimental descriptor wallets introduced in **v0.21.0** by default use the Hierarchical Deterministic Wallet derivation proposed by BIP 49. ([PR #16528](https://github.com/bitcoin/bitcoin/pull/16528))
* [`BIP 54`](https://github.com/bitcoin/bips/blob/master/bip-0054.md): Validation rules for the Consensus Cleanup soft fork are implemented as of version **v32.0**. No deployment is defined for the soft fork, outside of regtest where the rules are now always active.
* [`BIP 61`](https://github.com/bitcoin/bips/blob/master/bip-0061.mediawiki): The 'reject' protocol message (and the protocol version bump to 70002) was added in **v0.9.0** ([PR #3185](https://github.com/bitcoin/bitcoin/pull/3185)). Starting **v0.17.0**, whether to send reject messages can be configured with the `-enablebip61` option, and support is deprecated (disabled by default) as of **v0.18.0**. Support was removed in **v0.20.0** ([PR #15437](https://github.com/bitcoin/bitcoin/pull/15437)).
* [`BIP 65`](https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki): The CHECKLOCKTIMEVERIFY softfork was merged in **v0.12.0** ([PR #6351](https://github.com/bitcoin/bitcoin/pull/6351)), and backported to **v0.11.2** and **v0.10.4**. Mempool-only CLTV was added in [PR #6124](https://github.com/bitcoin/bitcoin/pull/6124).
* [`BIP 66`](https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki): The strict DER rules and associated version 3 blocks have been implemented since **v0.10.0** ([PR #5713](https://github.com/bitcoin/bitcoin/pull/5713)).
Expand Down
2 changes: 1 addition & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ add_library(bitcoin_common STATIC EXCLUDE_FROM_ALL
common/system.cpp
common/url.cpp
compressor.cpp
consensus/tx_verify.cpp
core_io.cpp
deploymentinfo.cpp
external_signer.cpp
Expand Down Expand Up @@ -188,7 +189,6 @@ add_library(bitcoin_node STATIC EXCLUDE_FROM_ALL
bip324.cpp
blockencodings.cpp
blockfilter.cpp
consensus/tx_verify.cpp
dbwrapper.cpp
deploymentstatus.cpp
flatfile.cpp
Expand Down
17 changes: 16 additions & 1 deletion src/consensus/consensus.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,26 @@ static const size_t MIN_SERIALIZABLE_TRANSACTION_WEIGHT = WITNESS_SCALE_FACTOR *
/** Interpret sequence numbers as relative lock-time constraints. */
static constexpr unsigned int LOCKTIME_VERIFY_SEQUENCE = (1 << 0);

/**
* Under BIP54, the first block in a difficulty adjustment period must not be more than 2
* hours (7200 seconds) earlier than the last block of the previous period.
*/
static constexpr int64_t MAX_TIMEWARP_BIP54{2 * 60 * 60};

/**
* Maximum number of seconds that the timestamp of the first
* block of a difficulty adjustment period is allowed to
* be earlier than the last block of the previous period (BIP94).
*/
static constexpr int64_t MAX_TIMEWARP = 600;
static constexpr int64_t MAX_TIMEWARP_TESTNET4 = 600;

/** The maximum number of potentially executed legacy signature operations in a single tx */
static constexpr unsigned int MAX_TX_BIP54_SIGOPS{2'500};

/**
* 64-byte transactions are invalid (BIP 54) due to serious flaws in the Merkle tree algorithm
* that make it so that such transactions may be re-interpreted as inner tree nodes.
*/
static constexpr unsigned int INVALID_TX_NONWITNESS_SIZE{64};

#endif // BITCOIN_CONSENSUS_CONSENSUS_H
1 change: 1 addition & 0 deletions src/consensus/params.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ constexpr bool ValidDeployment(BuriedDeployment dep) { return dep <= DEPLOYMENT_

enum DeploymentPos : uint16_t {
DEPLOYMENT_TESTDUMMY,
DEPLOYMENT_CONSENSUSCLEANUP, // Deployment of BIP 54
// NOTE: Also add new deployments to VersionBitsDeploymentInfo in deploymentinfo.cpp
// Removing an entry may require bumping MinBIP9WarningHeight.
MAX_VERSION_BITS_DEPLOYMENTS
Expand Down
32 changes: 31 additions & 1 deletion src/consensus/tx_verify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,44 @@ int64_t GetTransactionSigOpCost(const CTransaction& tx, const CCoinsViewCache& i
return nSigOps;
}

bool Consensus::CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee)
bool Consensus::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_BIP54_SIGOPS) {
return false;
}
}

return true;
}

bool Consensus::CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee, bool enforce_bip54)
{
// are the actual inputs available?
if (!inputs.HaveInputs(tx)) {
return state.Invalid(TxValidationResult::TX_MISSING_INPUTS, "bad-txns-inputs-missingorspent",
strprintf("%s: inputs missing/spent", __func__));
}

if (enforce_bip54 && !Consensus::CheckSigopsBIP54(tx, inputs)) {
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-legacy-sigops", "too many legacy sigops (BIP54)");
}

CAmount nValueIn = 0;
for (unsigned int i = 0; i < tx.vin.size(); ++i) {
const COutPoint &prevout = tx.vin[i].prevout;
Expand Down
8 changes: 7 additions & 1 deletion src/consensus/tx_verify.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,19 @@ class TxValidationState;
/** Transaction validation functions */

namespace Consensus {
/**
* Check the total number of non-witness sigops across the whole transaction, as per BIP54.
*/
bool CheckSigopsBIP54(const CTransaction& tx, const CCoinsViewCache& inputs);

/**
* Check whether all inputs of this transaction are valid (no double spends and amounts)
* This does not modify the UTXO set. This does not check scripts and sigs.
* @param[out] txfee Set to the transaction fee if successful.
* @param[in] enforce_bip54 Whether to perform the BIP54 sigops check.
* Preconditions: tx.IsCoinBase() is false.
*/
[[nodiscard]] bool CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee);
[[nodiscard]] bool CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee, bool enforce_bip54);
} // namespace Consensus

/** Auxiliary functions for transaction validation (ideally should not be exposed) */
Expand Down
4 changes: 4 additions & 0 deletions src/deploymentinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ const std::array<VBDeploymentInfo,Consensus::MAX_VERSION_BITS_DEPLOYMENTS> Versi
.name = "testdummy",
.gbt_optional_rule = true,
},
VBDeploymentInfo{
.name = "consensuscleanup",
.gbt_optional_rule = false,
},
};

std::string DeploymentName(Consensus::BuriedDeployment dep)
Expand Down
28 changes: 28 additions & 0 deletions src/kernel/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ class CMainParams : public CChainParams {
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].threshold = 1815; // 90%
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].period = 2016;

// Deployment of the Consensus Cleanup (BIP 54)
consensus.vDeployments[Consensus::DEPLOYMENT_CONSENSUSCLEANUP].bit = 3;
consensus.vDeployments[Consensus::DEPLOYMENT_CONSENSUSCLEANUP].nStartTime = Consensus::BIP9Deployment::NEVER_ACTIVE;
consensus.vDeployments[Consensus::DEPLOYMENT_CONSENSUSCLEANUP].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;

ApplyDeploymentOptions(opts.dep_opts);

consensus.nMinimumChainWork = uint256{"0000000000000000000000000000000000000001128750f82f4c366153a3a030"};
Expand Down Expand Up @@ -260,6 +265,11 @@ class CTestNetParams : public CChainParams {
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].threshold = 1512; // 75%
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].period = 2016;

// Deployment of the Consensus Cleanup (BIP 54)
consensus.vDeployments[Consensus::DEPLOYMENT_CONSENSUSCLEANUP].bit = 3;
consensus.vDeployments[Consensus::DEPLOYMENT_CONSENSUSCLEANUP].nStartTime = Consensus::BIP9Deployment::NEVER_ACTIVE;
consensus.vDeployments[Consensus::DEPLOYMENT_CONSENSUSCLEANUP].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;

ApplyDeploymentOptions(opts.dep_opts);

consensus.nMinimumChainWork = uint256{"0000000000000000000000000000000000000000000017dde1c649f3708d14b6"};
Expand Down Expand Up @@ -362,6 +372,11 @@ class CTestNet4Params : public CChainParams {
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].threshold = 1512; // 75%
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].period = 2016;

// Deployment of the Consensus Cleanup (BIP 54)
consensus.vDeployments[Consensus::DEPLOYMENT_CONSENSUSCLEANUP].bit = 3;
consensus.vDeployments[Consensus::DEPLOYMENT_CONSENSUSCLEANUP].nStartTime = Consensus::BIP9Deployment::NEVER_ACTIVE;
consensus.vDeployments[Consensus::DEPLOYMENT_CONSENSUSCLEANUP].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;

ApplyDeploymentOptions(opts.dep_opts);

consensus.nMinimumChainWork = uint256{"0000000000000000000000000000000000000000000009a0fe15d0177d086304"};
Expand Down Expand Up @@ -507,6 +522,11 @@ class SigNetParams : public CChainParams {
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].threshold = 1815; // 90%
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].period = 2016;

// Deployment of the Consensus Cleanup (BIP 54)
consensus.vDeployments[Consensus::DEPLOYMENT_CONSENSUSCLEANUP].bit = 3;
consensus.vDeployments[Consensus::DEPLOYMENT_CONSENSUSCLEANUP].nStartTime = Consensus::BIP9Deployment::NEVER_ACTIVE;
consensus.vDeployments[Consensus::DEPLOYMENT_CONSENSUSCLEANUP].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;

ApplyDeploymentOptions(options.dep_opts);

// message start is defined as the first 4 bytes of the sha256d of the block script
Expand Down Expand Up @@ -591,6 +611,14 @@ class CRegTestParams : public CChainParams
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].threshold = 108; // 75%
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].period = 144; // Faster than normal for regtest (144 instead of 2016)

// Deployment of the Consensus Cleanup (BIP 54)
consensus.vDeployments[Consensus::DEPLOYMENT_CONSENSUSCLEANUP].bit = 3;
consensus.vDeployments[Consensus::DEPLOYMENT_CONSENSUSCLEANUP].nStartTime = Consensus::BIP9Deployment::ALWAYS_ACTIVE;
consensus.vDeployments[Consensus::DEPLOYMENT_CONSENSUSCLEANUP].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;
consensus.vDeployments[Consensus::DEPLOYMENT_CONSENSUSCLEANUP].min_activation_height = 0; // No activation delay
consensus.vDeployments[Consensus::DEPLOYMENT_CONSENSUSCLEANUP].threshold = 108; // 75%
consensus.vDeployments[Consensus::DEPLOYMENT_CONSENSUSCLEANUP].period = 144;

consensus.nMinimumChainWork = uint256{};
consensus.defaultAssumeValid = uint256{};

Expand Down
6 changes: 3 additions & 3 deletions src/node/miner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ int64_t GetMinimumTime(const CBlockIndex* pindexPrev, const int64_t difficulty_a
int64_t min_time{pindexPrev->GetMedianTimePast() + 1};
// Height of block to be mined.
const int height{pindexPrev->nHeight + 1};
// Account for BIP94 timewarp rule on all networks. This makes future
// activation safer.
// Account for BIP94 timewarp rule on all networks. This makes BIP54
// activation safer since BIP94 sets a tighter bound.
if (height % difficulty_adjustment_interval == 0) {
min_time = std::max<int64_t>(min_time, pindexPrev->GetBlockTime() - MAX_TIMEWARP);
min_time = std::max<int64_t>(min_time, pindexPrev->GetBlockTime() - MAX_TIMEWARP_TESTNET4);
}
return min_time;
}
Expand Down
37 changes: 1 addition & 36 deletions src/policy/policy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <coins.h>
#include <consensus/amount.h>
#include <consensus/consensus.h>
#include <consensus/tx_verify.h>
#include <consensus/validation.h>
#include <policy/feerate.h>
#include <primitives/transaction.h>
Expand Down Expand Up @@ -164,35 +165,6 @@ bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& 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.
*
Expand All @@ -208,8 +180,6 @@ static bool CheckSigopsBIP54(const CTransaction& tx, const CCoinsViewCache& inpu
* 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.
*/
TxValidationState ValidateInputsStandardness(const CTransaction& tx, const CCoinsViewCache& mapInputs)
{
Expand All @@ -218,11 +188,6 @@ TxValidationState ValidateInputsStandardness(const CTransaction& tx, const CCoin
return state; // Coinbases don't use vin normally
}

if (!CheckSigopsBIP54(tx, mapInputs)) {
state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs", "non-witness sigops exceed bip54 limit");
return state;
}

for (unsigned int i = 0; i < tx.vin.size(); i++) {
const CTxOut& prev = mapInputs.AccessCoin(tx.vin[i].prevout).out;

Expand Down
5 changes: 2 additions & 3 deletions src/policy/policy.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,13 @@ static constexpr unsigned int MINIMUM_BLOCK_RESERVED_WEIGHT{2000};
static constexpr unsigned int DEFAULT_BLOCK_MIN_TX_FEE{1};
/** The maximum weight for transactions we're willing to relay/mine */
static constexpr int32_t MAX_STANDARD_TX_WEIGHT{400000};
/** The minimum non-witness size for transactions we're willing to relay/mine: one larger than 64 */
/** The minimum non-witness size for transactions we're willing to relay/mine: one larger than 64.
* BIP54 makes 64-byte transactions invalid, but smaller than that are still non-standard. */
static constexpr unsigned int MIN_STANDARD_TX_NONWITNESS_SIZE{65};
/** Maximum number of signature check operations in an IsStandard() P2SH script */
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{100};
/** Default for -bytespersigop */
Expand Down
1 change: 1 addition & 0 deletions src/rpc/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1508,6 +1508,7 @@ UniValue DeploymentInfo(const CBlockIndex* blockindex, const ChainstateManager&
SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_CSV);
SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_SEGWIT);
SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_TESTDUMMY);
SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_CONSENSUSCLEANUP);
return softforks;
}
} // anon namespace
Expand Down
3 changes: 3 additions & 0 deletions src/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ add_executable(test_bitcoin
bech32_tests.cpp
bip32_tests.cpp
bip324_tests.cpp
bip54_tests.cpp
blockchain_tests.cpp
blockencodings_tests.cpp
blockfilter_index_tests.cpp
Expand Down Expand Up @@ -144,6 +145,8 @@ include(TargetDataSources)
target_json_data_sources(test_bitcoin
data/base58_encode_decode.json
data/bip341_wallet_vectors.json
data/bip54_coinbases.json
data/bip54_timestamps.json
data/blockfilters.json
data/key_io_invalid.json
data/key_io_valid.json
Expand Down
Loading
Loading