Take Groth16 verification on chain.

Generate a verifier for each Groth16 verification key, convert Rust proofs into the EIP-2537 transport, and verify the same public statement from Solidity.

Know the execution target

The generated contract uses the BLS12-381 G1 MSM precompile at 0x0c and pairing precompile at 0x0f, as defined by EIP-2537. Deploy only to a network and EVM revision where those precompiles are active. An unavailable precompile is treated as verification failure.

The verifier is circuit-specific because it embeds the Groth16 verification key and exact public-input count. A new trusted setup or circuit shape produces a new key and therefore requires a newly generated contract.

Ethereum compatibilityEIP-2537 was activated on Ethereum with Pectra. Other EVM networks can have different activation schedules; check the target chain rather than assuming that Solidity support implies precompile support.

Generate the circuit-specific verifier

Persist the canonical verification key with VerifyingKey::to_bytes(), then run the repository exporter. Select the same supported curve backend used by the rest of the application.

terminal
cargo run --release -p dusk-groth16 \
  --bin dusk-groth16-solidity \
  --no-default-features \
  --features=bls-backend-blst,std -- \
  generate-verifier \
  verifying-key.bin \
  ProductVerifier \
  ProductVerifier.sol

The same operation is available as the allocation-only Rust API:

export.rs
let source = verifying_key.solidity_verifier("ProductVerifier")?;
std::fs::write("ProductVerifier.sol", source)?;

Convert the proof transport

The crate's canonical Groth16 proof remains a 192-byte compressed (A, B, C) encoding. EIP-2537 consumes uncompressed, big-endian field elements, so the generated contract instead accepts a 512-byte transport containing (-A, B, C):

  • bytes 0–127: -A, an EIP-2537 G1 point;
  • bytes 128–383: B, an EIP-2537 G2 point in c0 || c1 coefficient order;
  • bytes 384–511: C, an EIP-2537 G1 point.

Convert a proof directly in Rust with proof.to_eip2537(), or convert a saved canonical proof:

terminal
cargo run --release -p dusk-groth16 \
  --bin dusk-groth16-solidity \
  --no-default-features \
  --features=bls-backend-blst,std -- \
  encode-proof proof.bin proof.eip2537.bin
Keep both formats distinctThe 512-byte value is an EVM transport, not a new canonical proof encoding. Store and exchange canonical proof bytes unless the receiving boundary explicitly requires EIP-2537 calldata.

Preserve the public statement

ProductVerifier.sol
bool valid = verifier.verifyProof(
    eip2537Proof,
    publicInputs
);

The ABI is verifyProof(bytes,uint256[]). Public inputs must be canonical BLS scalar-field values and must appear in Composer emission order—the same order returned by the Rust prover and expected by the Rust verifier. Build these values from trusted application state rather than accepting an untrusted array merely because it arrived next to a proof.

Exercise the cross-language boundary

The checked-in Foundry project compiles a deterministic circuit-specific verifier and verifies a proof generated by the Rust stack. It also covers well-formed but incorrect proofs, wrong public inputs, non-canonical scalars, incorrect lengths, identity proof elements, non-canonical base-field coordinates, off-curve points, and wrong-subgroup points in every proof element.

terminal
make solidity-fmt CHECK=1
make solidity-test
forge build --root crates/groth16/tests/solidity \
  test/fixtures/SquareVerifier.sol --sizes

Foundry must support the Prague EVM revision. The deterministic fixture setup seed is for reproducible tests only and must never be used for production keys.

Review the security boundaries

  • The current Groth16 setup is circuit-specific and single-party. Generate setup randomness securely and destroy it; MPC ceremony flows are not implemented.
  • The contract rejects identity proof elements and public inputs outside the scalar field. The target EVM's EIP-2537 implementation validates every base-field coordinate, curve equation, and subgroup for proof and verification-key points. The contract checks call success and exact return sizes and fails closed.
  • Malformed EIP-2537 input burns all gas forwarded to a precompile. Generated contracts cap forwarded gas according to the finalized operation schedule.
  • Verification-key query points are embedded in runtime bytecode. Circuits with many public inputs must be checked against the target chain's contract-size limit.
  • The verifier does not prove that the embedded key came from a trustworthy setup or that its parameters are algebraically consistent. Key provenance and setup integrity remain application responsibilities.
  • The generator and contract are licensed under MPL-2.0. They have not yet undergone a dedicated security audit.