- Building a SNARK
- Functional Commitment Scheme
- KZG PCS
- Polynomial Interactive Oracle Proof
- The Resulting SNARK
- Key Observations
- Useful Proof Gadgets
- PLONK

# Building a SNARK

There are various paradigms on building SNARKs, but the general paradigm is two step:

- A
**functional commitment scheme**, where most of cryptography takes place - A suitable
**interactive oracle proof**(IOP), where most of the information theory takes place

# Functional Commitment Scheme

Well, first, what is a commitment scheme? A cryptographic commitment is like a physical-world envelope. For instance, Bob can put a data in an envelope, and when Alice receives this envelope she can be sure that Bob has committed to whatever value is in it. Alice can later reveal that value.

The commitment scheme has two algorithms:

- $commit(m,r)→com$ for some randomly chosen $r$
- $verify(m,com,r)→accept or reject$

The scheme must have the following properties:

**Binding**: cannot produce two valid openings for $com$**Hiding**: $com$ reveals nothing about the committed data

There is a standard construction using hash functions. Fix a hash function $H:M×R→C$ where

- $commit(m,r)=H(m,r)$
- $verify(m,com,r)=accept ifcom=H(m,r)$

## Committing to a function

Choose a family of functions $F={f:X→Y}$. What does it really mean to commit to a function? Well, consider the following interaction:

sequenceDiagram actor P as Prover actor V as Verifier note over P: f ∈ F P ->> V: com_f := commit(f, r) V ->> P: x ∈ X P ->> V: y ∈ Y, proof π note over V: Accept or Reject

Here, the proof $π$ is to show that $f(x)=y$ and $f∈F$.

More formally, a functional commitment scheme for $F$:

- $setup(λ)→pp$ is public parameters $pp$
- $commit(pp,f,r)→com_{f}$ is commitment to $f∈F$ with $r∈R$
- this should be a
**binding**scheme - optionally, it can be
**hiding**, which is good for zk-SNARK

- this should be a
- $eval(P,V)$ with a prover $P$ and verifier $V$, where for a given $com_{f}$ and $x∈X,y∈Y$:
- $P(pp,f,x,y,r)→short proofπ$, which is a SNARK for the relation: $f(x)=y$ and $f∈F$ and $commit(pp,f,r)=com_{f}$.
- $V(pp,com_{f},x,y,π)→accept or reject$
- Basically, the $eval$ system is a SNARK

### Function Families

There are 3 very important functional commitment types:

**Polynomial Commitments**: Committing to a univariate polynomial $f(X)∈F_{p}[X]$ where that fancy notation stands for the set of all univariate polynomials of degree at most $d$.**Multilinear Commitments**: Committing to a multilinear polynomial in $F_{p}[X_{1},…,X_{k}]$ which is the set of all the multilinear polynomials in at most $k$ variables.- A multilinear polynomial is when all the variables have degree at most 1. Here is an example: $f(x_{1},…,x_{7})=x_{1}x_{3}+x_{1}x_{4}x_{5}+x_{7}$.

**Linear Commitments**: Committing to a linear function $f_{v}(u)=⟨u,v⟩=∑_{i=1}u_{i}v_{i}$ which is just the dot product of two vectors.

Different SNARK systems may use different commitments. Note that linear commitments can be transformed into multilinear commitments, and those can be transformed into polynomial commitments. A good exercise!

From here on we will talk about Polynomial Commitments.

### Polynomial Commitment Scheme (PCS)

A PCS is a functional commitment for the family $F=F_{p}[X]$. The prover commits to a univariate polynomial $f∈F_{p}[X]$, later, they can prove that $v=f(u)$ for some public $u,v∈F_{p}$. As this is a SNARK, the proof size and verifier time should be $O_{λ}(gd)$.

- Using basic elliptic curves: Bulletproofs
- Using bilinear groups: KZG (trusted setup) (2010), Dory (transparent) (2020)
- Using groups of unknown order: Dark (20)
- Using hash functions only: based on FRI

