Verifier Contracts

Verifier Contracts

Solidity Verifier ABI and Calldata Specification


SnarkSide’s privacy-preserving execution flow relies on on-chain verifier contracts to authenticate zero-knowledge proofs submitted by traders, relayers, and liquidators. These verifier contracts are generated from Groth16 .zkey files and deployed per circuit domain—e.g., IntentVerifier.sol, VaultVerifier.sol, OracleVerifier.sol.

Each verifier is a stateless, deterministic contract that validates SNARKs using elliptic curve pairings over the BN254 curve (also known as alt_bn128) and returns a boolean value indicating proof validity. This section defines the verifier contract structure, ABI interface, calldata encoding scheme, and SnarkSide’s approach to safe, gas-efficient verification logic on Ethereum-compatible chains.


Contract Generation Process

All verifier contracts are generated via snarkjs:

snarkjs zkey export solidityverifier circuit_final.zkey Verifier.sol

The output is a Solidity contract that exposes:

  • A verifyProof(...) function

  • Verification key constants

  • Elliptic curve pairing check logic (Pairing.sol)

SnarkSide wraps each generated verifier in a domain-specific interface for internal use.


Solidity Interface (Generic)

function verifyProof(
    uint[2] memory a,
    uint[2][2] memory b,
    uint[2] memory c,
    uint[] memory input
) public view returns (bool r);

Inputs:

  • a, b, c: Elements of the zkSNARK proof (G1, G2, G1)

  • input: Array of public inputs (field elements ∈ Fr)

Output:

  • r = true if proof is valid with respect to hardcoded verification key

  • r = false if invalid or input mismatch


Calldata Layout

Verifier calldata is manually constructed or handled by SnarkSide middleware. For Groth16 on BN254, a valid calldata structure includes:

a[0]             // G1 x-coordinate
a[1]             // G1 y-coordinate
b[0][0]          // G2 x c0
b[0][1]          // G2 x c1
b[1][0]          // G2 y c0
b[1][1]          // G2 y c1
c[0]             // G1 x-coordinate
c[1]             // G1 y-coordinate
input[0]...input[n] // Public inputs (Fr elements)

Example (IntentVerifier):

IntentVerifier.verifyProof(
  [a0, a1],
  [[b00, b01], [b10, b11]],
  [c0, c1],
  [intentHash, vaultRoot, fundingRate, expiration, oracleCommitment]
);

All field elements are 254-bit integers, encoded as 32-byte big-endian words for RPC compatibility.


Gas Benchmarks

SnarkSide circuits are optimized for:

  • Minimal public inputs (≤6)

  • Short proof size (~192 bytes)

  • Batching-friendly verification (upgradable)

Verifier
Public Inputs
Avg Gas Cost

IntentVerifier

5

~420,000

VaultVerifier

6

~480,000

OracleVerifier

3

~390,000

LiquidationVerifier

4

~450,000

All verifiers use precompiled pairing operations available in EVM (via opcode 0x08), which ensures constant-time on-chain performance.


Security Properties

  • Proofs are circuit-specific and cannot be reused cross-domain.

  • Verifier rejects any malformed calldata or mismatched public input.

  • Reentrancy is impossible (pure view functions).

  • Optional: logging of successful proof hashes for replay protection or attestation.


Internal Routing

SnarkSide’s contracts use a shared ZkVerifierRegistry that maps circuit type → verifier address. This allows flexible verifier upgrades (e.g., Groth16 → Halo2) and batching strategies.

mapping(bytes32 => address) public verifierOf;

Summary

SnarkSide verifier contracts are compact, deterministic, and gas-optimized modules for validating Groth16 zero-knowledge proofs on-chain. Through careful structuring of calldata, constraint ordering, and public input minimization, these verifiers form the foundation of SnarkSide’s trustless privacy-preserving execution engine. As recursion and new proving systems mature, future versions will support dynamic proof aggregation and on-chain recursive composition, enabling even more advanced forms of intent confidentiality and transaction obfuscation.

Last updated