Prove Ethereum storage and account proofs with Flock, a post-quantum, hash-based SNARK that is fastest when a computation is dominated by batched calls to one standard hash.
Verifying an eth_getProof response is exactly that: a Merkle–Patricia-Trie (MPT)
path where every parent→child edge is keccak256(rlp(node)) == hash-stored-in-parent.
Both the Keccak hashing and the trie linking, routing, and value binding are
enforced in-circuit and bound to the public storage_root.
From a public statement, one Flock proof attests an MPT inclusion with nothing soundness-critical done natively:
- storage:
(storage_root, slot, value)⇒ the storage trie mapskeccak256(slot)torlp(value); - account:
(state_root, address, account)⇒ the state trie mapskeccak256(address)torlp([nonce, balance, storageRoot, codeHash]).
flock_storage (native-RLP) |
flock_rlp (in-circuit-RLP) |
|
|---|---|---|
| Keccak + node linking | in-circuit | in-circuit |
| RLP parse / nibble routing | native walk over public, hash-authenticated nodes | proven in-circuit (columnar scan + masked wiring) |
| Node bytes | public | private witness |
| Node byte-lengths | public | hidden (fixed buffer + variable-length Keccak) |
| Cost | cheapest | ~2× |
| Use | public Ethereum proofs | privacy-preserving coprocessor |
Both are sound: the Keccak chain + collision resistance force every node to equal the real trie node, so even the native routing runs against the real trie (the verifier re-derives it and trusts no prover-supplied offset).
cargo run --release --example end_to_end # proves one storage slot BOTH ways
cargo test --release # full suite, incl. the real mainnet fixture
python3 scripts/fetch_fixture.py # refresh the mainnet fixture (needs network)use flock_eth_proofs::flock_storage::{prove_storage, verify_storage};
// full_proof = the slot's eth_getProof nodes [root, …, leaf] as &[u8] slices
let proof = prove_storage(&storage_root, &slot, &value, &full_proof)?;
verify_storage(&proof, &storage_root, &slot, &value)?; // Err on a wrong statementFor private node bytes with hidden sizes, use
flock_rlp::{prove_storage_rlp, verify_storage_rlp}; for accounts,
flock_storage::{prove_account, verify_account}.
The end-to-end tests (tests/flock_inclusion.rs) prove the real mainnet WETH
storage slot (6 branch nodes) and account (8 nodes) through both paths, plus
synthetic extension-node and inlined-leaf paths and the soundness negatives.
Everything is bits and AND/XOR, and Flock's shape (C = I) means you can't
assert x == y; you must build one side to be the other. So:
- each node is hashed in-circuit, and a child's hash wires are plugged directly into the parent's on-path slot, so the link holds by construction;
- the leaf is rebuilt from the public
(slot, value)and plugged into its parent; - only the root hash needs a real equality to
storage_root, done via Flock's opening (with a "fold" that collapses the 256-bit check to one operation); - routing (which slot at each node) follows the nibbles of the public key.
The private path adds an in-circuit columnar RLP scanner (Keccak is inline, so bytes sit at fixed positions and the on-path item is picked by a per-position mask), plus variable-length Keccak so node sizes stay hidden.
See ARCHITECTURE.md for the full walkthrough and the design rationale.
- Inclusion only. Proving a slot is absent (exclusion) is handled by the
native
mpt::verify_proof, not yet by the circuits. flock_rlpstill leaks depth + node kinds (byte lengths are hidden); hiding depth needs fixed-max path padding.MAXNODE= 544 B is a fixed global (every branch pays 4 Keccak blocks); extension nodes are public by nature.