Skip to content

[one-shot implementation for discussion, not for merging]: feat(midnight-evm): add dispatcher-based partitioned verifier flow for Poseidon and IVC proofs#3

Open
jtcoolen wants to merge 40 commits intoEYBlockchain:mainfrom
jtcoolen:julian@sharded-contract
Open

[one-shot implementation for discussion, not for merging]: feat(midnight-evm): add dispatcher-based partitioned verifier flow for Poseidon and IVC proofs#3
jtcoolen wants to merge 40 commits intoEYBlockchain:mainfrom
jtcoolen:julian@sharded-contract

Conversation

@jtcoolen
Copy link

@jtcoolen jtcoolen commented Feb 27, 2026

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

Summary

This PR introduces and hardens a dispatcher-based partitioned (referred to as unrolled-sharded below) verifier path for Midnight EVM examples, so we can run benchmarks even when a single unrolled verifier contract would exceed EVM deployment/runtime size 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.

Testing

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

Align generated verifier source with Solidity 0.8.30 in the EVM codegen template.

- Change the emitted contract pragma in SolidityAssemblyCode from 0.8.19 to 0.8.30.
Adjust local EVM execution helpers so oversized verifier contracts can be deployed and profiled in simulation.

- Add serde_json dependency used by EVM utility flows.

- Silence non-critical solc stderr output during bytecode compilation.

- Relax revm code-size and gas-limit constraints in deploy_and_call for high-bytecode verifier benchmarks.
…ally

Vendor the halo2-lib workspace under vendor/halo2-lib and route both axiom-crypto and jtcoolen halo2-lib sources to local halo2-base/halo2-ecc paths.

This commit keeps the raw upstream snapshot state before applying the halo2-base BLS patches.
Apply upstream halo2-base commit 360a9d3 to the vendored tree so blsFr implements ScalarField in utils::scalar_field_impls.
Apply upstream halo2-base commit f56347d after 360a9d3, including 6-limb blsFq ScalarField support and halo2curves revision pinning in the vendored halo2-lib metadata.
Preserve the local vendored fix by reconstructing from little-endian bytes instead of assuming a [u64;4] representation.
Refresh Cargo.lock so halo2-base/halo2-ecc and halo2curves-axiom resolve to the pinned fork revisions deterministically.
Vendor halo2-base sources from jtcoolen/halo2-lib@f56347d without local modifications for reproducible builds.
Replace the hardcoded [u64;4] conversion with byte-based reconstruction so wider scalar fields are handled safely.
Migrate SDK proving/verifying type aliases and generic bounds from Bn256 to Bls12 across halo2 and EVM entry points.
Generalize EVM loader point encoding for wider base fields and update transcript IO sizing to distinguish proof-byte and EVM-memory representations.
Serialize G2 points as eight 32-byte words and update the EVM pairing call interface to consume the expanded layout.
Switch generated runtime precompile identifiers and calldata/returndata sizing to BLS12-381 G1/G2 operation layouts.
Use Prague EIP-2537 precompile addresses for BLS12-381 add, MSM, and pairing operations.
Remove the temporary legacy c1,c0 env-toggle path and keep a single canonical ordering for EVM pairing inputs.
Adjust limb decomposition parameters for BLS12-381 base-field arithmetic compatibility in halo2-ecc.
Align pinned toolchain with revm/alloy dependency MSRV requirements used by the BLS runtime path.
Update local EVM execution config/error reporting for Prague precompiles and tighten transcript runtime handling for allocated scalar/ec-point memory layout.
Update Cargo.lock to match runtime dependency/resolution changes introduced by the Prague revm hardening and toolchain alignment.
Capture solc stderr and include actionable diagnostics when bytecode generation fails or emits no output.
Keep the original arithmetization gate shape while moving the example flow to BLS12-381 params and EVM artifact generation/verification.
Factor reusable Halo2 layout and expression helpers so protocol builders can share remapping and selector logic.

