Inclusion arguments
For most of the programs used in the zkEVM’s Prover, the values recorded in the columns of execution traces are field elements. In many cases, there may be a need to restrict the sizes of these values to only a certain number of bits. For example the variate with uint32 type, needs restricting the range from 0 to . It is therefore necessary to devise a good control strategy for handling both underflows and overflows. This document introduces the addition of 2-byte numbers as the scene using Inclusion arguments.
Verify addition of 2-byte numbers
The addition of uint16 type is a common data type in programming. It can be constrained as the below trace table:
| Row | a | b | prevCarry | Carry | Add | Reset |
|---|---|---|---|---|---|---|
| 1 | 0x11 | 0x22 | 0x01 | 0x00 | 0x33 | 1 |
| 2 | 0x30 | 0x40 | 0x00 | 0x00 | 0x70 | 0 |
| 3 | 0xff | 0xee | 0x00 | 0x01 | 0xed | 1 |
| 4 | 0x00 | 0xff | 0x01 | 0x01 | 0x00 | 0 |
The constraint is: We use as the carry in and as the carry out. becasue the lowest endian byte of uint16 not use , we use as the flag of the lowest endian byte.
The snippet of PIL code as follows:
namespace TwoByteAdd(%N);
pol constant RESET;
pol commit a, b;
pol commit carry , prevCarry , add;
prevCarry ' = carry;
a + b + (1-RESET)*prevCarry = carry*2**8 + add;
The above equation (1) only constrains the coerectness of addition, but not constrains the columns of , is 8-bits integer which range form 0 to 255. It also not constrains the columns of , , is 1-bit bool type.
An inclusion argument is used for this purpose.
Inclusion argument
Given two vectors, and , it is said that:
is contained in if such that .
In other words, if one thinks of and as multisets and reduce them to sets (by removing the multiplicity), then is contained in if is a subset of .
A protocol (,) is an inclusion argument if the protocol can be used by to prove to that one vector is contained in another vector.
In the PIL context, the implemented inclusion argument is the same as the Plookup method provided in GW20, also discussed here
An inclusion argument is invoked in PIL with the in keyword.
Specifically, given two columns and , we can declare an inclusion argument between them using the syntax: {} in {}.
Generalized inclusion arguments
In PIL we can also write inclusion arguments not only over single columns but over multiple columns. That is, given two subsets of committed columns and of some program(s) we can write as,
{} in {}
A natural application for this generalization shows that a set of columns that a program repeatedly computes, probably with the same pair of inputs/outputs. Following on with the previous “2-byte addition” program example, one can construct five new constant polynomials; BYTE_A, BYTE_B, BIT_PRECARRY,BIT_CARRY and BYTE_ADD; containing all possible byte additions.
The execution trace of these polynomials can be constructed as follows, the total number of rows is = 131072:
| row | BYTE_A | BYTE_B | BIT_PRECARRY | BIT_CARRY | BYTE_ADD |
|---|---|---|---|---|---|
| 1 | 0x00 | 0x00 | 0x0 | 0x0 | 0x00 |
| 2 | 0x00 | 0x01 | 0x0 | 0x0 | 0x01 |
| 256 | 0x00 | 0xff | 0x0 | 0x0 | 0xff |
| 257 | 0x01 | 0x00 | 0x0 | 0x0 | 0x01 |
| 258 | 0x01 | 0x01 | 0x0 | 0x0 | 0x02 |
| 65535 | 0xff | 0xfe | 0x0 | 0x1 | 0xfd |
| 65536 | 0xff | 0xff | 0x0 | 0x1 | 0xfe |
| 65537 | 0x00 | 0x00 | 0x1 | 0x0 | 0x1 |
| 65538 | 0x00 | 0x01 | 0x1 | 0x0 | 0x2 |
| 65792 | 0x00 | 0xff | 0x1 | 0x1 | 0x00 |
| 65793 | 0x01 | 0x00 | 0x1 | 0x0 | 0x02 |
| 65794 | 0x01 | 0x01 | 0x1 | 0x0 | 0x03 |
| 131071 | 0xff | 0xfe | 0x1 | 0x1 | 0xfe |
| 131072 | 0xff | 0xff | 0x1 | 0x1 | 0xff |
Recall that there is no need to enforce constraints between these polynomials since they are constant and therefore, publicly known. An inclusion argument can be utilized and thus ensure a sound description of the “2-byte addition” program. The inclusion constraint is not only ensuring that all the values are single bytes, but also that the addition is correctly computed. The input is combination of five variables : BYTE_A, BYTE_B, BIT_PRECARRY,BIT_CARRY and BYTE_ADD and it must be found in the lookup table.
In addition, recall that we only have to take into account prevCarry whenever Reset is 0. PIL is flexible enough to consider this kind of situation involving Plookups. To introduce this requirement, the inclusion check can be implemented as follows:
include "config.pil";
namespace TwoByteAdd(%N);
pol constant BYTE_A, BYTE_B, BYTE_PREVCARRY, BYTE_CARRY , BYTE_ADD;
pol constant RESET;
pol commit a, b;
pol commit carry, prevCarry, add;
prevCarry' = carry;
{a, b, (1 - RESET)*prevCarry, carry, add} in {BYTE_A, BYTE_B, BYTE_PREVCARRY, BYTE_CARRY, BYTE_ADD};