feat(l1): add LambdaVM zkVM backend for the L1 prover#6722
Conversation
SP1, RISC0, OpenVM, and Zisk backends. LambdaVM (yetanotherco/lambda_vm) is a verifiable RV64IM zkVM with a transparent STARK-over-Goldilocks proof system; bringing it into ethrex closes the loop on a vertically integrated stack and gives the LambdaVM team a real EVM workload to harden against. Scope: L1 only, local execute/prove/verify via LambdaVM's `cli` binary — no L2 wiring and no on-chain verifier (mirrors the current Zisk stance). Guest binary at `crates/guest-program/bin/lambdavm/` (detached workspace, custom RV64IM target spec, nightly-2026-02-01 toolchain). `LambdaVmCrypto` overrides `keccak256` via LambdaVM's `keccak_permute` syscall and ECDSA secp256k1 via the shared pure-Rust k256 helpers — every other Crypto method inherits the trait default (ark-bn254, bls12_381, malachite, p256, kzg-rs), matching the OpenVM adapter pattern. Host backend at `crates/prover/src/backend/lambdavm.rs` shells out to the LambdaVM `cli` for execute/prove/verify and serializes inputs to a length-prefixed file matching LambdaVM's `PRIVATE_INPUT_START` layout. `BackendType::LambdaVM` is gated on the new `lambdavm` Cargo feature. Source is pinned via commit hash on `yetanotherco/lambda_vm` (`8af4e92e6e75638746fe9924f6bac715ffac9cb6`) and referenced from both `crates/guest-program/Cargo.toml` and the new `.github/actions/install-lambdavm/` composite action. Bumping LambdaVM is a single-commit operation touching exactly those two locations. The new `lambdavm` entry in the `lint_zk` matrix in `.github/workflows/pr-main_l2_prover.yaml` will exercise the install action and guest ELF build on every PR. Local validations: cargo check clean with and without the feature on both crates, clippy clean on all new files, no regressions on `--features sp1` / `--features risc0`. Real EVM block proving will be substantially slower than SP1/RISC0 until LambdaVM lands more accelerators — only keccak is accelerated today, so ECDSA, BN254, KZG, BLS12-381, sha256, and modexp run via pure-Rust crates. This is acknowledged in `docs/zkvm-integrations.md` and the guest-program README.
full zkVM integration playbook in the guest-program README. The initial LambdaVM commit added the backend but missed the surrounding plumbing that every zkVM needs once it ships a per-backend Cargo.lock: - Root `Makefile`: `update-cargo-lock` now bumps `crates/guest-program/bin/lambdavm/Cargo.toml`, and `check-cargo-lock` validates the lockfile via `cargo metadata --locked` (toolchain-free, same pattern as ZisK). Without this, CI's lockfile diff check on the prover lint job would fail on every PR that touches root deps. - `docs/developers/release-process.md`: lists `bin/lambdavm/Cargo.toml` in the version-bump set and `bin/lambdavm/Cargo.lock` in the expected-changes set so the next release cut won't drift. - `crates/guest-program/Makefile`: adds `lambdavm` / `l2-lambdavm` build targets, extends the `.PHONY` list, updates the help text, and adds `bin/lambdavm/out` to `clean`. - `crates/guest-program/README.md`: expands the "Integrating a New zkVM" guide to cover the steps the original guide missed — the host backend in `crates/prover/src/backend/`, the dispatch site in `prover.rs`, the CI install action and prover-lint matrix entry, the root-Makefile cargo-lock plumbing, the release-process doc update, and the zkvm-integrations.md landing page. Future zkVM integrations can follow the same checklist instead of re-deriving it.
actually uses the pinned nightly toolchain.
When `build.rs` runs as part of an outer `cargo check -p ethrex-guest-program
--features lambdavm-build-elf`, that outer cargo sets `RUSTC`, `CARGO`, and
`RUSTUP_TOOLCHAIN` env vars in the child environment. Those override the
`+nightly-2026-02-01` toolchain selector on the spawned `cargo build`, so the
inner build ends up using the outer (stable) toolchain — which then rejects
the `-Z build-std`, `-Z build-std-features`, and `-Z json-target-spec` flags
the LambdaVM target requires.
Same fix the Zisk build script already uses: set `RUSTC` explicitly to the
nightly toolchain's rustc binary (via `rustc_path("nightly-2026-02-01")`),
and clear `CARGO` / `RUSTUP_TOOLCHAIN` so the rustup proxy picks up the
right toolchain on its own. The `rustc_path` helper is now visible to both
`zisk-build-elf` and `lambdavm-build-elf`.
Verified locally on macOS arm64 with `LAMBDA_VM_SYSROOT_DIR=$HOME/.lambda-vm-sysroot`
and Homebrew LLVM on `$PATH`. Produces a 2.8MB RISC-V 64-bit ELF executable
(soft-float, lp64 ABI, statically linked, UCB RISC-V) at
`crates/guest-program/bin/lambdavm/out/riscv64im-lambda-vm-elf`. The prover
crate then sees and embeds it via `ZKVM_LAMBDAVM_PROGRAM_ELF`.
This closes task 8.1 partially: the guest ELF builds end-to-end. Running
`LambdaVmBackend::execute` against an actual block still needs the LambdaVM
`cli` binary installed; the install action covers that for CI.
|
🤖 Kimi Code ReviewOverall Assessment: This is a well-structured integration of LambdaVM as a new zkVM backend. The code follows the established patterns for other zkVMs (SP1, RISC0, Zisk, OpenVM) and includes comprehensive documentation. However, there are two critical issues that need addressing before merge: a race condition in the host backend and a supply chain security gap in the CI installer. 1. Race Condition in Prover Backend (Critical)File: The backend uses fixed file paths for input, proof, and ELF files: const INPUT_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/lambdavm_input.bin");
const PROOF_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/lambdavm_proof.bin");
const ELF_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/zkvm-lambdavm-program");If multiple Recommendation: Use unique temporary files per invocation. Pattern used in let input_path = format!("{}/lambdavm_input.{}.bin", std::env::temp_dir().display(), std::process::id());2. Supply Chain Risk: No Checksum Verification (Security)File: The action downloads a sysroot tarball from an external domain without checksum verification: retry curl -L "${SYSROOT_URL}" -o "${SYSROOT_TARBALL}"Recommendation: Add a SHA256 checksum check after download (and before extraction) to prevent supply chain attacks if the CDN is compromised: echo "expected_sha256 lambda-vm-sysroot-rv64im.tar.gz" | sha256sum -c -3. Future Rust Toolchain Date (Verify)File: The toolchain is pinned to Recommendation: Verify this is the correct, intended date. If LambdaVM requires a specific future nightly, add a comment explaining why this specific date is required. 4. Error Handling in Guest DeserializationFile: let input = { rkyv::from_bytes::<ProgramInput, Error>(&input).unwrap() };While panicking in a zkVM guest is acceptable (it produces a failed proof), consider using .expect("failed to deserialize ProgramInput")5. Missing
|
Lines of code reportTotal lines added: Detailed view |
🤖 Codex Code ReviewFindings
I didn’t spot an obvious correctness bug in the guest-side Keccak sponge itself, but this path is consensus-critical enough that it should have known-answer tests before merge. I couldn’t run Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
🤖 Claude Code ReviewNow I have enough context for a thorough review. Let me write it up. Code Review: PR #6722 — feat(l1): add LambdaVM zkVM backend for the L1 proverOverall, the integration follows the established Zisk pattern cleanly and the Keccak sponge implementation looks correct. Several issues are worth addressing before merge. Supply chain / SecuritySysroot download has no integrity verification. In retry curl -L "${SYSROOT_URL}" -o "${SYSROOT_TARBALL}"
sudo tar -xzf "${SYSROOT_TARBALL}" ...There is no Recommendation: Add a hardcoded expected hash and verify it before unpacking: echo "<expected-sha256> ${SYSROOT_TARBALL}" | sha256sum -cCorrectness
fn prover_type(&self) -> ProverType {
unimplemented!("LambdaVM is not yet enabled as a backend for the L2")
}Any L2 infrastructure code that queries Error messages contain empty stderr (
format!("LambdaVM execution failed: {}", String::from_utf8_lossy(&output.stderr))…and will always emit "LambdaVM execution failed (see stderr above)"Keccak implementation (
|
There was a problem hiding this comment.
Pull request overview
Adds LambdaVM as an additional zkVM backend within ethrex’s existing prover/guest-program integration pattern, including guest build tooling, host backend glue, CI installation, and documentation updates.
Changes:
- Introduces a LambdaVM guest workspace + target spec and wires guest ELF embedding/build via
lambdavm-build-elf. - Adds a LambdaVM host backend (
execute/prove/verify) and registers it behind thelambdavmfeature flag. - Extends CI, release, and developer docs to include the new detached workspace and integration steps.
Reviewed changes
Copilot reviewed 17 out of 23 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| Makefile | Tracks LambdaVM detached workspace in lockfile update/check targets. |
| docs/zkvm-integrations.md | Documents LambdaVM status, features, limitations, and usage. |
| docs/developers/release-process.md | Adds LambdaVM detached Cargo.toml/Cargo.lock to release bump expectations. |
| crates/prover/src/prover.rs | Adds BackendType::LambdaVM dispatch arm (feature-gated). |
| crates/prover/src/backend/mod.rs | Registers lambdavm module and BackendType::LambdaVM (feature-gated). |
| crates/prover/src/backend/lambdavm.rs | New LambdaVM backend that shells out to LambdaVM CLI and handles input/proof files. |
| crates/prover/Cargo.toml | Adds lambdavm and lambdavm-build-elf features. |
| crates/guest-program/src/lib.rs | Adds ZKVM_LAMBDAVM_PROGRAM_ELF embedding constant behind feature gates. |
| crates/guest-program/src/crypto/mod.rs | Adds LambdaVM crypto module and includes shared helpers for it. |
| crates/guest-program/src/crypto/lambdavm.rs | New LambdaVmCrypto with keccak-permute syscall + k256 ECDSA recovery. |
| crates/guest-program/README.md | Expands “Integrating a New zkVM” guide and documents LambdaVM guest details/features. |
| crates/guest-program/Makefile | Adds lambdavm / l2-lambdavm build targets and clean rule update. |
| crates/guest-program/Cargo.toml | Adds lambda-vm-syscalls dep + lambdavm/lambdavm-build-elf features. |
| crates/guest-program/build.rs | Adds LambdaVM guest build step requiring sysroot + pinned nightly. |
| crates/guest-program/bin/lambdavm/src/main.rs | New LambdaVM guest entrypoint reading private input + committing encoded output. |
| crates/guest-program/bin/lambdavm/rust-toolchain.toml | Pins guest workspace toolchain to nightly-2026-02-01 + rust-src. |
| crates/guest-program/bin/lambdavm/riscv64im-lambda-vm-elf.json | Adds custom RV64IM target spec for LambdaVM ELF builds. |
| crates/guest-program/bin/lambdavm/Cargo.toml | New detached workspace manifest for LambdaVM guest. |
| crates/guest-program/bin/lambdavm/Cargo.lock | New detached workspace lockfile for LambdaVM guest. |
| crates/guest-program/bin/lambdavm/.cargo/config.toml | Target-specific rustflags and CC/CFLAGS env wiring for sysroot builds. |
| Cargo.lock | Adds transitive deps for lambda-vm-syscalls in the root workspace lock. |
| .github/workflows/pr-main_l2_prover.yaml | Adds lambdavm to backend lint matrix and installs LambdaVM in CI. |
| .github/actions/install-lambdavm/action.yml | New composite action to install nightly, sysroot, and build/cache LambdaVM CLI. |
Comments suppressed due to low confidence (1)
.github/workflows/pr-main_l2_prover.yaml:97
- CI currently runs
cargo check/clippywith-F "${{ matrix.backend }},ci". For LambdaVM this does not enablelambdavm-build-elf(or buildethrex-guest-program --features lambdavm-build-elf), so the new guest ELF build + sysroot wiring incrates/guest-program/build.rswon’t be exercised in CI.
- name: Check ${{ matrix.backend }} backend
run: |
cargo check -r -p ethrex-prover -F "${{ matrix.backend }},ci"
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let output = Command::new("cli") | ||
| .args(["execute", ELF_PATH, "--private-input", INPUT_PATH]) |
| .stdin(Stdio::inherit()) | ||
| .stderr(Stdio::inherit()) | ||
| .output() | ||
| .map_err(BackendError::execution)?; |
| /// Prove assuming input is already serialized to INPUT_PATH. | ||
| /// | ||
| /// Returns the proof bytes plus the parsed stdout (used by `prove_timed` | ||
| /// to extract `Proving time: <float>s`). | ||
| fn prove_core( | ||
| &self, | ||
| _format: ProofFormat, | ||
| ) -> Result<(LambdaVmProveOutput, String), BackendError> { |
| path: /opt/lambda-vm-sysroot | ||
| key: lambda-vm-sysroot-rv64im-v1 | ||
|
|
||
| - name: Prepare LambdaVM sysroot | ||
| if: steps.cache-sysroot.outputs.cache-hit != 'true' | ||
| shell: bash | ||
| env: | ||
| SYSROOT_TARBALL: /tmp/lambda-vm-sysroot-rv64im.tar.gz | ||
| SYSROOT_URL: https://lambda.alignedlayer.com/lambda-vm-sysroot-rv64im.tar.gz | ||
| run: | | ||
| source "$GITHUB_WORKSPACE/.github/scripts/retry.sh" | ||
|
|
||
| retry curl -L "${SYSROOT_URL}" -o "${SYSROOT_TARBALL}" | ||
| sudo mkdir -p /opt/lambda-vm-sysroot | ||
| sudo tar -xzf "${SYSROOT_TARBALL}" -C /opt/lambda-vm-sysroot --strip-components=1 | ||
| rm -f "${SYSROOT_TARBALL}" | ||
|
|
| - name: Cache LambdaVM CLI binary | ||
| id: cache-cli | ||
| uses: actions/cache@v4 | ||
| with: | ||
| path: ~/.cargo/bin/cli |
Greptile SummaryThis PR integrates LambdaVM (
Confidence Score: 4/5The new LambdaVM backend is structurally sound and faithfully mirrors the ZisK integration. The issues found are quality/observability concerns that won't break the happy path. Three non-blocking issues exist: BackendError messages will contain empty strings when the CLI fails (because Stdio::inherit() on stderr is used alongside output.stderr), parse_proving_time can panic on a NaN or negative f64 from Duration::from_secs_f64, and the cli binary name is very generic and could resolve to a different tool on PATH. None of these affect correct end-to-end proving when the environment is set up as expected; they surface only in error/edge-case paths. crates/prover/src/backend/lambdavm.rs — the three issues (empty error messages, parse_proving_time panic guard, generic binary name) all concentrate here.
|
| Filename | Overview |
|---|---|
| crates/prover/src/backend/lambdavm.rs | New LambdaVM host backend that shells out to the cli binary. Three issues: Stdio::inherit() causes empty stderr in all BackendError messages; parse_proving_time can panic on NaN/negative f64 via Duration::from_secs_f64; the generic cli binary name is fragile on PATH. |
| crates/guest-program/src/crypto/lambdavm.rs | New LambdaVmCrypto adapter implementing Keccak-256 via LambdaVM's keccak_permute syscall. Padding (0x01/0x80), rate (136 bytes), lane ordering, and squeeze all match the Keccak-256 spec. ECDSA delegation to shared k256 helpers mirrors the OpenVM pattern correctly. |
| .github/actions/install-lambdavm/action.yml | New composite CI action that installs the nightly toolchain, downloads/caches the sysroot, and builds/caches the cli binary. Caching keys are pinned to the commit hash. Mirrors existing ZisK/SP1 install action patterns well. |
| crates/guest-program/build.rs | Adds build_lambdavm_program that resolves the sysroot path, invokes the nightly cargo build with correct toolchain isolation (clears RUSTC/CARGO/RUSTUP_TOOLCHAIN), and copies the ELF to bin/lambdavm/out/. Pattern matches the ZisK build function. |
| crates/prover/src/backend/mod.rs | Registers LambdaVM variant in BackendType enum and FromStr, with correct cfg(feature = "lambdavm") guards matching every other backend. |
| crates/guest-program/bin/lambdavm/src/main.rs | Thin guest entry point: reads private input via LambdaVM syscall, deserializes with rkyv, runs execution_program with LambdaVmCrypto, and commits the output. Mirrors the ZisK guest wrapper faithfully. |
Sequence Diagram
sequenceDiagram
participant P as ethrex-prover
participant B as LambdaVmBackend
participant FS as Filesystem
participant CLI as cli binary
P->>B: prove(input, format)
B->>FS: write_elf_file()
B->>B: serialize_input - rkyv + 4-byte LE length prefix
B->>FS: write lambdavm_input.bin
B->>CLI: cli prove elf -o proof --private-input input --time
CLI-->>FS: write lambdavm_proof.bin
CLI-->>B: stdout with Proving time
B->>FS: read lambdavm_proof.bin
B-->>P: LambdaVmProveOutput + Duration
P->>B: verify(proof)
B->>FS: write_elf_file()
B->>FS: write lambdavm_proof.bin
B->>CLI: cli verify proof elf
CLI-->>B: exit code
B-->>P: Ok or BackendError
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 3
crates/prover/src/backend/lambdavm.rs:69-85
**Empty stderr in error messages**
`execute_core`, `prove_core`, and `verify_core` all set `.stderr(Stdio::inherit())`, which forwards the child's stderr directly to the parent process — so `output.stderr` will always be an empty `Vec`. The `String::from_utf8_lossy(&output.stderr)` in the three error-message `format!` calls will always produce an empty string, rendering `BackendError` messages like `"LambdaVM execution failed: "` with no diagnostic content. Any caller that logs or surfaces the `BackendError` text gets no information about what actually went wrong. The same pattern exists in the ZisK backend; consider fixing both by using `Stdio::piped()` for stderr when diagnostic capture is needed, or removing the `output.stderr` interpolation from the error string.
### Issue 2 of 3
crates/prover/src/backend/lambdavm.rs:238-240
`Duration::from_secs_f64` panics if the parsed `f64` is `NaN`, infinite, or negative. `str::parse::<f64>()` succeeds for `"nan"`, `"inf"`, and `"-1.0"`, so if the CLI ever emits such a value the prover thread will panic instead of falling back to wall-clock time. Adding a finite/non-negative guard keeps the fallback path intact.
```suggestion
if let Ok(secs) = secs_str.parse::<f64>() {
if secs.is_finite() && secs >= 0.0 {
return Some(Duration::from_secs_f64(secs));
}
}
```
### Issue 3 of 3
crates/prover/src/backend/lambdavm.rs:70-75
**Generic `cli` binary name**
`Command::new("cli")` resolves against `$PATH`, and the binary is installed as `~/.cargo/bin/cli`. The name `cli` is extremely common — if any other `cli` binary appears earlier on the user's `$PATH`, the prover will silently invoke the wrong binary and produce confusing failures. Using a more specific binary name (e.g., `lambda-vm-cli`) in both the install action and the `Command::new` calls would eliminate this ambiguity.
Reviews (1): Last reviewed commit: "Fix the LambdaVM guest build script so t..." | Re-trigger Greptile
| fn execute_core(&self) -> Result<(), BackendError> { | ||
| let output = Command::new("cli") | ||
| .args(["execute", ELF_PATH, "--private-input", INPUT_PATH]) | ||
| .stdin(Stdio::inherit()) | ||
| .stderr(Stdio::inherit()) | ||
| .output() | ||
| .map_err(BackendError::execution)?; | ||
|
|
||
| if !output.status.success() { | ||
| return Err(BackendError::execution(format!( | ||
| "LambdaVM execution failed: {}", | ||
| String::from_utf8_lossy(&output.stderr) | ||
| ))); | ||
| } | ||
|
|
||
| Ok(()) | ||
| } |
There was a problem hiding this comment.
Empty stderr in error messages
execute_core, prove_core, and verify_core all set .stderr(Stdio::inherit()), which forwards the child's stderr directly to the parent process — so output.stderr will always be an empty Vec. The String::from_utf8_lossy(&output.stderr) in the three error-message format! calls will always produce an empty string, rendering BackendError messages like "LambdaVM execution failed: " with no diagnostic content. Any caller that logs or surfaces the BackendError text gets no information about what actually went wrong. The same pattern exists in the ZisK backend; consider fixing both by using Stdio::piped() for stderr when diagnostic capture is needed, or removing the output.stderr interpolation from the error string.
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/prover/src/backend/lambdavm.rs
Line: 69-85
Comment:
**Empty stderr in error messages**
`execute_core`, `prove_core`, and `verify_core` all set `.stderr(Stdio::inherit())`, which forwards the child's stderr directly to the parent process — so `output.stderr` will always be an empty `Vec`. The `String::from_utf8_lossy(&output.stderr)` in the three error-message `format!` calls will always produce an empty string, rendering `BackendError` messages like `"LambdaVM execution failed: "` with no diagnostic content. Any caller that logs or surfaces the `BackendError` text gets no information about what actually went wrong. The same pattern exists in the ZisK backend; consider fixing both by using `Stdio::piped()` for stderr when diagnostic capture is needed, or removing the `output.stderr` interpolation from the error string.
How can I resolve this? If you propose a fix, please make it concise.| if let Ok(secs) = secs_str.parse::<f64>() { | ||
| return Some(Duration::from_secs_f64(secs)); | ||
| } |
There was a problem hiding this comment.
Duration::from_secs_f64 panics if the parsed f64 is NaN, infinite, or negative. str::parse::<f64>() succeeds for "nan", "inf", and "-1.0", so if the CLI ever emits such a value the prover thread will panic instead of falling back to wall-clock time. Adding a finite/non-negative guard keeps the fallback path intact.
| if let Ok(secs) = secs_str.parse::<f64>() { | |
| return Some(Duration::from_secs_f64(secs)); | |
| } | |
| if let Ok(secs) = secs_str.parse::<f64>() { | |
| if secs.is_finite() && secs >= 0.0 { | |
| return Some(Duration::from_secs_f64(secs)); | |
| } | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/prover/src/backend/lambdavm.rs
Line: 238-240
Comment:
`Duration::from_secs_f64` panics if the parsed `f64` is `NaN`, infinite, or negative. `str::parse::<f64>()` succeeds for `"nan"`, `"inf"`, and `"-1.0"`, so if the CLI ever emits such a value the prover thread will panic instead of falling back to wall-clock time. Adding a finite/non-negative guard keeps the fallback path intact.
```suggestion
if let Ok(secs) = secs_str.parse::<f64>() {
if secs.is_finite() && secs >= 0.0 {
return Some(Duration::from_secs_f64(secs));
}
}
```
How can I resolve this? If you propose a fix, please make it concise.| let output = Command::new("cli") | ||
| .args(["execute", ELF_PATH, "--private-input", INPUT_PATH]) | ||
| .stdin(Stdio::inherit()) | ||
| .stderr(Stdio::inherit()) | ||
| .output() | ||
| .map_err(BackendError::execution)?; |
There was a problem hiding this comment.
Command::new("cli") resolves against $PATH, and the binary is installed as ~/.cargo/bin/cli. The name cli is extremely common — if any other cli binary appears earlier on the user's $PATH, the prover will silently invoke the wrong binary and produce confusing failures. Using a more specific binary name (e.g., lambda-vm-cli) in both the install action and the Command::new calls would eliminate this ambiguity.
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/prover/src/backend/lambdavm.rs
Line: 70-75
Comment:
**Generic `cli` binary name**
`Command::new("cli")` resolves against `$PATH`, and the binary is installed as `~/.cargo/bin/cli`. The name `cli` is extremely common — if any other `cli` binary appears earlier on the user's `$PATH`, the prover will silently invoke the wrong binary and produce confusing failures. Using a more specific binary name (e.g., `lambda-vm-cli`) in both the install action and the `Command::new` calls would eliminate this ambiguity.
How can I resolve this? If you propose a fix, please make it concise.
Motivation
LambdaVM (
yetanotherco/lambda_vm) was open-sourced as a verifiable RV64IM zkVM with a transparent STARK-over-Goldilocks proof system, 128-bit security, and LogUp lookups. Bringing it into ethrex as a first-class zkVM backend closes the loop on a vertically integrated stack and gives the LambdaVM team a real EVM workload to harden against.A proof-of-concept guest already exists in the lambda_vm repo at
executor/programs/rust/ethrex/, but it was written against an older revision of the guest-program API (before theCryptotrait was extracted) and won't compile against current ethrexmain. This PR brings the integration into ethrex itself, alongside the existing SP1, RISC0, OpenVM, and Zisk backends.Description
Wires LambdaVM into the existing zkVM backend framework, scoped to L1 local proving (mirrors the current Zisk stance — no L2 prover wiring, no on-chain verifier).
Guest side
crates/guest-program/bin/lambdavm/— detached workspace, custom RV64IM target spec (riscv64im-lambda-vm-elf), pinnednightly-2026-02-01toolchain withrust-src, thinmain.rsmirroring the Zisk wrapper.crates/guest-program/src/crypto/lambdavm.rs—LambdaVmCryptooverrideskeccak256via LambdaVM'skeccak_permutesyscall and ECDSA secp256k1 via the shared pure-Rustk256helpers. Every otherCryptomethod inherits the trait default (ark-bn254, bls12_381, malachite, p256, kzg-rs) — same shape as the OpenVM adapter.build.rsbuilds the guest ELF under thelambdavm-build-elffeature. Sysroot path resolved fromLAMBDA_VM_SYSROOT_DIR(default/opt/lambda-vm-sysroot); inner toolchain pinned viaRUSTC=$(rustc_path("nightly-2026-02-01"))withCARGO/RUSTUP_TOOLCHAIN/RUSTFLAGS/CARGO_ENCODED_RUSTFLAGScleared so the outer cargo's env doesn't force the inner build onto stable.Host side
crates/prover/src/backend/lambdavm.rs—LambdaVmBackendshells out to LambdaVM'sclibinary forexecute,prove,verify. Inputs serialized as[4-byte LE length][rkyv bytes]matching LambdaVM'sPRIVATE_INPUT_STARTmemory layout.prove_timedparsesProving time: <float>sfromcli prove --timestdout, falls back to wall-clock if missing.BackendType::LambdaVMregistered,FromStrarm added, dispatch site instart_proverwired.CI / release plumbing
.github/actions/install-lambdavm/— composite action: installsnightly-2026-02-01+rust-src, downloads the sysroot tarball (cached), builds theclibinary from the pinned LambdaVM commit (cached), puts it on$PATH..github/workflows/pr-main_l2_prover.yaml—lambdavmadded to thelint_zkmatrix, conditional install step added.Makefile—bin/lambdavm/Cargo.tomladded toupdate-cargo-lockandcheck-cargo-lock(metadata-only check, toolchain-free pattern matching ZisK).docs/developers/release-process.md—bin/lambdavm/Cargo.tomlin the version-bump list,bin/lambdavm/Cargo.lockin the expected-changes list.crates/guest-program/Makefile—lambdavm/l2-lambdavmbuild targets, help text,cleanrule extended.Docs
docs/zkvm-integrations.md— LambdaVM section with status, key features, integration details, limitations, build/run instructions.crates/guest-program/README.md— "Integrating a New zkVM" guide expanded from 6 to 11 steps, covering the missing host backend, CI install action, root-Makefile cargo-lock plumbing, release-process doc, and zkvm-integrations.md updates that future zkVM vendors will also need.Source pinning
LambdaVM is pinned by commit hash (
8af4e92e6e75638746fe9924f6bac715ffac9cb6) from bothcrates/guest-program/Cargo.toml(thelambda-vm-syscallsgit dep) and.github/actions/install-lambdavm/action.yml(theclicheckout target). Bumping the version is a single-commit operation touching exactly those two locations; inline comments cross-reference them.Limitations
Only
keccak_permuteis accelerated today. ECDSA, BN254, KZG, BLS12-381, sha256, and modexp run via pure-Rust crates — real EVM block proving will be substantially slower than SP1/RISC0 until LambdaVM lands more precompiles. Acknowledged indocs/zkvm-integrations.md.How to Test
Local build verified end-to-end on macOS arm64:
Regression:
cargo check -p ethrex-prover --features sp1/--features risc0still pass; default builds pull no new deps.cargo clippy -p ethrex-prover --features lambdavmand-p ethrex-guest-program --features lambdavmare warning-free on the LambdaVM files.The new
lambdavmentry in thelint_zkmatrix will exercise the install action, the guest ELF build, and clippy on every PR.Checklist
STORE_SCHEMA_VERSION(crates/storage/lib.rs) if the PR includes breaking changes to theStorerequiring a re-sync.Follow-ups (not blocking this PR)
LambdaVmBackend::execute/prove/verifyagainst a real L1 block on a developer machine with the LambdaVMcliinstalled.docs/zkvm-integrations.md.executor/programs/rust/ethrex/PoC in their repo or update it to point at this in-ethrex guest.