---
description: From SNARKs to EVM
---

# Verifying ZK SNARKS on Ethereum

Most of our smart contracts are very simple because the heavy lifting is done in a SNARK. To ensure full trustless-ness, these SNARKs must be verified by the EVM.&#x20;

We do this by programmatically generating a specialized smart contract for verifying each SNARK that we use. We do this using the [`snark-verifier`](https://github.com/axiom-crypto/snark-verifier/tree/main/snark-verifier) library, developed by the [Privacy Scaling Explorations](https://github.com/privacy-scaling-explorations/snark-verifier) group at the Ethereum Foundation, which generates Yul code for verifying any given ZK circuit.&#x20;

[Here](https://github.com/axiom-crypto/axiom-eth/blob/ea6bbce119ec3bda2b9b6bfdf5012893c1716bbc/src/block_header/helpers.rs#L349) is an example of how we generate the Yul code for the `AxiomV0` Verifier contract. All of the Yul code we used is open sourced, and we will soon be providing further instructions on how you can check the generation yourself.

We compiled the Yul code to bytecode using the command

```
solc --yul <YUL FILE> --bin | tail -1 > <BYTECODE FILE>
```

using `solc` Version: 0.8.17+commit.8df45f5f.Linux.g++.&#x20;

We encourage you to check for yourself that the following Yul files match the deployed bytecode!

<table><thead><tr><th width="304">Contract</th><th data-type="content-ref">Yul Code</th></tr></thead><tbody><tr><td><a href="https://etherscan.io/address/0xf0e3b9aada6d89ddeb34aab7e9cd1744cf90d82f#code"><code>AxiomV0</code> Verifier</a></td><td><a href="https://github.com/axiom-crypto/axiom-eth/blob/ea6bbce119ec3bda2b9b6bfdf5012893c1716bbc/data/headers/mainnet_10_7.yul">https://github.com/axiom-crypto/axiom-eth/blob/ea6bbce119ec3bda2b9b6bfdf5012893c1716bbc/data/headers/mainnet_10_7.yul</a></td></tr><tr><td><a href="https://etherscan.io/address/0xbf2c05d0362a640629b9b98be4c4e4f9a8e22841#code"><code>AxiomV0</code> Historical Verifier</a></td><td><a href="https://github.com/axiom-crypto/axiom-eth/blob/ea6bbce119ec3bda2b9b6bfdf5012893c1716bbc/data/headers/mainnet_17_7.yul">https://github.com/axiom-crypto/axiom-eth/blob/ea6bbce119ec3bda2b9b6bfdf5012893c1716bbc/data/headers/mainnet_17_7.yul</a></td></tr><tr><td><a href="https://etherscan.io/address/0xf9a86d0a0e0a8eddee4d518b257b185b717a543a#code"><code>AxiomV0StoragePf</code> Verifier</a></td><td><a href="https://github.com/axiom-crypto/axiom-eth/blob/b01155cc930eadb7c86d78cdfd9678c7809fab35/data/storage/storage_ts.yul">https://github.com/axiom-crypto/axiom-eth/blob/b01155cc930eadb7c86d78cdfd9678c7809fab35/data/storage/storage_ts.yul</a></td></tr><tr><td><a href="https://etherscan.io/address/0x4A6aa078188762aE520B7e6d8621ee045410D3D0#code"><code>AccountAge</code> Verifier</a></td><td><a href="https://github.com/axiom-crypto/axiom-apps/blob/2d1d2125904411e83b992ef7c2d429f6aed49f84/account-age/contracts/yul/mainnet_10.yul">https://github.com/axiom-crypto/axiom-apps/blob/2d1d2125904411e83b992ef7c2d429f6aed49f84/account-age/contracts/yul/mainnet_10.yul</a></td></tr><tr><td><a href="https://etherscan.io/address/0x0792878cb9cfd5f2ef0f90e3090255684ac3ae04#code"><code>UniswapV2Twap</code> Verifier</a></td><td><a href="https://github.com/axiom-crypto/axiom-apps/blob/0f16ba49d0b5bafeb655fc2325008796a1126327/uniswap-twap/contracts/yul/mainnet.yul">https://github.com/axiom-crypto/axiom-apps/blob/0f16ba49d0b5bafeb655fc2325008796a1126327/uniswap-twap/contracts/yul/mainnet.yul</a></td></tr></tbody></table>

The constructors for `AxiomV0`, `AxiomV0StoragePf`, `AccountAge`, `UniswapV2Twap` were called with their respective SNARK verifier contract addresses.&#x20;

When you call a function in one of the latter contracts, such as `AccountAge`'s `verifyAge` function, you must supply a ZK SNARK proof in the form of a `bytes` array in the calldata. The contract then calls the above verifier contract to verify the SNARK proof.

### Diving Deeper: SNARK Aggregation

Often, our SNARKs are too large to be directly verified on-chain with just one pass of `snark-verifier` (the contract hits the EVM bytecode limit!). In other cases, to reduce gas costs we would like to verify multiple SNARKs in one contract call simultaneously. We solve both of these problems by recursively aggregating our SNARKs using `snark-verifier`. This means that we take a batch of SNARKs, create a new SNARK that verifies the previous batch are all correct, and then verify this last SNARK on-chain with Yul code. The concept of recursive aggregation is very complex  :exploding\_head:, and we refer to this [post](https://0xparc.org/blog/groth16-recursion) for a more in-depth exploration.&#x20;
