Range-checks
In most cases, tables deal with U256 words, split into 32-bit limbs (to avoid overflowing the field). To prevent a malicious prover from cheating, it is crucial to range-check those limbs.
What to range-check?
One can note that every element that ever appears on the stack has been pushed. Therefore, enforcing a range-check on pushed elements is enough to range-check all elements on the stack. Similarly, all elements in memory must have been written prior, and therefore it is enough to range-check memory writes. However, range-checking the PUSH and MSTORE opcodes is not sufficient.
-
Pushes and memory writes for "MSTORE_32BYTES" are range-checked in "BytePackingStark", except PUSH operations happening in privileged mode. See push_general_view.
-
Syscalls, exceptions and prover inputs are range-checked in "ArithmeticStark".
-
The inputs and outputs of binary and ternary arithmetic operations are range-checked in "ArithmeticStark".
-
The inputs' bits of logic operations are checked to be either 1 or 0 in "LogicStark". Since "LogicStark" only deals with bitwise operations, this is enough to have range-checked outputs as well.
-
The inputs of Keccak operations are range-checked in "KeccakStark". The output digest is written as bytes in "KeccakStark". Those bytes are used to reconstruct the associated 32-bit limbs checked against the limbs in "CpuStark". This implicitly ensures that the output is range-checked.
Note that some operations do not require a range-check:
-
"MSTORE_GENERAL" read the value to write from the stack. Thus, the written value was already range-checked by a previous push.
-
"EQ" reads two -- already range-checked -- elements on the stack, and checks they are equal. The output is either 0 or 1, and does therefore not need to be checked.
-
"NOT" reads one -- already range-checked -- element. The result is constrained to be equal to , which implicitly enforces the range check.
-
"PC": the program counter cannot be greater than in user mode. Indeed, the user code cannot be longer than , and jumps are constrained to be JUMPDESTs. Moreover, in kernel mode, every jump is towards a location within the kernel, and the kernel code is smaller than . These two points implicitly enforce 's range check.
-
"GET_CONTEXT", "DUP" and "SWAP" all read and push values that were already written in memory. The pushed values were therefore already range-checked.
Range-checks are performed on the range , to limit the trace length.
Lookup Argument
To enforce the range-checks, we leverage logUp, a lookup argument by Ulrich Häbock. Given a looking table and a looked table , the goal is to prove that
In our case, and is composed of all the columns in each STARK that must be range-checked.
The logUp paper explains that proving the previous assertion is actually equivalent to proving that there exists a sequence such that:
The values of can be stored in different columns of length each. In that case, the equality becomes:
The 'multiplicity' of value is defined as the number of times appears in . In other words:
Multiplicities provide a valid sequence of values in the previously stated equation. Thus, if we store the multiplicities, and are provided with a challenge , we can prove the lookup argument by ensuring:
However, the equation is too high degree. To circumvent this issue, Häbock suggests providing helper columns and such that at a given row :
The helper columns can be batched together to save columns. We can batch at most helper functions together. In our case, we batch them 2 by 2. At row , we now have:
If is odd, then we have one extra helper column:
For clarity, we will assume that is even in what follows.
Let be a generator of a subgroup of order . We extrapolate and to get polynomials such that, for : .
We can define the following polynomial:
Constraints
With these definitions and a challenge , we can finally check that the assertion holds with the following constraints:
These ensure that We also need to ensure that is well constructed for all :
Note: if is odd, we have one unbatched helper column for which we need a last constraint:
Finally, the verifier needs to ensure that the table was also correctly computed. In each STARK, is computed starting from 0 and adding at most 1 at each row. This construction is constrained as follows: