Try   HackMD

Evaldas Drąsutis
IOTA Foundation

Formulas for polynomial KZG commitments in Lagrange basis

Summary

This post describes an experimental implementation of polynomial KZG commitments, aka Kate commitments. The implementation uses the trusted setup completely in Lagrange basis calculated upon secret

s, non-secret value
ω
and its powers. All computations are performed in the evaluation form. The powers of a secret on the curve
[si]1
nor any FFT operations are not needed in any stage.

The math mostly follows articles on KZG by Dankrad Feist, specifically this and this. We assume the reader is familiar with the content of it.

The implemented experimental Go package is based on DEDIS Advanced Crypto Library for Go Kyber v3 and uses

bn256 BLS suite. It can be found here: https://github.com/lunfardo314/verkle

General

Constant
d

d is a global constant which defines maximum length of the vector we want to commit to. The shorter vectors can be commited by providing zeroes as values, however we can't commit vectors longer than
d
.

The
ω
value

The

ω is a scalar in the underlying field with the property of
ωk1,k=1d1
.

We use values

ω0,ω1,,ωd1 to calculate the Lagrange basis. The Lagrange polynomial is defined the following way:
li(X)=j=0,jid1Xωiωjωi

Any vector
V=(v0,v1,,vd1)
on the field corresponds to the polynomial
f(X)=i=0d1vili(X)
. It makes
f(ωi)=vi
for
i=0d1
.

The auxiliarly polynomial

A(X)=i=0d1(Xωi) has its roots at
ωk,k=0d1

The formal derivate

A(X)=j=0d1ij(Xωi) has the following values at these roots:

A(ωj)=ij(ωjωi)

We could assume

ωd=1, i.e
ω
to be a primitive root of unity Then we'll have
A(X)=Xd1
and
A(X)=dXd1
,
A(ωi)=dωi
. It would make some calculation below more economic, however in this implementation we aim to avoid this assumption due to reasons explained below.

We can always assume

ω=rp1d where
p
is the number of elements of the scalar field and
r
is a field element selected so that the
ω
is a primitive root of unity of degree
d
(any non-zero and non-one
r
would give a root of unity but not any would give a primitive root of unity, which is needed in our case, because we need
ωi1,i=1d1
. In practice we can keep trying random elements
r
until
ω
is a primitive. See code in the experimental implementation).

However, the above implies

d is a divisor of
p1
. Since
d
is an essential constant of the setup, the assumption
ωd=1
significantly limits possible values of
d
and this, in turn, has consequences in performance. For example, in the BLS suite we use in this implementation, smallest divisor of
p1
after
25
is
5743
. If we want to create commitments of vectors of size
257
(
256
children plus terminal value in the node of a 257-ary verkle trie), we would still have to use
d=5743
.

So, we want to provide setup for calculations for arbitrary

d and therefore avoid assumption of
ωd=1
.

Trusted setup

The Lagrange basis for a secret

s is a collection of
d
values on the curve:
TLi=[li(s)]1i=0d1

Note that

TLi also depends on
ω
.
The trusted setup
T(d,ω,s)
is a collection of public values on
G1
and
G2
which includes:

  • d
  • ω
  • [li(s)]1i=0d1
  • [sωi]2i=0d1
    .

We assume

s is a scalar value on the field not known to anyone, a secret. The secret value
s
and public values
d
and
ω
are used to compute all the values in the trusted setup
T(d,ω,s)
. The secret value
s
is destroyed immediately after the generation of
T
so that noone in the world should know it.

The trusted setup is public, i.e. it does not contain any secrets. It is computationally non-feasible neither to re-construct

s from the given
T
nor to create another valid
T(d,ω,s)=T(d,ω,s)
for some other
ss
.

What do we want to achieve?

We aim to commit to vectors of scalar values on the underlying field

V=(v0,v1,,vd1) and to prove inclusion of value
vi
in a position
i
by providing the commitment to the whole vector (polynomial) and the proof (no need for the vector itself).

The commitment to the vector

V is a commitment to the polynomial
f(X)
such that
f(ωi)=vi
. The KZG commitment to the polynomial is
C=[f(s)]1
. It is calculated by the prover.

The proof

πi=πi(T,V,i) is a value on
G1
. It is calculated by the prover to prove the fact that vector
V
contains
vi
at the index
i
.

The condition

verify(T,C,πi,vi,i)=true is practically impossible to satisfy when
f(ωi)vi
. The
verify
function is calculated by verifier. It uses bilinear pairing
e:G1×G2GT
and the following equation to check the validity of the proof:
e(πi,[sωi]2)=e(C[vi]1,H)

where
H
is the generator of the group
G2

Calculating commitment to the vector

KZG commitment to the vector is:

C(T,V)=[f(s)]1
we calculate it from the trusted setup, i.e. not knowing the secret
s
:
C(T,V)=i=0d1vi×TLi

Note that once we have
TLi
constants precalculated, the
C
can be calulated in
O(d)
multiplications to scalar and additions on the curve. If the vector is sparse, i.e. with just few non-zero elements, the number of operations on curve is equal to the number of non-zero values.

Setting a new value at some position in the vector does not require complete re-calculation of

C, two operations on the curve is enough to update
C
. Let's say the old value
vi
is replaced with the new value
vi
. The commitment to the updated vector
C
can be calculated as
C=C+TLi×(vivi)
.

Calculating the proof

How do we calulate the proof

πi that value
vi
is in the position
i
of the vector?

The simple but naive and wrong approach can be found below in the Annex. Calculating the proof. Naive approach (wrong).

To calculate

πi correctly we have to perform true division of polynomials.
The function
qi(X)=f(X)viXωi
is a polynomial because
Xωi
is a factor of
f(X)vi
. The latter is a polynomial which has root
ωi
by the very construction of
f
.

The KZG proof of

f(ωi)=vi is
πi=[qi(s)]1
.

Just like we calculate

f in Lagrange basis:
f(X)=i=0d1vili(X)f(ωj)=i=0d1vili(ωj)=i=0d1f(ωi)li(ωj)f(s)=i=0d1f(ωi)li(s)

we aim to calculate
qi(X)
in Lagrange basis too:
qi(X)=m=0d1qi(ωm)lm(X)qi(s)=m=0d1qi(ωm)lm(s)

So, we only need to calculate the

qi(ωm) for each
m
but how? The problem is, in the direct calculation it leads to zero denominator, even if we know it has value there because
qi(X)
is a polynomial.

To calculate

qi(ωm), we follow formulas in the article mentioned above, the chapter Dividing when one of the points is zero. We adjust formulas to our use of
{ωj}
and to be more clear for our implementation.

Calculating
qi(ωm)
with
mi

qi(X)=f(X)viXωi=j=0d1vjlj(X)viXωiqi(ωm)=j=0d1(vjvi)lj(ωm)ωmωi=j=0,jid1(vjvi)lj(ωm)ωmωiqi(ωm)=vmviωmωi,mi

Almost there. Now we only have to figure out how to calculate

qm(ωm).

Calculating
qm(ωm)

We can rewrite formulas for the Lagrange polynomial:

lj(X)=1A(ωj)A(X)Xωjlj(X)Xωi=1A(ωj)A(X)(Xωj)(Xωi)A(ωi)A(ωi)=A(ωi)A(ωj)(1A(ωi)A(X)Xωi)1Xωj=A(ωi)A(ωj)li(X)Xωj
Note that the formulas above are defined for any
i,j=0d1
. Let's assume
X=ωm
in the above:
lj(ωm)ωmωi=A(ωi)A(ωj)li(ωm)ωmωjqi(ωm)=j=0,jid1(vjvi)lj(ωm)ωmωi==j=0,jid1(vjvi)A(ωi)A(ωj)li(ωm)ωmωjqm(ωm)=j=0,jmd1(vjvm)A(ωm)A(ωj)lm(ωm)ωmωj=j=0,jmd1(vjvm)A(ωm)A(ωj)1ωmωj==j=0,jmd1vj×A(ωm)A(ωj)1ωmωjvm×j=0,jmd1A(ωm)A(ωj)1ωmωj

The following pre-calculated values would improve performace:
TAmj=A(ωm)A(ωj)1ωmωjmjTKm=j=0,jmd1A(ωm)A(ωj)1ωmωjqm(ωm)=j=0,jmd1vj×TAmjvm×TKm

Final formulas to calculate KZG proof
πi

Note that we assume trusted setup is completely in Lagrange basis:

qi(ωm)=vmviωmωi,imqm(ωm)=j=0,jmd1vj×TAmjvm×TKmπi=j=0d1qi(ωj)×TLj
where
TAmj=A(ωm)A(ωj)1ωmωjmjTKm=j=0,jmd1A(ωm)A(ωj)1ωmωj=j=0,jmd1TAmj

Precalculation of

TKm allows significantly speed up calculation of
qm(ωm)
when
V
is sparse.

Precalculation of

TAmj could take a lot,
O(d2)
, of memory, so probably not worth it when
d
is large. Pre-calculation of
A(ωi)
would be enough.

Some observations

1. The formulas above are valid for arbitrary

d, i.e. not constrained by the condition
ωd=1
. We chose to (pre)calculate values
A(ωi)
instead

2. Funny enough, it is not clear how, using the trusted setup, we could even calculate a fake proof

πifake which would state (wrongly)
f(ωi)=y
for arbitrary
yvi
.

3. We can provide proof of the absense of a key in the verkle tree. For this we just need to provide proof of

vi=0 i.e.
f(ωi)=0
.

4. Since

qi(ωm) are all non-zero, calculation of
πi
requires
d
operations on the curve. So, calculating the proof is an expensive operation and it does not look like it is possible to optimize it.

5. It turns out, we can update any trusted setup with the additional secret

t by multiplying its public values without knowing the underlying secret. This property can be used to increase security of the trusted setup the following way:

  • let's say we have
    N
    unrelated parties
  • party
    0
    generates trusted setup
    T0
    for its secret
    s0
    , destroys it and passes
    T0
    to the party
    T1
    .
  • And so on: party
    j
    receives trusted setup
    Tj1
    from the previous party, generates it own secret
    sj
    , multiplies previous trusted setup to get the next:
    Tj=sj×Tj1
    and destroys the secret
    sj
    .
  • the last trusted setup
    T=TN1
    is secure if any one of
    N
    parties were honest, i.e. it destroyed and forgot its secret.

Using other domain than
ωi

Since we do not use the property of

ω being a root of unity on the field, i.e.
ωd=1
, the domain where Lagrange polynomials are calculated can be almost any collection of different values on the field.

For example, in the above we could replace

ωi with
i
, i.e we'd be using
0,1,2,d1
on the field as points to calculate the Lagrange basis. The pre-calculated values would become simpler and (pre) calculations faster:
TAmj=A(m)A(j)1mjmjTKm=j=0,jmd1A(m)A(j)1mj=j=0,jmd1TAmj

Annex. Calculating the proof. Naive approach (wrong)

The proof that vector (the committed polynomial) has value

vi in the i-th position is calculated the following way:
πi=πi(T,V,i)=[f(s)visωi]1

πi
is a point on
G1
, just like
C
. How do we calculate it? Let's expand the formula.

πi=[j=0d1vjlj(s)sωi]1[visωi]1=j=0d1vj[lj(s)sωi]1vi[1sωi]1
So, we only need to pracalculate values
[lj(s)sωi]1
and
[1sωi]1
and make it a part of the trusted setup, after that we can calculate
πi
for any
V
.Right? No!

As it was pointed out by Dankrad Feist in private email, after exposing value

[1sωi]1 in public we make
πi
fakeable:

πifake=j=0d1rj[lj(s)sωi]1ri[1sωi]1=1sωi(Cri[1]1)e(1sωi(Cri[1]1),[sωi]2)=e(C[ri]1,H)
for any
ri
.

The above means pairing equation is always satisfied for any value we want. It does not make any sense, so, the approach is wrong.