2021 10 A Specification For A ZK-EVM
2021 10 A Specification For A ZK-EVM
2021 10 A Specification For A ZK-EVM
October 2021
Abstract
We describe a zk-EVM arithmetization supporting the three following design goals: (1) support
for all EVM opcodes including internal smart contract calls, error management and gas manage-
ment (2) ability to execute bytecode as is (3) minimal prover time. We strive to provide an
arithmetization that respects the EVM specification as defined in the Ethereum yellow paper [1].
We provide an original and comprehensive approach of the zk-EVM problem which is technically
realizable using existing zero-knowledge proving schemes.
1 Introduction
1.1 Context and previous results
Increasing blockchains capacity, and hence lowering the transaction cost is a major technical challenge.
Rollups are promising technologies that would allow to considerably increase the capacity of the
Ethereum Blockchain. An introduction to Rollups, zk-EVMs and their role in improving Ethereum
capacity can be respectively found in [2, 3]. Recently, multiple attempts at building scalable and
practical rollup solutions have been positively received. zkSync [4], for instance, transpiles Yul into
a zk-VM friendly bytecode. Cairo [5], on the other hand, uses a custom architecture adapted to an
efficient STARK prover for smart contracts written in Cairo .
Other projects, such as Hermez [6] or Scroll Tech [7] aim to interpret directly the EVM bytecode,
without any intermediary transpiler or extra compilation step. This is also the approach we took.
1
1.3 Outline of this paper
The remainder of this paper is organized as follows:
1. Global organisation of the zk-EVM: provides a high level overview of the main components
of our zk-EVM architecture.
2. Tools and notations: introduces the basic mathematical tools and methods used in our arith-
metization,
3. Constraint sets: The low-level circuits for some of the components of our zk-EVM
To improve the readability of this paper, we have chosen to provide full constraint systems for a
few (representative) modules only, which we strive to describe as comprehensively as possible. Other
modules, like the main execution trace and the storage module, have been fully designed - however,
given the complexity of these components, we chose to postpone slightly their publication. If interested,
you may contact us directly for more information on these modules.
Given the complexity of our arithmetization, mistakes are to be expected.
Contents
1 Introduction 1
1.1 Context and previous results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Relation with existing projects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.3 Outline of this paper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
2
4 Word comparison module 24
4.1 Trace Columns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4.2 Trace constraints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
• A (block specific) ROM (Read-Only-Memory) contains the bytecode of the smart contracts to
be executed.
• The main execution trace which processes the instructions and builds the stack memory.
• A small set of Application specific modules for RAM, storage, binary or arithmetic opcodes.
• An instruction decoder which decomposes the EVM opcodes in a series of instruction flags.
3
The ROM’s (which one may imagine as the concatenation of the bytecodes of the smart contracts
called in a block) main purpose is to translate the contracts bytecode into a succession of instructions
that can be processed by the zk-EVM.
Every module is designed to deal only with a certain subset of the EVM instruction set. Modules
select the instructions that concern them using specific flags from the flag decomposition provided by
the instruction decoder. The correctness of these flags and parameters is guaranteed by means of an
inclusion proof into the instruction decoder — an immutable piece of public data. Each module has
its own internal execution trace and associated constraint system. Modules are linked to the global
execution module by means of a bussing mechanism (in practice: plookup inclusion proofs).
This organization is represented in the figure 1.
Figure 1: Modules of the zk-EVM. Arrows represent plookup inclusion proofs. N.B.: the Main execu-
tion trace, ALU, Storage, RAM, Binary and Word comparison modules also point to the instruction
decoder.
About submodules: The modules in our zk-EVM split into submodules linked together by means
of Plookup proofs. The submodules defining a given module satisfy a hierarchical relationship: a
parent submodule decomposes (a subset of module relevant) EVM instructions into atomic operations.
These operations are executed by its child submodule. This simplifies the mathematical formalization
of complex EVM operations, by balancing complex logic between different submodules.
The parent/child submodule structure is currently implemented in the RAM and ALU module.
Stack memory: The main execution trace also reproduces the stack architecture of the EVM. We
have chosen to rely on a read only memory model to reproduce the stack : the stack memory is a
mapping of Read-only-memory addresses (M em) to tuples (V al, P tr) composed of a stack value and
4
a pointer to the memory cell containing the previous stack element. The figure 2 provides an example
of such stack memory.
Mem 0 1 2 ··· N −1 N
V al v0 v1 v2 ··· vN −1 vN
P tr ∅ p1 p2 ··· pN −1 pN
Most of the EVM opcodes involve 0, 1 or 2 inputs, but some EVM instructions take more: CALL,
for instance, takes 7. Instructions like SW AP or DU P , implicitly take up to 16 (these instructions
may fetch the 16-th element of the stack and require access to all elements in between). Yet it proved
sufficient to keep track of only the two upper stack elements (rather than all 16 top stack elements),
thus our main execution trace only keeps track of the two upper stack elements. OPCODES that take
3 arguments or more (explicitly or implicitly) need to be processed in several steps (i.e. several lines
in the execution trace), successively removing the top two elements of the stack.
It is comparatively easy to adapt our stack trace to deal with inter smart contract calls, and
smart contract batches : the stack memory can be reinitialized by adding stack elements pointing to
themselves which prevents contract calls from interacting with the stack cells that it shouldn’t have
access to. Dealing with inter smart contract calls is a recurrent issue among the existing models of
zk-EVMs.
5
To deal with the aforementioned technical challenges, we have decided to divide the RAM module
into two submodules: the child RAM and the parent RAM submodules. The consistency between the
values of the parent and the child RAM submodules is guaranteed by a Plookup inclusion proof of the
parent RAM submodule into the child RAM submodule.
Parent RAM submodule: The parent RAM submodule processes the memory instructions com-
ing from the main execution trace - it decomposes these complex EVM instructions into simple
READ/WRITE memory instructions, that involve writing/reading at most two consecutive memory
words. The parent RAM submodule sends these simple instructions to the child RAM submodule.
Child RAM submodule: The child RAM submodule executes the simple instructions transmitted
by the parent RAM submodule, returns the result and verifies the consistency of the RAM memory
which amounts to checking that the RAM addresses are continuous and that the reading operations
are consistent with the previous values stored.
Worked out example. The figure 3 is an explanatory drawing of the interactions between the
parent and the child RAM with 4-byte long words. This figure explains the interactions between the
parent and the Child RAM for a RETURN operation, that stores the values of the current RAM
located from the address 0x13a2 to 0x12ab, to the caller RAM, at the address range [0xaace, 0xaac3].
Parent RAM:
0x0 0x4 0x8 0xc
: READ
0x40ff 0x15fe32aa 0x40ffeb
: WRITE
0x2 0xb
1 2 3 4 5 6
Child RAM:
Figure 4: Illustration of the relations between the parent and the child RAM, for a RETURN instruc-
tion.
1. Read the last two bytes from the current RAM address 0x13a0
2. Replace the last two bytes of the caller RAM address 0xaabe with the previously read bytes
3. Read the whole memory word of the current RAM starting at the address 0x13a4
4. Store this memory word to the caller RAM at the address 0xaac0
5. Read the first 3 bytes of the memory word at the address 0x13a8 of the current RAM.
6. Replace the first 3 bytes of the memory word at the address 0xaac4 of the caller RAM.
6
2.1.3 The arithmetic operations module
This module emulates the EVM 256-bit arithmetic. It is comprised of 3 submodules: the ALU256,
the ALU128 and the ALU64 which respectively perform arithmetic operations on 256 bit long words,
128 bit long words and 64 bit long words. These three modules are tied together by Plookup inclusion
proofs of the 256ALU into the 128ALU into the 64ALU. The figure 5 provides an example of such
submodule communication to perform a 256-bit addition of two field elements.
256ALU: The 256-bit ALU decomposes 256-bit long input field elements into two 128-bit long field
elements - the high 128 bits and the low 128 bits - and sends arithmetic operation requests to the 128
bit ALU. It further computes the final result by computing the high/low bit decomposition of it.
128ALU: The 128ALU follows the same pattern, except that, this time the 128-bit words are de-
composed into 64-bit words and arithmetic operation requests are sent to the 64ALU.
64ALU: The 64ALU does the actual computations: it checks that the inputs are indeed 64 bit words
by performing range proofs on the 16-bit decomposition of these 64-bit long words. The results are
sent back to the 128ALU, which then sends back the results to the 256ALU.
ALU256
ALU128
ALU64
Alh,64 + Blh,64 + carryll,64 = (Clh,64 , carrylh,64 ) Ahh,64 + Bhh,64 + carryhl,64 = (Chh,64 , carryhh,64 )
Figure 5: Field element decomposition and submodules communication inside the arithmetic module,
addition example.
Binary module: The binary module relies on Plookup to perform binary operations - one has
to prove the inclusion of quadruples (Inst, Byte1 , Byte2 , Byteres ) into a public logic table. Table 1
provides an illustration (with a byte decomposition, to simplify the representation) of this process -
the timestamp column allows keeping track of the instruction being currently executed (instructions
are executed in several steps, to decompose the inputs and perform bitwise operations).
7
Inst Timestamp Input1 Byte1 Carry1 Input2 Byte2 Carry2 Result Byteres Carryres Length
AND 1 0x1ea1ff 0xff 0x1ea1 0xff00ff00 0x00 0xff00ff 0x00a100 0x00 0x00a1 3
AND 1 0x1ea1ff 0xa1 0x1e 0xff00ff00 0xff 0xff00 0x00a100 0xa1 0x00 2
AND 1 0x1ea1ff 0x1e 0x00 0xff00ff00 0x00 0xff 0x00a100 0x00 0x00 1
AND 1 0x1ea1ff 0x0 0x00 0xff00ff00 0xff 0x00 0x00a100 0x00 0x00 0
XOR 2 0x1001 0x01 0x10 0x1010 0x10 0x10 0x0011 0x11 0x00 1
XOR 2 0x1001 0x10 0x00 0x1010 0x10 0x10 0x0011 0x00 0x00 0
.. .. .. .. .. .. .. .. .. .. .. ..
. . . . . . . . . . . .
Word Comparison module: The word comparison module computes the difference between the
bytes of two memory words, starting from the most significant byte and returns a comparison boolean:
for instance, when testing an inequality a < b, the word module returns 1 when this inequality is
verified, 0 otherwise.
Table 2 represents the execution of this module in a similar way as the binary module execution.
For the sake of the representation, we will use 8-bit (byte) decomposition instead of 16-bits word
decomposition.
TS Sw Eq Inst Input_1 Input_2 Res Prefix_1 Prefix_2 Comp B_1 B_2 BComp
0 1 0 SGT 0xa3ff02 0xa3ffb7 0 0xa3 0xa3 1 a3 a3 0
0 1 0 SGT 0xa3ff02 0xa3ffb7 0 0xa3ff 0xa3ff 1 ff ff 0
0 1 0 SGT 0xa3ff02 0xa3ffb7 0 0xa3ff02 0xa3ffb7 1 02 b7 1
1 0 1 LT 0x00a12c 0x00a12c 1 0x00 0x00 0 00 00 0
1 0 1 LT 0x00a12c 0x00a12c 1 0x00a1 0x00a1 0 a1 a1 0
1 0 1 LT 0x00a12c 0x00a12c 1 0x00a12c 0x00a12c 0 2c 2c 0
2 1 1 GT 0x0fabd9 0x0fc73d 0 0x0f 0x0f 1 0f 0f 0
2 1 1 GT 0x0fabd9 0x0fc73d 0 0x0fab 0x0fc7 1 ab c7 1
2 1 1 GT 0x0fabd9 0x0fc73d 0 0x0fabd9 0x0fc73d 1 d9 3d 0
.. .. .. .. .. .. .. .. .. .. .. .. ..
. . . . . . . . . . . . .
8
Figure 6: The above represents a portion of the tree of nested smart contract calls of a transaction.
The solid blue dots represent calls to a given smart contract A, the hollow ones represent calls to other
smart contracts. Red crosses represent reverts. The underlined integers k = [[ tk , tk+1 [[ represent the
STORAGE_TIMESTAMP intervals wherein a given smart contract performs storage operations
The smart contract calls within a given transaction form a rooted tree with directed edges (contract
calls) whose vertices (smart contracts) naturally carry labels: a smart contract address and a set of
smart contract numbers (1 + dv where dv is the outgoing degree of vertex v). For a given smart
contract address and storage address we define extra labels: a time interval representing the (storage
time stamp) validity interval of stored values, the revert stamp, the previous revert stamp (i.e. the
revert stamp containing the currently valid storage values) and the parent revert stamp (i.e. the revert
stamp to which to roll back in case of a revert). We stress that these labels depend not only on the
smart contract address but also on the storage address. This allows us to only propagate auxiliary
information only for the storage addresses that are effectively touched at a later point by the smart
contract. For a more detailed explanation of the tools we use to process reverted transactions (like the
revert stamps), please refer to the section 3.11.
The main properties of these labels are that, for a given smart contract address and storage address,
• the begin and end storage stamps labels with a given revert stamp form a partition by intervals
of either the empty interval (if the values at that address aren’t touched in the subtree) or of the
interval with the "entry" and "exit" storage time stamps of the (nested) call
• the Previous_Revert_Stamp indicates the Revert Stamp of the last relevant modification to the
data at the storage address
• the Parent_Revert_Stamp indicates the Revert Stamp to which to revert to when exiting the
9
Figure 7: We work with the smart contract A and a given storage address addr. We color the calls to
A in green if that call accesses (read or write) the address addr.
10
Figure 8: This simplified view of the nested calls to A discards all calls to other smart contracts as
well as all calls to A that don’t access the storage address S addr and whose descendants also don’t
...
access it. N.B. We use the short hand a ⌣ b to represent k=a,...,b k = [[ ta , tb+1 [[ to represent the
STORAGE_STAMP intervals of validity. Since the call (a) reverts, one must initialize the storage
address addr of the contract A for the REVERT_STAMP = 10, this initialized value remains valid
until the next time addr is modified by a call of A within REVERT_STAMP = 10. Only the call
...
(d) to A accesses addr at a REVERT_STAMP = 10. Its interval is thus 10 ⌣ 33. The call (b) to
A does’t access addr but one of its descendants does. We must thus load the currently valid value at
...
the storage address addr along with the validity period, 3 ⌣ 9. Its descendant (call (c)) access addr
and reverts and is the only call to A for that REVERT_STAMP = 12, thus its STORAGE_STAMP
...
validity interval is the whole time interval wherein (c) runs, i.e. 4 ⌣ 6. There are two calls to A at
REVERT_STAMP = 14, one which reverts, one which doesn’t revert on its own (but will be reverted
nonetheless, having positive REVERT_STAMP ). There are thus 3 STORAGE_STAMP intervals
...
partitioning the STORAGE_STAMP interval wherein REVERT_STAMP = 14: 25, 26 and 27 ⌣ 31
11
SC ADDR REV SS Inst Val Beg End PREV PARENT
A addr 10 τ0 INIT v0 t1 t10 6 6
A addr 10 τ1 SLOAD v0 t10 t34 10 6
A addr 10 τ2 SLOAD v0 t10 t34 10 6
A addr 10 τ3 SSTORE v1 t10 t34 10 6
A addr 10 τ4 SSTORE v2 t10 t34 10 6
A addr 10 τ5 SLOAD v2 t10 t34 10 6
A addr 11 τ6 INIT v1 t3 t10 10 10
A addr 12 τ7 INIT v1 t4 t7 11 11
A addr 12 τ8 SSTORE v3 t4 t7 12 11
A addr 12 τ9 SSTORE v4 t4 t7 12 11
A addr 12 τ10 SLOAD v4 t4 t7 12 11
A addr 14 τ11 INIT v2 t25 t26 10 10
A addr 14 τ12 SLOAD v2 t25 t26 14 10
A addr 14 τ13 SSTORE v6 t25 t26 14 10
A addr 14 τ14 SSTORE v6 t25 t26 14 10
A addr 14 τ15 SLOAD v6 t26 t27 14 10
A addr 14 τ16 SLOAD v6 t26 t27 14 10
A addr 14 τ17 SSTORE v7 t26 t27 14 10
A addr 14 τ18 SLOAD v7 t27 t32 14 10
A addr 14 τ19 SSTORE v8 t27 t32 14 10
A addr 14 τ20 SLOAD v8 t27 t32 14 10
A addr 14 τ21 SSTORE v9 t27 t32 14 10
A addr 14 τ22 SSTORE v10 t27 t32 14 10
Figure 9: The part of the storage execution trace that concerns itself with the storage opera-
tions performed by a smart contract in a block. It is ordered first by smart contract address
(here: A), then by storage address (here addr ), then by REVERT_STAMP, then by STOR-
AGE_STAMP. We use the shorthand Inst for INSTRUCTION, SS for STORAGE_STAMP, Beg
for Begin_STORAGE_STAMP, End for End_STORAGE_STAMP, REV for REVERT_STAMP,
PREV for PREVIOUS_REVERT_STAMP, PARENT for PARENT_REVERT_STAMP, Val for
VALUE.
12
current revert stamp.
We provide a worked out example below. In the pictures below the blue dots represent calls to the
same smart contract. We have given each node a label (a)-(g) to be distinguish between them.
Virtual columns/subcolumns: Using our high-level representation of the execution trace, a virtual
column is a power of two-periodic subset of rows of a given column of an execution trace. For instance,
given an execution trace of size 2i , and a column C of this execution trace, V C defined by:
V Ck = C3+24 ×k , ∀k ∈ [[ 0, 2i−4 [[
is the virtual column containing all rows of C whose row index is conguent to 3 mod 16.
A virtual subcolumn is a set of virtual columns obtained from another column or virtual col-
umn. Hence, the notion of virtual subcolumn supposes a relation dependance between the different
subcolumns belonging to the same column.
The main advantage of the notion of virtual column and virtual subcolumn is the fact that one is
not bound by the exact size of a given column: one column of an execution trace can be composed of
several virtual columns stacked together.
13
• The module specific time execution columns (blue box): are the core columns of each module.
The constraints that must be verified during the module execution are applied to these columns.
• The module specific space execution columns (black box): are the columns associated with
an underlying memory model specific to the module. These columns are used to check the
consistency of the memory model with the time execution part of the module. For instance,
the main execution trace space execution columns ensure the consistency of the Read only stack
memory; the ones of the child RAM submodule ensure the consistency of the RAM memory with
the read/write operation performed on the RAM.
• The range proof columns (orange box): are used for range proof purposes using the Cairo-style
range proofs that will be described in the next section
(Sub)Columns may belong to more than one group, and some modules may contain more than one
group for each category: for instance, in the main execution trace, there are two groups of space
execution columns - a first group ensures stack memory consistency; a second group ensures call stack
memory consistency. Besides, some modules may not use some groups of columns - the binary module,
for instance, doesn’t use range proof columns.
To simplify the formalisation, we have added a layer of abstraction, dividing the above groups of
columns into two bigger sets of columns: the time ordered columns and the space ordered columns.
The time ordered columns display an internal time coherence: two successive instructions appearing
in the time ordered part of a module are executed one after the other. This is needed to maintain a
global execution order coherence.
The space ordered columns often describe an underlying memory model - these columns are used to
enforce spatial coherence (e.g. memory address coherence). Two successive rows of a virtual column
belonging to the space ordered columns represent the same or two successive memory addresses. These
columns are needed to ensure the memory consistencies of the zk-EVM, i.e. that the values read from
a specific memory address match the ones previously stored at that same address.
• The two execution environments are usually completely independant of one another (i.e. the
called smart contract can’t access to the memory of the caller contract) as in the case of a CALL
instruction.
• Some interactions between the caller and the called contract are permitted in specific cases (e.g.
storing the returned data to the memory of the caller).
• Other less common opcodes (e.g. DELEGATECALL) allow the called smart contract to access
the caller smart contract’s execution environment.
• Two call operations of the same contract involve creating two different execution environments.
We have decided to handle inter-contract calls using a CALL STACK structure, that keeps track of
the execution environment of the caller contract - that structure is particularly useful in dealing with
chained contract calls as it is needed to retrieve successively the previous execution environments.
The consistency of the CALL STACK memory is ensured in the main execution trace, the same way
as the main STACK memory. The CALL STACK memory is a continuous READ ONLY MEMORY
that associates to a unique smart contract number, SC_N um, a n-tuple of associated information (such
as SC_ADDRESS, P rev_T op, P rev_RSP , P rev_P C, Ret_Of f set, Ret_Len, Caller_N um,
14
Time ordered columns Space ordered columns
Inst Flags
Inst Flags
15
Depth), specific to the set of contracts executed. In a sequence of smart contracts executions (each
of which may call other smart contracts) the smart contract number is defined as starting with 0 for
the first smart contract being executed and incremented by one every time another smart contract is
executed (triggered either by an internal call or when exiting a sequence of internal calls and moving
onto a new transaction). As such the smart contract number is distinct from the smart contract’s
address/identifier and a given contract may have several smart contract numbers if it is called several
times in a batch of transactions. A pointer N ext_SC_N um keeps track of the next CALL STACK
address that is not yet attributed (in that respect its role is similar to the WSP pointer for the main
STACK memory).
The figure 11 provides a worked out example of the evolution of the call stack associated with the
execution of a batch of smart contracts.
16
DEP T H = 0 DEP T H = 1 DEP T H = 2
SC n°0 (ROOT)
SC n°4
SC n°5
(a) A call stack graphical example
Step Instruction SC_N um PC Top RSP WSP Caller_N um P rev_P C P rev_T op P rev_RSP DEPTH
0 GLOB_IN IT 0 0 0 0 0 0 0 0 0 0
1 PUSH 1 0 2 1 3 0 0 0 0 0
2 PUSH 1 1 3 2 4 0 0 0 0 0
.. .. .. .. .. .. .. .. .. .. .. ..
. . . . . . . . . . . .
103 CALL 1 92 87 76 88 0 0 0 0 0
104 PUSH 2 0 89 88 90 1 93 87 76 1
105 JUMPI 2 1 90 89 91 1 93 87 76 1
.. .. .. .. .. .. .. .. .. .. .. ..
. . . . . . . . . . . .
208 CALL 2 750 169 157 173 1 93 87 76 1
209 PUSH 3 0 174 173 175 2 751 169 157 2
.. .. .. .. .. .. .. .. .. .. .. ..
. . . . . . . . . . . .
287 RETURN 3 75 254 232 263 2 751 169 157 2
288 PUSH 2 751 169 157 263 1 93 87 76 1
.. .. .. .. .. .. .. .. .. .. .. ..
. . . . . . . . . . . .
354 CALL 2 354 273 269 275 1 93 87 76 1
355 PUSH 4 0 276 275 277 2 354 273 269 2
.. .. .. .. .. .. .. .. .. .. .. ..
. . . . . . . . . . . .
360 RETURN 4 43 285 283 288 2 354 273 269 2
361 SWAP2 2 354 273 269 288 1 93 87 76 1
.. .. .. .. .. .. .. .. .. .. .. ..
. . . . . . . . . . . .
365 RETURN 2 250 299 296 305 1 93 87 76 1
365 MLOAD 1 93 87 76 305 0 0 1 0 0
.. .. .. .. .. .. .. .. .. .. .. ..
. . . . . . . . . . . .
387 RETURN 1 165 353 344 354 0 0 1 0 0
388 PUSH 5 0 355 354 356 0 0 0 0 0
.. .. .. .. .. .. .. .. .. .. .. ..
. . . . . . . . . . . .
453 RETURN 5 230 432 415 453 0 0 0 0 0
0 GLOB_EN D 0 1 0 0 0 0 0 0 0 0
(b) An example of a (partial) main execution trace call stack, associated with 11a
Figure 11: An example of the call stack evolution with inter-smart contract calls and batches.
17
2.3 Putting it all together
The architecture of our zk-EVM is rather complex - modules, instruction decoding, flags, bussing
system . . . This short section sums up the "gist" of how the modules and their constraint systems
relate to the main execution trace and assemble into a zk-EVM. Suppose that we want to prove the
execution of a smart contract whose bytecode and storage are stored in our zk-rollup. Here are the
main steps of how that works:
• We first load the full contract (EVM) bytecode into the ROM. We do this by building a
BytecodeROM column and a P CROM column. To prove that the bytecode of the ROM is cor-
rectly built, we compute the root hash associated with the ROM bytecode and compare it with
the codehash stored in the rollup.
• We then load the sequence instructions effectively carried out during the smart contract execu-
tion into the main execution trace. To verify that the opcodes executed are part of the ROM
bytecode, we perform a Plookup inclusion proof (table version) of the pair of commitments
(P Cexec trace , Bytecodeexec trace ) into the table (P CROM , BytecodeROM ).
Our zk-EVM (i.e. the main execution trace and the various modules) cannot directly interpreted
these EVM instructions: we have to translate the Bytecodeexec trace column into a sequence of module
specific flags. This is the role of the instruction decoder, a public, immutable, data table, whose
commitment to the values is publicly available for consistency checks - the instruction decoder allows
for the flag decomposition of the EVM opcodes into a sequence of flags (IDF lag1 , . . . , IDF lagn ) that
can be interpreted by the (main) execution trace and other relevant modules. The problem is then to
check the consistency between the instruction flags written into the execution traces (let’s note these
commitments ET F lag1 , . . . , ET F lagn ) and the corresponding instruction flags hard-coded into the
instruction decoder. This is solved with a plookup proof of consistency.
• We perform a plookup inclusion proof between the n+1-tuple of commitments (Bytecodeexec trace ,
ET F lag1 , . . . , ET F lagn ) and the associated tuple of commitments from the Instruction decoder
(EV Mopcode , IDF lag1 , . . . , IDF lagn )
Note that this process (instruction decomposition) is done both in the main execution trace and in
the module execution traces (using different sets of flags). The general idea is that the flags that are
loaded into the Main Execution Trace are used for the inclusion proofs linking certain columns of the
module execution traces to the main execution trace, while the flags loaded into a module are used for
the internal constraints that must be satisfied by the columns of the module execution trace.
• Now that the EVM instructions can be interpreted by our zk-EVM, we have to execute the
operations and verify that the computations have been well performed. Depending on the value
of the instruction flags, a different part of the zk-circuit is going to be activated.
– For instance, to ensure that the Memory instructions of the current contract (such as
M ST ORE, M LOAD, RET U RN, CALL, ...) are transmitted to the RAM module, a flag
column RAM _Instruction is set in the main execution trace. When this flag is set, some
constraints of the main execution trace become active - for example, the value of the RAM
timestamp has to increase during this clockcycle.
In the RAM module, other flags will be set to select the internal module behaviour - to
distinguish between the M ST ORE and M ST ORE8 instructions, a flag SIZE, contained
in the instruction decoder and loaded into the RAM, imposes the length of the word stored
in the RAM, etc. . .
The ram execution trace is augmented with a constant column (equal to 1) which will be
included (along with other columns, such as the instruction column, the time stamp column,
. . . ) into the main execution trace into the RAM _Instruction flag column.
18
– Another example may be the JUMP instruction that implies an exceptional modification of
the value of the PC - when the JU M P _F LAG is set, the circuit that performs the normal
update the PC is deactivated and the special PC update circuit is activated.
– Importantly, the constraint equations for the modules is independent of the precise se-
quence of module instruction being executed: there is a fixed, module-dependent set of
constraints ensuring the internal coherence of each module execution trace. The instruction-
specific behaviour is selected within that set of constraints through the instruction specific
flags.
• Every module is linked to the main execution trace by a Plookup proof: the M ODU LE_IN ST
flag (in the main execution trace) is set when an instruction that has to be transmitted to
a given module, the M ODU LE_ST AM P keeps track of the current instruction number be-
ing executed by the module, and the top stack elements acts as input values for the mod-
ules, hence every Plookup inclusion proof has to show that the (M ODU LE_IN ST , IN ST ,
M ODU LE_ST AM P , IN P U T1 , IN P U T2 ) module trace columns are included within the cor-
responding main execution trace columns.
19
of one another if there exists a permutation σ of the index set of rows {0, 1, . . . , N − 1} such that the
i-th row of B equals the σ(i)-th row of A i.e. if
∀i ∈ {0, 1, . . . , N − 1} ∀j ∈ {1, 2, . . . , r}, Bi,j = Aσ(i),j
Our arithmetization requires arguments proving that two matrices are row permutations of one another.
We can use the row permutation argument for column vectors described above to prove the row
permutation property.
Pr Indeed,Pit is enough to prove the row permutation property for a random linear
r
combination j=1 θj Aj and j=1 θj Bj (where the A1 , . . . , Ar and B1 , . . . , Br are the columns of A
and B respectively.) This argument requires the introduction of a single new column vector ΠB A of
length N constructed by accumulating quotients of the coefficients of the random linear combinations
of values in A and B:
B Y z − Pr θk Aℓ,k
ΠA i = Prk=1 .
z − k=1 θk Bℓ,k
0≤ℓ<i
In our applications the matrices A and B are constructed by specifying r column vectors A1 , A2 , . . . , Ar
for A and r column vectors B1 , B2 , . . . , Br for B (all of equal length N ). These column vectors are
obtained as columns/subcolumns of some module’s execution trace. We require such permutation
arguments for two reasons:
Space-time reordering the stack, memory and storage modules must satisfy two largely independent
sets of constraints: time consistency and space consistency. The stack, say, has to be consistent
with the temporal execution of the EVM — thus popping the top of the stack for instance must
modify the pointer to the top of the stack accordingly at the next clockcycle. On the other hand,
performing several pops in a row (or one of the stack pointers being sent a ways back down
below the top of the stack) will unearth values in the stack that may have been written there a
while back – thus we need to make sure that values once written to the stack are indeed the ones
recovered at a later stage through a pointer pointing to the corresponding stack address.
The execution trace of these modules will thus be tasked with proving both types of constraints
Cairo-style range proofs explained below
We use the notation B = A e (resp. B = A)e to signify that B is a row permutation of the column
vector A (resp. B is a row permutation of the matrix A). Whenever we use this notation it is tacitly
assumed that the execution trace is augmented with a column Π witnessing the permutation.
3.4 Plookup
The overall architecture of our zk-evm is modular — separate subunits are specialized in certain
subtasks: they obey independent constraint sets and have separate (but not unrelated) execution
traces. Thus one module may be tasked with 256 bit arithmetic while another deals with binary
operations and yet another deals with memory. Connections between these modules allow modules to
delegate computations that fall outside of their specialization to the relevant submodules.
Plookup arguments [10] are the means by which these connections are realized:
Plookup inclusion proofs between subcolumns we can relate the contents of subcolumns of one
execution trace to another by means of an inclusion proof. This serves two purposes: (1) it allows
the contents of the first column to flow as inputs into the second module where a specialized
computation might be verifiable or (2) to use the values of the first column to justify values of
the second column when the second module can’t verify the associated constraints (e.g. how
hashes enter a non hash module).
Plookup inclusion proofs for tables arithmetic operations that are inefficient in field arithmetic
(e.g. binary operations) are hardcoded in a plookup table and find their way into execution
traces by means of plookup inclusion proofs. Note that column contents can be filtered using
flag columns.
20
3.5 Small-range range proofs
Cairo-style range proofs (or short-range range proofs) apply to show that values in a column are within
a relatively short interval [[ a, b ]] of consecutive integers. Doing this requires reordering the column in
ascending order while filling in all missing gaps. We thus require a permutation argument (witnessed
by an extra column Π) along with row-wise checks to the effect that the reordered values start at a,
end with b, and from one row to the next the values can only remain the same or change by +1.
The GLOBAL_IN IT special opcode, executed before any other instruction, keeps track of the
total number of instructions and atomic module calls and initializes the module timestamp values to
zero for the next instruction. This number is compared to the sequentially built timestamp values of
the GLOBAL_EN D special opcode, executed at the last step of the execution trace.
21
other words, there are many times where it is enough for us to describe a small number of constraints
to implicitly force other constraints to hold for a priori unconstrained variables.
For instance, if the memory’s Read Stack Pointer (RSP ) and Pointer to the top of the stack (T op)
are reassigned after the execution of an instruction to historical values of themselves, they will be
reassigned coherently along with the corresponding values (V al(RSP ) and V al(T op)).
1. A reverted set is the set of the children of a reverted transaction. Any reverted transaction
belongs to a reverted set.
We introduce several tools and concepts to deal with the REVERT opcode and error flags:
• A Curr_REV virtual column that activates whenever the current transaction is reverted or a
child of a REVERTED transaction.
• A REV _F LAG virtual column that activates when a REVERT opcode is executed. When the
REV ERT _F LAG is set, the interrupt flag has to be set too.
• An IN T ERRU P T _F LAG that is a virtual column that activates whenever an error flag is set
or the REVERT opcode executed.
• A REV _ST AM P that is unique to every reverted set. Note that if a given reverted set, RC , is
included in another reverted set RP , then the reverted stamp associated with the transactions
belonging to RC is different from the ones belonging to RP − RC
• A P arent_REV _ST AM P : tracks the revert stamp of the reverted set in which the current
reverted set is included. By convention, if the current transaction isn’t included in any reverted
set, P arent_REV _ST AM P = 0
• A N ext_REV ERT _ST AM P : which tracks the number of the next revert stamp
To build coherently these flags, one has to detects when one enters or leave a reverted set and
how the reverted transactions automatically transmit the Curr_REV ERT ED flag value in case
of an inner-contract call. Basically, these three cases fully determine the Curr_REV ERT ED and
REV ERT _ST AM P updates :
22
1. If the current instruction is RET U RN or ST OP , the Curr_REV ERT ED is not set and
the Curr_REV ERT ED flag is set, then the parent contract must be reverted too (ie, its
Curr_REV ERT ED flag is set): because otherwise there is a contradiction between the correct
execution of the contract (it has returned a value) and the fact it has been reverted.
2. If the current instruction is CALL and the Curr_REV ERT ED flag is set then set the called
contract Curr_REV ERT ED flag.
3. If the current instruction is neither CALL, RETURN nor STOP, then verify that the Curr_REV ERT ED
flag of the next instruction is the same as the current instruction.
4. If the IN T ERRU P T _F LAG is set, check that the Curr_REV ERT ED flag is set. Set the cur-
rent REV ERT _ST AM P to the N ext_REV ERT _ST AM P and increment the N ext_REV ERT _ST AM P
Having defined the tools for REVERTED updates, one can now start to use them to update the
STORAGE in compliance with REVERT statements.
• Invalid instruction.
• JUMP/JUMPI to an invalid destination.
• Insufficient stack items.
• Stack overflow (stack size exceeds 1024).
• To deal with the insufficient gas error flag, one can perform a word comparison between the gas
value and its decrement for each step, raising an error flag when the gas value is insufficient for
the execution to pursue.
• The stack related errors are easily dealt with using stack size trackers and verifying that its value
is always comprised in [0,1023], setting an error flag if that’s not the case anymore.
• RETURNDATA related flags are dealt with by tracking RETURNDATASIZE and setting the
associated error flag in case of overflow.
• State modification during a static call is dealt by raising a flag when calling a forbidden instruction
during a static call.
• To check the validity of the destination of JUMP/JUMPI, one may impose a range check for the
maximum value of the PC in the current program, and add a P U SH_ARG flag to the ROM in
the case that one jumps to a PUSH argument in the bytecode. This case is considered to be an
illicit jump destination by the Ethereum yellowpaper.
23
• The instruction validity check is a bit more complex. Indeed, this amounts to verify that the
instruction opcode does not belong to the set of 80 EVM opcodes (which is not a continuous set
of values). Normally, this check is performed using Plookup, which can be hardly adapted for a
try/catch logic. A solution to perform instruction validity checks is to pad the opcode gaps with
INVALID instructions, and then impose an opcode maximum value. Then, for every instruction
one has to perform a range proof to verify that the opcode falls in the authorized set of values.
Inside that range of values, Plookup will distinguish between licit and INVALID opcodes inside
the instruction decoder.
1. A constant fee intrinsic to the operation currently executed. This is the case for most of the
common instructions of the EVM (ADD, SWAP, DUP, ...)
2. A memory extension fee that depends on the current size of the memory RAM. This extension
fee depends on the number of EVM words one have stored in the RAM so far and is updated
dynamically during the execution of the program. There is an equivalent of the RAM extension
fee for the storage - the first access of a given address in storage will be way more expensive than
the subsequent ones, and setting a storage value to zero induces a gas reimbursement.
3. A last category of gas fee is related to smart contract interactions. This fee is payed for the
execution of instructions like ∗CALL or CREAT E∗. These fees are called dynamical fees and
are the most complex category of fees to adapt to the zk-EVM framework.
The current fee value will be tracked using a gas virtual column. The current size of memory is
tracked in a M EM _SIZE variable that is updated in the child memory module as memory segments
are accessed. This M EM _SIZE variable allows us to perform this dynamic fee update during the
program execution.
• LT
• GT
• SLT
• SGT
which respectively return, for a := Input_1, b := Input_1, the boolean a ≤ b, a ≥ b, a < b and a > b.
24
• Inst column of instructions
• SwitchFlag
• EqFlag
• Input_1 The first word, remains constant during the instruction execution.
• Input_2 The second word, remains constant during the instruction execution.
• Comp This column is constant along a given time stamp and returns the result of the comparison
Input_1 < Input_2, i.e. Comp computes the value of SLT.
• Prefix_1 constructs the sequence of prefixes of Input_1, Byte by Byte
• Prefix_2 same for Input_2
• B_1 the sequence of Bytes making up Input_1 starting from its highest Byte to its lowest
• B_2 the sequence of Bytes making up Input_2 starting from its highest Byte to its lowest
• ByteComp this column contains the result of the comparison B_1 < B_2.
• Decided this column may switch from 0 to 1 as soon as one can know the result of Comp
Thus it is enough for us to compute the comparison a < b and this is the purpose of the Comp column
which always computes the boolean Input_1 < Input_2
1. The column Inst along with its flag columns SwitchFlag, EqFlag are Plookup verified against the
instruction decoder.
2. The columns B_1, B_2 and BComp are Plookup verified against a lookup-table that stores all
256 × 256 Byte comparisons.
3. WC_TimeStamp0 is initialized to the total number of calls to the Word Count module. This
condition is required to ensure that the inclusion proofs don’t "miss out" on requests to the word
count module.
4. for i = 1 WC_TimeStamp1 = 0
5. for i ≥ 1, WC_TimeStampi+1 = WC_TimeStampi or WC_TimeStampi+1 = WC_TimeStampi +
1, i.e. (WC_TimeStampi+1 −WC_TimeStampi )(WC_TimeStampi+1 −WC_TimeStampi −1) =
0.
25
6. for 1 ≤ i, WC_TimeStampi+32 = WC_TimeStampi or WC_TimeStampi+32 = WC_TimeStampi +
1 since words have a fixed length of 32 bytes and 32 lines are enough for word comparison.
7. Res, Comp and Decided are binary, thus for all i: Resi (Resi − 1) = 0, Compi (Compi − 1) = 0
and Decidedi (Decidedi − 1) = 0
The other variables are updated differently according to whether WC_TimeStampi = WC_TimeStampi−1
or not:
1. If - WC_TimeStampi = WC_TimeStampi−1 + 1:
(a) (B_1)i = (Prefix_1)i and (B_2)i = (Prefix_2)i ,
(b) (Prefix_1)i−1 = (Input_1)i−1 and (Prefix_2)i−1 = (Input_2)i−1 , i.e. we expect the prefixes
to equal the inputs at the end of a word comparison
(c) If - (B_1)i = (B_2)i then Decidedi−1 = 0
(d) Else If - (B_1)i ̸= (B_2)i then Decidedi = 1 and Compi = BCompi .
(e) If - (Input_1)i = (Input_2)i then Compi = 0
2. Else If - WC_TimeStampi = WC_TimeStampi−1 then
(a) (Prefix_1)i = 256 · (Prefix_1)i−1 + (B_1)i and (Prefix_2)i = 256 · (Prefix_2)i−1 + (B_2)i ,
(b) the inputs, the instruction and its flags, and the result of the comparison (Input_1 <
Input_2) remain constant throughout the execution of a single word comparison:
(Input_1)i−1 = (Input_1)i
(Input_2)i−1 = (Input_2)i
Insti−1 = Insti
SwitchFlagi−1 = SwitchFlagi
EqFlagi−1 = EqFlagi
Compi−1 = Compi
Resi−1 = Resi
(c) i. If - Decidedi−1 = 0:
A. If - (B_1)i = (B_2)i then Decidedi = 0
B. Else If - (B_1)i ̸= (B_2)i then Decidedi = 1 and Compi = BCompi
ii. Else If - Decidedi−1 = 1: then Decidedi = Decidedi−1 .
We now specify the computation of Res. We have already specified that Res remains constant during
the execution of an instruction.
1. If - SwitchFlagi = 0 AND EqFlagi = 0: then Resi = Compi
2. If - SwitchFlagi = 0 AND EqFlagi = 1: then Resi = 1 iff Compi = 1 or (Input_1)i = (Input_2)i ,
26
5 Constraint set for the Parent Memory module
5.1 Instructions treated
• CALL
• RETURN
• MSTORE
• MSTORE8
• MLOAD
• CALLDATALOAD
• RETURNDATACOPY
• RETURNDATASize
• CODECOPY
• EXTCODECOPY
• DELEGUATECALL
• STATICALL
• CALLCODE
• IN V ALID_IN ST RU CT ION
27
5.2.2 Instruction unpacking
• IOP C_F LAG: if set, selects interactive memory opcodes like CALL, RET U RN . Otherwise, se-
lects non interactive memory opcodes such as M LOAD, M ST ORE, M ST ORE8, CALLDAT ALOAD.
• M OP C_F LAG: Bit selection flag for non interactive memory opcodes. If set, selects M ST ORE, M ST ORE8.
Otherwise, selects M LOAD and CALLDAT ALOAD opcodes.
• CALLDAT A_F LAG: special case that makes the instruction linked memory offsets to be
written as a calldata (for the CALL instruction)
• IN IT _F LAG: initialize the RAM after a CALL instruction
• CRAM _EW d_Of f set: the word multiple address to end writing to / reading from.
• CRAM _BInt_Of f set: contains the internal beginning address of the word currently read/written.
• CRAM _EInt_Of f set: contains the internal ending address of the word currently read/written.
28
• CRAM _SC_N um: the SC memory to be loaded/written.
• CRAM _CALLDAT A_F LAG: a flag that determines whether the instruction acts on the RAM
or the CALLDATA
• M em_IN CREASE_F LAG: a flag that indicates whether the memory size is increased
• ∆Curr_M em_Size: the difference between the maximum and the current memory sizes.
• Gas_RAM : dynamic RAM gas cost paid by the instruction.
• Cost_RAM : current value of the memory cost function
5.2.7 Auxiliaries
• AU X_T RAN S
1. Unpack the first input (memory word offset) and set the instruction specific beginning word and
internal offsets:
2. Compute the memory word ending word/internal offset using the Size variable (which is equal
to 31 for MSTORE/MLOAD and is equal to 1 for MSTORE8)
29
(a) Unpack the second input (word to write to memory) and set the instruction specific ending
word and internal offsets:
(Input2 )i = V al(CRAM _Inst)i
(b) Set the Child RAM instruction to store the value of the second input
CRAM _Insti = 1
4. Else If - (M OP C_F LAG)i = 0 (MLOAD, CALLDATALOAD instruction) then:
(a) Unpack the result input (word to read from the memory) and set the instruction specific
ending word and internal offsets:
(Result)i = V al(CRAM _Inst)i
(b) Set the Child RAM instruction to read the value of the result input
CRAM _Insti = 0
3. Initialize the auxiliary begin internal and word offsets with the return offset:
Ret_Of f set(SC_N um)i+1 = 32 ∗ Aux_BW d_Of f seti+1 + Aux_BInt_Of f seti+1
4. Initialize the auxiliary end internal and word offsets with the return offset:
Ret_Of f set(SC_N um)i+1 + Ret_Len(SC_N um)i+1 =
32 ∗ Aux_EW d_Of f seti+1 + Aux_EInt_Of f seti+1
30
5.3.4 CALL specific initialization
If - CALL_F LAGi = 1
1. If - it is the first CALL step: RAM _ST AM Pi = RAM _ST AM Pi−1 + 1: initialize the
CALLDATA parameters:
(a) Unpack the memory data offset and length as instruction decomposition:
(Input2 )i+2 = 32 ∗ Inst_BW ord_Of f seti + Inst_BInt_Of f seti
(Input2 )i+2 + (Input1 )i+3 = 32 ∗ Inst_EW ord_Of f seti + Inst_EInt_Of f seti
(b) Initialize the return data offset as the auxiliary memory offsets:
Aux_BW d_Of f seti = 0
Aux_BInt_Of f seti = 0
(Input1 )i+3 = 32 ∗ Aux_EW ord_Of f seti + Aux_EInt_Of f seti
2. If - it is the last CALL step: RAM _ST AM Pi+1 = RAM _ST AM Pi + 1: initialize the
new CALLDATASize (done in the main execution trace constraints).
31
5.3.7 RAM gas cost computation:
• General cost computation:
∗COP Y _Costi = 0
Curr_EInt_Of f seti = 31
Curr_BInt_Of f seti = 0
3. Impose the values of Curr_Aux_[∅, B, E]_[W d, Int]_Of f set. The values of Aux_Of f set
are constrained depending on the values of Curr_Of f set: we can’t impose the exact values of
Curr_Aux, the same way as Curr_Inst, because the respective internal offsets may be different.
(a) If - RAM _ST AM Pi = RAM _ST AM Pi−1 +1 (initialization performed in the general
value constraints) then
i. Initialize the current auxiliary offsets
Curr_Aux_BW d_Of f seti = Aux_BW d_Of f seti
Curr_Aux_BInt_Of f seti = Aux_BInt_Of f seti
32
ii. Initialize the current auxiliary ending offsets
(b) If - Curr_BW d_Of f seti ̸= Curr_BW d_Of f seti−1 AND RAM _ST AM Pi−1 =
RAM _ST AM Pi (the current instruction offsets changed)
i. Setting the beginning offset
(c) Else If - Curr_BW d_Of f seti = Curr_BW d_Of f seti−1 AND RAM _ST AM Pi−1 =
RAM _ST AM Pi (the current instruction offsets did not change):
i. Keep every offset constant:
Curr_Aux_BW d_Of f seti = Curr_Aux_BW d_Of f seti−1
Curr_Aux_EW d_Of f seti = Curr_Aux_EW d_Of f seti−1
Curr_Aux_BInt_Of f seti = Curr_Aux_BInt_Of f seti−1
Curr_Aux_EInt_Of f seti = Curr_Aux_EInt_Of f seti−1
CRAM _CALLDAT Ai = 0
(b) Set the current child RAM begin/end interior offset to the current begin/end interior offset
33
(c) Same thing for the word offsets
4. Else If - IOP C_F LAGi = 1
(a) If - CRAM _Inst = 0 (READ from the current smart contract) then
i. If - RET U RN _F LAG = 1 OR CALL_F LAG = 1
A. Set the child RAM smart contract number to be the current smart contract number
CRAM _SC_N umi = SC_N umi
ii. Set the child RAM interior offsets to be the current interior offsets.
CRAM _BInt_Of f seti = Curr_BInt_Of f seti
CRAM _EInt_Of f seti = Curr_EInt_Of f seti
iii. Same thing for the word offsets
(b) If - CRAM _Inst = 1 (WRITE to the caller contract) then
i. If - RET U RN _F LAG = 1
A. Set the child RAM smart contract number to be the caller smart contract number
(return the data to the memory of the called contract)
CRAM _SC_N umi = Caller_N umi
ii. If - CALL_F LAG = 1
A. Set the child RAM smart contract number to be the next smart contract number
(write to the calldata of the next SC)
CRAM _SC_N umi = N ext_SC_N umi
iii. Set the child RAM interior offsets to be the return interior offsets.
CRAM _BInt_Of f seti = Curr_Aux_BInt_Of f seti
CRAM _EInt_Of f seti = Curr_Aux_EInt_Of f seti
iv. Same thing for the word offsets
v. Impose the previous value READ to be the new value stored:
V al(CRAM _Inst)i = V al(CRAM _Inst)i−1
(b) One has to increase the word offset once the writing and reading operations are done:
Curr_W d_Of f seti+2 = Curr_W d_Of f seti + 1
2. If - M OP C_F LAGi = 1, AND RAM _ST AM Pi+1 = RAM _ST AM Pi increase the
word offset.
Curr_W ord_Of f seti+1 = Curr_W ord_Of f seti + 1
34
5.3.11 End instruction constraints:
If - End_W d_Of f seti = Curr_W d_Of f seti , increase the RAM STAMP
RAM _ST AM Pi+1 = RAM _ST AM Pi + 1
Else If - End_W d_Of f seti ̸= Curr_W d_Of f seti , keep the same RAM STAMP
RAM _ST AM Pi+1 = RAM _ST AM Pi
35
6.2.2 CRAM execution specific columns
These columns are used within the child RAM module to perform the CRAM specific STORE/LOAD
operations :
• Curr_W d_Of f set: the current word offset to STORE to / READ from
• Curr_Byte_N um: auxiliary variable that keeps track of the current byte number.
• Inst_Range_F lag: a flag that is set if
• P rev_Carry: carry for the previous value that was stored in the RAM at (Curr_W d_Of f set, Curr_Int_Of f set)
• P rev_Byte: the previous byte being stored in the RAM at (Curr_W d_Of f set, Curr_Int_Of f set)
Curr_Byte_N umi = 31
36
5. Initialise the P rev_Carry:
P rev_Carryi = 0
6. Initialise the Inst_Carry:
Inst_Carryi = 0
• If - IN IT _OP ERAT IONi = 1:
1. Store a value:
CRAM _Insti = 1
2. Impose to store zero:
V al(CRAM _Inst)i = 0
3. If - Curr_W d_Of f seti = CRAM _EW d_Of f set:
(a) Increase the CRAM stamp
37
(b) If - Curr_Byte_N um = 0:
i. Check that the Curr_Carry and the P rev_Carry matches the Curr_V al and
the P rev_V al:
Curr_Carryi = Curr_V ali
P rev_Carryi = P rev_V ali
ii. If - Curr_W d_Of f seti = EW d_Of f seti :
A. Move to the next instruction: CRAM _ST AM Pi+1 = CRAM _ST AM Pi + 1
iii. Else If - Curr_W d_Of f seti = EW d_Of f seti − 1:
A. Move to the next word offset:
Curr_W d_Of f seti+1 = Curr_W d_Of f seti + 1
B. Reset the Curr_Byte_N um:
Curr_Byte_N umi+1 = 31
C. Reset the Curr_Carry and the P rev_Carry:
Curr_Carryi = 0
P rev_Carryi = 0
(c) If - 32 ∗ Curr_W d_Of f seti + Curr_Int_Of f seti = 32 ∗ BW d_Of f seti +
BInt_Of f seti : set the Inst_Range_F lag:
Inst_Range_F lagi = 1
(d) Else If - 32∗Curr_W d_Of f seti +Curr_Int_Of f seti = 32∗EW d_Of f seti +
EInt_Of f seti :
i. Turn off the Inst_Range_F lag:
Inst_Range_F lagi+1 = 0
ii. Check that the Inst_Carry is the V al(CRAM _Inst):
Inst_Carry = V al(CRAM _Inst)
(e) Else If - 32∗Curr_W d_Of f seti +Curr_Int_Of f seti ̸= 32∗BW d_Of f seti +
BInt_Of f seti AND 32 ∗ Curr_W d_Of f seti + Curr_Int_Of f seti ̸= 32 ∗
EW d_Of f seti + EInt_Of f seti keep the Inst_Range_F lag constant:
Inst_Range_F lagi+1 = Inst_Range_F lagi
(f) If - Inst_Range_F lagi = 1:
i. The current byte is the instruction byte:
Curr_Bytei = Inst_Bytei
ii. Shift the instruction carry:
Inst_Carryi+1 = Inst_Carryi + 28 ∗ Inst_Bytei
(g) Else If - Inst_Range_F lagi = 0:
i. The current byte is the previous byte:
Curr_Bytei = P rev_Bytei
ii. Don’t shift the instruction carry:
Inst_Carryi+1 = Inst_Carryi
38
6.4 Memory consistency columns
These columns are used to check the consistency of the RAM/Calldata of every smart contract
loaded in the ROM. These columns are the space reordered version of the constraint columns from
the child RAM (section 6.2). More precisely, the columns from 6.2 are lexicographically ordered by
(SC_N um, CALLDAT A_F LAG, Curr_W d_Of f set, CRAM _ST AM P ).
• CALLDAT
^ A_F LAG
• Curr_W
^ d_Of f set
• CRAM^
_ST AM P
• CRAM
^ _Inst: the child RAM instruction
• IN IT _OP
^ ERAT ION : the initialization flag.
• Curr_V
^ al
• P rev_V
^ al
• ∆CRAM^
_ST AM P
i
• ∆CRAM^ _ST AM P : the 16-bit decomposition of ∆CRAM^
_ST AM P to check that ∆CRAM^
_ST AM P ∈
[0, 2 ] (the difference has not overflown)
128
(a) If - CALLDAT
gA_F LAG = CALLDAT
i
gA_F LAG
i+1
IN V ALID_IN
^ ST RU CT ION i+1 = 1
39
B. Compute the ∆CRAM
^ _ST AM P :
∆CRAM
^ _ST AM P i = CRAM^
_ST AM P i+1 − CRAM^
_ST AM P i
C. Impose that the current value is the same as next previous value:
^ al
P rev_V ^
i+1 = Curr_V ali
^ al = Curr_V
Curr_V ^ al
i i+1
IN IT _OP
^ ERAT ION i+1 = 1
IN IT _OP
^ ERAT ION i+1 = 1
2. Else If - SC_N
g um ̸= SC_N
i
g um
i+1 : impose the next operation to be the initialization of
the memory cell:
IN IT _OP^ ERAT ION i+1 = 1
References
[1] DR. Gavin Wood. “Ethereum : A secure decentralised generalised transaction ledger”. In: (2021).
https://ethereum.github.io/yellowpaper/paper.pdf.
[2] Vitalik Buterin. An Incomplete Guide to Rollups. Jan. 2021. url: https://vitalik.ca/general/
2021/01/05/rollup.html.
[3] DeGate Team. An article to understand zkEVM, the key to Ethereum scaling. Sept. 2021. url:
https://medium.com/degate/an- article- to- understand- zkevm- the- key- to- ethereum- scaling-
ff0d83c417cc.
[4] ZK-sync official website. url: https://zksync.io/.
[5] Lior Goldberg, Shahar Papini, and Michael Riabzev. Cairo – a Turing-complete STARK-friendly
CPU architecture. Cryptology ePrint Archive, Report 2021/1063. https://ia.cr/2021/1063. 2021.
[6] Hermez official website. url: https://hermez.io/.
[7] Scroll tech github repository. url: https://github.com/scroll-tech/.
[8] Hermez presentation at the EthCC4. url: https://youtu.be/17d5DG6L2nw.
40
[9] Ariel Gabizon, Zachary J. Williamson, and Oana Ciobotaru. PLONK: Permutations over Lagrange-
bases for Oecumenical Noninteractive arguments of Knowledge. Cryptology ePrint Archive, Re-
port 2019/953. https://ia.cr/2019/953. 2019.
[10] Ariel Gabizon and Zachary J. Williamson. plookup: A simplified polynomial protocol for lookup
tables. Cryptology ePrint Archive, Report 2020/315. https://ia.cr/2020/315. 2020.
[11] Olivier Begassat Alexandre Belling. Using GKR inside a SNARK to reduce the cost of hash
verification down to 3 constraints. June 2020. url: https://ethresear.ch/t/using-gkr-inside-a-
snark-to-reduce-the-cost-of-hash-verification-down-to-3-constraints/7550.
41