Skip to content

[UTXO-BUG] MED-2: Merkle tree second-preimage vulnerability in compute_state_root()#2071

Merged
Scottcjn merged 1 commit intoScottcjn:mainfrom
ArokyaMatthew:utxo-bug/med2-merkle-second-preimage
Apr 5, 2026
Merged

[UTXO-BUG] MED-2: Merkle tree second-preimage vulnerability in compute_state_root()#2071
Scottcjn merged 1 commit intoScottcjn:mainfrom
ArokyaMatthew:utxo-bug/med2-merkle-second-preimage

Conversation

@ArokyaMatthew
Copy link
Copy Markdown
Contributor

Vulnerability Class

Medium — Merkle state root manipulation (50 RTC bounty)

The Bug

The Merkle tree duplicates the last hash when the element count is odd (hashes.append(hashes[-1])). This is a well-known second-preimage weakness:

  • UTXO set [A, B, C] pads to [H(A), H(B), H(C), H(C)]
  • A different UTXO set [A, B, C, C] naturally produces [H(A), H(B), H(C), H(C)]
  • Both produce the same Merkle root despite different UTXO sets

Two nodes with divergent UTXO sets can agree on state root, undermining consensus verification.

Fix

  1. Domain-separated padding: SHA256(0x01 || last_hash) instead of duplicating, making padding distinguishable from a real leaf
  2. Count-binding: Mix the UTXO count into each leaf hash so trees of different cardinality always produce different roots

Test Added

  • test_state_root_odd_count_unique — verifies roots differ across 3→4→5 box transitions

All 35 tests pass.

Files Changed

  • node/utxo_db.pycompute_state_root() rewritten with safe padding
  • node/test_utxo_db.py — 1 test added

Ref: Bounty #2819

My WALLET IS aroky-x86-miner

…e_state_root()

The Merkle tree duplicates the last hash when the element count is odd
(hashes.append(hashes[-1])). This is a well-known second-preimage
weakness: UTXO sets [A,B,C] and [A,B,C,C] produce identical roots,
meaning two nodes with different UTXO sets can agree on the state root.

Fix:
1. Use domain-separated padding: SHA256(0x01 || last_hash) instead of
   duplicating the last element, making padding distinguishable from a
   real leaf.
2. Mix the UTXO count into each leaf hash so tree is bound to a
   specific cardinality — sets of different sizes always differ.

Test added:
- test_state_root_odd_count_unique

All 35 tests pass.

Bounty: #2819 (Medium, 50 RTC)
@github-actions github-actions bot added BCOS-L1 Beacon Certified Open Source tier BCOS-L1 (required for non-doc PRs) node Node server related labels Apr 5, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 5, 2026

Welcome to RustChain! Thanks for your first pull request.

Before we review, please make sure:

  • Your PR has a BCOS-L1 or BCOS-L2 label
  • New code files include an SPDX license header
  • You've tested your changes against the live node

Bounty tiers: Micro (1-10 RTC) | Standard (20-50) | Major (75-100) | Critical (100-150)

A maintainer will review your PR soon. Thanks for contributing!

@zhuzhushiwojia
Copy link
Copy Markdown

Great security audit work! These fixes are critical for the protocol.

@ArokyaMatthew
Copy link
Copy Markdown
Contributor Author

@Scottcjn please review

@Scottcjn Scottcjn merged commit dcdc03e into Scottcjn:main Apr 5, 2026
3 checks passed
@Scottcjn
Copy link
Copy Markdown
Owner

Scottcjn commented Apr 5, 2026

Merged. Solid cryptographic fix — the naive Merkle tree odd-count duplication is a well-known second-preimage weakness. Domain-separated sentinel + count-binding in leaf hashes is the correct mitigation.

50 RTC paid.

Wallet: ArokyaMatthew | Reason: bounty #2819 MED-2 Merkle second-preimage fix

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.

3 participants