Skip to content

Polynomial Commitments

Polynomial commitments are a kind of vector commitment scheme that also allows for partial reveals, but with succint or logarithmic (wrt the prover complexity) proofs, irrespective of the size of the message. Polynomial commitment schemes are the foundational cryptographic primitive for things like computational integrity proofs (aka (ZK-)SNARKs) and verkle tries (a more efficient alternative to merkle-patricia tries).

Vector commitment schemes enable a committer to commit to an ordered sequence of nn values (m1,,mn)(m_1, \dots , m_n) in such a way that one can later open the commitment at specific positions (i.e., prove that mim_i is the ii-th committed message). For security, Vector Commitments are required to satisfy a notion referred to as positional binding which states that an adversary should not be able to open a commitment for two different values at the same position. Another property VCs must satisfy is succinctness, i.e. the size of the commitment string and of its openings has to be sublinear to the vector length. A merkle tree is a good example of a vector commitment.

A polynomial commitment scheme is a type of commitment scheme where rather than committing to an arbitrary sequence of bytes for a message mm we commit to a polynomial ϕ(x)\phi(x). Recall that a polynomial is of the form:

ϕ(x)=ϕ0+ϕ1x+ϕ2x2++ϕdxdϕ(x)=i=0d+1ϕixi \phi(x) = \phi_0 + \phi_1\cdot x + \phi_2\cdot x^2 + \cdots + \phi_d\cdot x^d\\ \phi(x) = \sum_{i=0}^{d+1} \phi_i x^i

Where ϕi\phi_i represents the coefficients of the polynomial.

Polynomial Interpolation

But why would we be committing to a polynomial in the first place!? The answer is, polynomial interpolation allows us to reverse engineer a polynomial function that encodes a message mm, by treating the elements contained within mm and it’s indices as a coordinate pair. i.e m={(x0,y0),(x1,y1)(xn,yn)}m = \{(x_0, y_0), (x_1, y_1) \dots (x_n, y_n)\}. Where xix_i is the index and yiy_i is the value for the index in mm.

More formally, polynomial interpolation states that: given any arbitrary coordinate pairs (xi,yi)i[n](x_i, y_i)_{i\in[n]}, We can find or interpolate a polynomial ϕ(x)\phi(x) of degree (n1)\le (n - 1) such that the evaluation of every xix_i over the polynomial produces yiy_i. ie ϕ(xi)=yi,i[n]\phi(x_i)=y_i,\forall i\in[n]. There are a few ways to achieve polynomial interpolation, but two most popular methods are Lagrange Interpolation and Fast Fourier Transforms (FFT).

FFTs are a more efficient method for polynomial interpolation, especially when working with large sets of points. It involves transforming the set of points into a frequency domain representation, where the polynomial can be expressed as a series of sinusoidal functions.

Lagrange Interpolation on the other hand involves constructing a polynomial function that is a sum of smaller polynomial functions, each of which passes through a single point in the set. The Lagrange interpolation L(x)\mathcal{L}(x) is formally represented by:

L(x)=i = 1nyiLi(x),\mathcal{L}(x) = \sum\limits_{i \space = \space 1}^{n} y_i \cdot \mathcal{L}_i(x), \\

where Li(x)\mathcal{L}_i(x) is given as:

Li(x)=j = 1, i  jn(xxjxixj). \mathcal{L}_i(x) = \prod\limits_{j \space = \space 1, \space \\ { i \space\ne \space j}}^{n} (\frac{x-x_j}{x_i - x_j}).

With this formula let’s perform the Lagrange interpolation of the data: [1,3,9,27][1, 3, 9, 27]. First we need to treat the data as a set of x,yx, y coordinates: [(1,1),(2,3),(3,9),(4,27)][(1, 1), (2, 3), (3, 9), (4, 27)].

L(x)=(y0L0(x))+(y1L1(x))+(y2L2(x))+(y3L3(x)) L0(x)=(xx1)(xx2)(xx3)(x0x1)(x0x2)(x0x3) L1(x)=(xx0)(xx2)(xx3)(x1x0)(x1x2)(x1x3) L2(x)=(xx0)(xx1)(xx3)(x2x0)(x2x1)(x2x3) L3(x)=(xx0)(xx1)(xx2)(x3x0)(x3x1)(x3x2) L0(x)=(x2)(x3)(x4)(12)(13)(14) L1(x)=(x1)(x3)(x4)(21)(23)(24) L2(x)=(x1)(x2)(x4)(31)(32)(34) L3(x)=(x1)(x2)(x3)(41)(42)(43) L0(x)=(x2)(x3)(x4)6 L1(x)=(x1)(x3)(x4)2 L2(x)=(x1)(x2)(x4)2 L3(x)=(x1)(x2)(x3)6 L(x)=116(x2)(x3)(x4)+312(x1)(x3)(x4)912(x1)(x2)(x4)+2716(x1)(x2)(x3) L(x)=13(4x318x2+32x+15)\mathcal{L}(x) = (y_0 \cdot \mathcal{L}_0(x)) + (y_1 \cdot \mathcal{L}_1(x)) + (y_2 \cdot \mathcal{L}_2(x)) + (y_3 \cdot \mathcal{L}_3(x)) \\ \space \\ \mathcal{L}_0(x) = \frac{(x - x_1) \cdot (x - x_2) \cdot (x - x_3)}{(x_0 - x_1) \cdot (x_0 - x_2) \cdot (x_0 - x_3)} \\ \space \\ \mathcal{L}_1(x) = \frac{(x - x_0) \cdot (x - x_2) \cdot (x - x_3)}{(x_1 - x_0) \cdot (x_1 - x_2) \cdot (x_1 - x_3)} \\ \space \\ \mathcal{L}_2(x) = \frac{(x - x_0) \cdot (x - x_1) \cdot (x - x_3)}{(x_2 - x_0) \cdot (x_2 - x_1) \cdot (x_2 - x_3)} \\ \space \\ \mathcal{L}_3(x) = \frac{(x - x_0) \cdot (x - x_1) \cdot (x - x_2)}{(x_3 - x_0) \cdot (x_3 - x_1) \cdot (x_3 - x_2)} \\ \space \\ \mathcal{L}_0(x) = \frac{(x - 2) \cdot (x - 3) \cdot (x - 4)}{(1 - 2) \cdot (1 - 3) \cdot (1 - 4)} \\ \space \\ \mathcal{L}_1(x) = \frac{(x - 1) \cdot (x - 3) \cdot (x - 4)}{(2 - 1) \cdot (2 - 3) \cdot (2 - 4)} \\ \space \\ \mathcal{L}_2(x) = \frac{(x - 1) \cdot (x - 2) \cdot (x - 4)}{(3 - 1) \cdot (3 - 2) \cdot (3 - 4)} \\ \space \\ \mathcal{L}_3(x) = \frac{(x - 1) \cdot (x - 2) \cdot (x - 3)}{(4 - 1) \cdot (4 - 2) \cdot (4 - 3)} \\ \space \\ \mathcal{L}_0(x) = \frac{(x - 2) \cdot (x - 3) \cdot (x - 4)}{-6} \\ \space \\ \mathcal{L}_1(x) = \frac{(x-1)(x - 3)(x - 4)}{2} \\ \space \\ \mathcal{L}_2(x) = \frac{(x-1)(x - 2)(x - 4)}{-2} \\ \space \\ \mathcal{L}_3(x) = \frac{(x-1)(x - 2)(x - 3)}{6} \\ \space \\ \mathcal{L}(x) = 1 \cdot -\frac{1}{6}(x - 2)(x - 3)(x-4) + 3 \cdot \frac{1}{2}(x-1)(x - 3)(x - 4) - 9 \cdot \frac{1}{2}(x-1)(x - 2)(x - 4) + 27 \cdot \frac{1}{6}(x-1)(x - 2)(x - 3) \\ \space \\ \mathcal{L}(x) = \frac{1}{3}(4x^3 - 18x^2 + 32x + 15)

We can graph the interpolated polynomial function to check if it does indeed pass through our data points.

At every point on the x-axis, corresponds with our data points on the y-axis

At every point on the x-axis, corresponds with our data points on the y-axis

This example of course uses polynomials whose coefficients are members of the set of real numbers, In practice, polynomial interpolation & commitments are done over a finite field.

Back to polynomial commitment schemes, once we have a polynomial function that encodes a message mm, we can create a succinct commitment CC to the polynomial. Using this commitment, we can generate a proof π\pi for any given coordinate pair (xi,yi)(x_i, y_i), such that a verifier who only has the commitment CC (and not the original polynomial ϕ(x)\phi(x)) can verify that in the original polynomial, the index xix_i does indeed correspond to value yiy_i. Remember that this commitment is also succint, C<ϕ(x)C < \phi(x), i.e. the string representation of the polynomial commitment is less than the string representation of the original polynomial.

There are quite a few polynomial commitment protocols that exist today. These protocols have different tradeoffs that make them suitable for different applications. The most popular of them all is KZG.


I won’t be discussing the details of the advanced cryptographic primitives like finite fields, cyclic groups of prime orders or bilinear mappings and will be leaving them as an exercise for the reader.

The KZG commitment scheme[1]^{[1]}, was introduced in 2010 by Aniket Kate, Gregory M. Zaverucha, and Ian Goldberg. Their paper outlines a protocol that produces constant-sized polynomial commitments and proofs of evaluation.

This protocol unfortunately requires a trusted set up, in that users must generate a secret parameter and discard this secret parameter otherwise malicious provers can create proofs of invalid evaluations. This trusted setup can be carried out in a distributed manner without relying on a central authority. All participants in the ceremony must collude to recover the secret parameter. However, if there is at least one honest participant who discards their share of the secret, the full secret cannot be reconstructed. This can be achieved using various MPC schemes that are available today.

The KZG commitment scheme at a high level works as follows:


First we define Fp\mathbb{F}_p as a finite field of prime order . Finite fields of prime order are simply a set given as Fp={0,p1}\mathbb{F}_p = \{0, p -1 \} where arithmetic operations over the elements of the set are performed modulo p\space p.

Next we define ee as a non-trivial bilinear mapping.

Finally, let gg be a generator for some pairing-friendly elliptic curve group G\mathbb{G} where G\mathbb{G} is a cyclic group of prime order pp:

G={0, g, 2g, 3g, , (p1)g}\mathbb{G} = \{0, \space g, \space 2\cdot g, \space 3 \cdot g, \space \dots, \space (p-1)\cdot g\}


Setup(s,t)Setup(s, t): Given some secret value ss, where sFps \in \mathbb{F}_p. This method generates a proving key PKPK, that allows anyone commit to a polynomial of degree t≤ t. This proving key, also called common reference string is of the form:

(gsi)i[0,t]=(g,gs,,gst)(g^{s^{i}})_{\forall i \in[0, t]} = (g, g^s, \dots, g^{s^t})

IMPORTANT: The secret value ss should not be recoverable, otherwise the prover can generate fraudulent proofs that can successfully fool the verifier.

PolyCommit(PK,ϕ(x))PolyCommit(PK, \phi(x)): This outputs a constant-sized commitment CC to a polynomial ϕ(x)\phi(x) for the public key PKPK.

More formally, Given some polynomial ϕ(x)Fp[x]\phi(x) \in \mathbb{F}_p[x] where deg(ϕ(x))tdeg(\phi(x)) \le t , the commitment is of the form C=gϕ(s)C = g^{\phi(s)}. Ideally, the prover doesn’t know ss, so instead they exploit a property of elliptic curve groups by using the proving key PKPK to compute i = 0,t(gsi)ϕi\prod\limits_{i \space = \space 0, }^{t}(g^{s^i})^{\phi_i} which is equivalent to gϕ(s)g^{\phi(s)}. It's also worth noting that this commitment is a single group element in G\mathbb{G} and is why KZG commitments have constant sized commitments irrespective of the degree of ϕ(x)\phi(x).

CreateWitness(PK,ϕ(x),i)CreateWitness(PK, \phi(x), i): This outputs i,ϕ(i),π\langle i, \phi(i), \pi \rangle, where π\pi is a proof for the correct evaluation of ϕ(x)\phi(x) at index ii. The prover computes the quotient polynomial ψ(x)\psi(x) given as:

ψ(x)=ϕ(x)ϕ(i)(xi)Fp[x]\psi(x) = \frac{\phi(x) - \phi(i)}{(x - i)} \in \mathbb{F}_p[x]

Now given the quotient polynomial, the evaluation proof π\pi is given as π=gψ(s)\pi = g^{\psi(s)}. This proof is based on the polynomial remainder theorem and is also constant sized as it is also a single group element in G\mathbb{G}.

VerifyEval(PK,C,i,ϕ(i),π)VerifyEval(PK, C, i, \phi(i), \pi): verifies that ϕ(i)\phi(i) is indeed the evaluation at the index ii of the polynomial committed in CC. A verifier who has the commitment CC, the evaluation y=ϕ(i)y = \phi(i) and a proof of evaluation π\pi can then compute:

e(Cgy,g)=e(π,gsgi)e(\frac{C}{g^{y}}, g) = e(\pi, \frac{g^s}{g^i})

If both pairings are equal, the verifier can be convinced that the prover does indeed know the full polynomial ϕ(x)\phi(x). It's also worth pointing out that the verifier always runs in constant time, regardless of the degree tt of the polynomial.


Polynomial commitments have many use cases, but in this section, we will only focus on applications that provide benefits to current blockchain architectures.

Verkle Tries

Armed with polynomial commitment schemes, we can revisit our merkle-patricia trie proof scheme[2]^{[2]} and make them even more efficient. Recall that merkle patricia trie proofs require the hashes of all sibling nodes along the path to the value. This means that proof sizes are klogk(n)k\log_k(n) where kk is the arity of the nodes.

We can remove the need for these sibling nodes in our proofs by instead performing a polynomial interpolation over the branch nodes and producing a polynomial commitment rather than a cryptographic hash. Then with this constant-sized commitment, we can produce constant-sized membership proofs for any of the child nodes in the branch node. This scheme is popularly known as the Verkle (vector commitment merkle) trie [3]^{[3]}.

Since polynomial commitments produce constant-sized membership proofs irrespective of the set size, this allows us to shorten the height of the verkle tree and achieve a smaller set of polynomial evaluation proofs. To accomplish this, we increase the arity of each branch node from 16 (as in Merkle Patricia Tries) to up to 2048. This effectively reduces the number of polynomial evaluation proofs needed to prove membership for any item in the tree. We can even further reduce the proof size for a Verkle tree by using KZG multiproofs. This allows us to reduce a set of KZG proofs to a single group element [4]^{[4]}.

Verkle trees enable higher bandwidth cross-chain messaging by producing state proofs for cross-chain messages of a constant size. They also have noteworthy applications in parachain scalability. As discussed in a previous article, parachain blocks are re-executed by the relay chain to achieve validity guarantees. However, by leveraging verkle trees, we can create more room for parachain transactions in the proof of validity payload that gets sent to the relay chain.

Proofs of computational integrity

Polynomial commitments can also be used to obtain cryptographic proofs of computational integrity. This allows a prover to convince a verifier that they have correctly executed a mutually known program with some (possibly private) inputs, and that this program has yielded some given outputs.

Proofs of computational integrity are powered by a combination of polynomial IOPs and polynomial commitment schemes. Functionally, a polynomial IOP allows a prover to convince a verifier that they know some computation trace for which a set of polynomial constraints holds. These polynomial constraints are known by the verifier ahead of time through a commitment to all constraint polynomials in the program. This computation trace can then be generated by a process known as arithmetization.

I won’t be discussing in-depth how the Polynomial IOPs work and will leave it as an exercise to the reader.

Through arithmetization, a prover can generate a two-dimensional array that encodes how different variables in a program change over time. This process uses a register-based model in which variables are stored in registers. The prover then turns on and off different gates for variables in the registers using polynomial constraints that evaluate to zero. The verifier, who has a commitment to all of the polynomial constraints, will query at any random point to confirm that the constraint polynomial evaluates to zero. If this check is satisfied, the verifier can conclude with a high probability that the prover does indeed possess a valid computation trace for the given inputs. (This zero-check is based on the Schwartz-Zippel lemma).

This is a powerful primitive that immensely scales blockchains. By running multiple state machines concurrently and producing "validity proofs" that confirm the correct execution of their state transition function, a consensus layer can verify these proofs cheaply and trust that they accurately describe a valid state transition without needing to re-execute the full block.

Proofs of computational integrity can also be applied to consensus-verifying bridges. They provide a cost-effective and indirect way to verify consensus proofs for a counter-party blockchain, which usually requires signatures from a supermajority of all validators. Naively verifying this consensus proof on-chain can be too expensive due to the lack of necessary host functions/precompiles or because of the sheer size of the super-majority set. Pairing polynomial commitments with polynomial IOPs for consensus verification can produce constant-sized proofs, regardless of the size of the consensus proof. This compression allows for a potentially massive set of signatures to be condensed into a constant-sized proof that can be verified in constant time.


[1]^{[1]} A. Kate, G. M. Zaverucha, I. Goldberg, Polynomial Commitments

[2]^{[2]} Seun Lanlege, State (Machine) Proofs

[3]^{[3]} Verkle Trees, John Kuszmaul

[4]^{[4]} Dankrad Feist, Kate Polynomial Commitments