Skip to content

feat: add the Groth16 terminal verifier, its export tooling, and cost measurements - #5

Merged
eigmax merged 9 commits into
ProjectZKM:mainfrom
felicityin:docs/verifier-cost-table
Nov 5, 2026
Merged

eigmax merged 9 commits into
ProjectZKM:mainfrom
felicityin:docs/verifier-cost-table

Conversation

@felicityin

@felicityin felicityin commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Adds the Groth16 terminal path for the shrink proof. The Groth16 verifier and its adapter now live in contracts/ziren-groth16-verifier, a standalone Foundry project pinned to solc 0.8.30.
  • Adds ZirenGroth16ProofVerifier, which implements IZirenProofVerifier. The interface is in contracts/shared/ and both Foundry projects import it through the @ziren-shared remapping, so there is one copy.
  • Adds crates/ziren-groth16-export (export_groth16), which exports a Groth16 terminal proof as JSON.
  • Adds scripts/groth16_args.py and scripts/groth16_verify.py. The verify script builds the contracts, deploys the verifier and adapter to a local node, checks the accept and reject cases, and reports gas. It takes the recursion root from the proof's public inputs and computes the output digest from public_values.bin.
  • Updates the verifier cost table to the current shrink proof, and adds the zero-run transport cost.
  • Documents the full flow in docs/groth16-verification.md.

Measurements

Groth16 terminal, fibonacci guest (input 10), local Osaka Anvil with --gas-limit 60000000, optimizer enabled:

Cost Gas
Verification transaction 233,450
of which calldata (388-byte verify call) 5,092
Execution 207,358
Deploy Groth16Verifier 1,135,523
Deploy ZirenGroth16ProofVerifier 326,771

Shrink proof (206,726-byte packet): verification 13,751,027 gas, deployment of all modules 144,547,906 gas. With zero-run transport: verification 13,628,720 gas.

Testing

  • forge test in contracts/ziren-groth16-verifier: 9 tests pass. Four of them run the real Groth16Verifier on the fixture proof.
  • forge test in contracts/metis-output-oracle: 12 tests pass.
  • scripts/groth16_verify.py ran end to end on the box anvil, and every check passed with exit code 0.
  • The export example was built and run on the box (CPU only, CUDA_VISIBLE_DEVICES=).

Not covered / open questions

  • Recursion root. The adapter uses the root the prover computes from the current vk_map.bin (0x007ad394…).
  • Exit code. It is asserted inside the recursion core circuit, not exposed as a Groth16 public input. The wrap chain was not traced for every path.
  • Digest mapping. The output digest → input[1] mapping (SHA-256 of the committed public values, top three bits cleared) is checked only for the fibonacci statement. Metis aggregation outputs have not been run through this path.
  • Verifier pragma. Groth16Verifier.sol is a copy of the Ziren v2.0.0 verifier with pragma changed to 0.8.30.
  • Gas depends on the build. Measured figures use optimizer-enabled builds. The earlier optimizer-off figures are not reproduced here.

Measure a complete 206,726-byte raw packet on a local Osaka Anvil node
with the enforced 16,777,216-gas cap: verification 13,751,027 gas
(calldata 3,173,300), deployment of all modules 144,547,906 gas. The same
build forked from Sepolia at block 12066266 gives identical figures.

The previous row was measured on a 182 KB packet from Ethereum block
26240087 and is replaced, since it no longer describes the current proof.
Measure the same complete shrink proof with the lossless field zero-run
and full-hash dictionary transport: verification 13,628,720 gas, calldata
2,845,696 gas, packet 182,190 bytes, against 13,751,027 gas for the raw
packet. Deployment is unchanged at 144,547,906 gas because it depends on
bytecode size, not on the packet transport.
Move the Groth16 verifier and its adapter into contracts/ziren-groth16-verifier
as a standalone Foundry project pinned to solc 0.8.30. The adapter is renamed
ZirenGroth16ProofVerifier and implements IZirenProofVerifier, which is renamed
from IZirenShrinkVerifier and shared with the metis output oracle.

Add crates/ziren-groth16-export, which exports a Groth16 terminal proof as
JSON, and scripts/groth16_args.py and scripts/groth16_verify.py, which
convert the proof, deploy the verifier and adapter to a local node, and check
the accept and reject cases. The output digest is computed from
public_values.bin and must match the proof's committed value.

Document the flow in docs/groth16-verification.md and link it from
docs/development.md.
Move IZirenProofVerifier to contracts/shared and import it from both Foundry projects through the @ziren-shared remapping, replacing the two copies. Reword the interface doc to describe the contract rather than the shrink entry.

Factor the repeated cast calls in groth16_verify.py into a helper, keep the adapter transaction check with the others, and wrap long lines. groth16_args.py returns a dataclass and reads the proof with Path.read_text. Reflow the adapter's contract comment.
Add Groth16VerifierTest, which runs the real Groth16Verifier on the fibonacci fixture proof: the proof and a public input are accepted or rejected as expected, and the adapter accepts the fixture packet through the real verifier.

Update the measured figures in docs/groth16-verification.md and the shrink verifier README to the optimizer-enabled build. The verification transaction is 233,450 gas, the calldata share is 5,092 gas, the verifier deployment is 1,135,523 gas and the adapter deployment is 326,771 gas. Describe the proof as a Groth16 proof of the fibonacci guest and drop the earlier optimizer-off figures.
@felicityin felicityin changed the title doc: update the verifier cost table to the current shrink proof feat: add the Groth16 terminal verifier, its export tooling, and cost measurements Nov 5, 2026
Split export_groth16 into three modes. prove runs setup, core proving, compression and native shrink, and saves shrink.bin and public_values.bin. replay wraps a saved shrink.bin with wrap_bn254 and wrap_groth16_bn254 without proving again. groth16 runs both steps in one process and writes only the proof JSON.

Add zkm-primitives and bincode to the crate for the saved proof files, and update the Groth16 verification doc and the crate README to describe the modes.
replay now reads groth16_proof.json from a prove output directory and checks its layout. It runs no shrink, wrap or proving, and does not verify the proof cryptographically. Its argument check is fixed: replay takes two arguments, so the top-level check now requires two and each mode checks its own count.

Restructure the export example around three modes. prove runs the whole chain through a prove helper. groth16 takes a saved compressed proof, and both groth16 and prove share groth16_from_compressed, which was previously named wrap_to_json. read_compressed rejects files shorter than the SDK tag. Name the encoded proof length as a constant.

Update the crate README and the verification doc to describe the new modes and outputs.
@eigmax
eigmax merged commit f7fcb14 into ProjectZKM:main Nov 5, 2026
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