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:

RowabprevCarryCarryAddReset
10x110x220x010x000x331
20x300x400x000x000x700
30xff0xee0x000x010xed1
40x000xff0x010x010x000

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:

rowBYTE_ABYTE_BBIT_PRECARRYBIT_CARRYBYTE_ADD
10x000x000x00x00x00
20x000x010x00x00x01
2560x000xff0x00x00xff
2570x010x000x00x00x01
2580x010x010x00x00x02
655350xff0xfe0x00x10xfd
655360xff0xff0x00x10xfe
655370x000x000x10x00x1
655380x000x010x10x00x2
657920x000xff0x10x10x00
657930x010x000x10x00x02
657940x010x010x10x00x03
1310710xff0xfe0x10x10xfe
1310720xff0xff0x10x10xff

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};