Skip to content

feat(evm): add unrolled-sharded verifier flow for Midnight Poseidon and IVC#12

Open
jtcoolen wants to merge 24 commits into
julian@midnight-proof-supportfrom
julian@sharded-contract
Open

feat(evm): add unrolled-sharded verifier flow for Midnight Poseidon and IVC#12
jtcoolen wants to merge 24 commits into
julian@midnight-proof-supportfrom
julian@sharded-contract

Conversation

@jtcoolen

@jtcoolen jtcoolen commented Feb 27, 2026

Copy link
Copy Markdown
Owner

[Only the last 9 commits apply, the first commits come from #1 and #2]

Summary

This PR adds and stabilizes the unrolled-sharded verifier path for Midnight EVM examples, enabling on-chain-compatible benchmarking when monolithic unrolled verifier bytecode exceeds deploy limits.

  • Adds unrolled-sharded verifier artifact generation (dispatcher + shard contracts + manifest + sizing metadata).
  • Wires unrolled-sharded deployment/call benchmarking through REVM (deploy shards → deploy dispatcher → verify via dispatcher call).
  • Updates Poseidon and IVC example flows to emit and benchmark sharded artifacts.
  • Fixes truncated-challenges compatibility by restoring truncate_scalar_to_128 in the EVM loader.
  • For truncated-challenges runs, explicitly skips compact/hybrid generation (unsupported path).
  • For oversized monolithic unrolled IVC verifier code, skips unrolled REVM benchmark with a clear EIP-170/EIP-3860 reason instead of failing with a misleading revert message.
  • Restores EvmLoader::code_mut() accessor required by transcript/decider code paths after history cleanup.

Design / implementation details

What changed (core)

  • New sharded artifact types were added so codegen can return dispatcher + shards + sizing metadata:

    • snark-verifier/src/loader/evm/code.rs:8
    • snark-verifier/src/loader/evm/code.rs:31
  • SolidityAssemblyCode started tracking emitted runtime blocks (runtime_blocks) in addition to the flat runtime string, enabling splitting:

    • snark-verifier/src/loader/evm/code.rs:54
    • snark-verifier/src/loader/evm/code.rs:89

Sharding algorithm

  • Implemented in EvmLoader::unrolled_sharded_verifier_artifacts():

    • snark-verifier/src/loader/evm/loader.rs:393
  • Enforces EVM limits (24,576 runtime via EIP-170, 49,152 initcode via EIP-3860) and starts from coarse 64-statement groups:

    • snark-verifier/src/loader/evm/loader.rs:34
    • snark-verifier/src/loader/evm/loader.rs:36

Flow

  1. Compile a candidate statement group.
  2. If too large or uncompilable, bisect recursively until each group is individually deployable.
  3. Binary-search the largest contiguous window that still fits to maximize shard size.
  4. Compile final shard contracts + dispatcher and produce a manifest.

Key functions

  • candidate compile: snark-verifier/src/loader/evm/loader.rs:267
  • recursive split: snark-verifier/src/loader/evm/loader.rs:283
  • window binary search: snark-verifier/src/loader/evm/loader.rs:324
  • final shard build: snark-verifier/src/loader/evm/loader.rs:357

Contract architecture

  • Each shard is generated as its own Solidity fallback contract that executes only its statement range and returns a 32-byte success flag:

    • snark-verifier/src/loader/evm/loader.rs:107
  • A dispatcher contract stores shard addresses and loops through them with delegatecall, bubbling revert data and checking returned success:

    • snark-verifier/src/loader/evm/loader.rs:136

Deployment / execution path (REVM)

  • New REVM helper deploys all shard contracts, ABI-encodes address[] constructor args for dispatcher, deploys dispatcher, then calls it with verifier calldata:

    • snark-verifier/src/loader/evm/util/executor.rs:123
    • constructor arg encoding: snark-verifier/src/loader/evm/util/executor.rs:227
  • compile_solidity_runtime was added so runtime size can be checked during partitioning:

    • snark-verifier/src/loader/evm/util.rs:112

Public API exposure

  • Re-exports added for sharded artifacts and deploy helper:

    • snark-verifier/src/loader/evm.rs:7
    • snark-verifier/src/loader/evm.rs:14
  • SDK added:

    • artifact generation API: snark-verifier-sdk/src/midnight_adapter/evm.rs:35
    • revm sharded verification API: snark-verifier-sdk/src/midnight_adapter/evm.rs:66

Validation / adoption via examples

  • Poseidon example switched to writing dispatcher+shard artifacts and using sharded REVM verification:

    • snark-verifier-sdk/examples/midnight_poseidon_evm.rs:30
    • snark-verifier-sdk/examples/midnight_poseidon_evm.rs:235
  • New IVC example was added to stress large verifier code and compare unrolled vs sharded deployability against EIP limits:

    • snark-verifier-sdk/examples/midnight_ivc_evm.rs:403
    • snark-verifier-sdk/examples/midnight_ivc_evm.rs:558

Why

Monolithic unrolled IVC verifier bytecode exceeds EVM deploy limits, so on-chain-compatible benchmarking must use sharding.
Unrolled-sharded keeps dispatcher/shards deployable while preserving verifier semantics and giving realistic gas measurements.

How to run (examples)

1) Poseidon

