Skip to content

fix: verify merkle_root in BFT _validate_proposal against miners list#769

Merged
Scottcjn merged 1 commit intoScottcjn:mainfrom
wsimon1982:fix/bft-validate-merkle-root
Mar 9, 2026
Merged

fix: verify merkle_root in BFT _validate_proposal against miners list#769
Scottcjn merged 1 commit intoScottcjn:mainfrom
wsimon1982:fix/bft-validate-merkle-root

Conversation

@wsimon1982
Copy link
Copy Markdown
Contributor

Fixes #766

Problem

_validate_proposal in node/rustchain_bft_consensus.py checked that miner IDs in distribution existed in the miners list and that the total reward summed to 1.5 RTC — but it never re-computed the Merkle root from the submitted miners list and compared it to proposal["merkle_root"].

A Byzantine leader could recycle a valid merkle_root from a previous epoch while submitting a falsified miners list that still passes the miner-ID / total-reward checks. Honest nodes would send PREPARE messages for a proposal whose attestation data is inconsistent with the digest they are voting on.

Reproducer

bft = BFTConsensus("node-A", ":memory:", "secret")
miners_real = [{"miner_id": "miner-1", "weight": 1.0}]
miners_fake = [{"miner_id": "miner-1", "weight": 1.0}, {"miner_id": "miner-2", "weight": 0.0}]
real_merkle = bft._compute_merkle_root(miners_real)
fake_proposal = {
    "epoch": 1, "miners": miners_fake, "total_reward": 1.5,
    "distribution": {"miner-1": 1.5}, "proposer": "byzantine-leader",
    "merkle_root": real_merkle,  # stale / mismatched
}
assert bft._validate_proposal(fake_proposal)  # passes before fix — should fail

Solution

Recompute the Merkle root from the miners list and assert equality:

expected_merkle = self._compute_merkle_root(miners)
if proposal.get("merkle_root") != expected_merkle:
    logging.warning("Proposal merkle_root mismatch")
    return False

Fixes Scottcjn#766

_validate_proposal checked that miner IDs in 'distribution' existed in
the 'miners' list and that the total reward summed to 1.5 RTC, but it
never re-computed the Merkle root from the submitted miners list and
compared it to proposal['merkle_root'].

A Byzantine leader could therefore recycle a valid merkle_root from a
previous epoch while supplying a falsified miners list that still passes
the miner-ID / total-reward checks.  Honest nodes would send PREPARE
messages for a proposal whose attestation data is inconsistent with the
digest they are voting on.

Add a Merkle root recomputation and equality check before returning True
so that any mismatch between the claimed merkle_root and the actual
miners data causes _validate_proposal to return False.
@github-actions github-actions bot added BCOS-L1 Beacon Certified Open Source tier BCOS-L1 (required for non-doc PRs) node Node server related size/S PR: 11-50 lines labels Mar 9, 2026
Copy link
Copy Markdown
Owner

@Scottcjn Scottcjn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important consensus security fix. Without this, a Byzantine leader could recycle a valid merkle_root from a previous epoch while submitting a falsified miners list — honest nodes would still send PREPARE. The fix correctly recomputes _compute_merkle_root(miners) and validates before accepting.

Verdict: MERGE — 50 RTC bounty (consensus security hardening).

🤖 Reviewed with GPT-5.4 + Claude Opus dual-brain analysis

@Scottcjn Scottcjn merged commit 4bcd553 into Scottcjn:main Mar 9, 2026
3 checks passed
createkr pushed a commit to createkr/Rustchain that referenced this pull request Mar 22, 2026
…Scottcjn#769)

Fixes Scottcjn#766

_validate_proposal checked that miner IDs in 'distribution' existed in
the 'miners' list and that the total reward summed to 1.5 RTC, but it
never re-computed the Merkle root from the submitted miners list and
compared it to proposal['merkle_root'].

A Byzantine leader could therefore recycle a valid merkle_root from a
previous epoch while supplying a falsified miners list that still passes
the miner-ID / total-reward checks.  Honest nodes would send PREPARE
messages for a proposal whose attestation data is inconsistent with the
digest they are voting on.

Add a Merkle root recomputation and equality check before returning True
so that any mismatch between the claimed merkle_root and the actual
miners data causes _validate_proposal to return False.

Co-authored-by: wsimon1982 <wsimon1982@googlemail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

BCOS-L1 Beacon Certified Open Source tier BCOS-L1 (required for non-doc PRs) node Node server related size/S PR: 11-50 lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: BFT _validate_proposal does not verify merkle_root against miners list

2 participants