We will focus on KZG, as it is much simpler and commonly used.

# KZG PCS

The name stands for Kate-Zaverucha-Goldberg. It operates on a cyclic group $G:={0,G,2G,3G,…,(p−1)G}$ of order $p$ where $G$ is the generator. Note that in such a setting, $pG=0$.

## Setup

The setup phase $setup(λ)→pp$ works as follows:

- Sample random $α∈F_{p}$
- $pp=(H_{0}=G,H_{1}=αG,H_{2}=α_{2}G,…,H_{d}=α_{d}G)∈G_{d+1}$
- Delete $α$ or you get in trouble! (trusted setup)

Note that you can't do something like $H_{0}/H_{1}=α$ because division is not defined for these guys!

## Commitment

The commitment phase $commit(pp,f)→com_{f}$ is as follows:

- Compute $com_{f}:=f(α)G∈G$, but wait we don't have $α$ so what do we do?
- We have a univariate polynomial $f(X)=f_{0}+f_{1}X+…+f_{d}X_{d}$
- We use the public parameters $pp$ to compute $com_{f}=f_{0}H_{0}+f_{1}H_{1}+…+f_{d}H_{d}$
- If you expand $H$ in there, you will notice that the entire thing is equal to $f(α)G$.
- This is a
**binding**commitment, but it is not**hiding**for now.

## Evaluation

We have $commit(pp,f)→com_{f}$ where $com_{f}=f(α)G∈G$ at this point. The evaluation phase $eval(P,V)$ will work as follows:

- Prover $P(pp,f,u,v)$ has the goal of proving $f(u)=v$ and generate some proof $π$.
- Verifier $V(pp,com_{f},u,v,π)$ will verify that proof.

The trick is to see that if $f(u)=v$ then $u$ is a root of $f^ =f−v$. It is because $f^ (u)=f(u)−v=v−v=0$. There is a very well known result of this, that $(X−u)$ divides $f^ $. This also means that there is quotient polynomial $∃q∈F_{p}[X]$ such that $q(x)(X−u)=f(X)−v$.

With this, the Prover will calculate the quotient polynomial $q(X)$ and will commit to it to find $com_{q}$. This will be the proof $π=com_{q}∈G$. The verifier will accept the proof $π$ only if $(α−u)com_{q}=com_{f}−vG$.

Note that verifier is using $α$ here, even though it was secret. The truth is, it is not actually using $α$ but instead uses a **pairing**, and the only thing verifier needs to know for that is $H_{0}=G$ and $H_{1}=αG$, which are part of public parameters $pp$.

Computing $q(x)$ is pretty expensive for large $d$, and this part takes most of the computational time of the entire algorithm.

### Remarks

KZG has been generalized for committing to $k$-variate polynomials (PST 2013). There are many more generalizations of it. KZG also provides batch proofs, where a prover can prove a bunch of commitments in a single computation.

The **difficulty** with KZG is that it requires a trusted setup for $pp$, and $pp$ size is linear in $d$.

# Polynomial Interactive Oracle Proof

Now we are at the second part of a SNARK. Let $C(x,w)$ be some arithmetic circuit. Let $x∈F_{p}$. Poly-IOP is a proof system that proves $∃w:C(x,w)=0$ as follows:

- We preprocess the circuit $C$ as similar to previous steps. $setup(C)→(S_{p},S_{v})$ where $S_{v}=(com_{f_{0}},com_{f_{−1}},…,com_{f_{−s}})$.
- An interactive proof is played as shown below:

sequenceDiagram actor P as Prover P(S_p, x, w) actor V as Verifier V(S_v, x) loop i = 1, 2, ..., (t-1) P ->> V: f_i ∈ F note over V: r_i ← F_p V ->> P: r_i end P ->> V: f_t ∈ F note over V: verify^(f_{-s}, ..., f_t)(x, r_1, ..., r_(t-1))

Here the $verify$ in the end is just an efficient function that can evaluate $f_{i}$ at any point in $F_{p}$. Also note that $r_{i}$ is randomly chosen only after the prover has committed to $f_{i}$.

The Verifier here is just creating random numbers. Using Fiat-Shamir transform, such an interactive proof can be made non-interactive!

As usual, we expect the following properties:

**Complete**: if $∃w:C(x,w)=0$ then verifier always accepts.**Knowledge Sound**: Let $x∈F_{p}$ . For every $P∗$ that convinces the verifier with some non-negligible probability $β$, there is an efficient extractor $E$ such that for some negligible $ϵ$:

$Pr[E(x,f_{1},r_{1},…,f_{t−1},r_{t−1},f_{t})→w:C(x,w)=0]≥β−ϵ$

**Zero-Knowledge**: This is optional, but will be required for a zk-SNARK.

# The Resulting SNARK

The resulting SNARK will look the following: there will be $t$ number of polynomials committed, and $q$ number of evaluation queries (points) for the verification. This is parameterized as $(t,q)$ POLY-IOP.

For the SNARK:

- Prover send $t$ polynomial commitments
- During Poly-IOP verify, the PCS $eval$ is run $q$ times.
- Note that $eval$ is made non-interactive via Fiat-Shamir transform.

The length of the SNARK proof is $t$ polynomial commitments + $q$ evaluation proofs.

- Prover Time: $t×time(commit)+q×time(prove)+time(IOP_{prove})$
- Verifier Time: $q×time(eval_{verify})+time(IOP_{verify})$

Usually, both $t,q≤3$ so these times are really short, in fact, in constant time w.r.t the polynomial count which is awesome.

We will build a Poly-IOP called Plonk. Plonk + PCS will make up a SNARK, and optionally can be extended to a zk-SNARK.

# Key Observations

We can do zero test and equality test on committed polynomials, and these are used on almost all SNARK constructions.

## Zero Test

A really key fact for $0=f∈F_{p}[X]$ (for some random non-zero polynomial with degree at most $d$) is as follows:

- for $r←F_{p}$ it holds that $Pr[f(r)=0]≤d/p$

We know that $f$ has at most $d$ roots. $r$ is chosen at random from a size $p$, do the probability that $r$ "hits" a root value is easy to see that $d/p$.

Suppose that $p≈2_{256}$ and $d≤2_{40}$. Then, $d/p$ is negligible! So it is really unlikely that a randomly chosen field element will be the root for $f$.

With this in mind, if you do get $f(r)=0$ for $r←F_{p}$ then $f$ is identically zero with very high probability. This gives you a simple zero test for a committed polynomial!

This condition holds even for multi-variate polynomials!

## Equality Test

Here is a related observation from the Zero Test.

Let $f,g∈F_{p}[X]$. For $r←F_{p}$, if $f(r)=g(r)$ then $f=g$ with very high probability! This comes from the observation above (think of $f−r=0$).

# Useful Proof Gadgets

Let $w∈F_{p}$ be a primitive $k$-th root of unity (meaning that $w_{k}=1)$. Set $H:={1,ω,ω_{2},…,ω_{k−1}}⊆F_{p}$. Let $f∈F_{p}[X]$ and $b,c∈F_{p}$ where $d≥k$.

There are efficient poly-IOPs for the following tasks:

**Zero Test**: Prove that $f$ is identically zero on $H$**Sum Check**: Prove that $∑_{a∈H}f(a)=b$ where the verifier has $com_{f},b$.**Product Check**: Prove that $∏_{a∈H}f(a)=c$ where the verifier has $com_{f},c$.

We will only look at zero test.

## Zero Test on $H$

There is a cute lemma that will be used here: $f$ is zero on $H$ if and only if $f(X)$ is divisible by $X_{k}−1$.

sequenceDiagram actor P as Prover actor V as Verifier note over P: q(X) ← f(X) / (X^k - 1) P ->> V: q note over V: r ← F_p V ->> P: r note over P: evaluate q(r) and f(r) P ->> V: q(r), f(r) note over V: accept if f(r) = q(r) * (r^k - 1)

