The prover uses the sumcheck protocol to certify that wires at each layer of a circuit are correctly calculated from the preceding layer. For a layer $j$, the prover must demonstrate that for every output wire index $g$, the following equations hold:
- $V[j][g] = \sum_{l, r} Q[j][g, l, r] V[j + 1][l] V[j + 1][r]$ (Correct calculation)
- $0 = \sum_{l, r} Z[j][g, l, r] V[j + 1][l] V[j + 1][r]$ (In-circuit assertions)
These are combined into a single equation using random verifier challenges:
claim = SUM_{l, r} QUAD[j][l, r] V[j + 1][l] V[j + 1][r].
At each layer, the protocol starts with two claims representing linear combinations of output wire values, bound by verifier challenges $G[0]$ and $G[1]$. Through successive rounds, the function inside the summation is reduced in dimensionality, and the claim values are updated until the function becomes a constant. The final claim values are encrypted with a one-time pad before being sent to the verifier.
Challenge Generation Note:
- Before the first round,
MAX_BINDINGS = 40 challenges are generated and discarded to reserve space for future protocol extensions. - For the initial output wire binding,
MAX_BINDINGS = 40 challenges are generated, and the remainder are discarded. - For all subsequent layers, challenges for binding output wires are generated one at a time without extra unused challenges.
def sumcheck_circuit(
field: FiniteField,
circuit: Circuit,
wires: list[list[FiniteRingElement]],
pad: list[LayerPad[FiniteRingElement]],
transcript: Transcript) -> list[LayerProof]:
for _ in range(MAX_BINDINGS):
# Discard initial challenges. These are reserved for possible
# future use.
_ = transcript.generate_field(field)
challenges = [
transcript.generate_field(field)
for _ in range(MAX_BINDINGS)
]
G = (
challenges[:circuit.log_num_outputs],
challenges[:circuit.log_num_outputs],
)
proof: list[LayerProof] = []
for j, layer in enumerate(circuit.layers):
alpha = transcript.generate_field(field)
# Form the combined quad, QZ = Q + beta * Z, to handle
# in-circuit assertions.
beta = transcript.generate_field(field)
QZ = layer.quad + beta * layer.Z
# QZ is three-dimensional, QZ[g, l, r].
QUAD = QZ.bindv(G[0]) + alpha * QZ.bindv(G[1])
# Having bound g, QUAD is now effectively two-dimensional,
# QUAD[l, r].
QUAD = QUAD.drop_dimension()
layer_proof, G = sumcheck_layer(
field,
QUAD,
wires[j + 1],
layer.log_num_input_wires,
pad[j],
transcript
)
proof.append(layer_proof)
return proof