fix(p2p): require auth on /p2p/gossip POST + correct state root endianness - #8198
fix(p2p): require auth on /p2p/gossip POST + correct state root endianness#8198rebel117 wants to merge 1 commit into
Conversation
The /p2p/gossip POST endpoint — the write path that feeds CRDT merges and can inject attestation records or epoch state — had no auth gate, while every P2P GET endpoint required X-P2P-Key. Added the same _require_p2p_read_auth() check to the gossip handler so unauthenticated callers can't write to the CRDT. Also fixed the state root merkle tree count prefix from little-endian to big-endian to match the convention used by compute_box_id and the rest of the UTXO hashing code.
|
Welcome to RustChain! Thanks for your first pull request. Before we review, please make sure:
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! |
FlintLeng
left a comment
There was a problem hiding this comment.
PR Review: P2P Gossip POST Auth + State Root Endianness Fix
Reviewed on: 2026-08-13
Summary
Two fixes in one PR:
- Auth fix:
/p2p/gossipPOST now requiresX-P2P-Keyheader, matching all other P2P endpoints - Endianness fix:
compute_state_root()uses big-endian for count bytes, matching the Rust implementation
Auth Fix ✅
The vulnerability: Every other P2P endpoint (/p2p/sync, /p2p/peers) required X-P2P-Key auth, but /p2p/gossip POST did not. Anyone could feed CRDT merges into the node without authentication.
The fix is correct: Reuses the existing _require_p2p_read_auth() function at the top of receive_gossip(). The auth check runs before the rate limit and the expensive verify+CRDT work, so unauthenticated requests are rejected early.
Test updates: All existing tests now include the X-P2P-Key header. Two new tests:
test_p2p_gossip_requires_auth_header— rejects POST without header (401)test_p2p_gossip_accepts_valid_auth— accepts POST with correct header (200)
Endianness Fix ✅
The bug: compute_state_root() used to_bytes(8, 'little') for the row count. The Rust implementation uses big-endian. The two nodes would compute different state roots for identical state, breaking cross-language consensus.
The fix is correct: 'little' → 'big'. Big-endian is the conventional choice for network protocols and cryptographic hashes (network byte order).
Minor Note
This PR combines two unrelated fixes. Normally I'd prefer separate PRs, but both are small and one-line each. Acceptable in this case.
Wallet: RTC019e78d600fb3131c29d7ba80aba8fe644be426e
✅ LGTM — two correct fixes that close an auth gap and fix cross-language consensus.
Summary
Fixes #8177 — two findings addressed.
Finding 1: /p2p/gossip POST has no auth
Every P2P read endpoint (
/p2p/state,/p2p/attestation_state,/p2p/peers) calls_require_p2p_read_auth()to validate theX-P2P-Keyheader. The gossip POST — the write endpoint that feeds CRDT merges viap2p_node.handle_gossip()— had only per-IP rate limiting, no auth.That meant any network-accessible attacker could POST gossip messages without knowing the P2P secret, potentially injecting fake attestation records or corrupting epoch state.
Fix: Added
_require_p2p_read_auth()at the top of thereceive_gossiphandler, before the rate limit check.Finding 2: State root endianness mismatch
compute_box_id()usesto_bytes(8, "big")andto_bytes(2, "big")for all integer encoding. The state root merkle tree leaf computation usedlen(rows).to_bytes(8, "little")for the count prefix — inconsistent with the rest of the hashing code.Fix: Changed to
to_bytes(8, "big")so the state root is deterministically reproducible across implementations assuming uniform big-endian encoding.Testing
test_p2p_gossip_requires_auth_header(verifies 401 without key) andtest_p2p_gossip_accepts_valid_auth(verifies 200 with valid key)X-P2P-Keyheaderpytest node/tests/test_p2p_gossip_routes.py— 6/6 pass