Arithmetization
The goal of this process is to start thinking about the execution of the program mathematically. We can think of a program as having two important things:
- Memory: a place to store all the intermediate steps of its computation. It can be seen as a vector that stores values.
- Logic: it can be seen as a system of polynomial constraints , that describe which are valid executions.
For a given program, not every "memory" vector will correspond to its logic. Some will be random garbage and some will actually be the intermediate steps of an execution of that program. As programming languages are formal languages, once a program has been written it uniquely defines which corresponds to a valid execution. is just a system of polynomial contraints that is zero when the variables in have the relationship determined by the program:
In zero-knowledge the logic of the agreed program encoded in is known by the prover and the verifier. The content of the computation, encoded in is only known to the prover who has run the computer program. Sometimes is called the witness and is noted by the letter .
This looks confusing at first. Let's see examples of how and look like. There are two different strategies to intuetivily come up with them.
Circuits
The first strategy is to think about the program as a "hardware circuit". This circuit has sum and multiplication gates. Each gate has inputs and outputs. For example, a sum gate has two inputs (x, y) and one output (z). The sum gate enforces the constraint x + y == z. Combining several sum and multiplication gates you end up basically with a complex system of constraints :

To obtain a witness you can compute the circuit gates. A disadvantage is that circuits are rigid. If you want to prove different programs (other than claim_raccoon_coins), you have to design a different circuit for each of them. Also, using gates is too low-level. There are tools to recycle common circuits called gadgets.
Execution trace
Another strategy is to design a virtual machine. A virtual machine essentially has a set of registers to store values and a set of instructions that describe valid transitions from a state to the following.
Small example
When executing a VM we obtain a series of states. This sequence of states is what we call an execution trace. Here we have the trace of a simple VM that was executed for 3 steps:
OP A B C 1 1 1 2 0 3 2 8 1 3 0 11 This machine is very simple. OP is a register that stores a binary opcode.
AandBstore operands, andCstores the cumulative results (starting at zero). IfOP == 1thenA + Bis added toC. IfOP == 0thenA * Bis added toC. If we see , , and as polynomials such that over the domain , then this can be expressed as the following system of equations :Boundary constraint:
Enforces that the VM starts at zero.
Transition constraint:
Enforces that the next value of
C, represented by , is equal to the sum or product of and , depending on . Remember that this constraints will be evaluated over a domain .Note: and can be obtained through interpolation. In this case the witness is the image of , that is, the result of the computation.
The advantage of this strategy is flexibility. You can code different programs with the VM and they will be accepted. You don't need to design different circuits for different programs. The disadvantage is that you may have performance overhead compared to circuits.
Common practices in constraints
The goal of circuits and execution traces is to intuitively derive the constraints that discriminate correct computation from garbage. If this is not enough for this task, we can add any additional constraints. Here're some frequent practices in proving systems.
Selectors
An example of a selector is the OP column in the execution trace example. Here, a polynomial "selects" which operation is chosen (add or mul). This type of selector polynomials appear frequently inside constraints.
Example:
The selector defines whether or will be equal to .,
Nothing matters
Although this two arithmetization strategies (circuits and execution trace) may seem different, note that both just end up being a system of polynomial constraints . This system is sometimes called R1CS (Rank one constraint system) depending on its form. Having these polynomials in is good, because we need them for our next step: the polynomial commitment scheme.