cd snark-verifier-clean

RUN_REVM=1 \
MIDNIGHT_EVM_MODE=unrolled-sharded \
MIDNIGHT_ARTIFACT_DIR=/tmp/midnight_poseidon_unrolled_sharded \
cargo +1.88.0 run --release \
  --example midnight_poseidon_evm \
  --features midnight,loader_evm,revm \
  -p snark-verifier-sdk
proof bytes: 3968
verifier mode: unrolled-sharded
calldata bytes: 4000
wrote /tmp/midnight_poseidon_unrolled_sharded/midnight_poseidon.calldata
unrolled-sharded dispatcher runtime bytes: 196
unrolled-sharded dispatcher initcode bytes: 961
unrolled-sharded shard runtime sizes (bytes): [19570]
unrolled-sharded shard initcode sizes (bytes): [19598]
wrote /tmp/midnight_poseidon_unrolled_sharded/MidnightPoseidonVerifierUnrolledShardedDispatcher.sol
wrote /tmp/midnight_poseidon_unrolled_sharded/midnight_poseidon_unrolled_sharded_dispatcher.bytecode
wrote /tmp/midnight_poseidon_unrolled_sharded/midnight_poseidon_unrolled_sharded_shards.bytecode
wrote /tmp/midnight_poseidon_unrolled_sharded/midnight_poseidon_unrolled_sharded_manifest.txt
revm gas (unrolled-sharded): 637514

2) IVC

Note that the verifier contract does not check the final accumulator yet.

cd snark-verifier-clean

RUN_REVM=1 \
MIDNIGHT_EVM_OUT_DIR=/tmp/midnight_ivc_unrolled_sharded \
cargo +1.88.0 run --release \
  --example midnight_ivc_evm \
  --features midnight,loader_evm,revm,truncated-challenges \
  -p snark-verifier-sdk
computed vk and pk in 17.486349666s
collapsed accumulator (step 0): 77 field elements (2464 bytes)
generated IVC proof step 0 (Poseidon) in 36.890915625s
collapsed accumulator (step 1): 77 field elements (2464 bytes)
generated IVC proof step 1 (EVM) in 35.189767959s
proof bytes: 8544
unrolled deployment code bytes: 102348
unrolled runtime code bytes: 102318
unrolled runtime deployable (EIP-170 <= 24576): false
unrolled initcode deployable (EIP-3860 <= 49152): false
unrolled-sharded dispatcher runtime bytes: 196
unrolled-sharded dispatcher initcode bytes: 961
unrolled-sharded shard runtime sizes (bytes): [23646, 24443, 23623, 23769, 7357]
unrolled-sharded shard initcode sizes (bytes): [23674, 24471, 23651, 23797, 7385]
unrolled-sharded dispatcher runtime deployable (EIP-170 <= 24576): true
unrolled-sharded dispatcher initcode deployable (EIP-3860 <= 49152): true
unrolled-sharded shard runtime deployable (all): true
unrolled-sharded shard initcode deployable (all): true
calldata bytes: 11072
wrote /tmp/midnight_ivc_unrolled_sharded/MidnightIvcVerifier.sol
wrote /tmp/midnight_ivc_unrolled_sharded/midnight_ivc.bytecode
wrote /tmp/midnight_ivc_unrolled_sharded/midnight_ivc.calldata
wrote /tmp/midnight_ivc_unrolled_sharded/MidnightIvcVerifierUnrolledShardedDispatcher.sol
wrote /tmp/midnight_ivc_unrolled_sharded/midnight_ivc_unrolled_sharded_dispatcher.bytecode
wrote /tmp/midnight_ivc_unrolled_sharded/midnight_ivc_unrolled_sharded_shards.bytecode
wrote /tmp/midnight_ivc_unrolled_sharded/midnight_ivc_unrolled_sharded_manifest.txt
=== REVM deployment + verification gas benchmarks ===
skipping unrolled revm benchmark: verifier exceeds EIP deploy limits (runtime=102318 > 24576, initcode=102348 > 49152)
revm gas (unrolled-sharded): 1359482

@jtcoolen jtcoolen force-pushed the julian@sharded-contract branch 9 times, most recently from 889312f to 1b2f7df Compare February 27, 2026 18:13
@jtcoolen jtcoolen force-pushed the julian@midnight-proof-support branch 3 times, most recently from cbfb181 to 3d38168 Compare March 2, 2026 12:56
jtcoolen added 9 commits March 2, 2026 13:10
Introduce reusable solc invocation paths for deployment and runtime bytecode generation.

Persist emitted runtime statement blocks in SolidityAssemblyCode for later partitioning.
Extract reusable EVM codegen helpers for limb packing, point copying, sized staticcalls, and cached modexp inversion input setup.

Remove dead BLS12-381 G1Add precompile path and keep behavior-focused helper refactors isolated from sharding feature additions.
Add sharded verifier artifact/manifest types and implement statement-range partitioning with compile-time EVM code-size checks.

Emit dispatcher and shard Solidity/bytecode artifacts from EvmLoader and export the new artifact interfaces.
Normalize deploy/call match-arm formatting and interpolate reason/output values directly in error strings.

Keep runtime behavior unchanged while reducing noise before introducing the unrolled-sharded executor path.
Add a revm execution path that deploys verifier shards, deploys the dispatcher with encoded shard addresses, and invokes verification calldata.

Export the new unrolled-sharded deploy/call helper through evm util re-exports.
Add SDK APIs to produce unrolled-sharded verifier artifacts and execute sharded verifier calls in revm.

Register the midnight_ivc_evm example target for reproducible sharded verifier flow usage.
Keep branch buildable after dropping e641f5b by replacing the remaining staticcall_with_lengths use in ec_point_multi_scalar_mul.
@jtcoolen jtcoolen force-pushed the julian@sharded-contract branch from 1b2f7df to bc24c57 Compare March 2, 2026 13:16
jtcoolen and others added 8 commits April 11, 2026 23:41
…arded runtime

What:
- Add transcript-focused tests in `snark-verifier/src/system/halo2/transcript/evm.rs`.
- Add an IR-level guard that verifies transcript challenge checks are emitted into generated sharded Solidity artifacts.
- Add revm-backed tests for per-operation Rust vs Solidity transcript trace matching and for sharded-contract transcript equivalence, including a negative control.

Why:
- We need stronger confidence that transcript semantics stay aligned between native Rust verification and generated EVM verifier code.
- This catches regressions at both codegen/IR stage and runtime deployment stage.

How:
- Build deterministic scalar/identity-point transcript scenarios.
- Compare challenge/hash evolution step-by-step for Rust and Solidity execution.
- Inject explicit challenge equality checks into generated EVM loader output and assert pass/fail behavior in revm.

Reproduce:
- `cargo test -p snark-verifier --features revm -- --nocapture`
- `cargo clippy -p snark-verifier --lib --tests --features revm -- -D warnings`

Extent:
- 1 file changed: `snark-verifier/src/system/halo2/transcript/evm.rs`

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…, and sharded verifiers

What:
- Add a comprehensive `midnight_adapter::evm` test module under `snark-verifier-sdk`.
- Build deterministic Midnight Poseidon proofs and collect the complete Rust verifier Fiat-Shamir challenge sequence.
- Parse generated EVM verifier runtime to locate challenge stores, inject equality checks against Rust challenges, and assert behavior for:
  - unrolled Solidity verifier execution,
  - unrolled-sharded dispatcher+shard execution,
  - negative controls with a mutated challenge.
- Extend coverage to a small case matrix: `(repeats=1, k=6, seed=7)`, `(1,6,11)`, `(4,8,7)`.

Why:
- We needed stronger end-to-end confidence that generated Solidity verifier flows preserve the same FS transcript as native Rust verification, not just a single-path sanity check.
- The negative controls ensure the test actually detects transcript divergence.

How:
- Reuse `MidnightProofBundle` path to build verifier loaders from real proof artifacts.
- Instrument the generated EVM program by appending transcript equality assertions at challenge memory locations.
- Execute both unrolled and sharded verifiers in revm with the same calldata and validate pass/fail expectations.

Reproduce:
- `cargo test -p snark-verifier-sdk --lib --features midnight,loader_evm,revm midnight_poseidon_full_fiat_shamir_transcript_matches_rust_and_sharded_solidity -- --nocapture`
- `cargo test -p snark-verifier-sdk --lib --features midnight,loader_evm,revm -- --nocapture`
- `cargo clippy -p snark-verifier-sdk --lib --tests --features midnight,loader_evm,revm -- -D warnings`

Extent:
- 1 file changed: `snark-verifier-sdk/src/midnight_adapter/evm.rs`

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
What:
- Extend `snark-verifier-sdk/src/midnight_adapter/evm.rs` transcript tests to compare full Fiat-Shamir trace events, not only challenge vectors.
- Add trace event capture (`Absorb`, `Squeeze{hash,challenge}`) for a new `TracingMidnightEvmHash` used in Midnight verifier runs.
- Add native snark-verifier transcript wrapper that records the same full event stream and asserts recomputed hash->challenge consistency.
- Verify Midnight trace == snark-verifier trace, then enforce those expected challenges in both unrolled and sharded EVM verifier execution (plus mutated negative controls).

Why:
- Challenge-only checks can miss divergence in absorb ordering/bytes while still producing partial signals.
- Full event-stream equivalence gives stronger guarantees that transcript semantics match between Midnight verifier, Rust verifier flow, and generated Solidity verifier execution.

How:
- Instrument Midnight transcript hash implementation to store deterministic per-step events.
- Instrument native transcript wrapper to record absorbed bytes, squeeze hashes, and reduced challenges.
- Convert both into a common trace model and compare for exact equality before executing EVM verifiers.
- Keep existing multi-case matrix and both positive/negative execution checks.

Reproduce:
- `cargo test -p snark-verifier-sdk --lib --features midnight,loader_evm,revm midnight_poseidon_full_fiat_shamir_transcript_matches_rust_and_sharded_solidity -- --nocapture`
- `cargo test -p snark-verifier-sdk --lib --features midnight,loader_evm,revm -- --nocapture`
- `cargo clippy -p snark-verifier-sdk --lib --tests --features midnight,loader_evm,revm -- -D warnings`

Extent:
- 1 file changed: `snark-verifier-sdk/src/midnight_adapter/evm.rs`

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
What:
- Vendors midnight-proofs v0.7.0 into vendor/midnight-proofs and patches crates-io resolution via Cargo.toml.
- Exposes verifier-side trace internals needed by downstream tests (public fields/clone derives on verifier trace structs).
- Adds plonk::prepare_with_trace returning VerifierAlgebraicTrace with parsed trace, sampled challenges, intermediate evaluations, and query count.

Why:
- We need deterministic access to Midnight verifier internals to compare Rust and Midnight verifier variable-level behavior, not only FS transcript squeezes.

How:
- Switched dependency to local path patch in root Cargo.toml.
- Added a trace-producing verification path in vendor/midnight-proofs/src/plonk/verifier.rs.
- Updated related verifier submodules (lookup/permutation/trash/vanishing/traces) to expose the data needed for assertions.

Reproduce:
- cargo test -p snark-verifier-sdk --lib --features midnight,loader_evm,revm midnight_poseidon_full_fiat_shamir_transcript_matches_rust_and_sharded_solidity -- --nocapture
- cargo clippy -p snark-verifier-sdk --lib --tests --features midnight,loader_evm,revm -- -D warnings

Extent:
- 81 files changed, ~25.9k insertions (full crate vendor + trace API exposure).

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…fier

What:
- Extends midnight_adapter EVM tests to collect a full verifier-variable trace from Midnight via prepare_with_trace.
- Collects Rust-side verifier variables from PlonkProof parsing/verification.
- Adds deterministic equivalence checks for challenge sequence, opening challenge (z == x), and ordered evaluation-read stream.
- Preserves existing transcript equality checks and unrolled/sharded Solidity positive+negative controls.

Why:
- FS transcript equality alone can miss verifier-internal divergence.
- This adds coverage for internal verifier state that directly drives algebraic checks and opening preparation.

How:
- Uses CircuitTranscript<TracingMidnightEvmHash> + vendor trace API to obtain Midnight verifier internals.
- Replays snark-verifier parsing path with tracing transcript to extract corresponding Rust-side variables.
- Normalizes values to EVM byte encoding and compares field-by-field in the test harness.

Reproduce:
- cargo test -p snark-verifier-sdk --lib --features midnight,loader_evm,revm midnight_poseidon_full_fiat_shamir_transcript_matches_rust_and_sharded_solidity -- --nocapture
- cargo clippy -p snark-verifier-sdk --lib --tests --features midnight,loader_evm,revm -- -D warnings

Extent:
- 1 file changed in snark-verifier-sdk/src/midnight_adapter/evm.rs (+197/-36).

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
… trace

