Recall: Polynomial Commitments

We will use polynomial commitments in this lecture, so let's quickly recall what they are!

  • The prover would like to commit to some polynomial .
  • An function uses evaluate some values for this polynomial, without revealing it. For example, pick some public .
    • Prover will convince that and .
    • Verifier will only know and a polynomial commitment , also shown as sometimes.
  • The proof size for and the verifier time should both be in . Spoilers: it will turn out be constant which is really a magical thing to me.

KZG Poly-commit Scheme

In this lecture, we will use KZG [Kate-Zaverucha-Goldberg'10] polynomial commitment scheme.

Fix some finite cyclic group of order . This group basically has some generator value and the group consists of it's multiplications:

The group has addition operation defined on it, where you can add to obtain where .

Trusted Setup:

KZG starts with a trusted setup to produce public parameters. This is done as follows:

  1. Sample some random .
  2. Compute . Basically, you have group elements, each multiplied to some power of tau () and multiplied by . These computed values will be the public parameters .

  1. Finally and most importantly, delete . If this number is leaked and in wrong hands, they can create fake proofs! This is why the setup must take place in a trusted environment. We will actually see a better way for this setup, where multiple parties will take place in the ceremony and it suffices for only one of them to be trusted!

Commitment:

A commitment will take the public parameters along with the polynomial to be committed, and produces the commitment.

  • The commitment is shown as
  • Our commitment will be .

But wait, was deleted after setup so how do we obtain this? Well, think of the polynomial as follows:

Notice that for every (including ) you write you get the following:

We can very well do this because we know what each is, they are given us within the public parameters . If you expand you notice that:

We got the commitment we've wanted! Note that this commitment is binding, but not hiding as is.

Evaluation:

Let us now see how a verifier evaluates the commitment.

  • Prover knows and wants to prove that .
  • Verifier knows .

We will have some series of if-and-only-if's now, which will connect everything really nicely.

  • if and only if is a root of . This makes sense because if indeed then which would make a root for .
  • is a root of if and only if the polynomial divides . You might be familiar with this property already throughout this lecture.
  • divides if and only if such that . This is another way of saying that since divides there will be no remainder left from this division, and there will be some resulting quotient polynomial .

With this knowledge in mind, here is the plan:

  1. Prover computes and commits to as . Remember that commitment results in a single group element only.
  2. Prover send the proof . That's right, the entire proof is just the commitment to which means the proof size is a single group element, independent of the degree .
  3. Verifier accepts if

You may notice that there is here, which is supposed to be secret; and you are right. What actually happens is that something called pairing is used here to hide while still allowing the above computation. In doing so, only and will be used, which again makes this thing independent of degree .

So are we really independent of ? Well, the prover must compute the quotient polynomial and the complexity of that is related to , so you will lose from prover time when you have large degrees.

You might ask, how to prove that this is a secure poly-commit scheme? We are not going into that today…

Properties of KZG

KZG has some cool properties!

  • Generalizations: It has been shown that you can use KZG to commit to -variate polynomials [Papamanthou-Shi-Tamassia'13]
  • Batch Proofs: Suppose you have commitments to polynomials and you have values to reveal in each of them, meaning that you basically want to prove all evaluations defined by for and .
    • Normally, this would require evaluations, but thanks to KZG we can actually do this in a single proof that is a single group element!
  • Linear-time Commitments: How long does it take to commit to a polynomial of degree ? Well, we would really like this to be in linear time with , and turns out it is possible to do so. This deserves a sub-section on its own though, so let us do that.

Linear-time Commitments

The way we calculate the commitment will change based on how we represent a polynomial . There are several ways.

  • Coefficient Representation: Simply, keep a record of coefficients to construct the polynomial.
    • would mean that we are storing an array of values .
    • We can compute the commitment in linear time since we just have to multiply with for , giving us:
  • Point-Value Representation with NTT: A polynomial of degree can be defined by points. So, we have points and their evaluations .
    • Computing naively would be to construct the coefficients to basically convert point-value representation to coefficient representation, and then compute the commitment as shown in that case.
    • Converting from point-value to coefficient representation takes time using Number Theoretic Transform (NTT) which is closely related to Fourier Transform. However, this is more than linear time, we want to do better!
  • Point-Value Representation with Lagrange Interpolation: Thankfully, there is a linear-time algorithm to commit to a polynomial in point-value representation. The idea is to use Lagrange Interpolation to compute the commitment.

The idea here is that our public parameters will actually be in Lagrange form, and the process of getting this just a linear transformation that everyone can do. So, we obtain public parameters that looks like:

This way, the commitment can be compute in linear time :

Fast Multi-point Proof Generation

Let and . Suppose that the prover needs an evaluation proof for all . Normally, this would require time because proving one takes time linear in and there are values.

Thanks to [Feist-Khovratovic'20] there is a much faster algorithm to do this.

  • If is a multiplicative group then it takes time in
  • otherwise, it takes time in

Dory Poly-commit Scheme

KZG has some difficulties:

  • it requires a trusted setup to compute the public parameters
  • size is linear in the degree

Can we do any better? Kind of yeah! Dory [Lee'20] is a polynomial commitment scheme with the following properties:

  • 🟢 It has transparent setup, so there is no need for a trusted setup
  • 🟢 is still just a single group element, independent of degree
  • 🔴 proof size is group elements; KZG took constant time.
  • 🔴 verification time is ; KZG took constant time.
  • 🟢 prover time is

PCS to Commit to a Vector

Poly-commit schemes are a drop-in replacement for Merkle Trees, which we have used before to make vector commitments.

Suppose you have some vector . To commit to this vector, the prover will interpolate a polynomial such that for . Then, the prover can simply commit to this polynomial as we have described above.

If a verifier wants to query some vector elements, like "show me that and " this translate to "show me and " and we know we can prove this in a single group element using a batch proof thanks to KZG.

If we were to use a Merkle Tree, each evaluation proof would have size and for proofs this would mean proof size, a lot bigger than the constant proof size of KZG.

For more applications of using PCS in Merkle Trees, see Verkle Trees!

Proving Properties of Committed Polynomials

Before we start, we would like to make note of a shorthand notation: when we say the verifier queries a polynomial at some random point to get we actually mean that the prover computes and a proof of this evaluation , then it sends back to the verifier.

Also note that everything we will do in our interactive proofs will be public-coin protocols, so although what we will do looks interactive just keep in mind that we can use Fiat-Shamir transform to make them non-interactive.

Equality Testing

Recall that in KZG, the verifier could test if just by knowing , also shown as . For bit more complex equality tests, that won't be enough.

For example, suppose that the verifier has and would like to see if . To do this, the verifier has to query the prover on all four polynomials at some random field element and test equality.

Important Proof Gadgets for Uni-variates

Let where . Let be a polynomial of degree and . The verifier has a commitment to this polynomial, .

We will now construct efficient poly-IOPs for the following proof gadgets:

  • Equality Test: prove that are equal. We know that evaluating them at a random point and seeing if they are equal does the trick, assuming degree is much smaller than the size of the finite field.
  • Zero Test: prove that is identically zero on , meaning that it acts like a zero-polynomial for every value in , but of course it can do whatever it wants for values outside of but in .
  • Sum Check: prove that
  • Product Check: prove that
  • Permutation Check: prove that evaluations of over is a permutation of evaluations of over
  • Prescribed Permutation Check: prove that evaluations of over is a permutation of evaluations of over , with a "prescribed" permutation . This permutation is a bijection

To start, we need to introduce the concept of a vanishing polynomial.

Definition: The vanishing polynomial of (as defined above) is:

with degree . Then, let be a primitive -th root of unity, meaning that . If the set is defined as follows:

then . This is really nice, because for such cases, evaluating for some random field element means just taking and subtracting one, which costs around field operations, thanks to repeated-squaring method of multiplication.

Zero Test

In the following graph, we denote Z(r) for . Also remember that when we say "the verifier queries some polynomial and the prover shows its evaluation", what we mean is that in the background the prover computes them and sends the result along with an evaluation proof.

With that said, let us see the zero-test poly-IOP.

sequenceDiagram
	actor P as Prover(f)
	actor V as Verifier(com_f)

	note over P: q(X) = f(X) / Z(X)
	P ->> V: com_q
	note over V: r ← F_p
	V ->> P: query f(X) and q(X) at r
	P ->> V: show f(R) and q(R)

	note over V: accept if f(R) = q(R) * Z(R)


Let's analyze the costs in this IOP:

  • The verifier made two polynomial queries (although a batch proof could have been done), and also it computed on it's own which takes time .
  • The prover time is dominated by the time to compute and then commit to it, which runs in time .

Product Check and Sum Check

Prod-check and sum-check are almost identical, so we will only look at prod-check. Again, our claim is that and we would like to prove that.

Set to be a polynomial of degree . Define the evaluations of this polynomial as follows:

  • and so on, with the final evaluation of being equal to the product itself!

You can see that we can define for . It is also important to notice the recurrence relation between and :

which is made possible because consists of powers of . The lemma we will use with these is the following:

  • if
  • and for all
  • then,

Let's write the interactive proof! The idea will to construct another polynomial which is:

which implies that if a zero-test on for passes, then prod-check passes.

sequenceDiagram
	actor P as Prover(f)
	actor V as Verifier(com_f)

	note over P: construct t(X)
	note over P: construct t1(X) = t(ωX) - t(X) * f(ωX)

	note over P: set q(X) = t1(X)/(X^k - 1)
	P ->> V: com_q, com_t
	note over V: r ← F_p
	V ->> P: query t(X) at ω^(k-1), r, ωr
	P ->> V: show t(ω^(k-1)), t(r), t(ωr)
	V ->> P: query q(X) at r
	P ->> V: show q(r)
	V ->> P: query f(X) at ωr
	P ->> V: show f(r)

	note over V: accept if t(ω^(k-1)) = 1
	note over V: and if t(ωr) - t(r)f(ωr) = q(r)(r^k - 1)


The cost of this protocol is as follows:

  • Proof size is two commitments () and five evaluations, and keeping in mind that evaluations can be batched, the entire proof size is just 3 group elements.
  • Prover time is dominated by computing that runs in time
  • Verifier time is dominated by computing and , both in time

Note that almost the same protocol works for rational functions. There, our claim is and we construct a similar polynomial, only this time is divided by in the definition. Then, the lemma is also similar:

  • if
  • and for all
  • then,

Almost the same!

Permutation Check

We have two polynomials and we want to show that

  • is just a permutation of
  • essentially proving that is same as , but just permuted.

To prove this, we will do what is known as the Lipton's trick [Lipton'89]. We will construct two auxiliary polynomials:

Now notice that if and only if is a permutation of . This is because the product is a series of multiplications, which is a commutative operation.

Normally, to prove that the prover would only have to show the evaluation of them at a random point, given by the verifier. However, computing these polynomials are a bit expensive, so instead the prover will do a clever trick: do a prod-check on the following rational function:

We have just mentioned that prod-check can be done on rational functions, so we can very well do this! The cost of this proof is just two commitments, and six evaluations.

Prescribed Permutation Check

Again we have two polynomials and a permutation . The verifier has commitments to these . Our claim is that for all , in other words, is a permutation of over as described by .

At a first glance, it is tempting to do a simple zero-test on , right? Nope, notice that results in a polynomial of degree , but we wanted to have a linear time prover; this results in a quadratic time prover!

Instead, we have a clever method that will run in linear time. We start with the following observation: if the set of pairs is a permutation of then for all .

Here is a quick example of this:

  • Permutation:
  • First set of pairs:
  • Second set of pairs:

For the proof itself, we actually need bivariate polynomials; univariate polynomials will not be enough. Nevertheless, the proof is much similar to the previously described permutation check.

Define two auxiliary polynomials, which will be bivariate polynomials of total degree :

The lemma here is that if then is a permutation of . The proof of this is left as exercise, though if you want to try, you might make use of the fact that is a unique factorization domain.

The protocol continues with the verifier generating two random points and sending these to the prover. Again, instead of actually evaluating the auxiliary polynomials, the prover will do a prod-check over what they describe:

This protocol is sound and complete, assuming is negligible. The cost of this protocol is just like the cost described for prod-check.

PLONK

The time has come! PLONK [Gabizon-Williamson-Ciobotaru'19] is a poly-IOP for a general circuit .

But, before we delve into PLONK, we must realize that PLONK itself in practice is more like an abstract IOP, that when used with some poly-commit scheme will result in a SNARK system. Here are some examples in practice:

Poly-commit SchemePoly-IOPSNARK System
KGZ'10, uses pairingsPLONKAztec, JellyFish
Bulletproofs, no pairings requiredPLONKHalo2
FRI, uses hashesPLONKPlonky2

With that said, let us begin.

Step 1: Compile circuit to computation trace

We will use an example circuit with an example evaluation. Our circuits have gates with two inputs and a single input, also shown as "gate fan-in = 2".

flowchart LR
	x1((x_1)) --> a1[+]
	x2((x_2)) --> a1
	x2((x_2)) --> a2[+]
	w1((w_1)) --> a2
	a1 --> m1[x]
	a2 --> m1[x]
	m1 --> o(( ))

The circuit above computes . Suppose that the public inputs are and the secret input (witness) is . As a result, the output of this circuit is 77, which is also public. The prover will try to prove that he knows a value of that makes the output 77 with the given public inputs.

flowchart LR
	x1((x_1)) --5--> a1[+]
	x2((x_2)) --6--> a1
	x2((x_2)) --6--> a2[+]
	w1((w_1)) --1--> a2
	a1 --11--> m1[x]
	a2 --7--> m1[x]
	m1 --77--> o(( ))

We compile this evaluation into a computation trace, which is simply a table that shows inputs and outputs for each gate, along with circuit inputs.

  • Circuit inputs are .
  • Gate traces are given in the following table.
Gate No.Left InputRight InputOutput
Gate 05611
Gate 1617
Gate 211777

Step 1.5: Encode trace as a polynomial

We have spent a lot of time learning how to commit to polynomials, so let's get them to work! First, some definitions:

  • is the circuit size, equal to number gates in the circuit.
  • is the number of inputs to the circuit, which is the number of public inputs and the secret inputs combined.
  • We then let
  • Let where .

The plan is to encode the entire computation trace into a polynomial such that:

  • encodes all inputs with correspond to input
  • encodes all wires, which it does :
    • corresponds to the left input of gate
    • corresponds to the right input of gate
    • corresponds to output of gate

In the circuit example above, there are 12 points, which defines a degree-11 polynomial. To interpolate a polynomial to the values in the computation trace, the prover can actually use Fast Fourier-Transform (FFT) to compute the coefficients of polynomial in time .

However, in general we won't compute the coefficients, but instead use the point-value representation of the polynomial as described above.

Step 2: Prove validity of

So the prover has computed , and committed to it as . It sends it to the verifier, but the verifier must make sure that indeed belongs to the correct computation trace. To do that, it must do the following:

  1. encodes the correct inputs
  2. Every gate is evaluated correctly
  3. The "wiring" is implemented correctly
  4. The output of last gate is 0. Well, in this example the output is 77, but generally the verifier expects a 0 output, remember how we say .

(1) encodes the correct inputs

Remember both prover and verifier has the statement . They will now interpolate a polynomial that encodes the -inputs to the circuit:

  • for
  • In our example: so is quadratic (i.e. it is defined by 3 points).
  • Constructing takes linear time proportional to the size of input .

Next, they will agree on the points encoding the input . Then, the prover will use a zero-test on to prove that for all .

It is quite easy to do this, because vanishing polynomial for is often calculated quickly.

(2): Every gate is evaluated correctly

The idea here is to encode gate types using a selector polynomial . Remember that in our example we encoded the two gate inputs and an output as to the power for some gate . Now, we will encode the "types" of these gates.

Define such that :

  • if gate is an addition gate +
  • if gate is a multiplication gate x

In our example, . Notice that the selector polynomial depends on the circuit, but not on the inputs! So in fact, the selector polynomial is a product of the preprocessing setup: prover will have itself, and the verifier will have a commitment to it .

Now, we make a really nice observation: it should hold that:

Here, are the left input, right input and output respectively. Prover will use a zero-test on the set to prove that :

(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 are encoded with respect to their constraints. In our example:

Define a polynomial that implements a single left-rotation:

Why we do this fantastic thing is due to a lemma; if then the wire constraints are satisfied. The idea behind this bizarre method is that, if is indeed invariant (does not change its behavior) under such a rotation, then the wiring must be correct. This is because had the wirings be false, the rotation would cause a value to be different and this would not hold.

Remember that has degree but we want prover to work in linear time only. This is where the prescribed permutation check we have described in this lecture comes into play.

(4) Output of last gate is 0

Proving the last one is easy, just show that .

Final Step: The PLONK Poly-IOP

The setup procedure results in and and is transparent, no need for trust! The prover knows and verifier knows .

sequenceDiagram
	actor P as Prover
	actor V as Verifier

	note over P: construct T(X)
	P ->> V: com_T
	note over V: construct V(x)
	note over P, V: ✅ T encodes the inputs
	note over P, V: ✅ gates are evaluated correctly
	note over P, V: ✅ wiring is correct
	note over P, V: ✅ output of last gate is 0


The resulting PLONK proof is a short one, it has commitments! Furthermore, the verifier is fast. Although the SNARK we have described is not zero-knowledge, it is quite easy to make it into a zkSNARK. There are actually generic transformations that can convert any poly-IOP into a zero-knowledge poly-IOP.

The paper proves that if is negligible, then this PLONK poly-IOP is complete and knowledge sound. Try and see for yourself where that 7 comes from.

PLONK Extensions

The main challenge is to reduce the prover time. Furthermore, just using + and x gates might feel a bit too constraining. We do have alternative solutions though! Each of the following try to improve the prover time in various ways.

HyperPlonk

What HyperPlonk [Chen-Bünz-Boneh-Zhang'22] does is that they replace with where . As such, the polynomial becomes a multilinear polynomial with variables. Zero-test is then replaced by a multilinear sum-check that runs in linear time.

Plonkish Arithmetization: Custom Gates

In our example, we had gates with two inputs and an output, along with selector polynomials that cover addition and multiplication. Furthermore, each constraint was specific to the row itself. It is possible to generalize this usage to obtain custom gates, that can even make use of multiple rows! Custom gates are included in the gate check step. This is used in AIR (Algebraic Intermediate Representation).

Plookup: Lookup Tables

There is an extension of Plonkish Arithmetization, that actually enables one to ensure that some values in the computation trace are present in a pre-defined list, basically acting like a look-up argument!