Skip to content

fix(aggregation mode): support SP1 v6 proofs on the local devnet - #2309

Merged
JuArce merged 1 commit into
feat/aggregation-mode-bump-sp1-v6from
fix/agg-mode-devnet-sp1-v6
Oct 15, 2026
Merged

JuArce merged 1 commit into
feat/aggregation-mode-bump-sp1-v6from
fix/agg-mode-devnet-sp1-v6

Conversation

@JuArce

@JuArce JuArce commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Why

After #2308, the local devnet can't verify SP1 v6 aggregated proofs:

  • The devnet SP1 gateway (0x06530DD1…) only has the v5.0.0 route.
  • The proof aggregation services only allow the old chunk aggregator vk.

This applies to both the anvil states and the ethereum-package preload in network_params.yaml. Also, current anvil versions (1.7.1) can't load the committed anvil states: they fail parsing the stored transactions.

How

This runs make anvil_upgrade_proof_aggregator_sp1 (the script added in #2308), after fixing it so it works with current anvil. It changes:

  • sp1 and alignedlayer anvil states: deploys the v6.1.0 Groth16 verifier (0xd7CA37ff…F77d) and adds route 0x4388a21c to the gateway.
  • alignedlayer state: allows 0x0061c444…3757 (SP1) in both proof aggregation services, the real one (0xcbEAF3BD…) and the mock one (0xB0D4afd8…).
  • network_params.yaml: the preloaded contracts are regenerated from the new alignedlayer state.
  • contracts/deployments/31337.json: records V6_1_0_SP1_VERIFIER_GROTH16.

Changes to the script:

  • It drops the stored transactions of the states and keeps the block headers. The accounts already hold the whole state, and the chain keeps its block number.
  • It works on temporary copies and only replaces the committed states if every step succeeds.
  • It refuses to run if a node is already listening on localhost:8545.
  • It regenerates the network_params.yaml preload, skipping the hardfork system contracts anvil adds (e.g. the EIP-2935 one), since the ethereum-package genesis already includes them.

The v5 route is kept, and nothing else changes:

  • Anvil states: besides the changes above, only the deployer and owner nonces change, plus the EIP-2935 contract that anvil 1.7.1 adds.
  • network_params.yaml: only the preload changes. It differs from before only in the gateway route slot, the new verifier, the allowlist slot of both services and the CREATE2 deployer nonce.

Verified

  • make anvil_start loads the new state with anvil 1.7.1. Route 0x4388a21c points to the new verifier, which reports VERSION() = v6.1.0 and VERIFIER_HASH() = 0x4388a21c…. The new vk is allowed in both services.
  • I checked the gateway with a real, public SP1 v6.1.0 Groth16 proof (sp1-sdk 6.2.4, from input-output-hk/plutus-groth16-wrapper). It verifies on Succinct's mainnet gateway and on the devnet gateway. A tampered copy reverts with ProofInvalid.
  • On devnet, verifyAggregationSP1 called from the aggregator address behaves as expected:
    • With the new vk, it passes the allowlist and reaches the v6.1.0 verifier.
    • With an unknown vk, it reverts with InvalidVerifyingProgram.
  • I built a genesis from the network_params.yaml preload and booted it with anvil --init. It has the same route and allowlist, and the real v6.1.0 proof verifies.
  • Full flow on the ethereum-package devnet (Kurtosis, Apple M3 Pro, CPU proving), following the steps below:
    • Gateway and DB:
      • The gateway accepted sp1_fibonacci_6_1_0.
      • The migrations, including failed, applied.
    • Aggregation:
      • The aggregator built the compressed proof (about 70 s) and the Groth16 proof (15.97M constraints, about 28 s).
      • The zkVM merkle root matched the one computed outside the zkVM.
    • On-chain:
      • verifyAggregationSP1 succeeded in a blob tx, with proof selector 0x4388a21c and vk 0x0061c444….
      • It emitted AggregatedProofVerified, and isMerkleRootVerified returned true.
    • Results:
      • The task ended verified.
      • make verify_aggregated_proof_sp1 found the proof in the aggregated proof.
    • Reproducibility: the SP1 programs built locally in Docker give the committed sp1_chunk_aggregator_vk_hash (0x0061c444…).

How to test (ethereum-package devnet)

These steps were run on macOS on Apple Silicon. They should work on Linux too.

0. Prerequisites

  • Docker running. On Apple Silicon, Docker Desktop runs the amd64 SP1 images itself.
  • Rust, cast (foundry), kurtosis, protoc and go, e.g. brew install kurtosis-tech/tap/kurtosis-cli protobuf go.
  • Check out this branch.

1. Patch the gnark library (one-time, only needed for native Groth16)

sp1-sdk 6.9.0's native-gnark feature doesn't build anymore. It depends on github.com/consensys/gnark-ignition-verifier, which was removed from GitHub and the Go proxy. That code is only used to set up the PLONK circuit, so for local tests we stub it in a local copy:

cargo fetch --manifest-path aggregation_mode/Cargo.toml
mkdir -p ~/sp1-gnark-patch
cp -R ~/.cargo/registry/src/index.crates.io-*/sp1-recursion-gnark-ffi-6.9.0 ~/sp1-gnark-patch/
cd ~/sp1-gnark-patch/sp1-recursion-gnark-ffi-6.9.0/go
cat > sp1/trusted_setup/trusted_setup.go <<'GO'
package trusted_setup

// Local stub: github.com/consensys/gnark-ignition-verifier is no longer downloadable.
// Only used to build the PLONK circuit, not to prove Groth16.

import (
	"github.com/consensys/gnark-crypto/kzg"
	"github.com/consensys/gnark/constraint"
)

func DownloadAndSaveAztecIgnitionSrs(startIdx int, fileName string) {
	panic("gnark-ignition-verifier is unavailable")
}

func ToLagrange(scs constraint.ConstraintSystem, canonicalSRS kzg.SRS) kzg.SRS {
	panic("gnark-ignition-verifier is unavailable")
}
GO
sed -i '' '/gnark-ignition-verifier/d' go.mod go.sum   # on Linux: sed -i
cd -

2. Build the aggregator

Build it before starting Kurtosis, so the two don't compete for Docker memory. The SP1 programs are built in Docker during this step.

GNARK_PATCH="patch.crates-io.sp1-recursion-gnark-ffi.path=\"$HOME/sp1-gnark-patch/sp1-recursion-gnark-ffi-6.9.0\""
RISC0_SKIP_BUILD=1 cargo build --config "$GNARK_PATCH" \
  --manifest-path aggregation_mode/proof_aggregator/Cargo.toml \
  --release --features prove,sp1-sdk/native-gnark --bin proof_aggregator_cpu

3. Start the devnet

make ethereum_package_start
cast block-number --rpc-url http://localhost:8545   # wait until it is > 0 (~2 min genesis delay)

4. Gateway and payments poller (one terminal each)

make agg_mode_gateway_start_ethereum_package          # Postgres + migrations + gateway
make agg_mode_payments_poller_start_ethereum_package

5. Pay and submit an SP1 v6 proof

make agg_mode_gateway_send_payment
make agg_mode_get_quotas               # wait until quota_limit is 32 (payment processed)
make agg_mode_gateway_send_sp1_proof   # "Proof submitted successfully. Task ID: ..."

6. Run the aggregator

Run the binary directly. make proof_aggregator_start_ethereum_package can rebuild it without native-gnark, which falls back to the Docker Groth16 image.

make reset_last_aggregated_block
AGGREGATOR=sp1 ./aggregation_mode/target/release/proof_aggregator_cpu config-files/config-proof-aggregator-ethereum-package.yaml

Expected logs, in order:

  1. Merkle root verification successful: roots match
  2. Proof sent and verified, tx hash ...
  3. Process finished successfully

The first run downloads the v6.1.0 Groth16 circuit files (6.2 GB) to ~/.sp1/circuits. Peak memory was about 12 GB.

7. Verify the proof inclusion like a user

make ethereum_package_inspect | grep cl-1-lighthouse   # beacon "http: 4000/tcp -> http://127.0.0.1:<port>"
make verify_aggregated_proof_sp1 NETWORK=devnet RPC_URL=http://localhost:8545 BEACON_URL=http://127.0.0.1:<port> FROM_BLOCK=0
# "Your proof has been verified in the aggregated proof with merkle root ..."
docker exec aggregation-mode-postgres-1 psql -U postgres -tAc "select status from tasks"   # verified

8. Tear down

# stop the gateway and poller (Ctrl+C)
make agg_mode_docker_clean
make ethereum_package_rm
git checkout -- aggregation_mode/Cargo.lock config-files/proof-aggregator.last_block_fetched.json   # the gnark patch changes Cargo.lock: don't commit it

On a Linux x86 machine with enough RAM, steps 1 and 2 can probably be skipped by running AGGREGATOR=sp1 make proof_aggregator_start_ethereum_package, which uses the Docker Groth16 image. That path was not tested. To test on anvil instead, use make anvil_start with the non-ethereum_package targets; there is no beacon node there, so step 7's inclusion check doesn't apply.

🤖 Generated with Claude Code

Run upgrade_proof_aggregator_sp1.sh on the devnet. It deploys the SP1
v6.1.0 Groth16 verifier, adds its route to the devnet SP1 gateway and
allows the new SP1 chunk aggregator vk in both proof aggregation
services. The anvil states and the ethereum-package preloaded contracts
are updated.

The script now drops the stored transactions, which current anvil
versions can't parse, and keeps the block headers. It also updates
network_params.yaml, skipping the hardfork system contracts that anvil
adds.
@claude

claude Bot commented Oct 14, 2026

Copy link
Copy Markdown

Reviewed: no security, bug or performance issues found. The only real code is upgrade_proof_aggregator_sp1.sh. It uses the well-known public anvil keys, only for local devnet. It cleans up its temp files and anvil process, and only replaces the committed states after every step succeeds. The rest of the diff is generated state and config. I didn't re-verify those generated artifacts.

@github-actions

Copy link
Copy Markdown

Codex Code Review

  • Medium — Script hangs indefinitely if Anvil fails to start. In upgrade_proof_aggregator_sp1.sh:63, the readiness loop retries forever without checking whether $anvil_pid has exited. A rejected snapshot or occupied port leaves the upgrade stuck permanently. Check the child process with kill -0 "$anvil_pid" and add a startup timeout that exits with an error.

No security vulnerabilities found in the diff.

@github-actions

Copy link
Copy Markdown

Changes to gas cost

Generated at commit: db3b574f65eea60057c94f5981029b6f511bb61f, compared to commit: 864b69bb291fca607dd85a43f55e3db3e5e46176

🧾 Summary (10% most significant diffs)

Contract Method Avg (+/-) %
AlignedLayerServiceManager createNewTask 0 ➖ 0.00%

Full diff report 👇
Contract Deployment Cost (+/-) Method Min (+/-) % Avg (+/-) % Median (+/-) % Max (+/-) % # Calls (+/-)
AlignedLayerServiceManager 4,398,072 (0) createNewTask 56,874 (-12) -0.02% 76,484 (0) 0.00% 77,050 (+12) +0.02% 77,800 (-48) -0.06% 257 (0)

@MarcosNicolau
MarcosNicolau self-requested a review October 15, 2026 20:40
@JuArce
JuArce merged commit bc2182f into feat/aggregation-mode-bump-sp1-v6 Oct 15, 2026
4 checks passed
@JuArce
JuArce deleted the fix/agg-mode-devnet-sp1-v6 branch October 15, 2026 20:49
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.

2 participants