Skip to content

feat(network): verify Zakura header roots before storing#485

Closed
p0mvn wants to merge 1 commit into
split/pr458-state-confirmed-rootsfrom
split/pr458-header-root-validation
Closed

feat(network): verify Zakura header roots before storing#485
p0mvn wants to merge 1 commit into
split/pr458-state-confirmed-rootsfrom
split/pr458-header-root-validation

Conversation

@p0mvn

@p0mvn p0mvn commented Jul 6, 2026

Copy link
Copy Markdown

Summary

  • Wire header-sync root verification through the reactor and startup driver.
  • Persist only header-authenticated confirmed root prefixes and rebuild the frontier tree across restarts.
  • Update Zakura VCT design docs and changelog for the new trust boundary.

Test plan

  • cargo fmt --all -- --check
  • cargo test -p zebra-chain commitment_aux_verify
  • cargo test -p zebra-state best_header_history_tree
  • cargo test -p zebra-network deliver_rootless_solicited_headers_does_not_disconnect_the_peer

Stack

Depends on the state confirmed-roots PR.

AI disclosure

Used Cursor to split PR #458 into stacked branches and prepare this PR.

Wire header-sync root verification through the reactor and startup driver so only header-authenticated confirmed root prefixes are persisted and rebuilt across restarts.
@v12-auditor

v12-auditor Bot commented Jul 6, 2026

Copy link
Copy Markdown

Note

Complete: Audit complete. V12 found two issues worth reviewing.

Open the full results here.

FindingSeverityDetails
F-96419 🟠 High
Rootless responses livelock retries

A remote Zakura header-sync peer can advertise an eligible tip, receive a root-carrying GetHeaders, and reply with a non-empty Headers frame that omits tree-aux roots. The decoder accepts that shape as a correlated response and leaves tree_aux_roots empty for the reactor. The reactor then treats the response as non-misbehavior, records only advisory state, clears the scheduler assignment, immediately retries the same range, and reschedules before any commit or peer scoring. Because RangeScheduler::retry pushes the range to the front and clear_assignment removes the only same-peer exclusion, the same attacker-controlled peer can be selected again as soon as its outstanding response was popped. Repeating this pins the victim below the root/VCT handoff without causing a disconnect or invalid-range penalty.

F-96420 🟠 High
Pending tree cross-peer poisoning

The reactor can install the wrong in-memory history tree when multiple peers have pending commits for the same delivered height interval. Pending commits are keyed with the peer, but a successful HeaderRangeCommitted event sent back from the driver does not include the peer, committed headers, or a unique pending-commit key. On success, the reactor calls pending_header_history_tree(start_height, tip_height), which iterates all pending values and matches only the delivered start and end heights, then installs the first matching verified_roots.tree(). With fanout, an attacker can keep a different aux-verified fork pending for the same range while an honest peer's range commits, allowing unordered map iteration to select the attacker's tree while publishing the honest committed tip hash. The durable state write uses the committed action's roots, but the reactor's in-memory best_header_history_tree becomes inconsistent with its published best_header_hash and best_header_parent_hash.

Analyzed 14 files, diff d3ddb6f...ee09881.

@p0mvn p0mvn closed this Jul 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant