Verifying ZK SNARKS on Ethereum
From SNARKs to EVM
Last updated
From SNARKs to EVM
Last updated
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.
We do this by programmatically generating a specialized smart contract for verifying each SNARK that we use. We do this using the snark-verifier
library, developed by the Privacy Scaling Explorations group at the Ethereum Foundation, which generates Yul code for verifying any given ZK circuit.
Here 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
using solc
Version: 0.8.17+commit.8df45f5f.Linux.g++.
We encourage you to check for yourself that the following Yul files match the deployed bytecode!
The constructors for AxiomV0
, AxiomV0StoragePf
, AccountAge
, UniswapV2Twap
were called with their respective SNARK verifier contract addresses.
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.
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 , and we refer to this post for a more in-depth exploration.