This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Verity is a formally verified Ethereum consensus client to be written in Lean 4, by Nyx Foundation. The project is pre-implementation: there is no Lean or Rust source yet. The only working component today is the mdBook documentation site under docs/. Most pages in docs/src/ are intentional placeholders that list "Planned topics" — treat them as a content roadmap, not finished docs.
The intended architecture (per the docs and README references) targets the Lean Consensus specification and the lean roadmap, including post-quantum signatures, with the verified Lean core compiled via Lean's C backend into a static library and consumed by the Rust runtime over a C ABI (no Aeneas). The Lean side already exists: NyxFoundation/formal-leanSpec holds the Lean 4 model and its proposition catalog, and Verity Consensus is defined as that model's compiled, exported subset (see docs/src/concepts/formal-verification.md). The Rust runtime is not implemented yet — verify against actual code before treating any of it as present.
This repository is a monorepo, not a docs-only repo: the Rust implementation will live here alongside docs/.
Owner-ratified ground rules for the first Rust code. Do not re-open these without an explicit owner conversation.
- Strategy: Rust-first. Everything runs in Rust initially; Lean-compiled logic is adopted per-logic later (stable / proved / measured-within-budget first; STF and fork choice last, as they track a volatile upstream spec).
- Goal: a full node from the start (networking included), not a fixtures-passing library. leanSpec fixture conformance is still the CI backbone.
- Crates: start with a single
verity-consensuscrate (no chain/validator split). Proposer selection lives chain-side as a pure function next to STF/fork choice. Shared types stay a module until a second crate exists. - Dependencies:
- XMSS:
leansigas a git dependency on branchdevnet4, with the revision pinned inCargo.lock(see below for why the manifest cannot hold it), for per-validator sign / verify. Aggregation and aggregate-proof verification come fromleanVM(renamed upstream fromleanMultisigon 2026-08-14; the old URL redirects) — both sit behindverity-crypto's one capability contract, so a doc naming only one of them is incomplete, not contradictory. - SSZ:
libssz0.2.2 (lambdaclass). NyxFoundation/leanSSZ (proven Lean SSZ, C ABI PoC complete) is deliberately NOT adopted initially — it is the future Lean-adoption candidate for SSZ. - Networking: upstream
rust-libp2p(QUIC, gossipsub, reqresp). Fork only if a concrete need materializes, as lambdaclass did for ethlambda. - Storage: RocksDB behind a backend trait with an in-memory sibling implementation (ethlambda's
StorageBackendsplit). Decided 2026-08-10 — see Storage below. - leanSig pins Plonky3 to nothing —
Cargo.lockis load-bearing here. leanSig's manifest declaresp3-* = { git = ".../Plonky3.git" }with neither rev nor branch, so a fresh resolve floats to Plonky3's default-branch HEAD. That HEAD (v0.6.0) moved tonum-bigint0.5 while leanSig's code is written against 0.4, so leanSig stops compiling with no change on our side. The pin that holds it is3f67d136c71bec40f180c85d0bb2b654acddef22(v0.5.1) — the rev ethlambda locks against the same leanSig rev.- The pin was re-established when
verity-cryptolanded (2026-08-27), withcargo update p3-field --precise 3f67d136c71bec40f180c85d0bb2b654acddef22. It had been pruned from the lockfile when the day-one canary crate was retired and nothing depended on leanSig any more. [patch]does not work for this: cargo rejects a patch resolving to the same source, and a patch pointing at a textually different URL is silently dropped as "not used in the crate graph" because the pinned v0.5.1 cannot satisfy the v0.6.0 intra-workspace requirements. Usecargo update p3-field --precise <rev>, which moves every crate from that git source at once.- Consequence: never run a bare
cargo update. It refloats Plonky3 to HEAD and breaks the build. The failure is loud (anum_biginttype mismatch inside leanSig), not silent — if you see it, this is why.
- The pin was re-established when
- leanSig is referenced by branch, and that is forced, not a preference. leanVM depends on leanSig by branch (
devnet4). Cargo keys a git package by URL and reference, so?branch=devnet4and?rev=<sha>become two distinct packages in the graph even when both resolve to the same commit — and every leanSig type crossing the leanSig/leanVM boundary then fails to match.[patch]cannot merge them either. SoCargo.tomlmatches leanVM's reference exactly andCargo.lockcarries the revision, exactly as it already does for Plonky3.- Bump leanSig and leanVM in matched pairs. The leanVM rev decides which leanSig commit enters the graph.
e2592df4e30fdddbbf8ae26a333116c68cec7026is the leanVM commit whose own lockfile resolves leanSig to15cbdd43ec8525aa43fea2f42cafc5ed366084ae. Moving one without re-checking the other splits leanSig in two again. - CI builds
--locked. With two revisions living only inCargo.lock, a build allowed to re-resolve is a build that silently follows whatever those branches moved to.--lockedmakes any drift show up as a lockfile diff in review instead.
- Bump leanSig and leanVM in matched pairs. The leanVM rev decides which leanSig commit enters the graph.
leanSig,leanVM,Plonky3, andTomWambsgans/leanSig(a fast-keygen fork of leanSig that leanVM depends on, unmerged upstream) are unpublished git sources, sodeny.tomllists them under[sources] allow-git. That entry allows the host; the revisions are pinned inCargo.lock. leanVM's crates are workspace members with nolicensefield and no per-crate LICENSE file, sodeny.tomlcarries a[[licenses.clarify]]entry for each stating the repository's Apache-2.0.
- XMSS:
- Storage (2026-08-10): aggregate proofs are persisted and pruned on a 21,600-slot (~1 day) window, in their own table keyed
slot ‖ rootso pruning is a slot-ordered range delete. Two facts fix this:- Measured from leanSpec's
fixtures-prod-scheme.tar.gz: an aggregate block proof is 155–236 KB (median 190 KB) against ~100–800 B for everything else. At 4 s slots that is ~4.1 GB/day of proofs vs ~5 MB/day of blocks and states — the large-value, bulk-delete workload is what selects an LSM engine. - leanSpec sets
MIN_SLOTS_FOR_BLOCK_REQUESTS = 3600(4 hours) and aBlocksByRangeresponder MUST serve that window. One day is an operational choice 6× above that floor: it is how far a peer can fall behind and still catch up over P2P instead of needing a checkpoint. leanSpec's reference node meets the requirement in memory and persists no proofs at all; we persist so the guarantee survives a restart. - Do not re-derive these numbers from the devnet-scheme fixtures in the leanSpec source tree — those are much smaller (XMSS signature 424 B there vs 2,536 B in the production scheme) and will mislead.
- Measured from leanSpec's
- Differential testing: consume leanSpec's release asset
fixtures-prod-scheme.tar.gzin CI, sha256-pinned incrates/verity-types/fixtures.sha256and bumped manually. The Rustfixturesjob is separate from the fast gate. Locally, setVERITY_FIXTURESto an extracted tree to run the SSZ vectors; without it,cargo testskips them. leansig-test-keys'prod_scheme.tar.gzsupplies pre-generated production-scheme keys so signing tests do not pay key generation; it is sha256-pinned incrates/verity-crypto/test-keys.sha256, consumed by the same fixtures job, and reached throughVERITY_TEST_KEYS. - Toolchain: Rust edition 2024, resolver 3, latest stable pinned via
rust-toolchain.toml(external floor: leanSig requires ≥1.87; no nightly needed). - License: MIT (Nyx Foundation copyright).
- Devnet: always track the latest devnet generation; never hardcode a generation in docs or code comments.
- Verification harness: NOT wired in from day one (no bolero/proptest in the initial scaffold or CI); introduced later per
docs/design/model-check.md's tool-to-zone mapping. - Known caveat: leanSig internally depends on
ethereum_ssz, so two SSZ implementations coexist transitively — harmless, but mind type conversions at the signature boundary.
The docs are an mdBook. All commands run from the docs/ directory.
# CI pins these exact versions — match them locally to avoid drift.
# mdbook-mermaid 0.16.2 is the newest release compatible with mdBook 0.4.40
# (0.17.0 targets the mdBook 0.5 JSON protocol and fails to parse).
cargo install mdbook --version 0.4.40
cargo install --locked mdbook-mermaid --version 0.16.2
cd docs # preprocessors use paths relative to docs/ — always build from here
mdbook build # outputs to docs/book/ (gitignored)
mdbook serve # live-reload preview at http://localhost:3000docs/book.toml— mdBook config (title, themenavy, GitHub edit links pointing atNyxFoundation/verity).docs/src/SUMMARY.md— the table of contents. Every page must be registered here or mdBook will not render it.docs/preprocessors/strip-frontmatter.py— mdBook preprocessor (needspython3on PATH) that strips the required YAML frontmatter before rendering, so it never shows on the published site.docs/mermaid.min.js,docs/mermaid-init.js— vendored mdbook-mermaid assets; regenerate withmdbook-mermaid install .when bumping mdbook-mermaid.docs/wrangler.toml— serves the builtbook/as Cloudflare Workers static assets (verity-docs).
Per global rules, every .md under docs/ requires YAML frontmatter (title, last_updated, tags) — except docs/generated/ and docs/vendor/. The existing docs/src/ pages predate this rule and lack it; add frontmatter when you next edit a page, and include it in any new page from creation.
develop— DEV. Default branch. Feature PRs land here.main— PRD. Receivesdevelopvia release; docs.verityclient.com deploys from here only.
Checks (Rust, Quality, Secret Scan, Docs build) run on pull requests and on pushes to both develop and main.
.github/workflows/docs.yml runs only when docs/** or the workflow file changes:
- build (PRs + pushes to
developormain):mdbook buildwith mdBook0.4.40, uploads the site as an artifact. - deploy (push to
main/ PRD only): downloads the artifact and deploys to Cloudflare Workers (docs.verityclient.com) viawrangler deploy. RequiresCLOUDFLARE_API_TOKENandCLOUDFLARE_ACCOUNT_IDsecrets.
Because the workflow is path-filtered, changes outside docs/ do not trigger it.