estimation
In this document we make some back-of-the-envelope computations to see how feasible it is to verify SNARKs on main-net. We have some preliminary benchmarks made by Kenneth that show how expensive each function is. The goal of this document is to use those values and estimate SNARK verification on-chain. Similarly, we will estimate how much data the script needs to process, to understand if we are within the script data budget.
We analyse two SNARKs systems, Groth16 and vanilla-Plonk.
The current limit for on-chain scripts is 10,000,000,000 of the units in the file, but the basic Plutus stuff will add a lot on top of the costs of executing the builtins.
The values of interest for this analysis are the following (were x is the size of the input in bits divided by 64):
bls12_381_G1_compress : 3341914
bls12_381_G1_uncompress : 16511372
bls12_381_G1_add : 1046420
bls12_381_G1_equal : 545063
bls12_381_G1_hashToCurve : 66311195 + 23097*x
bls12_381_G1_mul : 94607019 + 87060*x (we use 94955259, with x = 4)
bls12_381_G1_neg : 292890
bls12_381_G2_compress : 3948421
bls12_381_G2_uncompress : 33114723
bls12_381_G2_add : 2359410
bls12_381_G2_equal : 1102635
bls12_381_G2_hashToCurve : 204557793 + 23271*x
bls12_381_G2_mul : 190191402 + 85902*x
bls12_381_G2_neg : 307813
bls12_381_GT_finalVerify : 388656972
bls12_381_GT_millerLoop : 402099373
bls12_381_GT_mul : 2533975
blake2b_256 : 358499 + 10186*x (521475, with x = 16)
addInteger : 85664 + 712*max(x,y) (88512, with x = y = 4)
multiplyInteger : 1000 + 55553*(x+y) (641924, with x = y = 4, and we include the price of modular reduction, as we need one per mult)
divideInteger : if x>y
then 809015 + 577*x*y
else 196500
modInteger : 196500
expInteger : We estimate 32 mults and adds (23373952)
The verifier needs to run the following check:
\[e([A]_1, [B]_2) =? e([\alpha]_1, [\beta]_2) * e\left(\sum_{i=0}^l a_i \left[\frac{\beta * u_i(x) + \alpha * v_i(x) + w_i(x)}{\alpha}\right]_1, [\gamma]_2\right) * e([C]_1, [\delta]_2)\]
Note that the above is a pairing equation, meaning that we are checking if
\(e(a,b) = e(c,d) * e(e,f) * e(g,h)\). This check can be translated to:
\[
\begin{aligned}
lhs &\leftarrow ml(a,b) \\
rhs &\leftarrow ml(c,d) * ml(e,f) * ml(g,h) \\
\texttt{accept}&\iff \texttt{final_verify}(lhs, rhs)
\end{aligned}
\]
We can do some ZK engineering to bring the number of l to 1, with some minor effect on the verifier (in the best case, a hash function of some public values). This means that the number of operations required for a Groth16 verifier are:
bls12_381_G1_uncompress
(16511372)bls12_381_G2_uncompress
(33114723)bls12_381_G1_mul
(97392939)bls12_381_G1_add
(1046420)bls12_381_GT_millerLoop
(402099373)bls12_381_GT_mul
(2533975)bls12_381_GT_finalVerify
(388656972)This results in a total of 2,299,066,153. With the limit of 10,000,000,000, this means that a Groth16 proof uses around 23% of the execution budget. Note, however, that the basic Plutus operations add a lot on top of the costs of executing the builtins, so we shouldn't expect to reach the limit.
In terms of size, we are handling:
resulting in 608 bytes. The current limit for scripts is 16384 bytes, which means a Groth16 verifier uses less than 4% of the data limit per script.
We assume there are no public inputs, so l = 1.
The number of operations required for a vanilla plonk verifier are (the numbering refers to each step of the verifier as in the original paper as per Feb-2023):
bls12_381_G1_uncompress
(16511372)less than
checks, over Integer
type (free or failure)less than
check, over Integer
type (free or failure)blake2b_256
of at most 1KB (521475)multiplyInteger
(641924)divideInteger
(196500)multiplyInteger
(641924)multiplyInteger
(641924)addInteger
(88512)modInteger
(196500)bls12_381_G1_uncompress
(16511372)multiplyInteger
(641924)bls12_381_G1_mul
(97392939)bls12_381_G1_add
(1046420)bls12_381_G1_add
(1046420)multiplyInteger
(641924)bls12_381_G1_mul
(97392939)bls12_381_G1_add
(1046420)bls12_381_G1_uncompress
(16511372)multiplyInteger
(641924)addInteger
(88512)modInteger
(196500)bls12_381_G1_mul
(97392939)bls12_381_G2_uncompress
(33114723)bls12_381_G1_mul
(97392939)bls12_381_G1_add
(1046420)bls12_381_GT_millerLoop
(402099373)bls12_381_GT_finalVerify
(388656972)In summary, the number of operations are the following:
We can see that this is a bit more convoluted than Groth16.. But we have less miller loops. A preliminary check gets us a computation of 3,255,167,757, which is 33% of the execution budget.
For the amount of data that the verifier needs to operate with:
This results in 1280 bytes, which is around 8% of the allowed budget.