CCS

Table of contents

Credits

This document has been possible thanks to the reviews of @asn-d6 and @oskarth as well as the immense help from @arnaucube and Edu(from PSE) who almost completely derived R1CS&Plonkish -> CCS.
Also thanks to Srinath Setty for his help on any question we've had while reading his paper as well as while elaborating this doc.

CCS and R1CS (an intro)

Due to NP-completeness equivalence, we can easily translate any R1CS statement and translate it into a CCS statement which is completelly equivalent.

To do so, it contais certain information as:

  • m
    = Number of constraints.
  • n
    = Number of columns of matrices.
  • N
    = Number of non-zero entries on rows.
  • l
    = Size of the public input vector.
  • x
    = A vector containing public input instances.
  • w
    = A vector containing the witness that satisfies the instance
    S
    .

An R1CS structure

S is satisfied if:

(Az)(Bz)(Cz)=0 where
z=(w,1,x)Fn

CCS in general

In general, a CCS structure

S consists of:

  • Size bounds {
    m,n,N,l,t,q,dN
    where
    n<l
    }
    • t
      => Maximum size of the domain of the multisets.
    • q
      => Number of multisets used within the CCS instance.
    • d
      => Represents the maximum cardinality of each multiset. Which translates to the amount of different elements contained inside each multiset.
  • A sequence of matrices
    M0..Mt1
    that define the fixed parts of our circuit.
  • A sequence of multisets
    S0,...,Sq1
    where
    sSi
    ,
    dom(s)={0..t1}
  • A sequence of constants
    [c0,...,cq1]

Some of thesedon't really translate to any property in particular. Indeed they're just parameters that are used to be able to adapt all the arithmetizations into CCS.

A CCS instance

(S,x) is satisfied if only if:
i=0q1cijSi(Mjz)=0

If we re-write this a bit, we get:

{c0{(MS0[0]z)(MS0[1]z)...(MS0[t1]z)}MiS0z}+...+{cq1{(MSq1[0]z)(MSq1[1]z)...(MSq1[t1]z)}MiSq1z}=0

R1CS -> CCS

Let's actually see how we can translate R1CS into CCS:

To do so, we need to set some parameters:

  • m
    =
    m
    in the R1CS instance.
  • n
    =
    n
    in the R1CS instance.
  • N
    =
    N
    in the R1CS instance.
  • l
    =
    l
    in the R1CS instance.
  • t
    = 3
  • q
    = 2
  • d
    = 2
  • M
    =
    [MA,MB,MC]
    (R1CS matrices A, B and C)
  • S
    =
    [S0={0,1},S1={2}]
  • c
    =
    {c0=1,c1=1}

If we apply these constants to the aformentioned equation of a CCS instance:

1{AzBz}-1{Cz}=0

Let's actually see a sage example done by @arnaucube:

# R1CS-to-CCS (https://eprint.iacr.org/2023/552) Sage prototype

# utils
def matrix_vector_product(M, v):
    n = M.nrows()
    r = [F(0)] * n
    for i in range(0, n):
        for j in range(0, M.ncols()):
            r[i] += M[i][j] * v[j]
    return r
def hadamard_product(a, b):
    n = len(a)
    r = [None] * n
    for i in range(0, n):
        r[i] = a[i] * b[i]
    return r
def vec_add(a, b):
    n = len(a)
    r = [None] * n
    for i in range(0, n):
        r[i] = a[i] + b[i]
    return r
def vec_elem_mul(a, s):
    r = [None] * len(a)
    for i in range(0, len(a)):
        r[i] = a[i] * s
    return r
# end of utils


# can use any finite field, using a small one for the example
F = GF(101)

# R1CS matrices for: x^3 + x + 5 = y (example from article
# https://www.vitalik.ca/general/2016/12/10/qap.html )
A = matrix([
    [F(0), 1, 0, 0, 0, 0],
    [0, 0, 0, 1, 0, 0],
    [0, 1, 0, 0, 1, 0],
    [5, 0, 0, 0, 0, 1],
    ])
B = matrix([
    [F(0), 1, 0, 0, 0, 0],
    [0, 1, 0, 0, 0, 0],
    [1, 0, 0, 0, 0, 0],
    [1, 0, 0, 0, 0, 0],
    ])
C = matrix([
    [F(0), 0, 0, 1, 0, 0],
    [0, 0, 0, 0, 1, 0],
    [0, 0, 0, 0, 0, 1],
    [0, 0, 1, 0, 0, 0],
    ])

print("R1CS matrices:")
print("A:", A)
print("B:", B)
print("C:", C)


z = [F(1), 3, 35, 9, 27, 30]
print("z:", z)

assert len(z) == A.ncols()

n = A.ncols() # == len(z)
m = A.nrows()
#  l = len(io) # not used for now

# check R1CS relation
Az = matrix_vector_product(A, z)
Bz = matrix_vector_product(B, z)
Cz = matrix_vector_product(C, z)
print("\nR1CS relation check (Az ∘ Bz == Cz):", hadamard_product(Az, Bz) == Cz)
assert hadamard_product(Az, Bz) == Cz


# Translate R1CS into CCS:
print("\ntranslate R1CS into CCS:")

# fixed parameters (and n, m, l are direct from R1CS)
t=3
q=2
d=2
S1=[0,1]
S2=[2]
S = [S1, S2]
c0=1
c1=-1
c = [c0, c1]

M = [A, B, C]

print("CCS values:")
print("n: %s, m: %s, t: %s, q: %s, d: %s" % (n, m, t, q, d))
print("M:", M)
print("z:", z)
print("S:", S)
print("c:", c)

# check CCS relation (this is agnostic to R1CS, for any CCS instance)
r = [F(0)] * m
for i in range(0, q):
    hadamard_output = [F(1)]*m
    for j in S[i]:
        hadamard_output = hadamard_product(hadamard_output,
                matrix_vector_product(M[j], z))

    r = vec_add(r, vec_elem_mul(hadamard_output, c[i]))

print("\nCCS relation check (∑ cᵢ ⋅ ◯ Mⱼ z == 0):", r == [0]*m)
assert r == [0]*m

Output:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

CCS and Plonkish (intro)

Before starting, there are a few new things to notice here:
We define a plonkish instance

S to have the following:

  • m
    = Number of constraints.
  • n
    = Number of public and private witness.
  • l
    = Size of the public input vector.
  • e
    = Size of the selectors vector (amount of different selector values).
  • t
    = Domain of the
    S
    instance. Translates to the length of a
    Ti
    instance. ie. Number of columns involved on a constraint inside
    T
    .
  • g
    = A multivariate polynomial with
    t
    variables which is represented as a sum of
    q
    monomials of at most degree
    d
    .
  • s
    = A vector of constants called selectors of size
    e
    .
  • T
    = A matrix which contains the indexes of values at
    z
    which build up to
    g
    .

Plonkish -> CCS

Let's actually see how we can translate Plonkish into CCS:

Before starting, there are a few new things to notice here:
Differently from R1CS translation which is direct, we have some work to do which isn't simply set

S and
c
.

Note that the vanilla plonk equation is:

qmab+qla+qrb+qoc+qc=0

Plonkish=[qmqlqrqoqcabc10010x0x0x010010x1x1x102210x2x3x400000x5x5x5]

Note that the Vanilla Plonk on the top is doing:

r0 -> Bool check of
x0

r1
-> Bool check of
x1

r2
-> Addition check:
2a + 2b = c

r3
-> Null constrait (this checks nothing)

After seeing the Plonkish we want to translate, we alreay can infer some things:

  • m
    = 4 (4 rows in our plonkish = 4 constraints).
  • n
    =
    Fn
    = 6 (We have 6 different witness variables
    {x0...x5}
    .
    We will go through each
    Ti
    and each value of the row
    i
    is the index of the vector
    s
    from which we will get a value from to set it in our matrix.
  • t
    =
    8
    (Number of Plonkish instance columns).
  • e
    =
    5
    (Number of different values ammong the selectors).
  • d
    =
    3
    Note that
    qmab
    has degree 3.

To start, we need to define our selectors

s instance and also, our witness instance
w
.

s=[0,1,1,2]w=[x0,x1,x2,x3,x4,x5]

With this, we can define our instance

z.
Remember that in the paper, we have 2
z
instances defined. The plonkish one and the CCS one.
If we focus on the Plonkish one, we can see the definition:
z=(w, x, s)Fn+e
.

Hence, this means we can define

z as the union of
w
and
s
(and also
x
but we don't have public witness in this scheme).
zPlonkish=[x0,x1,x2,x3,x4,x5,0,1,1,2]

Note that we have also our
z
definition for CCS. Which will also be used here.
zCCS={w,1,x}FnzCCS=[x0,x1,x2,x3,x4,x5,1]

Remember that we can see

g as a multivariate polynomial from it's definition if we consider each of the plonkish instance columns one variable:

g(qm, qm, ql, qo, qc, a, b, c)=qmab+qla+qrb+qoc+qc

The next step is to define

T. Which remember, indexes into
g
elements of
zPlonkish
(which is collecting all the values that are used in the plonkish instance) and setting them into
g
.

With that, we define

T for this example as:
T=[00076686111766862346997655566666]

Matrix derivation

The following rust code summarizes the actual algorithm to derive the matrixes for the CCS instance

# The following CCS instance values have been provided by Carlos
# (https://github.com/CPerezz) and Edu (https://github.com/ed255),
# and this sage script was made to check the CCS relation.
T = [
    [0, 0, 0, 7, 6, 6, 8, 6],
    [1, 1, 1, 7, 6, 6, 8, 6],
    [2, 3, 4, 6, 9, 9, 7, 6],
    [5, 5, 5, 6, 6, 6, 6, 6]
]

# z_plonkish = [F(1), 0, 1, 2, 3, 10, 42, 0,1,-1,2]
# z_ccs = [F(1), 0, 1, 2, 3, 10, 42, 1]

z_plonkish = [x_0,x_1,x_2,x_3,x_4,x_5,0,1,-1,2,]
z_ccs = [1,x_0,x_1,x_2,x_3,x_4,x_5]
## Checks performed by this Plonk/CCS instance:
# - binary check for x0, x1
# - 2*x2 + 2*x3 == x4


# CCS parameters
q=5
d=3
S = [[3,0,1], [4,0], [5,1], [6,2], [7]]
c = [1, 1, 1, 1, 1]
t = 8
n = 6
l = 0
m=4

# Initialize the result matrixes
matrixes = [[[0 for _ in range(t-1)] for _ in range(m)] for _ in range(t)]

# Iterate over i and j
for i in range(m):
    for j in range(t):
        k_j = T[i][j]
        if k_j >= n:
            matrixes[j][i][n - l - 1] = z_plonkish[k_j - n]  
        elif k_j >= n - l - 1:
            matrixes[j][i][k_j + 1] = 1
        else:
            matrixes[j][i][k_j] = 1

Let's run the first iteration of this step explaining it here. Then the rest can be inferred.

Step i=0, j=0..t-1

Here, we select the row

i from
T
and call it
Ti
.

T0=[0, 0, 0, 7, 6, 6, 8, 6]

  • When
    j=0
    ->
    kj=0
    .

Note that

zPlonkish[0]=0 (which indexes to
x0
in
z
).

So following the algorithm above, sice

kj<n we select
1
.
Then we can set the coeff of
M[0]0,0=1
->
M[0]00=1
.

  • When

    j=1 ->
    kj=0
    -> We therefore select
    1
    .
    Then we set
    M[1]0,0=1

  • When

    j=2 ->
    kj=0
    . -> We therefore select
    1
    .
    Then we set
    M[2]0,0=1

  • When

    j=3 ->
    kj=7
    . Note now we fall into the
    kjn
    . Hence we take
    kjn
    as result.

zPlonkish[76]=1 (Note here that this 1 points to the second position of
s
which actually corresponds to the value 1 of the selector)
.

Then we set

M[3]i0=s[kjn] ->
M[3]0,0=1
.

  • When

    j=4 ->
    kj=6
    ->
    s[66]=s[0]=0

    Then we set
    M[4]0,0=0

  • When

    j=5 -> Happens the same as above.
    Then we set
    M[5]0,0=0

  • When

    j=6 ->
    kj=8
    ->
    s[86]=s[2]=1
    (Assume
    1
    in some
    GF
    is essentially a positive number (p-1)).
    Then we set
    M[6]0,0=1

  • When

    j=7 ->
    kj=6
    ->
    s[66]=s[0]=0

    Then we set
    M[7]0,0=0

Up to this point, we should have the following matrixes:

M0=[100000000000000000000000]

M1=[100000000000000000000000]

M2=[100000000000000000000000]

M3=[100000000000000000000000]

M4=[000000000000000000000000]

M5=[000000000000000000000000]

M6=[100000000000000000000000]

M7=[000000000000000000000000]

Note that in the paper is mentioned that any coefficient of the matrixes contained in

M that is not set is equal to 0!

Step i=3, j=7 (end of the algorithm)

After the 4th iteration (So

i=3,
j=7
) the algorithm stops and we should obtain the following matrix vector
M
as a result:

M0=[100000010000001000000001]

M1=[100000010000000100000001]

M2=[100000010000000010000001]

M3=[100000100000000000000000]

M4=[000000000000200000000000]

M5=[000000000000200000000000]

M6=[100000100000100000000000]

M7=[000000000000000000000000]

Now that we have obtained

M it's time to get
c
and
S
. And it's also time to add the 1 fixed instance for which we have padded our matrixes to our vector
z
.

Leading on

z=[1,x0,x1,x2,x3,x4,x5,0,1,1,2]

Obtaining c

c is defined in a CCS instance as a vector of constants.
In the paper, we read:

For

i{0,1,...,q1}, set
ci
to the coefficient of the
i
th monomial of
g
.

This is easy to see. If we represent the multivariate polynomial

g understanding each column name as a variable:
g=qmab+qla+qrb+qoc+qc

where the monomials that form it are:
{qmab, qla, qrb, qoc, qc}

We can easily see that we don't have any constants/coefficients multiplying any of the monomials.
Hence, we conclude that they're all

1.

c=[1, 1, 1, 1, 1]

Obtaining S

S is defined as multisets of domain
t1
.
This has a really easy translation if we actually name things.

Let's name each variable of the

g polynomial (same as naming each column of the plonkish instance).

In this case, if we recall, the plonkish instance was:

Plonkish=[abcqmqlqrqoqcx0x0x010010x1x1x11001002210x2x3x400000x5x5x5]

We will name/index all the columns in the

PLONKISH matrix from
{0..7}
.

Naming therefore ->

{qm=0,ql=1,qr=2,qo=3,qc=4,a=5,b=6,c=7}.

Then we read in the paper:

For

i{0,1,...,q1}, set if the
i
th monomial contains a variable
j
, where
j{0,1,...,t1}
, add
j
to the multiset
Si
with multiplicity equal to the degree of the variable.

Note that if we represent the multivariate polynomial

g with the indexes instead of the string-based representation of them we obtain:

S=[[3, 0, 1], [4, 0], [5, 1] [6, 2], [7]]

Code to verify the previous example

@arnaucube provided a modified version of the above code in which we can verify the instance used for the example on it.

# Plonk-CCS (https://eprint.iacr.org/2023/552) Sage prototype

# utils
def matrix_vector_product(M, v):
    n = M.nrows()
    r = [F(0)] * n
    for i in range(0, n):
        for j in range(0, M.ncols()):
            r[i] += M[i][j] * v[j]
    return r
def hadamard_product(a, b):
    n = len(a)
    r = [None] * n
    for i in range(0, n):
        r[i] = a[i] * b[i]
    return r
def vec_add(a, b):
    n = len(a)
    r = [None] * n
    for i in range(0, n):
        r[i] = a[i] + b[i]
    return r
def vec_elem_mul(a, s):
    r = [None] * len(a)
    for i in range(0, len(a)):
        r[i] = a[i] * s
    return r
# end of utils


# can use any finite field, using a small one for the example
F = GF(101)

# The following CCS instance values have been provided by Carlos
# (https://github.com/CPerezz) and Edu (https://github.com/ed255),
# and this sage script was made to check the CCS relation.

## Checks performed by this Plonk/CCS instance:
# - binary check for x0, x1
# - 2*x2 + 2*x3 == x4
M0 = matrix([
    [F(0),   1, 0, 0, 0, 0, 0],
    [0,   0, 1, 0, 0, 0, 0],
    [0,   0, 0, 1, 0, 0, 0],
    [0,   0, 0, 0, 0, 0, 1],
    ])
M1 = matrix([
    [F(0),   1, 0, 0, 0, 0, 0],
    [0,   0, 1, 0, 0, 0, 0],
    [0,   0, 0, 0, 1, 0, 0],
    [0,   0, 0, 0, 0, 0, 1],
    ])
M2 = matrix([
    [F(0),   1, 0, 0, 0, 0, 0],
    [0,   0, 1, 0, 0, 0, 0],
    [0,   0, 0, 0, 0, 1, 0],
    [0,   0, 0, 0, 0, 0, 1],
    ])
M3 = matrix([
    [F(1), 0, 0, 0, 0, 0,   0],
    [1, 0, 0, 0, 0, 0,   0],
    [0, 0, 0, 0, 0, 0,   0],
    [0, 0, 0, 0, 0, 0,   0],
    ])
M4 = matrix([
    [F(0), 0, 0, 0, 0, 0,   0],
    [0, 0, 0, 0, 0, 0,   0],
    [2, 0, 0, 0, 0, 0,   0],
    [0, 0, 0, 0, 0, 0,   0],
    ])
M5 = matrix([
    [F(0), 0, 0, 0, 0, 0,   0],
    [0, 0, 0, 0, 0, 0,   0],
    [2, 0, 0, 0, 0, 0,   0],
    [0, 0, 0, 0, 0, 0,   0],
    ])
M6 = matrix([
    [F(-1), 0, 0, 0, 0, 0,   0],
    [-1, 0, 0, 0, 0, 0,   0],
    [-1, 0, 0, 0, 0, 0,   0],
    [0, 0, 0, 0, 0, 0,    0],
    ])
M7 = matrix([
    [F(0), 0, 0, 0, 0, 0,   0],
    [0, 0, 0, 0, 0, 0,   0],
    [0, 0, 0, 0, 0, 0,   0],
    [0, 0, 0, 0, 0, 0,   0],
    ])

z = [F(1), 0, 1, 2, 3, 10, 42]

print("z:", z)

assert len(z) == M0.ncols()

# CCS parameters
n = M0.ncols() # == len(z)
m = M0.nrows()
t=8
q=5
d=3
S = [[3,0,1], [4,0], [5,1], [6,2], [7]]
c = [1, 1, 1, 1, 1]

M = [M0,M1,M2,M3,M4,M5,M6,M7]

print("CCS values:")
print("n: %s, m: %s, t: %s, q: %s, d: %s" % (n, m, t, q, d))
print("M:", M)
print("z:", z)
print("S:", S)
print("c:", c)

# check CCS relation (this is agnostic to Plonk, for any CCS instance)
r = [F(0)] * m
for i in range(0, q):
    hadamard_output = [F(1)]*m
    for j in S[i]:
        hadamard_output = hadamard_product(hadamard_output,
                matrix_vector_product(M[j], z))

    r = vec_add(r, vec_elem_mul(hadamard_output, c[i]))

print("\nCCS relation check (∑ cᵢ ⋅ ◯ Mⱼ z == 0):", r == [0]*m)
assert r == [0]*m

The most important thing for the code above is if we use the example with symbols rather than GF elements. If we do this symbolic analisis, we get the following r relation as a result of applying all the Haddamard products.

r = [x0^2 - x0, x1^2 - x1, 2*x2 + 2*x3 - x4, 0]

If we see the following r expresion that results from each of it's elements represent exactly the constraints our plonkish instance had.
If you recall, our plonkish instance was defined as:

r0 -> Bool check of
x0

r1
-> Bool check of
x1

r2
-> Addition check:
2a + 2b = c

r3
-> Null constrait (this checks nothing)

So as you see, each row of Plonkish has translated into an element of the vector r which represents the symbolic result of

SCCS.

  • The first and the second elements of
    r
    actually encode the same operation: x^2 - x Which is the boolean constraint!.
  • The third element of
    r
    actually contains the third constraint in the plonkish instance. Is obvious that 2*x2 + 2*x3 - x4 is doublings of two variables and equaling it to another one as a constraint.
  • Finally, the last is a null constraint, hence the last element of r equals 0.

SuperSpartan

SuperSpartan translates to simply to the classical Spartan protocol but using CCS as arithmetization system.

To check the correctness of

(I,W)RCCS (instance
(I,W)
actually satisfies the RCCS structure) is based on checking that the following identity holds:
0=?a{0,1}logmeq~(τ,a)i=0q1cijSi(y{0,1}logmMj~(a,y)Z~(y))  (1)

What this is saying, is that if

(I,W)RCCS, then, we can apply the sumcheck protocol to the polynomial:
g(a):=eq~(τ,a)i=0q1cijSi(y{0,1}logmMj~(a,y)Z~(y))  (2)

This is esentially saving to the verifier the costs of computing the right hand side of the first equation of this section to actually evaluate
g
at a random input
ra
.

So once we are here, we can see that the problem can actually be translated to compute

t sumchecks in parallel for:
j{0,..,t1}   y{0,1}logmMj~(ra,y)Z~(y)  (3)

Note that here

Z~ is defined as:
Z~(X0,..,Xlog n1)=(1X0)W~(X1,..,Xlog n1)+X0(1,x)~(X1,..,Xlog n1)  (4)

In Spartan we use a trick to actually move from N sumchecks to 2 using fiat shamir. But here we have

t. But here there's no mention on how we do reduce the sumcheck number so that we can actually lower the work load

VerifierProverVerifierProverMLE(W), $\widetilde{Z}$.Sample $\tau$$\tau$Apply Sumcheck reduction to eq (2)Sample $\r_a$$r_a$$g(r_a)$Sample $\gamma$$\gamma$Apply sumcheck protocol again to eq (4)Sample $r_y$ for each $M_i$$[r_{0_{y}},...,r_{t-1_{y}}]$$[MLE(M_0(r_{0_{y}})) \cdot MLE(Z(r_{0_{y}})),...,MLE(M_{t-1}(r_{t-1_{y}})) \cdot MLE(Z(r_{0_{y}}))]$For each $M_i$ check: MLE(M_i) = v_iCheck MLE(Z_{r_y}) = v_z

SuperSpartan - IOP for CCS to Snark for CCS

In this section we get a clear description of the protocol which I think is worth rephrasing here:

Prover's work

  1. Commit to an
    O(log n)
    -variate polynomial
    Z~
    .
  2. Performs the sumcheck of equation:
    Z~(X0,..,Xlog n1)=(1X0)W~(X1,..,Xlog n1)+X0(1,x)~(X1,..,Xlog n1)
  3. Proves the evaluations of
    Z~
    at particular random points sampled by the Verifier.
  4. If the CCS instance is not uniform (Different
    Mi
    's within the same
    RCCS
    ), prove one evaluation at a random point sampled by de Verifier (
    ry,ra
    ) of each of the
    [M0,..,Mt1]
    instances.
    Note that it's possible depending on the proving system to actually batch together all the
    M
    instances so that a single evaluation can be performed instead of
    t1

Verifier's work

  1. Participates in the sumcheck protocol over:
    Z~(X0,..,Xlog n1)=(1X0)W~(X1,..,Xlog n1)+X0(1,x)~(X1,..,Xlog n1)

    I should elaborate more on what that exactly means.
  2. Verifies the proofs of the evaluations of
    Z~
    .
  3. If the CCS instance is not uniform (Different
    Mi
    's within the same
    RCCS
    ), verify the evaluation at a random point previously sampled (
    ry,ra
    ) for each of the
    [M0,..,Mt1]
    instances.
    Note that it's possible depending on the proving system to actually batch together all the
    M
    instances so that a single evaluation can be performed instead of
    t1

CCS+ & SuperSpartan+

The paper also mentions the existance of an extension over SuperSpartan called SuperSpartan+ and an extension over CCS called CCS+.

CCS+

The description for this is very brief. Indeed it only mentions that in addition to the terms we already know a CCS instance has, a CCS+ instance also contains:

  • A lookup table
    T
    which is a set of field elements.
  • A sequence of lookup operations
    L
    where each entry is in the range
    [n]
    .

Then, it's mentioned that in addition to all the previous requirements for a

RCCS instance to be correct, now, in addition,
oL , z[o]T
(for each lookup operation
o
in
L
, the witness
z[o]
actually needs to be inside of the lookup table
T
for the
RCCS+
instance to actually be valid).

SuperSpartan+

In order for this to work, the Verifier needs to have oracle access to the polynomials

T and
a
where
T
is the table that encodes the range or set and
a
the values claimed to be inside of
T
.

In order to support lookups, SuperSpartan+ encodes all the lookup operations

L in a sparse matrix
ML
where
i[L] , ML[i][L[i]]=1
and all other entries are set to 0.

Then the prover sets the polynomial

a~ together with
w~
and
a
is claimed to be the MLE of the vector
MLz

As done in the original protocol, using the sum-check invocation the problem is reduced to check that the claimed relationship
MLz=a

Questions left:

  • How do we go from
    t1
    sumchecks to just 1 for the
    Mi
    instances? I assume doing the same tricks as in Spartan. But it's not clear.
  • Is the mermaid diagram acurate? Is it missing anything? Does it relate/map 1:1 to this section?
  • How do lookups fit into this? CCS+ does not define any equation that considers it.