Disallow 64-byte transactions
BIP: 53 Layer: Consensus (soft fork) Title: Disallow 64-byte transactions
No reviewsSpecification
BIP: 53 Layer: Consensus (soft fork) Title: Disallow 64-byte transactions Authors: Chris StewartStatus: Draft Type: Specification Assigned: 2025-04-11 License: BSD-3-Clause
Abstract
This BIP describes the rationale for disallowing transactions that are serialized to 64 bytes without the transaction's witness. We describe the weaknesses to the Merkle tree included in Bitcoin block headers, and various exploits for those weaknesses.
Specification
This BIP disallows Bitcoin transactions that are serialized to 64 bytes in length without their witness.
Motivation
Bitcoin block headers include a commitment to the set of transactions in a given block, which is implemented by constructing a Merkle tree of transaction ids (double-SHA256 hash of a transaction) and including the root of the tree in the block header. This in turn allows for proving to a Bitcoin light client that a given transaction is in a given block by providing a path through the tree to the transaction. However, Bitcoin’s particular construction of the Merkle tree has several security weaknesses, including at least two forms of block malleability that have an impact on the consensus logic of Bitcoin Core, and an attack on light clients, where an invalid transaction could be ”proven” to appear in a block by doing substantially less work than a SHA256 hash collision would require. This has been mitigated by Bitcoin Core's relay policy and the RPC interface since 2018.
Block malleability
64-byte transactions introduce block malleability. Malicious peers can construct consensus valid and invalid 64-byte transactions that have the same serialization as the concatenation of 2 hashes in the Merkle tree.
Assume we have a valid Bitcoin block with 2 transactions in it with Txid0 and Txid1. The Merkle root for this block is H(Txid0||Txid1). A malicious user could find a 64-byte transaction Tm that serializes to Txid0||Txid1. Next that user relays the block containing the malicious Tm rather than the valid Bitcoin transactions that correspond to Txid0 and Txid1.
Block malleability with consensus INVALID transactions
The peer receiving the malicious block marks the block as invalid, as Tm is not a valid transaction according to network consensus rules. Other peers on the network receive the valid block containing T0 and T1 and add the block to their blockchain. Peers that receive the invalid block before the valid block will never come to consensus with their peers due to the malicious user finding a collision within the block's Merkle root. Finding this collision is approximately 22 bits worth of work.
This attack vector was fixed in Bitcoin Core 0.6.2, re-introduced in 0.13.x and patched again in 0.14.
Block malleability with consensus VALID transactions
Producing a valid Bitcoin transaction Tm that adheres to network consensus rules requires 224 bits of work. This is computationally and financially expensive but theoretically possible. This can lead to a persistent chain split on the network.
Attack on SPV clients
BIP37provides a partial Merkle tree format that allows you to verify that your Bitcoin transaction is included in a Merkle root embedded in a Bitcoin block header. Notably this format does not commit to the height of the Merkle tree.
Suppose a (valid) 64-byte transaction T is included in a block with the property that the second 32 bytes (which are less constrained than the first 32 bytes) are constructed so that they collide with the hash of some other fake, invalid transaction F. The attacker can fool the SPV client into believing that F was included in a Bitcoin block rather than T with 81 bits of work. Disallowing 64-byte transactions reduces implementation complexity for SPV wallets.
Rationale
SPV clients
Attacks on SPV clients could be mitigated by knowing the depth of the Merkle tree. Requiring SPV clients to request both the coinbase and payment transaction could mitigate this attack. To produce a valid coinbase transaction at the same depth that our fake transaction F occurs at would require 224 bits of work. As mentioned above, this is computationally and financially expensive, but theoretically possible. This design would increase the size of SPV proofs by 70%.
Backward compatibility
There have been 5 64-byte transactions that have occurred in the Bitcoin blockchain as of this writing with the last transaction 7f2efc6546011ad3227b2da678be0d30c7f4b08e2ce57b5edadd437f9e27a612 occurring at block height 419,606.
Pre-segwit 64-byte transactions
Pre-segwit 64-byte transactions cannot spend a UTXO protected by a digital signature. The largest scriptSig a pre-segwit 64-byte transaction can have is 4 bytes. non standard hashlock UTXOs in the Bitcoin blockchain. None of them have a 0-3 byte pre-image. This means they cannot be spent by a 64-byte transaction.
Pre-segwit 64-byte transactions that spend a non-standard UTXO that are inherently malleable. Policy rules such as CLEANSTACK, MINIMALDATA, PUSHONLY are not consensus rules. If a user has a way to confirm an already non-standard 64-byte transaction - they can malleate the transaction by violating policy rules to change the size of the transaction to a size other than 64 bytes.
Segwit 64-byte transactions
This BIP disallows single-input single-output segwit transactions that pay to a 2-byte witness program. The only known use case for this type of transaction is ephemeral anchor outputs.
Reference implementation
/**
* We want to enforce certain rules (specifically the 64-byte transaction check)
* before we call CheckBlock to check the Merkle root. This allows us to enforce
* malleability checks which may interact with other CheckBlock checks.
* This is currently called both in AcceptBlock prior to writing the block to
* disk and in ConnectBlock.
* Note that as this function is called before merkle-tree checks, it must never return a
* non-malleable error condition.
*/
static bool ContextualBlockPreCheck(const CBlock& block, BlockValidationState& state, const ChainstateManager& chainman, const CBlockIndex* pindexPrev)
{
if (DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_64BYTETX)) {
for (const auto& tx : block.vtx) {
if (::GetSerializeSize(TX_NO_WITNESS(tx)) == 64) {
return state.Invalid(BlockValidationResult::BLOCK_MUTATED, "64-byte-transaction", strprintf("size of tx %s without witness is 64 bytes", tx->GetHash().ToString()));
}
}
} return true;
}
The sample implementation is currently open here:
https://github.com/bitcoin-inquisition/bitcoin/pull/24/files
Copyright
This BIP is licensed under the BSD-3-Clause License.Acknowledgements
Suhas Daftuar, AJ Towns, Sergio Demian Lerner, Greg Maxwell, Matt Corallo, Antoine Poinsot, Dave Harding and Eric Voskuil
Discussion (0 threads)
Loading discussions...