What:
- Extends the Midnight EVM transcript equivalence harness to parse challenge-store pointers from generated Solidity assembly.
- Adds assembly extraction for both unrolled and sharded Solidity outputs.
- Adds explicit assertion that assembly-derived challenge-store pointers match runtime-emitted challenge-store pointers before injecting runtime checks.
- Keeps existing positive/negative revm execution checks for unrolled and sharded verifier paths.

Why:
- Runtime-only checks verify behavior, but do not directly validate that the IR→Solidity assembly pass preserves transcript challenge-store structure.
- This closes that gap by binding assembly artifacts to the runtime trace points used for challenge assertions.

How:
- Added parsers for the pattern mstore(<ptr>, mod(hash, f_q)) in Solidity source and runtime blocks.
- Added assert_assembly_trace_ptrs_match_runtime to compare normalized pointer sets.
- Updated loader builders to perform assembly-vs-runtime validation first, then inject expected challenge checks on a fresh loader instance.

Reproduce:
- cargo test -p snark-verifier-sdk --lib --features midnight,loader_evm,revm midnight_poseidon_full_fiat_shamir_transcript_matches_rust_and_sharded_solidity -- --nocapture
- cargo clippy -p snark-verifier-sdk --lib --tests --features midnight,loader_evm,revm -- -D warnings

Extent:
- 1 file changed: snark-verifier-sdk/src/midnight_adapter/evm.rs
- 98 insertions, 9 deletions.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Switch unrolled sharding from delegatecall to explicit state-carrying call semantics so memory-dependent shards remain sound, and harden dispatcher behavior for empty shard sets.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
What:
- add an ignored integration test at snark-verifier-sdk/tests/midnight_ivc_sharded_calldata_fuzz.rs
- generate a real Midnight IVC bundle, build unrolled-sharded verifier artifacts, and deploy/call them in revm
- assert baseline calldata verifies, then fuzz-mutate calldata bits and assert every mutated case fails

Why:
- lock in the sharded verifier safety invariant that invalid calldata must not verify
- prevent regressions of the prior sharding bug where cross-shard state loss could make invalid inputs pass

How:
- reuse the IVC circuit/proof flow from the example to produce realistic artifacts and calldata
- run deterministic fuzzing (ChaCha8Rng seed=7, 12 single-bit mutations)
- validate failures via deploy_unrolled_sharded_and_call returning Err

Reproduce:
- cargo test -p snark-verifier-sdk --test midnight_ivc_sharded_calldata_fuzz --features midnight,loader_evm,revm -- --ignored --nocapture

Extent:
- test-only change, one new integration test file, no production code changes

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
jtcoolen and others added 7 commits April 12, 2026 23:55
What:
- add snark-verifier-sdk/tests/midnight_keccak_transcript_alignment.rs
- verify challenge outputs from MidnightEvmHash match snark-verifier EvmTranscript under EVM encoding semantics for scalar/ec-point absorbs

Why:
- transcript divergence across Midnight/native and generated Solidity semantics can silently break verification
- this adds a direct regression guard on Fiat-Shamir challenge consistency at the SDK boundary

How:
- compute expected challenges with Midnight transcript hash (MidnightEvmHash)
- replay identical absorbs/squeezes through EvmTranscript on NativeLoader
- compare both challenge scalars after deterministic field repr conversion

Reproduce:
- cargo test -p snark-verifier-sdk --test midnight_keccak_transcript_alignment --features midnight,loader_evm -- --nocapture

Extent:
- test-only change; one new integration test file; no production code changes

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
What:
- refactor IVC fuzz test to reuse generated artifacts via OnceLock cache
- add structure-aware rejection corpus (zeroed/truncated/swapped payload variants)
- add unrolled-vs-sharded differential verdict parity test on shared mutation corpus
- add bytecode tampering checks for shard and dispatcher deployments

Why:
- single bit-flip fuzzing alone is a narrow guard and can miss acceptance-edge regressions
- we need stronger guarantees that sharded verification rejects malformed calldata, stays behaviorally aligned with unrolled verification, and fails under artifact tampering

How:
- introduce helpers for artifact generation and acceptance checks (unrolled + sharded)
- split mutations into rejection-required vs parity-only corpora
- run multiple ignored slow tests that deploy artifacts in revm and assert explicit outcomes

Reproduce:
- cargo test -p snark-verifier-sdk --test midnight_ivc_sharded_calldata_fuzz --features midnight,loader_evm,revm -- --ignored --nocapture

Extent:
- test-only changes in one file: snark-verifier-sdk/tests/midnight_ivc_sharded_calldata_fuzz.rs
- no production verifier/runtime code changes

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant