Skip to content

Latest commit

 

History

History
193 lines (137 loc) · 9.32 KB

File metadata and controls

193 lines (137 loc) · 9.32 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.


What this repo is

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.


Build and test commands

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 architecture

Proof system

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 and hash

  • Field: KoalaBear = MontyField31<KoalaBearParameters>, prime p = 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 to state[0] only.
  • Key utilities in crates/utils/src/poseidon.rs:
    • poseidon16_compress([F; 16]) -> [F; 8] — Davies-Meyer (permute+add), returns first 8 elements
    • poseidon16_permute([F; 16]) -> [F; 16]
    • poseidon16_compress_pair(&[F;8], &[F;8]) -> [F;8]

Crate map

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)

How proofs work (high level)

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.


VRF construction

Parameters confirmed from source (see crates/vrf/FINDINGS.md)

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 key derivation

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.

Circuit / proof approach

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.


zkDSL quick reference

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 only
  • Array(n) — allocate n field elements in VM memory
  • hint_witness("name", ptr) — pop next entry from hints["name"] into memory at ptr
  • poseidon16_compress(left_ptr, right_ptr, result_ptr) — built-in precompile
  • assert a == b — adds a SNARK constraint; debug_assert(...) is runtime-only
  • unroll(start, end) — compile-time loop unrolling; range(start, end) for runtime loops
  • Memory is write-once (SSA); Mut variables bypass this for scalar values
  • Public input lives at mem[0..public_input.len()]; runtime allocs follow

VRF-specific code quality rules

These supplement the workspace-level lint rules (clippy::all, clippy::pedantic):

  1. VrfSecretKey wraps a single KoalaBear. Because KoalaBear does not implement Zeroize, the key uses a manual impl Zeroize (volatile byte-slice write via unsafe std::slice::from_raw_parts_mut) and a manual impl Drop that calls self.zeroize(). Do not attempt #[derive(Zeroize, ZeroizeOnDrop)] — it will not compile.
  2. vrf_prove and vrf_verify return Result<_, VrfError> and never panic.
  3. The VRF crate must not introduce any new hash dependencies. Use only utils::poseidon16_compress.
  4. The compiled Bytecode is cached in a OnceLock (same pattern as rec_aggregation::get_aggregation_bytecode).
  5. Every public item in crates/vrf/src/ requires a doc comment that states the security model and assumption.

Implementation status (as of Session 004, 2026-05-19)

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

Key reference files

  • crates/vrf/FINDINGS.md — confirmed field/Poseidon/domain-tag parameters
  • crates/vrf/BENCHMARK_RESULTS.md — proof timing and size (filled after benchmarks run)
  • project-files/technical-background.md — cryptographic background and ERRATA
  • project-files/decision-log.md — records of settled design choices
  • project-files/open-questions.md — unresolved questions that may affect the EIP
  • minimal_zkVM.pdf — VM design document