This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
A post-quantum VRF implementation targeting Ethereum's RANDAO mechanism, intended as an EIP. The construction is a PRF-commitment VRF (y = Poseidon1(vrf_sk || encode(slot, randao_mix))) built on top of leanMultisig, a minimal zkVM that aggregates hash-based (leanXMSS) validator signatures. This repo is a fork of leanMultisig; the VRF crate lives at crates/vrf/.
Reference documents: project-files/project-charter.md, project-files/technical-background.md, claude-code-impl-prompt.md.
All Rust commands run from the repo root (this directory).
# Build everything (requires target-cpu=native, set in .cargo/config.toml)
cargo build --release
# Build the VRF crate only
cargo build --release -p vrf
# Run all VRF tests
cargo test -p vrf
# Run a single test by name
cargo test -p vrf -- test_prove_verify_roundtrip
# Run clippy (workspace lint rules include -D warnings)
cargo clippy -p vrf -- -D warnings
# Run benchmarks (must be --release)
cargo bench --release -p vrf 2>&1 | tee vrf/BENCHMARK_RESULTS.md
# Run the main XMSS aggregation benchmark (reference, not VRF)
cargo run --release -- xmss --n-signatures 1550 --log-inv-rate 1
# Run zkDSL programs as plain Python (for local testing without proof)
export PYTHONPATH=$(pwd)/crates/lean_compiler
python vrf/zkdsl/vrf.py.cargo/config.toml sets rustflags = ["-C", "target-cpu=native"], which is required for the SIMD-accelerated Poseidon and WHIR paths.
leanMultisig proves zkVM execution using:
- WHIR (multilinear polynomial commitments, ePrint 2024/1586) as the PCS
- SuperSpartan with AIR-specific optimisations for the constraint system
- Logup with buses for cross-table lookups (modelled after Cairo/OpenVM)
The security level is ≈124 bits (Johnson bound + degree-5 KoalaBear extension field). 128-bit security requires larger digests and is a TODO upstream.
- Field:
KoalaBear = MontyField31<KoalaBearParameters>, primep = 2^31 - 2^24 + 1 = 0x7f000001 - Poseidon:
Poseidon1KoalaBear16— width 16, Rf=8 full rounds, Rp=20 partial rounds, S-box degree 3 (not 5 or 7). Partial rounds apply the S-box tostate[0]only. - Key utilities in
crates/utils/src/poseidon.rs:poseidon16_compress([F; 16]) -> [F; 8]— Davies-Meyer (permute+add), returns first 8 elementsposeidon16_permute([F; 16]) -> [F; 16]poseidon16_compress_pair(&[F;8], &[F;8]) -> [F;8]
| Crate | Role |
|---|---|
crates/backend/koala-bear |
KoalaBear field + Poseidon1 permutation |
crates/backend/air |
Air + AirBuilder traits |
crates/backend/fiat-shamir |
Proof<F>, ProverState, VerifierState |
crates/backend/sumcheck |
Sumcheck protocol |
crates/whir |
WHIR polynomial commitment |
crates/lean_vm |
zkVM ISA, tables (Poseidon16Precompile, ExecutionTable), types (F, EF) |
crates/lean_compiler |
zkDSL compiler: .py source → Bytecode |
crates/lean_prover |
prove_execution / verify_execution, WHIR config |
crates/sub_protocols |
AirSumcheckSession, stacked PCS |
crates/rec_aggregation |
XMSS aggregation programs (zkDSL + recursion) |
crates/xmss |
XMSS signer/verifier (non-circuit) |
crates/vrf/ |
VRF crate (this project) |
Programs are written in the zkDSL (see crates/lean_compiler/zkDSL.md), compiled to Bytecode via lean_compiler::compile_program, then proved via lean_prover::prove_execution(bytecode, public_input, &witness, &whir_config, false). The witness supplies private data through ExecutionWitness { hints: HashMap<String, Vec<Vec<F>>> }. Verification uses lean_prover::verify_execution(bytecode, public_input, proof) — the verifier only needs the bytecode, public input, and proof (not the witness).
The existing XMSS program lives in crates/rec_aggregation/zkdsl_implem/. The VRF program lives in crates/vrf/zkdsl/vrf.py.
| Item | Value |
|---|---|
| KoalaBear prime | p = 2^31 - 2^24 + 1 = 2,130,706,433 |
| Poseidon S-box | degree 3 (not 5 or 7) |
| Poseidon width | 16 |
DOMAIN_VRF_INPUT |
p-1 = 2,130,706,432 |
DOMAIN_VRF_PK |
p-2 = 2,130,706,431 |
DOMAIN_VRF_DERIVE |
p-3 = 2,130,706,430 |
Domain tags are >= p-3 so they can never appear in 3-byte-packed field elements (max 2^24 - 1), and don't collide with any leanSig tweak separators (0x00, 0x01, 0x02) or leanMultisig SNARK domain sep.
vrf_sk = poseidon16_compress([seed_fe(11), DOMAIN_VRF_DERIVE, 0×4])[0]
pk_vrf = poseidon16_compress([vrf_sk, DOMAIN_VRF_PK, 0×14])[0]
y = poseidon16_compress([vrf_sk, encode_vrf_input(slot, mix)])[0]
encode_vrf_input returns 15 field elements (3-bytes-per-FE, little-endian): [DOMAIN_VRF_INPUT, slot_e0, slot_e1, slot_e2, mix_e0, ..., mix_e10]. Combined with vrf_sk at position 0, the Poseidon input is always exactly 16 elements.
The VRF proof is a zkVM execution proof of a short zkDSL program (crates/vrf/zkdsl/vrf.py) that checks both Poseidon calls against the public inputs [pk_vrf, vrf_input[15], y] using the private hint vrf_sk. It uses lean_prover::prove_execution at the same WHIR security parameters as the XMSS aggregation.
See leanMultisig/crates/lean_compiler/zkDSL.md for the full reference. Key points:
from snark_lib import *— ignored by the compiler, present for Python IDE support onlyArray(n)— allocatenfield elements in VM memoryhint_witness("name", ptr)— pop next entry fromhints["name"]into memory atptrposeidon16_compress(left_ptr, right_ptr, result_ptr)— built-in precompileassert a == b— adds a SNARK constraint;debug_assert(...)is runtime-onlyunroll(start, end)— compile-time loop unrolling;range(start, end)for runtime loops- Memory is write-once (SSA);
Mutvariables bypass this for scalar values - Public input lives at
mem[0..public_input.len()]; runtime allocs follow
These supplement the workspace-level lint rules (clippy::all, clippy::pedantic):
VrfSecretKeywraps a singleKoalaBear. BecauseKoalaBeardoes not implementZeroize, the key uses a manualimpl Zeroize(volatile byte-slice write viaunsafe std::slice::from_raw_parts_mut) and a manualimpl Dropthat callsself.zeroize(). Do not attempt#[derive(Zeroize, ZeroizeOnDrop)]— it will not compile.vrf_proveandvrf_verifyreturnResult<_, VrfError>and never panic.- The VRF crate must not introduce any new hash dependencies. Use only
utils::poseidon16_compress. - The compiled
Bytecodeis cached in aOnceLock(same pattern asrec_aggregation::get_aggregation_bytecode). - Every public item in
crates/vrf/src/requires a doc comment that states the security model and assumption.
All VRF crate source files are written and the crate compiles against rust-toolchain.toml channel = "1.88.0" (required for let_chains, stabilised in 1.88.0). The 1.88 toolchain must be installed via rustup toolchain install 1.88.0 --profile minimal before building (network permitting).
| File | Status |
|---|---|
crates/vrf/src/key.rs |
Written — VrfSecretKey, VrfPublicKey, domain constants |
crates/vrf/src/input.rs |
Written — pack_bytes_3, encode_vrf_input |
crates/vrf/src/eval.rs |
Written — vrf_eval, VrfOutput |
crates/vrf/src/circuit.rs |
Written — get_vrf_bytecode() (OnceLock + include_dir) |
crates/vrf/zkdsl/vrf.py |
Written — two-Poseidon zkDSL circuit |
crates/vrf/src/proof.rs |
Written — vrf_prove, vrf_verify |
crates/vrf/src/error.rs |
Written — VrfError enum |
crates/vrf/src/lib.rs |
Written — public re-exports |
crates/vrf/tests/correctness.rs |
Written — prove/verify roundtrip + rejection tests |
crates/vrf/tests/uniqueness.rs |
Written — key/slot/mix change tests |
crates/vrf/tests/kat.rs |
Written — KAT stubs (hardcoded values TBD after first run) |
crates/vrf/benches/vrf_bench.rs |
Written — Criterion benchmarks |
rust-toolchain.toml |
Written — channel = "1.88.0" |
Test status (Session 004):
All 32 tests pass with cargo test -p vrf (Rust 1.88.0, pinned via rust-toolchain.toml).
- 15 unit tests (src/): all pass
- 8 correctness tests (prove/verify roundtrip): all pass
- 6 KAT tests (5 pinned vectors from 2026-05-19): all pass
- 4 uniqueness tests: all pass
clippy -D warnings: clean
Remaining before ethresear.ch post:
- Push fork to GitHub once user approves
crates/vrf/FINDINGS.md— confirmed field/Poseidon/domain-tag parameterscrates/vrf/BENCHMARK_RESULTS.md— proof timing and size (filled after benchmarks run)project-files/technical-background.md— cryptographic background and ERRATAproject-files/decision-log.md— records of settled design choicesproject-files/open-questions.md— unresolved questions that may affect the EIPminimal_zkVM.pdf— VM design document