Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 88 additions & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,37 +13,62 @@ Critical context for AI agents working on this repo. Read this before making cha

## OpenVM Version Sensitivity

This project uses **OpenVM v2.0.0** as its ZKVM. Guest executables (`.vmexe`) and host code **must be built from the exact same OpenVM version**. Even a minor version bump can change:
This project uses **OpenVM `develop-v2.1.0` branch** (RV64 guest toolchain) as its ZKVM. Guest executables (`.vmexe`) and host code **must be built from the exact same OpenVM version**. Even a minor version bump can change:

- The guest/host data layout (hint streams, public inputs)
- The Halo2 SRS degree requirement
- The EVM verifier contract ABI
- Field algebra APIs
- ECC constructor signatures

### v2.1.0 (RV64) migration notes

Compared to v2.0.0, the `develop-v2.1.0` branch changes:

- Guest target is now `riscv64im-unknown-openvm-elf` (built into the `openvm-1.94.1`
rust fork toolchain). Guest builds MUST use `OPENVM_RUST_TOOLCHAIN=openvm-1.94.1`
(the default in the Makefile and in `openvm-build`). The old
`riscv32im-risc0-zkvm-elf` / `nightly-2025-11-20` combination is gone.
- Crate renames: `openvm-rv32im-{guest,transpiler,circuit}` → `openvm-riscv-{guest,transpiler,circuit}`.
- `openvm.toml`: `[app_vm_config.rv32i]`/`rv32m` → `rv64i`/`rv64m`.
- SDK API: `Sdk::riscv32`/`AppConfig::riscv32` → `riscv64`; `Sdk::execute*` now
takes a compiled instance — call `sdk.compile*` / `sdk.compile_metered_cost`
first, then `sdk.execute(&compiled, ...)` / `sdk.execute_metered_cost(&compiled, ...)`.
- Hint stream words are 8 bytes: `hint_store_u32!` → `hint_store_u64!` /
`hint_buffer_chunked`, and the hint-stream length prefix is a `u64`.
- User public values are **single bytes** (1 byte per cell, stored in the low byte
of a u32 field element). `NUM_PUBLIC_VALUES` is 32 cells (= 32 bytes); the
32-byte pi hash fills all 32 cells. (Earlier v2.1.0 snapshots used u16 cells;
upstream restored byte-sized public values in commit `b3c95cd00`.)
- Guest cfg gates: `target_os = "zkvm"` → `target_os = "openvm"`.
- Host toolchain: `rust-toolchain.toml` uses `nightly-2026-01-18` (required by the
openvm-sdk `tco` feature).

### How to update OpenVM dependencies correctly

OpenVM is declared as a **git dependency** (`tag = "v2.0.0"`) in `Cargo.toml`, and the exact commit is also pinned in `Cargo.lock`. The `openvm-org/openvm.git` and `openvm-org/stark-backend.git` entries MUST stay on matching tags — `openvm`'s own `Cargo.toml` pins a specific `stark-backend` tag, and a mismatch produces duplicate-registry / type-mismatch errors. Because the tag is immutable, the declared ref and the locked commit should always agree. The real hazard is a bare `cargo update`: it will **not** change the OpenVM tag, but it will bump unrelated crates.io packages (e.g. `alloy`, `revm`) which often break compatibility with the `scroll-tech/reth` and `sbv` forks.
OpenVM is declared as a **git dependency** (`branch = "develop-v2.1.0"`) in `Cargo.toml`, and the exact commit is also pinned in `Cargo.lock`. The `openvm-org/stark-backend.git` entries MUST stay on the tag that `openvm`'s own `Cargo.toml` pins for that branch (currently `tag = "v2.0.0"`) — a mismatch produces duplicate-registry / type-mismatch errors. A branch ref moves: after fetching, verify the locked commit is the one you expect. The real hazard is a bare `cargo update`: it will bump unrelated crates.io packages (e.g. `alloy`, `revm`) which often break compatibility with the `scroll-tech/reth` and `sbv` forks.

**Do NOT run a global `cargo update` unless you are prepared to upgrade the entire `alloy`/`revm`/`reth`/`sbv` dependency chain together.**

To move to a newer OpenVM tag, retarget every `openvm-org/openvm.git` and `openvm-org/stark-backend.git` entry in `Cargo.toml` to the new tag, then refresh only those git sources — `cargo metadata` is enough — rather than a global `cargo update`. Verify with `git diff Cargo.lock` that no other package's version/source changed. Then rebuild guests and run tests as described below.
To move to a newer OpenVM ref, retarget every `openvm-org/openvm.git` entry in `Cargo.toml` to the new tag/branch and set `openvm-org/stark-backend.git` to whatever tag that openvm ref's own `Cargo.toml` pins, then refresh only those git sources — `cargo metadata` is enough — rather than a global `cargo update`. Verify with `git diff Cargo.lock` that no other package's version/source changed. Then rebuild guests and run tests as described below.

