This note attempts to be a summary of the Basefold paper and talk by Binyi Chen.

The paper makes three major contributions that are all interlinked. It introduces

  1. a linear code (which we call "foldable code") that generalizes the Reed-Solomon code such that it no longer requires FFT-friendly fields,
  2. an Interactive Oracle of Proximity (IOPP), which uses the foldable code, and can be thought of as FRI but for multilinear polynomials, and
  3. a polynomial commitment scheme for multilinear polynomials, which mixes the IOPP and sum-check together.

Let's dive in!

Foldable code

The authors present a code family that generalizes Reed-Solomon code such that the code is still linear, but doesn't rely on FFT-friendly fields. The code family is defined as

Encd+1(ml||mr)=[Encd(ml)+tl(d)Encd(mr)]||[Encd(ml)+tr(d)Encd(mr)]

where "

||" represents concatenation, and

m=ml||mr,mF2d+1,Encd:F2dF2d+c,tl(d)F2d+c,tr(d)F2d+c.

In words,

2c is the blowup factor.
tl(d)
and
tr(d)
are parameters of the code.

The Basefold authors define

Encd+1 as being "foldable".

Notice that

{tl(d)}d=0d=k,
{tr(d)}d=0d=k
and
Enc0
, where
2k
is the length of the original message to encode, fully define the code. That is, each assignment of values to these parameters results in a different code. We will defer to the paper and the talk for how those values are chosen in practice.

Linearity of the code

The only property of interest for this discussion is that

Enc0 is a linear function; that is, it can be represented is a
1×2c
matrix. We can then show by induction that
Encd
for all
d
are also linear. That is, assuming that
Encd
is linear, show that
Encd+1
is linear. Let
M
be the matrix that represents
Encd
. Then,

Encd+1(ml||mr)=[Mml+tl(d)Mmr]||[Mml+tr(d)Mmr]=[Mml+TlMmr]||[Mml+TrMmr]=[MTlM0000MTrM][mlmr]

where

Tl(d) is a diagonal matrix where the entries along the diagonal are the elements of
tl(d)
(and similarly for
Tr(d)
). Hence, since
Encd+1
can be represented by a matrix, it is linear, which completes the proof sketch.

Reed-Solomon generalization check

This section can be safely skipped upon first read

We claimed earlier that the code presented above is a generalization of the Reed-Solomon code. Hence, we need to show that there exists

{tl(d)}d=0d=k,
{tr(d)}d=0d=k
and
Enc0
which result in a Reed-Solomon code.

Recall the definition of a Reed-Solomon code. For

f(x)F[X]2d+1 (i.e.
f(x)
is a polynomial of degree less or equal than
2d+1
),

RSd+1(f(x))=f(x)[ω,ω2,,ω2d+1+c]

where the brackets signify that

f(x) is evaluated over all these points, the result stored in a list of corresponding length.
ω
is the generator of a group of size
2d+1+c
.

f(x) even/odd decomposition

Next, define

fe(x) and
fo(x)
to be the polynomials whose coefficients correspond to the event and odd coefficients of
f(x)
, respectively. For example, if
f(x)=ax3+bx2+cx+d
, then
fe(x)=ax+c
and
fo(x)=bx+d
. Formally, this is written as

f(x)=fe(x2)+xfo(x2)

RSd+1
decomposition

Next, since the Reed-Solomon code is linear, the following holds:

RSd+1(f(x))=RSd+1(fe(x2)+xfo(x2))=RSd+1(fe(x2))+RSd+1(x)RSd+1(fo(x2))

where

is the Hadamard product.

Next, notice the following

RSd+1(fe(x2))=fe(x2)[ω,ω2,,ω2d+1+c]=fe(x)[ω2,ω4,,ω2d+2+c]=fe(x)[ω2,ω4,,ω2d+1+c,ω2,ω4,ω2d+1+c]=RSd(fe(x))||RSd(fe(x))

Naturally, the same holds for

RSd+1(fo(x2)). Therefore, we can rewrite
RSd+1(f(x))
as

RSd+1(f(x))=RSd(fe(x))+[ω,ω2,,ω2d+c]RSd(fo(x))||RSd(fe(x))+[ω2d+c+1,,ω2d+c+1]RSd(fo(x))

Notice that this fits the foldability property, where

ml=fe(x)mr=fo(x)tl=[ω,ω2,,ω2d+c]tr=[ω2d+c+1,,ω2d+c+1]

This comes with a caveat. The foldability property requires the coefficients of

fe(x) to be stored in the left part of
m
, followed by the coefficients of
fo(x)
. And this must be true recursively. Hence, if

f(x)=a7x7++a1x+a0

then

f(x)'s coefficients must be stored as

[a0,a4,a2,a6,a1,a5,a3,a7]

We leave it as an exercise to verify why this is correct.

IOPP

The Basefold paper introduces a new Interactive Oracle Proof of Proximity (IOPP) analogous to FRI, but for multilinear polynomials. That is, the protocol convinces the verifier that the prover has a multilinear polynomial (or, to be more precise, a function "close" to one). Where FRI uses Reed-Solomon codes, Basefold's IOPP uses the foldable code presented in the previous sections. Finally, similar to FRI, Basefold's IOPP also has a "commit" phase, where the prover commits to codes using Merkle trees for a number of layers, and a "query" phase, where the verifier queries random points in the codes to check for consistency between the layers.

Formally, the goal of the prover is to show that it has some polynomial

f(d)F[X1,,Xd]1. Throughout this post, we will assume that all multilinear polynomials
f(x1,,xn)=fl(x1,,xn1)+xnfr(x1,,xn1)
are stored as
f=fl(x1,,xn1)||fr(x1,,xn1)
in coefficient form. In words,
fl
is the multilinear polynomial formed from all the coefficients of
f
which do not have an
xn
term, while
fr
is the multilinear polynomial formed formed from the coefficients which do have an
xn
term.

For example, if

f(x1,x2)=ax1x2+bx1+cx2+d, then
fl(x1)=bx1+d
and
fr(x1)=ax1+c
. Therefore,
f
would be stored as an array of coefficients in the following order:
[b,d,a,c]
.

Commit phase

In the first round, the prover encodes

f(d)(x1,,xd) as
π(d)=Encd(f(d))
, where
π(d)F2d+c
. It builds a Merkle tree, where elements of
π(d)
are the leaves of the tree, and sends the Merkle root to the verifier.

The verifier then sends a random

rdRF. The prover uses this random value to build the multilinear polynomial for the next layer:

f(d1)(x1,,xd1)=fl(d)(x1,,xd1)+rdfr(d)(x1,,xd1)

The protocol repeats similarly for another

d1 rounds.

Notice: by construction,

fd1(x1,,xd1)=fd(x1,,xd1,rd). In words,
fd1
is
fd
where the last variable has been fixed to
rd
. If we extend this observation all the way down to the last round,
f0=fd(r1,,rd)
, for random
r1rd
. This will be an important point in the last section when we talk about the Basefold polynomial commitment scheme.

Query phase

The goal of the query phase is for the verifier to check the consistency between

π(j) and
π(j1)
for
j{d,1}
. Concretely, from 2 points in
π(j)
, the verifier will be able to compute a single point in
π(j1)
, for which the prover will send a Merkle proof. In order to do that, we will need to make use of the foldability property and linearity of the code.

Let

π(d)=πl(d)||πr(d). Recall that the foldability property of the code states that

πl(d)=Encd1(fl(d))+tl(d)Encd1(fr(d))πr(d)=Encd1(fl(d))+tr(d)Encd1(fr(d))

For readability purposes, we will define the following variables

El=Encd1(fl(d))Er=Encd1(fr(d))

With this we are ready to start discussing how queries work. The verifier samples

i{0,,2d+c1}. The prover sends
πl(d)[i]
,
πr(d)[i]
, and their corresponding Merkle proofs.

Next, the verifier infers

El[i] and
Er[i]
. The previous definition of
πl(d)
and
πr(d)
, when zooming in on the
ith
index, can be written as

[1tl(d)[i]1tr(d)[i]][El[i]Er[i]]=[πl(d)[i]πr(d)[i]]

or equivalently,

[El[i]Er[i]]=[1tl(d)[i]1tr(d)[i]]1[πl(d)[i]πr(d)[i]]

Finally, recall from the commit phase that

π(d1)=Encd1(f(d1))=Encd1(fl(d)+rdfr(d))

We can use the linearity property of the code to get

Encd1(fl(d)+rdfr(d))=Encd1(fl(d))+rdEncd1(fr(d))=El+rdEr

When we focus on a single index

i, we get

π(d1)[i]=El[i]+rdEr[i]

Hence, the verifier was able to compute

π(d1)[i], which was our initial goal.

Note that

π(d1)[i] either lies in the first or second half of
π(d1)
; that is,
π(d1)[i]=πl(d1)[i2]
when
i<|π(d1)|2
, or
π(d1)[i]=πr(d1)[i2]
otherwise. Whichever one it is, the prover only needs to send the Merkle proof for it, since the verifier already computed the value. Naturally, the prover also needs to send the value & Merkle proof for the other half (which the verifier didn't compute).

The same process then repeats for each round. In the last round, the prover only sends the Merkle proof for

π(0)[i(0)]. The verifier ensures that the Merkle proof check passes with the value of
π(0)[i(0)]
that it computed, completing the query.

Note: we use

i(0) to signify the index in the last round, where
i(0)=i2d
.

As in FRI, the verifier performs a number of queries, depending on the desired security level (i.e. the more queries, the more secure).

Polynomial commitment scheme

The Basefold polynomial commitment scheme relies heavily on sum-check. Refer to this article for a refresher.

The Basefold paper culiminates into a polynomial commitment scheme (PCS) for multilinear polynomials. That is, it allows a prover to commit to a multilinear polynomial

f, and convince the verifier that
f(z)=y
for some point
zFd
chosen by the verifier. The core idea is to interleave sum-check and the IOPP.

In this section, we will freely switch between the equalivalent notations for vectors

zFd:
z=(z1,,zd)
.

First, we rewrite

f(z1,,zd) in a form that is amenable to being sum-checked.

f(z1,,zd)=(x1,,xd)Bdf(x1,,xd)eqz(x1,,xd)

where

Bd={0,1}d is the
d
-dimensional boolean hypercube, and
eqz(x1,,xd)=i=1dxizi+(1xi)(1zi)
. The above equality is true by definition of multilinear polynomials.

For a refresher on multilinear polynomials, see this article.

Recall that sum-check requires the verifier to be able to evaluate

f(x)eqz(x) at a random point
r=(r1,,rd)
generated during the protocol. The key realization to make is that in the commit phase of IOPP, the last polynomial computed by the prover
f(0)=f(d)(r1,,rd)
! So if we reuse the same random
r
generated in the IOPP for sum-check, then the verifier can use
f(0)
sent by the prover as its query
f(d)(r)
.

Note: this requires the prover to send

f(0) in the last step of the IOPP instead of
π(0)
. The verifier can then compute
π(0)=Enc0(f(0))
on its own in constant time.

Hence, the PCS protocol is as follows:

  1. Run the commit phase of the IOPP.
    • This generates
      r=(r1,,rd)
  2. Run sum-check, except reusing
    (r1,,rd)
    as random values
    • Use
      f(0)
      as the value for
      f(d)(r1,,rd)
    • The final verifier check is
      g1(r1)=f(0)eqz(r)
      , where
      g1
      is the last round polynomial sent by the prover.
  3. Run the query phase of the IOPP

Pause and ponder

In a sense, sum-check almost gives you a multilinear PCS; the only missing piece is that last query

f(d)(r). But the prover already computes that query
f(d)(r)
, as well as all intermediary polynomials (
f(d1)(x1,,xd1)=f(d)(x1,,xd1,rd)
,
f(d2)(x1,,xd2)=f(d)(x1,,xd2,rd1,rd)
, etc) during the protocol. Hence, the core idea of the Basefold PCS is to commit to these intermediary polynomials, and in a way, have the verifier track the correct transformation from
f(i)
to
f(i1)
.

But in order to do that, what was missing is a linear code for multilinear polynomials that allows the verifier to track the correct transformation between codewords (i.e. the "foldable" property). Additionally, it's crucial for the IOPP to be computing

f(i1)=fl(i)+rifr(i) (that is, the same computation done during sum-check).

All in all, the Basefold PCS is a very clever construction at the intersection of all these ideas.