What happened
On 20 August 2026 Bitcoin Core merged
pull request 35161. It changes no
behaviour. It adds a documentation comment to ComputeMerkleRoot, explains inside the loop
why one check runs at every level of the tree, and adds a test called
merkle_test_mutated_return_value. The
header now states
the contract: "Compute a Merkle root from the provided leaf hashes. If non-null, *mutated
is set to true if two identical hashes are paired at any tree level before the odd-count
hash duplication step, and false otherwise." The behaviour it describes dates from the fix
for CVE-2012-2459. Until this month it lived in a warning comment and in the memory of the
people who had reviewed the code.
What it changes
A block header commits to its transactions through a single 32-byte merkle root: hash the transaction ids in pairs, hash those results in pairs, repeat until one value is left. Bitcoin's version has a quirk the source file has warned about for years. When a level holds an odd number of hashes, the last one is duplicated so the count comes out even.
That quirk lets two different transaction lists produce the same root. The comment in
src/consensus/merkle.cpp
gives the example: the lists [1,2,3,4,5,6] and [1,2,3,4,5,6,5,6] build to the same root
"(because the hash of both of (F) and (F,F) is C)". So anyone can take a valid block, append
copies of its last two transactions, and hand the result to a node. Same merkle root, same
header, same proof of work. The copy is plainly invalid, since it
spends the same inputs twice. The damage is in what happens next: "If the receiving node
proceeds to mark that block as permanently invalid however, it will fail to accept further
unmodified (and thus potentially valid) versions of the same block." One forged copy would
lock a node out of the real block, which carries the same hash.
Core's defence is to notice the duplication and treat it "identically to the block having an
invalid merkle root". The rejection carries its own code, BLOCK_MUTATED, described in
consensus/validation.h
as "the block's data didn't match the data committed to by the PoW".
What the pull request pins down is the part a refactor could break without anyone noticing. The two identical hashes need not be leaves. In the example above they meet one level up, so the loop now says to "Check every level because equal pairs can appear above the leaves, as in the [1,2,3,4,5,6,5,6] construction described above." The other half of the contract is that the root returned is computed from the whole input whether or not a duplicate was found, so a caller cannot read a raised flag as permission to ignore the value it came with. The new test holds both in place.
What it does not change
No consensus rule moved. No node validates anything differently, and nothing needs upgrading. The vulnerability was fixed in 2012. What happened in August 2026 is that its defence acquired a description and a test aimed straight at it.
Writing a rule down does not make the code safe to change. It makes a wrong change fail out loud. This work exists because the question came up during earlier refactoring and was settled in review rather than in the file, which is exactly the state that produces a regression when a different person reads the same loop two years later and sees a redundant comparison worth deleting. The comment now concedes that continuing after the first duplicate is "redundant", and says why it stays anyway: mutated blocks should not propagate, and the comparison count is the same either way.
The quirk itself is untouched. Odd levels still duplicate the last hash, which is why the file opens by addressing someone who is not a Bitcoin developer at all: "WARNING! If you're reading this because you're learning about crypto and/or designing a new system that will use merkle trees, keep in mind that the following merkle tree algorithm has a serious flaw related to duplicate txids".
Context
CVE-2012-2459 belongs to a small set of Bitcoin design decisions that are agreed to be mistakes and cannot be taken back, because the rule that produces them is the rule every node already follows. What can be done is contain them, and then keep them contained. The containment has held for fourteen years. This change is about the second part, and the pull request is open about what prompted it: two earlier rounds of work on the same function, where the behaviour had to be re-established from the discussion each time.
