plonky2/evm/spec/tables/memory.tex
Hamy Ratoanina 30c944f778
Remove bootstrapping (#1390)
* Start removing bootstrapping

* Change the constraint for kernel code initializing

* Update specs

* Apply comments

* Add new global metadata to circuit methods

* Change zero-initializing constraint

* Apply comment

* Update circuit size range for recursive test
2023-11-30 10:04:08 -05:00

80 lines
4.1 KiB
TeX

\subsection{Memory}
\label{memory}
For simplicity, let's treat addresses and values as individual field elements. The generalization to multi-element addresses and values is straightforward.
Each row of the memory table corresponds to a single memory operation (a read or a write), and contains the following columns:
\begin{enumerate}
\item $a$, the target address
\item $r$, an ``is read'' flag, which should be 1 for a read or 0 for a write
\item $v$, the value being read or written
\item $\tau$, the timestamp of the operation
\end{enumerate}
The memory table should be ordered by $(a, \tau)$. Note that the correctness of the memory could be checked as follows:
\begin{enumerate}
\item Verify the ordering by checking that $(a_i, \tau_i) \leq (a_{i+1}, \tau_{i+1})$ for each consecutive pair.
\item Enumerate the purportedly-ordered log while tracking the ``current'' value of $v$.
\begin{enumerate}
\item Upon observing an address which doesn't match that of the previous row, if the address is zero-initialized
and if the operation is a read, check that $v = 0$.
\item Upon observing a write, don't constrain $v$.
\item Upon observing a read at timestamp $\tau_i$ which isn't the first operation at this address, check that $v_i = v_{i-1}$.
\end{enumerate}
\end{enumerate}
The ordering check is slightly involved since we are comparing multiple columns. To facilitate this, we add an additional column $e$, where the prover can indicate whether two consecutive addresses changed. An honest prover will set
$$
e_i \leftarrow \begin{cases}
1 & \text{if } a_i \neq a_{i + 1}, \\
0 & \text{otherwise}.
\end{cases}
$$
We also introduce a range-check column $c$, which should hold:
$$
c_i \leftarrow \begin{cases}
a_{i + 1} - a_i - 1 & \text{if } e_i = 1, \\
\tau_{i+1} - \tau_i & \text{otherwise}.
\end{cases}
$$
The extra $-1$ ensures that the address actually changed if $e_i = 1$.
We then impose the following transition constraints:
\begin{enumerate}
\item $e_i (e_i - 1) = 0$,
\item $(1 - e_i) (a_{i + 1} - a_i) = 0$,
\item $c_i < 2^{32}$.
\end{enumerate}
The last constraint emulates a comparison between two addresses or timestamps by bounding their difference; this assumes that all addresses and timestamps fit in 32 bits and that the field is larger than that.
\subsubsection{Virtual memory}
In the EVM, each contract call has its own address space. Within that address space, there are separate segments for code, main memory, stack memory, calldata, and returndata. Thus each address actually has three compoments:
\begin{enumerate}
\item an execution context, representing a contract call,
\item a segment ID, used to separate code, main memory, and so forth, and so on
\item a virtual address.
\end{enumerate}
The comparisons now involve several columns, which requires some minor adaptations to the technique described above; we will leave these as an exercise to the reader.
\subsubsection{Timestamps}
Memory operations are sorted by address $a$ and timestamp $\tau$. For a memory operation in the CPU, we have:
$$\tau = \texttt{NUM\_CHANNELS} \times \texttt{cycle} + \texttt{channel}.$$
Since a memory channel can only hold at most one memory operation, every CPU memory operation's timestamp is unique.
Note that it doesn't mean that all memory operations have unique timestamps. There are two exceptions:
\begin{itemize}
\item Before the CPU cycles, we write some global metadata in memory. These extra operations are done at timestamp $\tau = 0$.
\item Some tables other than CPU can generate memory operations, like KeccakSponge. When this happens, these operations all have the timestamp of the CPU row of the instruction which invoked the table (for KeccakSponge, KECCAK\_GENERAL).
\end{itemize}
\subsubsection{Memory initialization}
By default, all memory is zero-initialized. However, to save numerous writes, we allow some specific segments to be initialized with arbitrary values.
\begin{itemize}
\item The read-only kernel code (in segment 0, context 0) is initialized with its correct values. It's checked by hashing the segment and verifying
that the hash value matches a verifier-provided one.
\end{itemize}