Try   HackMD

Dividing In Lagrange basis when one of the points is zero - Generalised

Reference

The formulas were derived by reading the following academic article here

Problem

In the multipoint protocol, we had a polynomial of the form:

g(X)=r0f0(X)y0Xz0+r1f1(X)y1Xz1++rm1fm1(X)ym1Xzm1

In our context,

zi is an element in the domain, so naively we cannot compute this division in lagrange form. We also do not want to use monomial form, as we would need to interpolate our polynomials, which is exp

Simplifying the problem:

We have

f(X)g(X)=f(X)Xxm=i=0d1fiLi(X)Xxm

In what follows, we re-derive all of the necessary formulas that will allows us to divide by a linear polynomial that vanishes on the domain in lagrange basis, where the domain can be arbitrary.

Lagrange polynomial

We briefly restate the formula for a lagrange polynomial:

Li(X)=ji,j=0Xxjxixj

The i'th lagrange polynomial evaluated at

xi is 1 and 0 everywhere else on the domain

First form of the barycentric interpolation formula

We introduce the polynomial

A(X)=(Xx0)(Xx1)...(Xxn).

We also introduce the derivative of

A(X)=j=0d1ij(Xxi) .

You can derive this yourself by generalising the product rule: https://en.wikipedia.org/wiki/Product_rule#Product_of_more_than_two_factors

In general this derivative does not have a succinct/sparse form. We do however have a succinct form if the domain is the roots of unity!

Now note that

A(xj)=i=0,ij(xjxi)

If we plug in

xk into
A(X)
all the terms with
Xxk
will vanish, this is why the sum disappears into a single product.

We can use

A and
A
to re-define our lagrange polynomial as :

Li(X)=A(X)A(xi)(Xxi)

Looking at the original lagrange formula,

A(xi) is the denominator and
A(X)Xxi
is the numerator.

The first barycentric form for a polynomial

f(X) can now be defined as :

f(X)=i=0d1A(X)A(xi)(Xxi)fi

Remarks

  • A(X)
    is not dependent on the values of
    fi
    and so can be brought out of the summation.
  • A(X)
    is only dependent on the domain, so it can be precomputed, along with
    A(X)

Re-defining the quotient

Note that our original problem was that the polynomial:

i=0d1fiLi(X)Xxm

Had a

Xxm term in the denominator. We will use the first barycentric form as a way to get rid of this.

First we rewrite

Li(X)Xxm using the first form:

Li(X)Xxm=A(X)A(xi)(Xxi)(Xxm)

We then note that:

A(X)=Lm(X)A(xm)(Xxm)

I just re-arranged the formula for the first form to equal

A(X) for
Lm(X)

We can hence plug this into our previous equation:

Li(X)Xxm=Lm(X)A(xm)(Xxm)A(xi)(Xxi)(Xxm)

Simplifying since we have a

Xxm in the numerator and denominator:

Li(X)Xxm=A(xm)Lm(X)A(xi)(Xxi)

Note that when the elements in the domain are roots of unity;

A(xk)=d(xk)d1=dxk

The nice simplification here is due to two reasons: roots of unity form a cyclic group, and we can succinctly represent the d'th roots of unity in a sparse equation

Xd1 which is nice to derivate.

We have now re-defined

q(X) to not include
Xxm
!

We now summarise and state that:

q(X)=i=0d1fiLi(X)Xxm=fiA(xm)Lm(X)A(xi)(Xxi)

Explicit formulas for each case

Computing
qm

When dealing with the point which vanishes on zero, the above formula becomes:

Note:

Lm(xm)=1

qm=q(xm)=i=0d1A(xm)A(xi)fixmxi

Computing
qj

For the case that the evaluation does not vanish on the domain, we can use the original formula.

For all

jm

qj=q(xj)=i=0d1fiLi(xj)xjxm

We note that the terms of the sum are zero, except for when

i=j from the definition of the lagrange polynomial , hence we can simplify this to be:

qj=fjxjxm

Optimisations

If we use the formulas as shown above,

qm will take
d
steps due to the sum, and
qj
will take
d1
steps. We describe a way to reduce this complexity in the code.

1. Rewrite
qm
in terms of
qj

Note that if we multiply

qm by
11
we get:

qm=q(xm)=i=0d1A(xm)A(xi)fixixm

We can now substite in

qi

qm=q(xm)=i=0d1A(xm)A(xi)qi

2. Removing field inversions in
qj

Note that

qj has a division which is many times more expensive than a field multiplication. We now show a way to precompute in such a way that we do not need to invert elements.

With the roots of unity, we were able to use the fact that they formed a group.

Again note that:

qj=fjxjxm

The expensive division occurs here

1xjxm. In our particular case, we note that the domain is the discrete interval
[0,255]
this means we need only to precompute
1xi
for
xi[255,255]
. This is 510 values, so we would store
51032=16Kb
. If this is too much space, one could halve the storage by not storing the negated points.

How would I lookup and store these values in practice?

First we imagine that we have stored the values in an array as such:

[11,12,13,14...1255,11,12,...1255]

We first note that we can easily get from

1k to
1k
in the array by jumping forward 255 indices. Our strategy will be to find
1k
then jump to
1k
if we need to.

Example

We want to compute

10255.

  • Compute the
    abs(0255)=255=i

In practice, we can use an if statement to check whether 255 or 0 is larger, and subtract accordingly.

  • Note that
    1i
    is at index
    i1
  • Since our original computation was
    0255
    which is negative, we need to get the element at index:
    (i1)+255
    where
    i=255
    .

3. Precompute
A(xm)A(xi)

With the roots of unity, we did not need this optimisation as

A(xm)A(xi) equaled
ωiωm
which was trivial to fetch from the domain due to the roots of unity forming a domain.

For our case, we will need to store precomputed values, if we want to efficiently compute

qm in
O(d)
steps, and to also avoid inversions.

The strategy is that, we precompute

A(xi) and
1A(xi)
. Given that we have 256 points in the domain. This will cost us
256232 bytes=16kB
.

How would I lookup and store these values in practice?

Similar to the previous optimisation, we store

A(xi) in an array as such:

[A(0),A(1),A(2),A(3)...A(255),1A(0),1A(1),1A(2),...1A(255)]

Example

We want to compute

A(0)A(5)

  • We can fetch
    A(0)
    by looking up the element at index
    0
    in the array.
  • We can fetch
    1A(5)
    by looking up the element at index 5, then jumping forward 256 positions.

In general:

  • To fetch
    A(xi)
    we need to fetch the element at index
    i
  • To fetch
    1A(xi)
    we need to fetch the element at index
    i+256

Gotcha: You may produce an off by one error, by not realising that the second optimisation skips ahead 255 points for negative values, while the third optimisation skips ahead 256. This is because the second optimisation omits the value

10.

Evaluate polynomial in evaluation form on a point outside of the domain

Suppose

z is a point outside of the domain.

f(z)=i=0d1fiLi(z)=i=0d1A(z)A(xi)(zxi)fi=A(z)i=0d1fiA(xi)(zxi)

Optimising:

  • We already store precomputations for
    1A(xi)
  • We should compute
    zxi
    separately, then batch invert using the montgomery trick, so that we only pay for one inversion.