Zero Knowledge Multiplication

Zero Knowledge Multiplication of Polynomials

Using the polynomial commitment scheme from the previous chapter, a prover can show that they have three polynomials l(x)l(x), r(x)r(x), and t(x)t(x) and prove that t(x)=l(x)r(x)t(x) = l(x)r(x).

For this algorithm to work, the verifier must believe that the polynomial evaluations are correct – but this is something we showed in the previous chapter. Most of the steps here are simply repeating the polynomial commitment algorithm we did previously.

At a high level, the prover commits to l(x)l(x), r(x)r(x), and t(x)t(x) and sends the commitments to the verifier. Then, the verifier chooses a random value for xx as uu and asks the prover to evaluate the polynomials at uu. The verifier then checks that the evaluations were done correctly and that the evaluation for l(x)l(x) multiplied by the evaluation for r(x)r(x) equals the evaluation for t(x)t(x).

For example, suppose that the first polynomial is l(x)=2xl(x)=2x and the second is r(x)=x+1r(x) = x + 1. Then t(x)=2x(x+1)=2x2+2t(x)=2x(x+1) = 2x^2+2. The verifier can sample any random xx value, and the result of the product l(x)r(x)l(x)r(x) will be t(x)t(x). The plot below shows an example of the verifier choosing x=2x=2:

random-polynomial-multiplication

The verifier would then check that 3×4=123 \times 4 = 12 and accept the prover’s claim.

The Schwartz-Zippel lemma states that if f(x)g(x)f(x) \neq g(x) then the probability that f(u)=g(u)f(u) = g(u) for some random value uu is less than d/pd/p where dd is the maximum degree of the two polynomials and pp is the order of the finite field. If dpd \ll p (dd much much less than pp), then the probability of uu being an intersection point of two non-equal polynomials is negligible.

Specifically, suppose the prover is lying and l(x)r(x)t(x)l(x)r(x) \neq t(x). In that case, for a random uu, l(u)r(u)t(u)l(u)r(u) \neq t(u) with extremely high probability. If l(x)r(x)t(x)l(x)r(x) \neq t(x), then l(x)r(x)l(x)r(x) and t(x)t(x) only intersect in at most dd points (the maximum degree of either l(x)r(x)l(x)r(x) or t(x)t(x)), and it is extremely unlikely that the verifier would randomly pick a uu that is one of the dd intersection points.

To get a sense of scale, dd in our case is 2, but the curve order of our elliptic curves (and hence the order of the field) is about 22542^{254}. So if t(x)l(x)r(x)t(x) \neq l(x)r(x), then the probability of t(u)=l(u)r(u)t(u) = l(u)r(u) is 1/22531/2^{253} which is vanishingly small.

We now describe the algorithm in detail, and then show an optimization.

Steps to prove knowledge of polynomial multiplication

The prover commits two linear (degree 1) polynomials l(x)l(x), r(x)r(x), a quadratic (degree 2) polynomial t(x)t(x), and sends the commitments to the verifier. The verifier responds with a random value uu, and the prover evaluates lu=l(u)l_u = l(u), ru=r(u)r_u = r(u), and tu=t(u)t_u = t(u) along with the proofs of evaluation πl,πr,πt\pi_l, \pi_r, \pi_t. The verifier asserts that all the polynomials were evaluated properly and that tu=lurut_u = l_ur_u.

Setup

The prover and verifier agree on elliptic curve points GG and BB with an unknown discrete log relationship (i.e. the points are chosen randomly).

Prover commits to l(x)l(x), r(x)r(x), and t(x)t(x)

The prover creates three polynomials:

l(x)=a+sLxr(x)=b+sRxt(x)=l(x)r(x)=(a+sLx)(b+sRx)=ab+(asR+bsL)x+sLsRx2\begin{align*} l(x) &= a + s_Lx \\ r(x) &= b + s_Rx \\ t(x) &= l(x)r(x) = (a + s_Lx)(b + s_Rx) = ab+(as_R+bs_L)x+s_Ls_Rx^2\\ \end{align*}

So they need to produce a total of 7 Pedersen commitments for each of the coefficients, which will require seven blinding terms α0,α1,β0,β1,τ0,τ1,τ2\alpha_0, \alpha_1, \beta_0, \beta_1, \tau_0, \tau_1, \tau_2

L0=aG+α0B// constant coefficient of l(x)L1=sLG+α1B// linear coefficient of l(x)R0=bG+β0B// constant coefficient of r(x)R1=sRG+β1B// linear coefficient of r(x)T0=abG+τ0B// constant coefficient of t(x)T1=(asR+bsL)G+τ1B// linear coefficient of t(x)T2=sLsRG+τ2B// quadratic coefficient of t(x)\begin{align*} L_0&=aG + \alpha_0B &&\text{// constant coefficient of }l(x)\\ L_1&=s_LG + \alpha_1B &&\text{// linear coefficient of }l(x)\\ \\ R_0&=bG + \beta_0B &&\text{// constant coefficient of }r(x)\\ R_1&=s_RG + \beta_1B &&\text{// linear coefficient of }r(x)\\ \\ T_0 &= abG + \tau_0 B &&\text{// constant coefficient of }t(x)\\ T_1 &=(as_R + bs_L)G + \tau_1B &&\text{// linear coefficient of }t(x)\\ T_2 &= s_Ls_RG + \tau_2B &&\text{// quadratic coefficient of }t(x) \end{align*}

The prover sends (L0,L1,R0,R1,T0,T1,T2)(L_0, L_1, R_0, R_1, T_0, T_1, T_2) to the verifier.

Verifier generates random scalar uu

… and sends the field element uu to the prover.

Prover evaluates the three polynomials and creates three proofs

The prover plugs in uu to the polynomials and computes the sum of the blinding terms of the polynomial coefficient commitments when uu is applied.

lu=a+sLuru=b+sRutu=ab+(asR+bsl)u+sLsRu2πl=α0+α1uπr=β0+β1uπt=τ0+τ1u+τ2u2\begin{align*} l_u &= a + s_Lu\\ r_u &= b + s_Ru\\ t_u &= ab + (as_R + bs_l)u + s_Ls_Ru^2\\ \\ \pi_l &= \alpha_0 + \alpha_1u \\ \pi_r &= \beta_0 + \beta_1u \\ \pi_t &= \tau_0 + \tau_1u + \tau_2u^2 \end{align*}

The prover sends the values (lu,ru,tu,πl,πr,πt)(l_u, r_u, t_u, \pi_l, \pi_r, \pi_t) to the verifier. Note that these are all field elements, not elliptic curve points.

Final verification step

The verifier checks that each of the polynomials were evaluated correctly and that the evaluation of t(u)t(u) is the product of the evaluation of l(u)l(u) and r(u)r(u). The first three checks are proofs that the polynomial was evaluated correctly with respect to the commitment to the coefficients, and the last check verifies that the output of the polynomials have the product relationship as claimed.

luG+πlB=?L0+L1u// Check that l(u) was evaluated correctlyruG+πrB=?R0+R1u// Check that r(u) was evaluated correctlytuG+πtB=?T0+T1u+T2u2// Check that t(u) was evaluated correctlytu=?luru// Check that t(u)=l(u)r(u)\begin{align*} l_uG + \pi_l B &\stackrel{?}= L_0+L_1u &&\text{// Check that }l(u) \text{ was evaluated correctly}\\ r_uG + \pi_r B &\stackrel{?}= R_0+R_1u &&\text{// Check that }r(u) \text{ was evaluated correctly}\\ t_uG + \pi_t B &\stackrel{?}= T_0+T_1u+T_2u^2&&\text{// Check that }t(u) \text{ was evaluated correctly}\\ t_u &\stackrel{?}= l_ur_u &&\text{// Check that }t(u)=l(u)r(u) \end{align*}

When we expand the terms, we see they balance if the prover was honest:

(a+sLu)luG+(α0+α1u)πlB=?(aG+α0B)L0+(sLG+α1B)L1u(b+sRu)ruG+(β0+β1u)πrB=?(bG+β0B)R0+(sRG+β1B)R1u(ab+(asR+bsl)u+sLsRu2)tuG+(τ0+τ1u+τ2u2)πtB=?(abG+τ0B)T0+(asR+bsL+τ1B)T1u+(sLsR+τ2B)T2u2tu=?luru\begin{align*} \underbrace{(a + s_Lu)}_{l_u}G + \underbrace{(\alpha_0 + \alpha_1u)}_{\pi_l} B &\stackrel{?}= \underbrace{(aG + \alpha_0B)}_{L_0}+\underbrace{(s_LG + \alpha_1B)}_{L_1}u \\ \underbrace{(b + s_Ru)}_{r_u}G + \underbrace{(\beta_0 + \beta_1u)}_{\pi_r} B &\stackrel{?}= \underbrace{(bG + \beta_0B)}_{R_0}+\underbrace{(s_RG + \beta_1B)}_{R_1}u \\ \underbrace{(ab + (as_R + bs_l)u + s_Ls_Ru^2)}_{t_u}G + \underbrace{(\tau_0 + \tau_1u + \tau_2u^2)}_{\pi_t} B &\stackrel{?}= \underbrace{(abG + \tau_0 B)}_{T_0}+\underbrace{(as_R + bs_L+ \tau_1B)}_{T_1}u+\underbrace{(s_Ls_R+ \tau_2B)}_{T_2}u^2 \\ t_u &\stackrel{?}= l_ur_u \end{align*}

Optimization: sending fewer commitments

In the first step, the prover sends 7 elliptic curve points, and in the final step, the verifier checks 4 equalities. We can improve the algorithm to only send 5 elliptic curve points and do 3 equality checks.

This is done by putting the constant coefficients of l(x)l(x) and r(x)r(x) into a single commitment and the linear coefficients of those polynomials into a separate commitment. By way of reminder, we defined l(x)l(x) and r(x)r(x) as

l(x)=a+sLxr(x)=b+sRx\begin{align*} l(x) &= a + s_Lx \\ r(x) &= b + s_Rx \\ \end{align*}

so aa and bb are the constant coefficients, and sLs_L and sRs_R are the linear coefficients.

This is similar to how we would commit a vector. We are in a sense committing the constant coefficients as a vector and the linear coefficients as another vector.

Setup

During the setup, we now need 3 elliptic curve points: GG, HH, and BB.

Polynomial commitment

A=aG+bH+αB// commit the constant termsS=sLG+sRH+βB// commit the linear termsT0=abG+τ0B// commit the constant coefficient of t(x)T1=(asR+bsL)G+τ1B// linear coefficient of t(x)T2=sLsRG+τ2B// quadratic coefficient of t(x)\begin{align*} A &= aG + bH + \alpha B &&\text{// commit the constant terms}\\ S &= s_LG + s_RH + \beta B &&\text{// commit the linear terms}\\ T_0 &= abG + \tau_0 B &&\text{// commit the constant coefficient of } t(x) \\ T_1 &=(as_R + bs_L)G + \tau_1B &&\text{// linear coefficient of }t(x)\\ T_2 &= s_Ls_RG + \tau_2B &&\text{// quadratic coefficient of }t(x) \end{align*}

Note that the coefficients of l(x)l(x) are applied to GG and the coefficients of r(x)r(x) are applied to HH. The prover sends (A,S,T0,T1,T2)(A, S, T_0, T_1, T_2) to the verifier, who responds with uu.

Polynomial evaluation

lu=l(u)=a+sLuru=r(u)=b+sRutu=t(u)=l(u)r(u)=ab+(asR+bsL)u+sLsRu2πlr=α+βuπt=τ0+τ1u+τ2u2\begin{align*} l_u &= l(u) = a + s_Lu\\ r_u &= r(u) = b + s_Ru\\ t_u &= t(u) = l(u)r(u) = ab + (as_R + bs_L)u + s_Ls_Ru^2\\ \pi_{lr} &= \alpha + \beta u \\ \pi_t &= \tau_0 + \tau_1u + \tau_2u^2 \end{align*}

lul_u, rur_u, tut_u, πlr\pi_{lr}, and πt\pi_t are computed as before, but the proof of evaluations for l(x)l(x) and r(x)r(x), which were formerly πl\pi_l and πr\pi_r are combined into a single one: πlr\pi_{lr}.

Final verification

A+Su=?luG+ruH+πlrBtuG+πtB=?T0+T1u+T2u2tu=?luru\begin{align*} A + Su &\stackrel{?}= l_uG + r_uH+\pi_{lr}B \\ t_uG + \pi_tB &\stackrel{?}= T_0 + T_1u + T_2u^2 \\ t_u &\stackrel{?}= l_ur_u \end{align*}

The check A+Su=?luG+ruH+πlrBA + Su \stackrel{?}= l_uG + r_uH+\pi_{lr}B expands to

(aG+bH+αB)A+(sLG+sRH+βB)uSu=(a+sLu)luG+(b+sRu)ruH+(α+βu)πlrB\underbrace{(aG + bH + \alpha B)}_A + \underbrace{(s_LG + s_RH + \beta B)u}_{Su} = \underbrace{(a + s_Lu)}_{l_u}G + \underbrace{(b + s_Ru)}_{r_u}H + \underbrace{(\alpha + \beta u)}_{\pi_{lr}}B

With some rearranging on the left-hand-side, we can see the equality check simultaneously checks that both l(x)l(x) and r(x)r(x) were evaluated correctly.

(a+sLu)G+(b+sRu)H+(α+βu)B=(a+sLu)luG+(b+sRu)ruH+(α+βu)πlrB(a + s_Lu)G + (b + s_Ru)H + (\alpha + \beta u)B=\underbrace{(a + s_Lu)}_{l_u}G + \underbrace{(b + s_Ru)}_{r_u}H + \underbrace{(\alpha + \beta u)}_{\pi_{lr}}B

As another way of looking at the check A+Su=?luG+ruH+πlrBA + Su \stackrel{?}= l_uG + r_uH+\pi_{lr}B, consider the following visualization:

A=aG+bH+αBSu=sLuG+sRuH+βuB||||||l(u)Gr(u)HπlrB\begin{align*} A &= &aG& + &bH &+ &\alpha B\\ Su &= &s_LuG& + &s_RuH &+ &\beta u B\\ &&\vcenter{\hbox{|}} \vcenter{\hbox{|}}&&\vcenter{\hbox{|}} \vcenter{\hbox{|}}&&\vcenter{\hbox{|}} \vcenter{\hbox{|}}\\ &&l(u)G&&r(u)H&&\pi_{lr}B \end{align*}

Zero Knowledge Multiplication of Scalars

Our proof that we multiplied two polynomials together correctly to obtain a third can be used to prove that we multiplied two scalars together to obtain a third. No changes to the algorithm are necessary, only a minor change in semantics (how we interpret the commitments).

Let’s say we want to prove that we carried out the multiplication ab=vab = v.

Problem statement

AA is a commitment to aa and bb, and VV is a commitment to vv where v=abv = ab. We wish to prove that AA and VV are committed as claimed without revealing aa, bb, or vv.

Solution

The high level idea is that a scalar can be turned into a polynomial by adding an arbitrarily chosen linear term, e.g. aa becomes a+sLxa + s_Lx and bb becomes b+sRxb + s_Rx. sLs_L and sRs_R are chosen randomly by the prover.

When the polynomials a+sLxa + s_Lx and b+sRxb + s_Rx are multiplied together, the multiplication of abab happens “inside” the polynomial multiplication.

(a+sLx)(b+sRx)=ab+(asR+bsL)x+sLsrx2(a + s_Lx)(b + s_Rx) = \boxed{ab} + (as_R + bs_L)x + s_Ls_rx^2

Recall the prover begins the algorithm by sending commitments:

A=aG+bH+αB// commitment to a and bS=sLG+sRH+βB// commit the linear termsV=abG+τ0B// commit the product VT1=(asR+bSL)G+τ1B// linear coefficient of t(x)T2=sLsRG+τ2B// quadratic coefficient of t(x)\begin{align*} A &= aG + bH + \alpha B &&\text{// commitment to }a\text{ and }b\\ S &= s_LG + s_RH + \beta B &&\text{// commit the linear terms}\\ V &= abG + \tau_0 B &&\text{// commit the product V} \\ T_1 &=(as_R + bS_L)G + \tau_1B &&\text{// linear coefficient of }t(x)\\ T_2 &= s_Ls_RG + \tau_2B &&\text{// quadratic coefficient of }t(x) \end{align*}

We simply change the “interpretation” of AA from being the constant terms of the polynomials to the constants aa and bb that we are multiplying. We change T0T_0 to VV to reflect the change of interpretation as a commitment to VV in the multiplication we are trying to prove we did correctly, i.e. v=abv = ab.

Exercise: Fill in the missing Python code to implement the algorithm described above.

from py_ecc.bn128 import G1, multiply, add, FQ, eq
from py_ecc.bn128 import curve_order as p
import random

def random_element():
    return random.randint(0, p)

# these EC points have unknown discrete logs:
G = (FQ(6286155310766333871795042970372566906087502116590250812133967451320632869759), FQ(2167390362195738854837661032213065766665495464946848931705307210578191331138))

H = (FQ(13728162449721098615672844430261112538072166300311022796820929618959450231493), FQ(12153831869428634344429877091952509453770659237731690203490954547715195222919))

B = (FQ(12848606535045587128788889317230751518392478691112375569775390095112330602489), FQ(18818936887558347291494629972517132071247847502517774285883500818572856935411))

# utility function
def addd(A, B, C):
    return add(A, add(B, C))

# scalar multiplication example: multiply(G, 42)
# EC addition example: add(multiply(G, 42), multiply(G, 100))

# remember to do all arithmetic modulo p
def commit(a, sL, b, sR, alpha, beta, gamma, tau_1, tau_2):
    pass
    # return (A, S, V, T1, T2)


def evaluate(f_0, f_1, f_2, u):
    return (f_0 + f_1 * u + f_2 * u**2) % p

def prove(blinding_0, blinding_1, blinding_2, u):
    # fill this in
    # return pi
    pass

## step 0: Prover and verifier agree on G and B

## step 1: Prover creates the commitments
a = ...
b = ...
sL = ...
sR = ...
t1 = ...
t2 = ...

### blinding terms
alpha = ...
beta = ...
gamma = ...
tau_1 = ...
tau_2 = ...

A, S, V, T1, T2 = commit(a, sL, b, sR, alpha, beta, gamma, tau_1, tau_2)

## step 2: Verifier picks u
u = ...

## step 3: Prover evaluates l(u), r(u), t(u) and creates evaluation proofs
l_u = evaluate(a, sL, 0, u)
r_u = evaluate(b, sR, 0, u)
t_u = evaluate(a*b, t1, t2, u)

pi_lr = prove(alpha, beta, 0, u)
pi_t = prove(gamma, tau_1, tau_2, u)

## step 4: Verifier accepts or rejects
assert t_u == (l_u * r_u) % p, "tu != lu*ru"
assert eq(add(A, multiply(S, u)), addd(multiply(G, l_u), multiply(H, r_u), multiply(B, pi_lr))), "l_u or r_u not evaluated correctly"
assert eq(add(multiply(G, t_u), multiply(B, pi_t)), addd(V, multiply(T1, u), multiply(T2, u**2 % p))), "t_u not evaluated correctly"

Learn more

This article is part of a series on Bulletproof ZKPs.

Ready to Get Started?Join Thousands of Users Today

Start your free trial now and experience the difference. No credit card required.

© 2025 Better-Start. All rights reserved.