Skip to content

fix(proofs): V1 layer proofs must be encoded in the family of their walk direction (#863) - #917

Merged
QuantumExplorer merged 2 commits into
developfrom
fix/863-proof-orientation
Sep 6, 2026
Merged

QuantumExplorer merged 2 commits into
developfrom
fix/863-proof-orientation

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Sep 6, 2026

Copy link
Copy Markdown
Member

Closes #863.

Verdict: real, reproduced at the public API

A V1 layer proof is a stream of ops in one of two families — upright (Push / Parent / Child, visiting keys ascending) or inverted (PushInverted / ParentInverted / ChildInverted, descending). execute checks per op that upright pushes ascend and inverted pushes descend, but it never ties the family to the left_to_right the verifier walks with, and it lets the two families mix in one stream.

Every bound-witness rule in execute_proof — "the previous push was key-bearing, so nothing lies between it and this key", "this is the first push, so it is the leftmost (rightmost) node", "the limit is met, so the abridged tail is fine" — assumes the visit order is the tree's in-order for an ascending walk and its exact reverse for a descending one. That only holds for a homogeneous stream in the walk's own family. Two ways to break it, both with an authentic root hash:

  • Opposite family. The honest ascending proof of {b, d} (root c abridged to its kv-hash) handed to a descending Key(c) query: b is met first, read as the rightmost node, Key(c) is consumed as "before this key", and c is verified absent although it is the root. Likewise the honest ascending limit-1 proof handed to a descending limit-1 range ("give me the latest entry") fills the page with the smallest key.
  • Mixed families. Push(b) · Push(d) · PushInverted(KVHash c) · ParentInverted · Parent rebuilds the honest tree byte-for-byte at the root, passes every per-op key check, and visits b, d, then the abridged root — so an ascending walk for Key(c) sees two adjacent key-bearing pushes and endorses the gap as an absence.

All of this goes through prove_query / verify_query with no hand-crafted bytes needed for the first case: it is literally an honest proof of a different query. On develop the new tests fail with got Ok([]) (false absence) and got Ok([[98]]) (page filled with b where the trusted read says d).

Fix (V1 only, no version gate)

Query::execute_proof now requires, for proof_version >= 1, that every op is in the family of the walk direction it was given. An op in the other family — or a mixed stream — is rejected before it reaches the bound-witness logic. V0 (proof_version == 0) is untouched: it is a locked wire format and its verifier is not changed.

No GroveVersion gate: honest V1 proofs are unaffected. Prover and verifier take each real level's direction from the same query_items_at_path, synthesized one-key levels keep reading the direction off the stream (#818, unchanged), and proof_stream_direction already refused mixed streams there. The check only removes forged shapes no prover emits, so nothing an honest GROVE_V3 node produces changes verdict.

Callers aligned:

  • merk_layer_root_hash (row-less root derivation for a subset query that stops at a tree element) reads the direction off the stream, since it cannot know the generating query's direction and reports no rows. It still refuses a mixed stream.
  • The axis-descent secondary walk and the aggregate carrier / single-key layer walks move from the lenient proof_version 0 to PROOF_VERSION_LATEST so a wrong-family secondary stream cannot fill a limited page from the wrong end. Their honest streams already satisfied strict mode.
  • The #[cfg(test)] indexed-axis oracles do the same.

Verified unaffected: the count-offset and aggregate verifiers walk the reconstructed tree structurally, not the visit order.

Tests

  • merk_integration_tests: the two opposite-family absences, the opposite-family limited page, the mixed-family absence (asserting the mixed stream really rebuilds the honest root), and honest homogeneous streams in both directions.
  • grovedb/src/tests/proof_orientation_tests.rs: the same forgeries end-to-end through prove_query / verify_query against trusted reads, plus honest ascending/descending reads (unlimited, limit 1, limit 2, single keys, and an honest absence) agreeing with trusted reads in both directions.
  • Existing merged_descending_subset_bound_tests cover the synthesized-path handling from fix: subset verification of a descending merged proof's branches (#815) #818 and keep passing.

One existing test was widened: combined_v1_envelope_non_tree_intermediate_rejected forges an Item into a tree slot of the single-key layer and asserted on the aggregate chain gate's message; with that walk now strict, the merk verifier refuses the same forgery one step earlier (KVValueHash node must not contain an item element). The assertion accepts either gate.

Local runs: cargo test -p grovedb-merk (731 + 9 pass), cargo test -p grovedb --features full,verify,estimated_costs,unsafe-dump-load,serde,zk_client (3034 pass, 2 ignored; --all-features needs the grovedbg download, which was unavailable here), cargo fmt. cargo clippy --all-targets -D warnings on the local stable 1.97 flags pre-existing lines in untouched files; the only lint in new code is fixed.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved proof verification to consistently use the latest proof protocol.
    • Rejects proofs with mismatched traversal directions or mixed operation orientations, including limited-range queries.
    • Prevents incorrectly oriented proofs from being accepted even when they produce a valid root hash.
  • Tests

    • Added comprehensive regression coverage for proof direction validation, including ascending, descending, limited, and nested proofs.
    • Confirmed that valid proofs continue to verify when query and proof directions match.

…lk direction (#863)

`execute` checks per op that upright pushes ascend and inverted pushes
descend, but it never ties the op family to the `left_to_right` the
verifier walks with, and it lets the two families mix in one stream.
Every bound-witness rule in `execute_proof` assumes the visit order is
the tree's in-order for an ascending walk and its exact reverse for a
descending one, which only holds for a homogeneous stream in the walk's
own family. So an honest ascending proof of `{b, d}` handed to a
descending `Key(c)` query verified `c` absent although it is the root,
an honest ascending limit-1 proof handed to a descending "latest entry"
page filled it with the smallest key, and a mixed stream that rebuilds
the honest tree while visiting an abridged root after both children
read an absence out of an authentic root hash.

`Query::execute_proof` now refuses, for V1 proofs, any op that is not in
the family of its walk direction. V0 is a locked wire format and is
untouched. No GroveVersion gate: prover and verifier take each real
level's direction from the same `query_items_at_path`, synthesized
one-key levels keep reading it off the stream (#818), and the check only
removes shapes no prover emits.

`merk_layer_root_hash` (row-less root derivation for a subset query that
stops at a tree element) reads the direction off the stream; the
axis-descent secondary walk, the aggregate carrier and single-key layer
walks, and the test-only indexed-axis oracles move from the lenient
proof_version 0 to strict so the same forgery cannot fill a limited
page from the wrong end. The count-offset and aggregate verifiers walk
the reconstructed tree structurally and were never affected.

Tests: the forgeries at the merk level and end-to-end through
prove_query / verify_query against trusted reads, plus honest
ascending/descending reads agreeing with trusted reads in both
directions. All fail on develop with a false absence or the wrong page.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: 2e30a1e1-a898-42e8-bc80-6ac988c01083

📥 Commits

Reviewing files that changed from the base of the PR and between 2f8ae6f and 739c520.

📒 Files selected for processing (9)
  • grovedb/src/operations/proof/aggregate_common.rs
  • grovedb/src/operations/proof/indexed_axis/verify.rs
  • grovedb/src/operations/proof/verify.rs
  • grovedb/src/tests/mod.rs
  • grovedb/src/tests/proof_orientation_tests.rs
  • grovedb/src/tests/provable_count_provable_sum_tree_tests.rs
  • merk/src/proofs/query/merk_integration_tests.rs
  • merk/src/proofs/query/mod.rs
  • merk/src/proofs/query/verify.rs

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The change enforces direction-consistent operation families for V1 Merk proofs. GroveDB verification uses the latest proof version and stream direction. New tests cover mismatched directions, mixed streams, limited queries, subset verification, and valid proofs.

Changes

Proof orientation enforcement

Layer / File(s) Summary
Merk orientation validation
merk/src/proofs/query/verify.rs, merk/src/proofs/query/mod.rs
V1 proof execution rejects operation families that do not match the query direction. The orientation classifier is re-exported and reused by stream-direction detection.
GroveDB verification integration
grovedb/src/operations/proof/aggregate_common.rs, grovedb/src/operations/proof/indexed_axis/verify.rs, grovedb/src/operations/proof/verify.rs
GroveDB proof paths use PROOF_VERSION_LATEST, derive stream direction where needed, and enforce direction matching for carrier and layer verification.
Orientation regression coverage
grovedb/src/tests/mod.rs, grovedb/src/tests/proof_orientation_tests.rs, grovedb/src/tests/provable_count_provable_sum_tree_tests.rs, merk/src/proofs/query/merk_integration_tests.rs
Tests cover valid directional proofs, mismatched directions, mixed operation families, limited queries, subset verification, authentic forged roots, and compatible rejection errors.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: ⚪ Minimal · up to 739c5

The orientation fix has no substantiated merge-blocking risk at the current head.

Sequence Diagram(s)

sequenceDiagram
  participant Query
  participant execute_proof
  participant ProofDecoder
  participant execute
  Query->>execute_proof: Execute proof with query direction
  execute_proof->>ProofDecoder: Decode proof operations
  execute_proof->>execute: Pass direction-validated operations
  execute->>Query: Return verified result
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.38% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 37 functions across 9 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: enforcing V1 proof operation-family orientation based on walk direction. It is concise and specific.
Linked Issues check ✅ Passed The changes satisfy issue #863. V1 verification now rejects opposite-family and mixed-family operation streams before direction-sensitive bound checks, while V0 behavior remains unchanged. The affecte…
Out of Scope Changes check ✅ Passed The changes remain within issue #863. Caller updates, the public helper export, regression tests, and affected proof-verification tests directly support the orientation fix and its validation. No unre…
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/863-proof-orientation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Sep 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.26%. Comparing base (9a21bbc) to head (739c520).
⚠️ Report is 1 commits behind head on develop.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #917      +/-   ##
===========================================
- Coverage    92.32%   92.26%   -0.06%     
===========================================
  Files          299      299              
  Lines        93205    93245      +40     
===========================================
- Hits         86052    86034      -18     
- Misses        7153     7211      +58     
Components Coverage Δ
grovedb-core 90.21% <100.00%> (-0.13%) ⬇️
merk 93.22% <100.00%> (+0.02%) ⬆️
storage 91.70% <ø> (ø)
commitment-tree 96.38% <ø> (ø)
mmr 95.11% <ø> (ø)
bulk-append-tree 92.78% <ø> (ø)
element 97.98% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…lower layer refused (#863)

Covers `merk_layer_root_hash`'s stream-direction read: a wider
descending proof re-verified by a narrower query that stops at the tree
element derives the inverted lower layer's root (a fixed ascending walk
would now reject it), and a mixed lower layer is refused by the
orientation read before any op executes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@QuantumExplorer
QuantumExplorer merged commit d94fade into develop Sep 6, 2026
11 checks passed
@QuantumExplorer
QuantumExplorer deleted the fix/863-proof-orientation branch September 6, 2026 21:22
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.

[audit][P15] Regular query proof orientation can produce an incorrect verified absence

1 participant