### After ANY OpenVM version upgrade, you MUST:

1. **Update the hardcoded version string** in `crates/build-guest/src/verifier.rs`:
```rust
let solidity_sdk_tag = "v2.0"; // MUST match openvm-solidity-sdk tag
let verifier_path = "v2.0-deferral"; // bundle/deferral verifier
let solidity_sdk_tag = "v2.1"; // MUST match openvm-solidity-sdk tag
let verifier_path = "v2.1-deferral"; // bundle/deferral verifier
```
(As of the `develop-v2.1.0` upgrade, `openvm-solidity-sdk` has no `v2.1` tag yet,
so the download fails and `auto` mode falls back to local verifier generation.)

2. **Force-rebuild ALL guest assets** (auto mode skips existing files):
```bash
# Local build
OPENVM_RUST_TOOLCHAIN=nightly-2025-11-20 cargo run --release -p scroll-zkvm-build-guest -- --mode force
OPENVM_RUST_TOOLCHAIN=openvm-1.94.1 cargo run --release -p scroll-zkvm-build-guest -- --mode force

# Docker build (matches CI)
OPENVM_RUST_TOOLCHAIN=nightly-2025-11-20 make build-guest
OPENVM_RUST_TOOLCHAIN=openvm-1.94.1 make build-guest
```
This regenerates: `app.elf`, `app.vmexe`, commitment `.rs` files, `agg_vk.bin`, `openVmVk.json`,
and the EVM verifier (`verifier.sol` + `verifier.bin`).
Expand All @@ -67,7 +92,7 @@ To move to a newer OpenVM tag, retarget every `openvm-org/openvm.git` and `openv
These are cached proving keys. They are **not** automatically invalidated on version bumps.

5. **Check SRS params** in `~/.openvm/params/`:
- OpenVM v2.0.0 requires `kzg_bn254_24.srs` (2 GB); the SNARK step in e2e tests also needs `kzg_bn254_22.srs` and `kzg_bn254_23.srs`
- OpenVM v2.x requires `kzg_bn254_24.srs` (2 GB); the SNARK step in e2e tests also needs `kzg_bn254_22.srs` and `kzg_bn254_23.srs`
- Download any missing file with `make $HOME/.openvm/params/<name>.srs`
- If a file is empty/corrupted, replace it (check for `.1` or `.part` suffixes from interrupted downloads)

Expand Down Expand Up @@ -102,7 +127,7 @@ This happens when:

**Fix**: Regenerate with:
```bash
OPENVM_RUST_TOOLCHAIN=nightly-2025-11-20 cargo run --release -p scroll-zkvm-build-guest -- --mode force
OPENVM_RUST_TOOLCHAIN=openvm-1.94.1 cargo run --release -p scroll-zkvm-build-guest -- --mode force
```
(The default `auto` mode will fall back to local generation if the download fails; use `RECOMPUTE_MODE=yes` to force local generation immediately.)

Expand All @@ -114,6 +139,43 @@ OPENVM_RUST_TOOLCHAIN=nightly-2025-11-20 cargo run --release -p scroll-zkvm-buil
### Docker build fails with stale CID
The `build-guest.sh` script may fail if a stale `build-guest.cid` file exists. Use local build (`cargo run -p scroll-zkvm-build-guest`) as fallback.

### Guest crashes with `upper 4 bytes must be zero` (TryFromIntError) in store/addi
**Symptoms**: `test-execute-chunk` / proving panics in `openvm_riscv_circuit` with a
register holding `0xfffffffffffffe60` (=-416) or similar sign-extended garbage, and the
stack pointer walks down by a fixed stride (e.g. 416) until it wraps.
**Cause**: Upstream `openvm-mem` (introduced in commit `d664effb1`, "use rust native
memory intrinsics") implements `copy_forward`/`copy_backward` with 64-byte aggregate
copies (`load::<64>`/`store::<64>`). LLVM lowers those aggregate copies into calls to
`memmove`, which makes `memmove` recursively call itself and overflow the guest stack.
**Fix**: We ship a local override at `patches/openvm-mem` (wired via
`[patch."https://github.com/openvm-org/openvm.git"]` in `Cargo.toml`) that does the
block copies with 8× `u64` loads-then-stores instead of one 64-byte aggregate. If you
bump OpenVM and upstream fixes `openvm-mem`, delete the `[patch]` entry and the
`patches/openvm-mem` directory.

### `subtree size exceeds the address space's configured leaf count` (bundle root/SNARK)
**Symptoms**: `test-e2e-bundle` fails during `gen_proof_snark` (the Halo2 wrap of the
bundle root proof) with this assert from
`crates/vm/src/system/cuda/merkle_tree/mod.rs`, for `DEFERRAL_AS` (address space 4)
with `num_cells=0`.
**Cause**: OpenVM commit `a935d8b3d` ("perf: sparse initial memory snapshot and GPU
Merkle build") added a strict leaf-count assert. The SDK's `compute_root_proof_heights`
builds the root config from a default `AppConfig::riscv64` (which has `deferral=None`),
so `apply_optimizations` zeroes `DEFERRAL_AS.num_cells` — but deferral is actually
active and the root proof touches that address space.
**Fix**: Fixed upstream by `46709d24` ("fix: keep the dummy root-keygen app config
self-consistent", #3118), which re-runs `apply_optimizations()` in
`compute_root_proof_heights` so the dummy root-keygen config keeps `DEFERRAL_AS`
allocated. We track `develop-v2.1.0` HEAD (`29fc511e`), which includes the fix. If you
ever need to pin between `a935d8b3d` and `46709d24`, this assert will come back.

### Field-independent instructions (#3109)
As of `29fc511e` ("refactor(v2.1): make program instructions field-independent"),
`Instruction` / `Program` / `VmExe` are **non-generic** (no `<F>`); operands are
`InstructionOperand(i32)` restricted to the signed 30-bit domain. This changes the
`app.vmexe` serialization format, so any OpenVM bump across this commit requires a
full force rebuild of guest assets.

## GPU Features

Two levels of GPU acceleration exist, wired as cargo features:
Expand Down Expand Up @@ -154,7 +216,7 @@ Do **not** reintroduce `sdk.prover()` / `sdk.agg_vk()` calls in read-only (verif
# Force rebuild all guest assets (required after OpenVM upgrade).
# Default RECOMPUTE_MODE=auto falls back to local generation if the download fails.
# Use RECOMPUTE_MODE=yes to skip the download and force local generation.
OPENVM_RUST_TOOLCHAIN=nightly-2025-11-20 cargo run --release -p scroll-zkvm-build-guest -- --mode force
OPENVM_RUST_TOOLCHAIN=openvm-1.94.1 cargo run --release -p scroll-zkvm-build-guest -- --mode force

# Run end-to-end tests (ALWAYS use make, never raw cargo test)
GPU=1 make test-e2e-bundle
Expand All @@ -176,6 +238,20 @@ can waste hours of CPU time. Always run it as:
cargo test --release -p scroll-zkvm-build-guest test_verifier
```

### RVR native execution toolchain (currently disabled)

The optional `rvr` feature of `openvm-sdk` compiles guest code to native C at runtime for
faster execution. It is **not** enabled in `Cargo.toml` by default (the default path uses
the interpreter, which is slower but well-tested). If you enable it, you need
**LLVM clang-22 + lld-22** on the host:

- Set `RVR_CC=clang-22` and `RVR_LD=lld` (or `RVR_LD=ld.lld`) when running tests.
- If the system clang is older, install clang-22/lld-22 via conda-forge and put it on `PATH`:
```bash
mamba install -y -c conda-forge clang=22 lld=22 llvm=22
PATH="/home/scroll/miniforge3/bin:$PATH" RVR_CC=clang-22 RVR_LD=lld GPU=1 make test-single-chunk
```

## Deferral Model (OpenVM v2+)

OpenVM v2 replaces the traditional root-verifier recursion with a **deferred compute model**:
Expand Down Expand Up @@ -219,3 +295,5 @@ If any of these mismatch, the EVM verifier will reject proofs with `ProofVerific
- `chunk-circuit`: requires `system.config.continuation_enabled = true`
- `batch-circuit` / `bundle-circuit`: aggregation FRI params are supplied in code via `AggregationConfig { params: default_agg_params() }`; the checked-in `openvm.toml` files do not contain `leaf_fri_params`
- FRI params format in OpenVM v2: `commit_proof_of_work_bits` + `query_proof_of_work_bits`
- VM extension sections in `openvm.toml` are `[app_vm_config.rv64i]` / `[app_vm_config.rv64m]` (RV64)
- Guest ELFs land in `target/riscv64im-unknown-openvm-elf/maxperf/`
Loading
Loading