This protocol is complete and sound, assuming $d/p$ is negligible.

# PLONK

PLONK is a Poly-IOP for a general circuit $C(x,w)$.

## Step 1: Compile Circuit to a Computation Circuit

Consider the following circuit (gate fan-in: 2, meaning that gates can take 2 inputs):

flowchart LR x1((x1)) --> +1[+ g0] x2((x2)) --> +1 x2 --> +2[+ g1] w1((w1)) --> +2 +1 --> x[x g2] +2 --> x x --> r(( ))

There are 3 gates here (namely $g_{0},g_{1},g_{2}$), 2 statement inputs $x_{1},x_{2}$ and a witness input $w_{1}$. The circuit outputs $(x_{1}+x_{2})(x_{2}+w_{1})$. Consider giving $x_{1}=5,x_{2}=6,w_{1}=1$. Here is what that computation would look like:

flowchart LR x1((x1)) -- 5 --> +1[+ g0] x2((x2)) -- 6 --> +1 x2 -- 6 --> +2[+ g1] w1((w1)) -- 1 --> +2 +1 -- 11 --> x[x g2] +2 -- 7 --> x x -- 77 --> r((77))

We would like to obtain a computation trace of this circuit evaluation. A computation trace is simply a table that shows the inputs, and the state of each gate (input1, input2, output). The output of the circuit is the output of the last gate in the circuit. Here is the computation trace for the circuit evaluation above:

Inputs | 5 | 6 | 1 |

Gate 0 | 5 | 6 | 11 |

Gate 1 | 6 | 1 | 7 |

Gate 2 | 11 | 7 | 77 |

At this point, we can forget about the circuit and focus on proving that a computation trace is valid. Note that input count does not have to be equal to number of inputs & output of a gate.

## Step 1.5: Encoding the Trace as a Polynomial

First, some notation:

- $∣C∣$ is the total number of gates in circuit $C$
- $∣I∣$ is equal to $∣I_{x}∣+∣I_{w}∣$ which is total number of inputs to $C$
- Let $d=3∣C∣+∣I∣$ which gives us the number of entries in the computation trace. In the example, that is $d=12$.
- $H={1,ω,ω_{2},…,ω_{d−1}}$

**The plan**: the prover will interpolate a polynomial $P∈F_{p}[X]$ that encodes the entire trace, such that:

- $P$ encodes all inputs: $P(ω_{−j})=input #j$ for $j=1,…,∣I∣$.
- $P$ encodes all wires: $∀l=0,…,∣C∣−1$:
- $P(ω_{3l})$ gives the left input to gate #$l$
- $P(ω_{3l+1})$ gives the right input to gate #$l$
- $P(ω_{3l+2})$ gives the output of gate #$l$

Prover uses FFT (Fast Fourier Transform) to compute the coefficients of $P$ in time $dg_{2}d$, almost in linear time!

## Step 2: Proving validity of $P$

Prover needs to prove that $P$ is a correct computation trace, which means the following:

- $P$ encodes the correct inputs
- Every gate is evaluated correctly
- The "wiring" is implemented correctly
- The output of last gate is 0

### (1) $P$ encodes the correct inputs

Remember both prover and verifier has the statement $x$. They will interpolate a polynomial $v(X)∈F_{p}[X]$ that encodes the $x$-inputs to the circuit:

- for $j=1,…,∣I_{x}∣:v(ω_{−j})=input #j$

Constructing $v(X)$ takes time proportional to the size of input $x$.

In our example: $v(ω_{−1})=5,v(ω_{−2})=6,v(ω_{−3})=1$ so $v$ is quadratic.

Next, they will agree on the points encoding the input:

- $H_{inp}:={ω_{−1},ω_{−2},…,ω_{−∣I_{x}∣}}$

Prover will prove (1) by using a zero-test on $H_{inp}$ to prove that:

- $P(y)−v(y)=0$ for all $y∈H_{inp}$

### (2): Every gate is evaluated correctly

The idea here is to encode gate types using a selector polynomial $S(X)$. Remember that in our example we encoded the two gate inputs and an output as $ω$ to the power $3l,3l+1,3l+2$ for some gate $l$. Now, we will encode the "types" of these gates.

Define $S(X)∈F_{p}[X]$ such that $∀l=0,…,∣C∣−1$:

- $S(ω_{3l})=1$ if gate $#l$ is an addition gate
`+`

- $S(ω_{3l})=0$ if gate $#l$ is a multiplication gate
`x`

In our example, $S(ω_{0})=1,S(ω_{3})=1,S(ω_{6})=0$, so $S$ is a quadratic polynomial.

Now, we make a really nice observation: $∀y∈H_{gates}:={1,ω_{3},ω_{6},…,ω_{3(∣C∣−1)}}$ it should hold that:

$S(y)×(P(y)+P(ωy))+(1−S(y))(P(y)×P(ωy))=P(ω_{2}y)$

Here, $P(y),P(ωy),P(ω_{2}y)$ are left input, right input and output respectively.

Prover will use a zero-test on the set $H_{gates}$ to prove that $∀y∈H_{gates}$:

$S(y)×(P(y)+P(ωy))+(1−S(y))(P(y)×P(ωy))−P(ω_{2}y)=0$

### (3) The wiring is correct

What do we mean by wiring? Well, if you look at the circuit (or the table) you will notice that some outputs become inputs on other gates. For example, the input 6 is a right input for gate 0, and a left input for gate 1 and such. Prover will have to prove that this wiring has been done correctly.

For that, the wires of $C$ are encoded with respect to their constraints. In our example:

- $P(ω_{−2})=P(ω_{1})=P(ω_{3})$
- $P(ω_{−1})=P(ω_{0})$
- $P(ω_{2})=P(ω_{6})$
- $P(ω_{−3})=P(ω_{4})$

Define a polynomial $W:H→H$ that implements a rotation:

- $W(ω_{−2},ω_{1},ω_{3})=(ω_{1},ω_{3},ω_{−2})$
- $W(ω_{0})=(ω_{−1})$
- $W(ω_{6})=(ω_{2})$
- $W(ω_{4})=(ω_{−3})$

Why we do this fantastic thing is due to a lemma; if $∀y∈H:P(y)=P(W(y))$ then the wire constraints are satisfied.

However, there is a problem: $P(W(y))$ has degree $d×d=d_{2}$ but we want prover to work in linear time $d$ only! PLONK uses a very nice trick: use product check proof to reduce this to a constraint of linear degree. This trick is called the "PLONK Permutation" trick.

### (4) Output of last gate is 0

Proving the last one is easy, just show $P(3_{3∣C∣−1})=0$.

## Final PLONK Poly-IOP

In the setup phase:

- $Setup(C)→(S_{p},S_{v})$ where
- $S_{p}:=(S,W)$
- $S_{v}:=(com_{S},com_{W})$

The prover has $(S_{p},x,w)$ and the verifier has $(S_{v},x)$.

- The prover will build $P(X)∈F_{p}[X]$ and give the commitment $com_{P}$ to the verifier.
- The verifier will then build $v(X)∈F_{p}[X]$

Finally, the prover will prove the four things described before:

**Inputs**

$∀y∈H_{inp}:P(y)−v(y)=0$

**Gates**

$∀y∈H_{gates}:S(y)×(P(y)+P(ωy))+(1−S(y))(P(y)×P(ωy))−P(ω_{2}y)=0$

**Wires**

$∀y∈H:P(y)−P(W(y))=0$

**Output**

$P(3_{3∣C∣−1})=0$

There is a theorem that shows this PLONK Poly-IOP is knowledge sound and complete! The resulting proof is around 400 bytes, and verification takes around 6ms. PLONK can actually handle circuits with more general gates than `+`

and `x`

, although we only used those operations in our gate constraints. The resulting SNARK can be made into a zk-SNARK, though it is not covered here.

There is also something called PLOOKUP: efficient Poly-IOP for circuits with lookup tables, which is very very useful.