- Add shared system::halo2::layout and system::halo2::expression modules.

- Switch Halo2 compiler internals to those helpers where metadata layout is unchanged.

- Replace iter::repeat_n with explicit repeat().take() and simplify native assertion control flow.
Extend PLONK protocol metadata so verifier flows can represent transcript/layout variants without overloading legacy fields.

- Add committed-instance and transcript extension fields on PlonkProtocol.

- Extend quotient metadata with chunk base and optional fixed chunk count.

- Wire Halo2 compiler defaults for the new metadata and re-export protocol types cleanly.
Extend PLONK proof parsing so protocols can hash committed-instance prefixes, bind optional instance lengths, and parse transcript extensions.

- Add read_with_committed_instances entrypoint and shared committed-instance absorption flow.

- Parse trailing challenges and extra commitments declared by protocol metadata.

- Support ZnMinusOne quotient recombination and account for extra commitments in query indexing.
Add deterministic unit tests for committed-instance transcript absorption and placeholder behavior.

- Verify committed-prefix plus hashed-length ordering in transcript writes.

- Verify deterministic errors for committed layout mismatches.

- Verify placeholder extension preserves declared instance-column layout.
Add Midnight-compatible KZG multiopen verification and expose an optional truncated challenge policy used by Midnight transcript variants.

- Implement Midnight KZG PCS proof reader/verifier for native and EVM loaders.

- Re-export Midnight PCS flavor from kzg modules and add truncated-challenges feature flag.

- Add EVM scalar truncation helper used by truncated challenge verification flows.
… memory

Harden EVM transcript absorption and decoding for identity points and non-contiguous memory sources.

- Re-copy disjoint memory-backed scalars/points into contiguous transcript buffer when required.

- Encode/decode point-at-infinity via EVM (0,0) sentinel consistently in native transcript flow.

- Add regression tests for identity encoding/decoding behavior.
…ntics

Optimize and harden EVM loader arithmetic paths used by generated verifiers.

- Route multi-scalar multiplication through batched G1MSM precompile calls.

- Add reusable staticcall helper, identity-safe constant point encoding, and zero-preserving scalar negation.

- Rewrite batch inversion flow to be zero-safe for aliased inputs and deterministic in memory-backed execution.
Wire optional Midnight dependencies into the SDK while preserving default feature behavior for existing users.

- Add Midnight optional dependencies and feature gate in snark-verifier-sdk Cargo manifest.

- Forward truncated-challenges feature to snark-verifier for aligned transcript behavior.

- Export midnight_adapter behind feature gating and update lockfile dependency graph.
Add SDK bridge primitives that map Midnight proof artifacts onto snark-verifier protocol and EVM verifier generation paths.

- Add conversion helpers and bundle constructors for Midnight params/VK/proof/instances.

- Add Midnight protocol builder preserving committed-instance, lookup, permutation, and trash layout semantics.

- Expose Solidity/bytecode/calldata generation and optional revm verification via MidnightProofBundle.
Add a Midnight Poseidon example and transcript adapter that exercise proof generation, verification, and EVM verifier artifact generation.

- Add Midnight EVM transcript hash helper with tests for scalar/point encoding behavior.

- Add midnight_poseidon_evm example to prove/verify and emit Solidity, bytecode, and calldata artifacts.

- Ignore generated Midnight artifact output directory and keep incidental example formatting tidy.
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.
@jtcoolen jtcoolen changed the title feat(midnight-evm): add dispatcher-based partitioned verifier flow for Poseidon and IVC proofs [one-shot implementation for discussion, not for merging]: feat(midnight-evm): add dispatcher-based partitioned verifier flow for Poseidon and IVC proofs Feb 27, 2026
@jtcoolen jtcoolen force-pushed the julian@sharded-contract branch from 889312f to 1b2f7df Compare February 27, 2026 18:13
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