Skip to content

Add a Jolt backend #403

Description

@andreacarotti9

Ere supports OpenVM, SP1 and ZisK today, and the zkEVM roadmap calls for more. I have a working Jolt backend and would rather agree its shape with you before opening a PR than after.

Code, measurements and raw data: https://github.com/andreacarotti9/jolt-eth

What works

Against ere v0.15.0, ere-guests v0.15.0 and Jolt 915faf4: the four backend crates mirroring the SP1 ones, zkVMKind::Jolt, a docker/jolt image, and stateless-validator-reth unmodified and no_std proving real EEST blocks with output matching statelessOutputBytes. There is an end-to-end test that compiles, executes, proves and verifies a block through zkVMProver.

Accelerators are the zkvm-standards C ABI on Jolt inlines (keccak256, sha256, secp256k1 verify and ecrecover, secp256r1). Anything without a Jolt substrate forwards to revm's software backend, so enabling the seam can only remove cycles.

Three questions

1. Jolt has no ambient guest IO. The other three export the zkvm-standards read_input / write_output symbols and inherit Platform's defaults. Jolt exports neither, because #[jolt::provable] bakes the IO addresses into the entrypoint it generates. So my backend defines a guest contract instead:

#[jolt::provable(/* sizes */)]
fn main(input: jolt::UntrustedAdvice<&[u8]>) -> Vec<u8> { .. }

Input::stdin goes into the untrusted advice region, the returned Vec<u8> comes back as PublicValues, and read_input / write_output are wired to a diagnostic panic rather than a wrong answer.

Is a documented per-backend guest contract acceptable, or would you rather the C ABI symbols were implemented over Jolt's memory layout so the defaults work?

2. The compiler shells out. A Jolt ELF is only valid if the jolt CLI built it, since the CLI owns the linker script and the runtime wiring, so ere-compiler-jolt runs jolt build instead of assembling rustflags like ere-compiler-sp1. The alternative is a second copy of a linker script that drifts silently.

Acceptable, given that alternative?

3. The key is 32 bytes, but the verifier also needs the ELF. JoltProgramVk is a SHA-256 digest of the guest's verifier preprocessing, and JoltVerifier re-derives that preprocessing from the ELF and checks it against the key. SP1 needs only its 32 bytes because it verifies a compressed, recursive proof whose circuit checks the real key internally; Jolt at this pin verifies the raw proof directly. The preprocessing is program-specific and large, 98 MB for the stateless validator against 244 KB for a toy guest at the same trace bound, so it cannot be factored out into the verifier setup.

Is "digest key, plus an ELF and a preprocessing pass to build a verifier" acceptable? If Ere assumes a verifier can be built from the key alone, a Jolt backend wants proof compression first, and I would rather know now.

One caveat, before any numbers circulate

Jolt's inlines are virtual instruction sequences expanded into the main trace, not precompiles with separate AIRs. Measured on a guest, keccak256 costs 24.5 cycles/byte inline against 60.5 in software: 2.5x, not orders of magnitude. Comparing a Jolt "cycle" to an SP1 "cycle" without that context gives the wrong answer, and I would rather the backend landed alongside the caveat.

Two unrelated bugs

Happy to open either separately.

  • ere-prover-sp1 cannot execute on aarch64 at v0.15.0: SP1ExecutorPool calls reset(), which SP1 v6.3.1 leaves as todo!() in the portable executor every non-x86_64 host gets.
  • ere-compiler-sp1's stock-nightly path cannot build the reth guest: it needs -Zjson-target-spec, and the nightlies that have it fail to -Zbuild-std that tree. SP1RustRv64imaCustomized works with --ignore-rust-version.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions