R1CS

The big question is how to represent the algebraic circuits underlying a computation so they’re verifiable by a verifier, given a claimed solution to those constraints. So we define a language called R1CS whose basic structure is:

Here, is the assignment vector, one value per variable, that satisfies the constraint system encoded in , , and . Each row of , , and holds the coefficients for one gate constraint. We’ll go on to explain how the constraints are set from the circuit into these matrices, using arithmetic gates.

First, we need every constraint to decompose into a statement of the form or (using lowercase here to distinguish these generic gate variables from the , , matrices above). For example, is a constraint that could be decomposed into:

  • 1st gate :
  • 2nd gate :
  • 3rd gate :
  • 4th gate :

So the constraint is now equivalent, just decomposed into simpler constraints that we can easily encode in the R1CS matrices. Since we’re dealing with arithmetic gates, we keep at most 3 variables in a constraint, since a gate has 2 inputs and 1 output, so it’s sensible to represent them this way for easy encoding.

Note — the here represents the Hadamard product in algebra, which is like a dot product except with element-wise multiplication, returning a vector of the same size rather than a scalar (as in the dot product).

For now, think of as containing all the variables involved in the constraints, plus a dummy variable for a constant value:

contains the solution, or what we call the assignments to all these variables.

Worked example — the matrices

Using this variable ordering, the , , matrices for all 4 gates work out to:

A

B

C

Row 1 = 1st gate (), row 2 = 2nd gate (), row 3 = 3rd gate (), row 4 = 4th gate ().

If we do row by row, then we can see that we arrive at the first constraint for the first row representing the first constraint, and so on for the other constraints.

Note that gates 3 and 4 (the additions) are encoded as multiplications by the constant in ; this is the standard trick for folding an addition constraint into the same form as a multiplication gate.

As you can see, in the equation , each row of these matrices represents one constraint. We do this for all the constraints and combine all the row vectors into matrices , , and .

To verify this trivially, the verifier would need to carry out the whole computation itself, which would be very inefficient.

So we arrive at the concept of Quadratic Arithmetic Programs (QAPs), which encode the whole R1CS system into polynomials, and once we move into that domain, there’s a lot of clever manipulation that becomes possible.

Essentially, we interpolate the columns of the R1CS matrices into polynomials using Lagrange interpolation.

Lagrange Interpolation

Given points, we can define a unique polynomial of degree at most (with coefficients) that passes through all of them. We construct this by building separate basis polynomials, each of which passes through exactly one of the given points and is strictly zero at the other given points; at any -value outside these points, the basis polynomial is otherwise unconstrained. Summing all of these basis polynomials (each scaled to match its point’s value) gives the final interpolated polynomial.

So instead of the original matrices (4 gates × 6 variables), we now get 6 sets of degree-3 polynomials, one polynomial per column/variable.

The Actual Working

Let’s call the first constraint’s first value , its second value , and so on, and similarly for the second constraint, and so forth for the rest. So the points defined for interpolation, for the first column, would be , and similarly for the other columns.

Each column’s interpolated polynomial is built exactly so that, for example, column 1’s polynomial evaluated at returns , and column 2’s polynomial evaluated at returns , and so on across all columns; this is precisely what interpolation guarantees, since the basis polynomial underlying each point is the only one contributing at that point (as described above) and the rest vanish there. So evaluating all 6 column polynomials at gives us back all of the first constraint’s values as a vector.

Sanity check — since there are 6 columns, evaluating all 6 polynomials at a given gives a 6-tuple vector, which is exactly the original constraint vector in the R1CS matrix for that gate.

Similarly, evaluating all the columns at gives us the second constraint’s row vector, and so on for all 4 constraints in our example. That’s how we recover all our constraints from the interpolated column polynomials.

That’s the major intuition here. We define things the same way for other values of , one for each gate in the circuit.

Verifying QAP

Now, if the verifier gives a random challenge, verify at these assignments, the prover would give an answer, and this is where the payoff of moving to polynomials comes in: rather than checking every gate individually, the verifier can check correctness at a single random point.

Each matrix (, , ) is interpolated column by column, giving 6 polynomials per matrix (one per variable), 18 in total. To check a specific assignment , we take the linear combination of each matrix’s column polynomials, weighted by :

where , , are the individual column polynomials. This gives one combined polynomial per matrix for this particular assignment.

We then define the target polynomial . Naively checking that this holds at every gate would mean evaluating at every one of the points individually.

Instead, we include another polynomial in this system, called , to make verification faster. We define:

depending on the number of gates/decomposed constraints. Since is zero at every point corresponding to a gate, checking that is correct at all gates simultaneously reduces to checking that is evenly divisible by , i.e. that exists as a polynomial with no remainder. If it does, we can conclude that all the computation encoded was done correctly.

If we try to falsify any solution to the matrices, the resulting changes and would no longer hit zero at one of the points , so it no longer divides evenly by , and we can distinguish falsified solutions from correct ones this way.


Lots of reference drawn from this article: Quadratic Arithmetic Programs: from Zero to Hero, which provides an amazing explanation of the steps of the above method.