Merge branch 'main' into generate_dummy_proof

This commit is contained in:
wborgeaud 2022-10-17 10:01:32 +02:00
commit 1ae922dd4d
66 changed files with 2019 additions and 724 deletions

View File

@ -7,14 +7,14 @@ edition = "2021"
[dependencies] [dependencies]
plonky2 = { path = "../plonky2", default-features = false, features = ["rand", "timing"] } plonky2 = { path = "../plonky2", default-features = false, features = ["rand", "timing"] }
plonky2_util = { path = "../util" } plonky2_util = { path = "../util" }
eth_trie_utils = "0.1.0" eth_trie_utils = "0.2.1"
anyhow = "1.0.40" anyhow = "1.0.40"
env_logger = "0.9.0" env_logger = "0.9.0"
ethereum-types = "0.14.0" ethereum-types = "0.14.0"
hex = { version = "0.4.3", optional = true } hex = { version = "0.4.3", optional = true }
hex-literal = "0.3.4" hex-literal = "0.3.4"
itertools = "0.10.3" itertools = "0.10.3"
keccak-hash = "0.9.0" keccak-hash = "0.10.0"
log = "0.4.14" log = "0.4.14"
num = "0.4.0" num = "0.4.0"
maybe_rayon = { path = "../maybe_rayon" } maybe_rayon = { path = "../maybe_rayon" }

View File

@ -6,21 +6,21 @@
Withour our zkEVM's kernel memory, Withour our zkEVM's kernel memory,
\begin{enumerate} \begin{enumerate}
\item An empty node is encoded as $(\texttt{MPT\_NODE\_EMPTY})$. \item An empty node is encoded as $(\texttt{MPT\_NODE\_EMPTY})$.
\item A branch node is encoded as $(\texttt{MPT\_NODE\_BRANCH}, c_1, \dots, c_{16}, \abs{v}, v)$, where each $c_i$ is a pointer to a child node, and $v$ is a value of length $\abs{v}$.\footnote{If a branch node has no associated value, then $\abs{v} = 0$ and $v = ()$.} \item A branch node is encoded as $(\texttt{MPT\_NODE\_BRANCH}, c_1, \dots, c_{16}, v)$, where each $c_i$ is a pointer to a child node, and $v$ is a pointer to a value. If a branch node has no associated value, then $v = 0$, i.e. the null pointer.
\item An extension node is encoded as $(\texttt{MPT\_NODE\_EXTENSION}, k, c)$, $k$ represents the part of the key associated with this extension, and is encoded as a 2-tuple $(\texttt{packed\_nibbles}, \texttt{num\_nibbles})$. $c$ is a pointer to a child node. \item An extension node is encoded as $(\texttt{MPT\_NODE\_EXTENSION}, k, c)$, $k$ represents the part of the key associated with this extension, and is encoded as a 2-tuple $(\texttt{packed\_nibbles}, \texttt{num\_nibbles})$. $c$ is a pointer to a child node.
\item A leaf node is encoded as $(\texttt{MPT\_NODE\_LEAF}, k, \abs{v}, v)$, where $k$ is a 2-tuple as above, and $v$ is a leaf payload. \item A leaf node is encoded as $(\texttt{MPT\_NODE\_LEAF}, k, v)$, where $k$ is a 2-tuple as above, and $v$ is a pointer to a value.
\item A digest node is encoded as $(\texttt{MPT\_NODE\_DIGEST}, d)$, where $d$ is a Keccak256 digest. \item A digest node is encoded as $(\texttt{MPT\_NODE\_HASH}, d)$, where $d$ is a Keccak256 digest.
\end{enumerate} \end{enumerate}
\subsection{Prover input format} \subsection{Prover input format}
The initial state of each trie is given by the prover as a nondeterministic input tape. This tape has a similar format: The initial state of each trie is given by the prover as a nondeterministic input tape. This tape has a slightly different format:
\begin{enumerate} \begin{enumerate}
\item An empty node is encoded as $(\texttt{MPT\_NODE\_EMPTY})$. \item An empty node is encoded as $(\texttt{MPT\_NODE\_EMPTY})$.
\item A branch node is encoded as $(\texttt{MPT\_NODE\_BRANCH}, \abs{v}, v, c_1, \dots, c_{16})$, where $\abs{v}$ is the length of the value, and $v$ is the value itself. Each $c_i$ is the encoding of a child node. \item A branch node is encoded as $(\texttt{MPT\_NODE\_BRANCH}, v_?, c_1, \dots, c_{16})$. Here $v_?$ consists of a flag indicating whether a value is present,\todo{In the current implementation, we use a length prefix rather than a is-present prefix, but we plan to change that.} followed by the actual value payload if one is present. Each $c_i$ is the encoding of a child node.
\item An extension node is encoded as $(\texttt{MPT\_NODE\_EXTENSION}, k, c)$, $k$ represents the part of the key associated with this extension, and is encoded as a 2-tuple $(\texttt{packed\_nibbles}, \texttt{num\_nibbles})$. $c$ is a pointer to a child node. \item An extension node is encoded as $(\texttt{MPT\_NODE\_EXTENSION}, k, c)$, $k$ represents the part of the key associated with this extension, and is encoded as a 2-tuple $(\texttt{packed\_nibbles}, \texttt{num\_nibbles})$. $c$ is a pointer to a child node.
\item A leaf node is encoded as $(\texttt{MPT\_NODE\_LEAF}, k, \abs{v}, v)$, where $k$ is a 2-tuple as above, and $v$ is a leaf payload. \item A leaf node is encoded as $(\texttt{MPT\_NODE\_LEAF}, k, v)$, where $k$ is a 2-tuple as above, and $v$ is a value payload.
\item A digest node is encoded as $(\texttt{MPT\_NODE\_DIGEST}, d)$, where $d$ is a Keccak256 digest. \item A digest node is encoded as $(\texttt{MPT\_NODE\_HASH}, d)$, where $d$ is a Keccak256 digest.
\end{enumerate} \end{enumerate}
Nodes are thus given in depth-first order, leading to natural recursive methods for encoding and decoding this format. Nodes are thus given in depth-first order, enabling natural recursive methods for encoding and decoding this format.

Binary file not shown.

View File

@ -51,7 +51,7 @@
\input{introduction} \input{introduction}
\input{framework} \input{framework}
\input{tables} \input{tables}
\input{tries} \input{mpts}
\input{instructions} \input{instructions}
\bibliography{bibliography}{} \bibliography{bibliography}{}

View File

@ -6,6 +6,7 @@ use plonky2::hash::hash_types::RichField;
use plonky2::iop::ext_target::ExtensionTarget; use plonky2::iop::ext_target::ExtensionTarget;
use crate::arithmetic::columns::*; use crate::arithmetic::columns::*;
use crate::arithmetic::utils::read_value_u64_limbs;
use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer};
use crate::range_check_error; use crate::range_check_error;
@ -94,15 +95,12 @@ where
} }
pub fn generate<F: RichField>(lv: &mut [F; NUM_ARITH_COLUMNS]) { pub fn generate<F: RichField>(lv: &mut [F; NUM_ARITH_COLUMNS]) {
let input0_limbs = ADD_INPUT_0.map(|c| lv[c].to_canonical_u64()); let input0 = read_value_u64_limbs(lv, ADD_INPUT_0);
let input1_limbs = ADD_INPUT_1.map(|c| lv[c].to_canonical_u64()); let input1 = read_value_u64_limbs(lv, ADD_INPUT_1);
// Input and output have 16-bit limbs // Input and output have 16-bit limbs
let (output_limbs, _) = u256_add_cc(input0_limbs, input1_limbs); let (output_limbs, _) = u256_add_cc(input0, input1);
lv[ADD_OUTPUT].copy_from_slice(&output_limbs.map(|c| F::from_canonical_u64(c)));
for (&c, output_limb) in ADD_OUTPUT.iter().zip(output_limbs) {
lv[c] = F::from_canonical_u64(output_limb);
}
} }
pub fn eval_packed_generic<P: PackedField>( pub fn eval_packed_generic<P: PackedField>(
@ -114,15 +112,20 @@ pub fn eval_packed_generic<P: PackedField>(
range_check_error!(ADD_OUTPUT, 16); range_check_error!(ADD_OUTPUT, 16);
let is_add = lv[IS_ADD]; let is_add = lv[IS_ADD];
let input0_limbs = ADD_INPUT_0.iter().map(|&c| lv[c]); let input0_limbs = &lv[ADD_INPUT_0];
let input1_limbs = ADD_INPUT_1.iter().map(|&c| lv[c]); let input1_limbs = &lv[ADD_INPUT_1];
let output_limbs = ADD_OUTPUT.iter().map(|&c| lv[c]); let output_limbs = &lv[ADD_OUTPUT];
// This computed output is not yet reduced; i.e. some limbs may be // This computed output is not yet reduced; i.e. some limbs may be
// more than 16 bits. // more than 16 bits.
let output_computed = input0_limbs.zip(input1_limbs).map(|(a, b)| a + b); let output_computed = input0_limbs.iter().zip(input1_limbs).map(|(&a, &b)| a + b);
eval_packed_generic_are_equal(yield_constr, is_add, output_computed, output_limbs); eval_packed_generic_are_equal(
yield_constr,
is_add,
output_computed,
output_limbs.iter().copied(),
);
} }
#[allow(clippy::needless_collect)] #[allow(clippy::needless_collect)]
@ -132,17 +135,18 @@ pub fn eval_ext_circuit<F: RichField + Extendable<D>, const D: usize>(
yield_constr: &mut RecursiveConstraintConsumer<F, D>, yield_constr: &mut RecursiveConstraintConsumer<F, D>,
) { ) {
let is_add = lv[IS_ADD]; let is_add = lv[IS_ADD];
let input0_limbs = ADD_INPUT_0.iter().map(|&c| lv[c]); let input0_limbs = &lv[ADD_INPUT_0];
let input1_limbs = ADD_INPUT_1.iter().map(|&c| lv[c]); let input1_limbs = &lv[ADD_INPUT_1];
let output_limbs = ADD_OUTPUT.iter().map(|&c| lv[c]); let output_limbs = &lv[ADD_OUTPUT];
// Since `map` is lazy and the closure passed to it borrows // Since `map` is lazy and the closure passed to it borrows
// `builder`, we can't then borrow builder again below in the call // `builder`, we can't then borrow builder again below in the call
// to `eval_ext_circuit_are_equal`. The solution is to force // to `eval_ext_circuit_are_equal`. The solution is to force
// evaluation with `collect`. // evaluation with `collect`.
let output_computed = input0_limbs let output_computed = input0_limbs
.iter()
.zip(input1_limbs) .zip(input1_limbs)
.map(|(a, b)| builder.add_extension(a, b)) .map(|(&a, &b)| builder.add_extension(a, b))
.collect::<Vec<ExtensionTarget<D>>>(); .collect::<Vec<ExtensionTarget<D>>>();
eval_ext_circuit_are_equal( eval_ext_circuit_are_equal(
@ -150,7 +154,7 @@ pub fn eval_ext_circuit<F: RichField + Extendable<D>, const D: usize>(
yield_constr, yield_constr,
is_add, is_add,
output_computed.into_iter(), output_computed.into_iter(),
output_limbs, output_limbs.iter().copied(),
); );
} }
@ -203,7 +207,7 @@ mod tests {
for _ in 0..N_RND_TESTS { for _ in 0..N_RND_TESTS {
// set inputs to random values // set inputs to random values
for (&ai, bi) in ADD_INPUT_0.iter().zip(ADD_INPUT_1) { for (ai, bi) in ADD_INPUT_0.zip(ADD_INPUT_1) {
lv[ai] = F::from_canonical_u16(rng.gen()); lv[ai] = F::from_canonical_u16(rng.gen());
lv[bi] = F::from_canonical_u16(rng.gen()); lv[bi] = F::from_canonical_u16(rng.gen());
} }

View File

@ -1,5 +1,7 @@
//! Arithmetic unit //! Arithmetic unit
use std::ops::Range;
pub const LIMB_BITS: usize = 16; pub const LIMB_BITS: usize = 16;
const EVM_REGISTER_BITS: usize = 256; const EVM_REGISTER_BITS: usize = 256;
@ -44,57 +46,42 @@ pub(crate) const ALL_OPERATIONS: [usize; 16] = [
/// used by any arithmetic circuit, depending on which one is active /// used by any arithmetic circuit, depending on which one is active
/// this cycle. Can be increased as needed as other operations are /// this cycle. Can be increased as needed as other operations are
/// implemented. /// implemented.
const NUM_SHARED_COLS: usize = 144; // only need 64 for add, sub, and mul const NUM_SHARED_COLS: usize = 9 * N_LIMBS; // only need 64 for add, sub, and mul
const fn shared_col(i: usize) -> usize { const GENERAL_INPUT_0: Range<usize> = START_SHARED_COLS..START_SHARED_COLS + N_LIMBS;
assert!(i < NUM_SHARED_COLS); const GENERAL_INPUT_1: Range<usize> = GENERAL_INPUT_0.end..GENERAL_INPUT_0.end + N_LIMBS;
START_SHARED_COLS + i const GENERAL_INPUT_2: Range<usize> = GENERAL_INPUT_1.end..GENERAL_INPUT_1.end + N_LIMBS;
} const GENERAL_INPUT_3: Range<usize> = GENERAL_INPUT_2.end..GENERAL_INPUT_2.end + N_LIMBS;
const AUX_INPUT_0: Range<usize> = GENERAL_INPUT_3.end..GENERAL_INPUT_3.end + 2 * N_LIMBS;
const AUX_INPUT_1: Range<usize> = AUX_INPUT_0.end..AUX_INPUT_0.end + 2 * N_LIMBS;
const AUX_INPUT_2: Range<usize> = AUX_INPUT_1.end..AUX_INPUT_1.end + N_LIMBS;
const fn gen_input_cols<const N: usize>(start: usize) -> [usize; N] { pub(crate) const ADD_INPUT_0: Range<usize> = GENERAL_INPUT_0;
let mut cols = [0usize; N]; pub(crate) const ADD_INPUT_1: Range<usize> = GENERAL_INPUT_1;
let mut i = 0; pub(crate) const ADD_OUTPUT: Range<usize> = GENERAL_INPUT_2;
while i < N {
cols[i] = shared_col(start + i);
i += 1;
}
cols
}
const GENERAL_INPUT_0: [usize; N_LIMBS] = gen_input_cols::<N_LIMBS>(0); pub(crate) const SUB_INPUT_0: Range<usize> = GENERAL_INPUT_0;
const GENERAL_INPUT_1: [usize; N_LIMBS] = gen_input_cols::<N_LIMBS>(N_LIMBS); pub(crate) const SUB_INPUT_1: Range<usize> = GENERAL_INPUT_1;
const GENERAL_INPUT_2: [usize; N_LIMBS] = gen_input_cols::<N_LIMBS>(2 * N_LIMBS); pub(crate) const SUB_OUTPUT: Range<usize> = GENERAL_INPUT_2;
const GENERAL_INPUT_3: [usize; N_LIMBS] = gen_input_cols::<N_LIMBS>(3 * N_LIMBS);
const AUX_INPUT_0: [usize; 2 * N_LIMBS] = gen_input_cols::<{ 2 * N_LIMBS }>(4 * N_LIMBS);
const AUX_INPUT_1: [usize; 2 * N_LIMBS] = gen_input_cols::<{ 2 * N_LIMBS }>(6 * N_LIMBS);
const AUX_INPUT_2: [usize; N_LIMBS] = gen_input_cols::<N_LIMBS>(8 * N_LIMBS);
pub(crate) const ADD_INPUT_0: [usize; N_LIMBS] = GENERAL_INPUT_0; pub(crate) const MUL_INPUT_0: Range<usize> = GENERAL_INPUT_0;
pub(crate) const ADD_INPUT_1: [usize; N_LIMBS] = GENERAL_INPUT_1; pub(crate) const MUL_INPUT_1: Range<usize> = GENERAL_INPUT_1;
pub(crate) const ADD_OUTPUT: [usize; N_LIMBS] = GENERAL_INPUT_2; pub(crate) const MUL_OUTPUT: Range<usize> = GENERAL_INPUT_2;
pub(crate) const MUL_AUX_INPUT: Range<usize> = GENERAL_INPUT_3;
pub(crate) const SUB_INPUT_0: [usize; N_LIMBS] = GENERAL_INPUT_0; pub(crate) const CMP_INPUT_0: Range<usize> = GENERAL_INPUT_0;
pub(crate) const SUB_INPUT_1: [usize; N_LIMBS] = GENERAL_INPUT_1; pub(crate) const CMP_INPUT_1: Range<usize> = GENERAL_INPUT_1;
pub(crate) const SUB_OUTPUT: [usize; N_LIMBS] = GENERAL_INPUT_2; pub(crate) const CMP_OUTPUT: usize = GENERAL_INPUT_2.start;
pub(crate) const CMP_AUX_INPUT: Range<usize> = GENERAL_INPUT_3;
pub(crate) const MUL_INPUT_0: [usize; N_LIMBS] = GENERAL_INPUT_0; pub(crate) const MODULAR_INPUT_0: Range<usize> = GENERAL_INPUT_0;
pub(crate) const MUL_INPUT_1: [usize; N_LIMBS] = GENERAL_INPUT_1; pub(crate) const MODULAR_INPUT_1: Range<usize> = GENERAL_INPUT_1;
pub(crate) const MUL_OUTPUT: [usize; N_LIMBS] = GENERAL_INPUT_2; pub(crate) const MODULAR_MODULUS: Range<usize> = GENERAL_INPUT_2;
pub(crate) const MUL_AUX_INPUT: [usize; N_LIMBS] = GENERAL_INPUT_3; pub(crate) const MODULAR_OUTPUT: Range<usize> = GENERAL_INPUT_3;
pub(crate) const MODULAR_QUO_INPUT: Range<usize> = AUX_INPUT_0;
pub(crate) const CMP_INPUT_0: [usize; N_LIMBS] = GENERAL_INPUT_0; // NB: Last value is not used in AUX, it is used in MOD_IS_ZERO
pub(crate) const CMP_INPUT_1: [usize; N_LIMBS] = GENERAL_INPUT_1; pub(crate) const MODULAR_AUX_INPUT: Range<usize> = AUX_INPUT_1;
pub(crate) const CMP_OUTPUT: usize = GENERAL_INPUT_2[0]; pub(crate) const MODULAR_MOD_IS_ZERO: usize = AUX_INPUT_1.end - 1;
pub(crate) const CMP_AUX_INPUT: [usize; N_LIMBS] = GENERAL_INPUT_3; pub(crate) const MODULAR_OUT_AUX_RED: Range<usize> = AUX_INPUT_2;
pub(crate) const MODULAR_INPUT_0: [usize; N_LIMBS] = GENERAL_INPUT_0;
pub(crate) const MODULAR_INPUT_1: [usize; N_LIMBS] = GENERAL_INPUT_1;
pub(crate) const MODULAR_MODULUS: [usize; N_LIMBS] = GENERAL_INPUT_2;
pub(crate) const MODULAR_OUTPUT: [usize; N_LIMBS] = GENERAL_INPUT_3;
pub(crate) const MODULAR_QUO_INPUT: [usize; 2 * N_LIMBS] = AUX_INPUT_0;
// NB: Last value is not used in AUX, it is used in IS_ZERO
pub(crate) const MODULAR_AUX_INPUT: [usize; 2 * N_LIMBS] = AUX_INPUT_1;
pub(crate) const MODULAR_MOD_IS_ZERO: usize = AUX_INPUT_1[2 * N_LIMBS - 1];
pub(crate) const MODULAR_OUT_AUX_RED: [usize; N_LIMBS] = AUX_INPUT_2;
pub const NUM_ARITH_COLUMNS: usize = START_SHARED_COLS + NUM_SHARED_COLS; pub const NUM_ARITH_COLUMNS: usize = START_SHARED_COLS + NUM_SHARED_COLS;

View File

@ -22,12 +22,13 @@ use plonky2::iop::ext_target::ExtensionTarget;
use crate::arithmetic::add::{eval_ext_circuit_are_equal, eval_packed_generic_are_equal}; use crate::arithmetic::add::{eval_ext_circuit_are_equal, eval_packed_generic_are_equal};
use crate::arithmetic::columns::*; use crate::arithmetic::columns::*;
use crate::arithmetic::sub::u256_sub_br; use crate::arithmetic::sub::u256_sub_br;
use crate::arithmetic::utils::read_value_u64_limbs;
use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer};
use crate::range_check_error; use crate::range_check_error;
pub(crate) fn generate<F: RichField>(lv: &mut [F; NUM_ARITH_COLUMNS], op: usize) { pub(crate) fn generate<F: RichField>(lv: &mut [F; NUM_ARITH_COLUMNS], op: usize) {
let input0 = CMP_INPUT_0.map(|c| lv[c].to_canonical_u64()); let input0 = read_value_u64_limbs(lv, CMP_INPUT_0);
let input1 = CMP_INPUT_1.map(|c| lv[c].to_canonical_u64()); let input1 = read_value_u64_limbs(lv, CMP_INPUT_1);
let (diff, br) = match op { let (diff, br) = match op {
// input0 - input1 == diff + br*2^256 // input0 - input1 == diff + br*2^256
@ -39,9 +40,7 @@ pub(crate) fn generate<F: RichField>(lv: &mut [F; NUM_ARITH_COLUMNS], op: usize)
_ => panic!("op code not a comparison"), _ => panic!("op code not a comparison"),
}; };
for (&c, diff_limb) in CMP_AUX_INPUT.iter().zip(diff) { lv[CMP_AUX_INPUT].copy_from_slice(&diff.map(|c| F::from_canonical_u64(c)));
lv[c] = F::from_canonical_u64(diff_limb);
}
lv[CMP_OUTPUT] = F::from_canonical_u64(br); lv[CMP_OUTPUT] = F::from_canonical_u64(br);
} }
@ -56,15 +55,17 @@ fn eval_packed_generic_check_is_one_bit<P: PackedField>(
pub(crate) fn eval_packed_generic_lt<P: PackedField>( pub(crate) fn eval_packed_generic_lt<P: PackedField>(
yield_constr: &mut ConstraintConsumer<P>, yield_constr: &mut ConstraintConsumer<P>,
is_op: P, is_op: P,
input0: [P; N_LIMBS], input0: &[P],
input1: [P; N_LIMBS], input1: &[P],
aux: [P; N_LIMBS], aux: &[P],
output: P, output: P,
) { ) {
debug_assert!(input0.len() == N_LIMBS && input1.len() == N_LIMBS && aux.len() == N_LIMBS);
// Verify (input0 < input1) == output by providing aux such that // Verify (input0 < input1) == output by providing aux such that
// input0 - input1 == aux + output*2^256. // input0 - input1 == aux + output*2^256.
let lhs_limbs = input0.iter().zip(input1).map(|(&a, b)| a - b); let lhs_limbs = input0.iter().zip(input1).map(|(&a, &b)| a - b);
let cy = eval_packed_generic_are_equal(yield_constr, is_op, aux.into_iter(), lhs_limbs); let cy = eval_packed_generic_are_equal(yield_constr, is_op, aux.iter().copied(), lhs_limbs);
// We don't need to check that cy is 0 or 1, since output has // We don't need to check that cy is 0 or 1, since output has
// already been checked to be 0 or 1. // already been checked to be 0 or 1.
yield_constr.constraint(is_op * (cy - output)); yield_constr.constraint(is_op * (cy - output));
@ -81,9 +82,9 @@ pub fn eval_packed_generic<P: PackedField>(
let is_lt = lv[IS_LT]; let is_lt = lv[IS_LT];
let is_gt = lv[IS_GT]; let is_gt = lv[IS_GT];
let input0 = CMP_INPUT_0.map(|c| lv[c]); let input0 = &lv[CMP_INPUT_0];
let input1 = CMP_INPUT_1.map(|c| lv[c]); let input1 = &lv[CMP_INPUT_1];
let aux = CMP_AUX_INPUT.map(|c| lv[c]); let aux = &lv[CMP_AUX_INPUT];
let output = lv[CMP_OUTPUT]; let output = lv[CMP_OUTPUT];
let is_cmp = is_lt + is_gt; let is_cmp = is_lt + is_gt;
@ -109,11 +110,13 @@ pub(crate) fn eval_ext_circuit_lt<F: RichField + Extendable<D>, const D: usize>(
builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder<F, D>, builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder<F, D>,
yield_constr: &mut RecursiveConstraintConsumer<F, D>, yield_constr: &mut RecursiveConstraintConsumer<F, D>,
is_op: ExtensionTarget<D>, is_op: ExtensionTarget<D>,
input0: [ExtensionTarget<D>; N_LIMBS], input0: &[ExtensionTarget<D>],
input1: [ExtensionTarget<D>; N_LIMBS], input1: &[ExtensionTarget<D>],
aux: [ExtensionTarget<D>; N_LIMBS], aux: &[ExtensionTarget<D>],
output: ExtensionTarget<D>, output: ExtensionTarget<D>,
) { ) {
debug_assert!(input0.len() == N_LIMBS && input1.len() == N_LIMBS && aux.len() == N_LIMBS);
// Since `map` is lazy and the closure passed to it borrows // Since `map` is lazy and the closure passed to it borrows
// `builder`, we can't then borrow builder again below in the call // `builder`, we can't then borrow builder again below in the call
// to `eval_ext_circuit_are_equal`. The solution is to force // to `eval_ext_circuit_are_equal`. The solution is to force
@ -121,14 +124,14 @@ pub(crate) fn eval_ext_circuit_lt<F: RichField + Extendable<D>, const D: usize>(
let lhs_limbs = input0 let lhs_limbs = input0
.iter() .iter()
.zip(input1) .zip(input1)
.map(|(&a, b)| builder.sub_extension(a, b)) .map(|(&a, &b)| builder.sub_extension(a, b))
.collect::<Vec<ExtensionTarget<D>>>(); .collect::<Vec<ExtensionTarget<D>>>();
let cy = eval_ext_circuit_are_equal( let cy = eval_ext_circuit_are_equal(
builder, builder,
yield_constr, yield_constr,
is_op, is_op,
aux.into_iter(), aux.iter().copied(),
lhs_limbs.into_iter(), lhs_limbs.into_iter(),
); );
let good_output = builder.sub_extension(cy, output); let good_output = builder.sub_extension(cy, output);
@ -144,9 +147,9 @@ pub fn eval_ext_circuit<F: RichField + Extendable<D>, const D: usize>(
let is_lt = lv[IS_LT]; let is_lt = lv[IS_LT];
let is_gt = lv[IS_GT]; let is_gt = lv[IS_GT];
let input0 = CMP_INPUT_0.map(|c| lv[c]); let input0 = &lv[CMP_INPUT_0];
let input1 = CMP_INPUT_1.map(|c| lv[c]); let input1 = &lv[CMP_INPUT_1];
let aux = CMP_AUX_INPUT.map(|c| lv[c]); let aux = &lv[CMP_AUX_INPUT];
let output = lv[CMP_OUTPUT]; let output = lv[CMP_OUTPUT];
let is_cmp = builder.add_extension(is_lt, is_gt); let is_cmp = builder.add_extension(is_lt, is_gt);
@ -210,7 +213,7 @@ mod tests {
lv[other_op] = F::ZERO; lv[other_op] = F::ZERO;
// set inputs to random values // set inputs to random values
for (&ai, bi) in CMP_INPUT_0.iter().zip(CMP_INPUT_1) { for (ai, bi) in CMP_INPUT_0.zip(CMP_INPUT_1) {
lv[ai] = F::from_canonical_u16(rng.gen()); lv[ai] = F::from_canonical_u16(rng.gen());
lv[bi] = F::from_canonical_u16(rng.gen()); lv[bi] = F::from_canonical_u16(rng.gen());
} }

View File

@ -18,12 +18,13 @@
//! a(x) = \sum_{i=0}^15 a[i] x^i //! a(x) = \sum_{i=0}^15 a[i] x^i
//! //!
//! (so A = a(β)) and similarly for b(x), m(x) and c(x). Then //! (so A = a(β)) and similarly for b(x), m(x) and c(x). Then
//! operation(A,B) = C (mod M) if and only if the polynomial //! operation(A,B) = C (mod M) if and only if there exists q such that
//! the polynomial
//! //!
//! operation(a(x), b(x)) - c(x) - m(x) * q(x) //! operation(a(x), b(x)) - c(x) - m(x) * q(x)
//! //!
//! is zero when evaluated at x = β, i.e. it is divisible by (x - β). //! is zero when evaluated at x = β, i.e. it is divisible by (x - β);
//! Thus exists a polynomial s such that //! equivalently, there exists a polynomial s such that
//! //!
//! operation(a(x), b(x)) - c(x) - m(x) * q(x) - (x - β) * s(x) == 0 //! operation(a(x), b(x)) - c(x) - m(x) * q(x) - (x - β) * s(x) == 0
//! //!
@ -34,12 +35,12 @@
//! coefficients must be zero. The variable names of the constituent //! coefficients must be zero. The variable names of the constituent
//! polynomials are (writing N for N_LIMBS=16): //! polynomials are (writing N for N_LIMBS=16):
//! //!
//! a(x) = \sum_{i=0}^{N-1} input0[i] * β^i //! a(x) = \sum_{i=0}^{N-1} input0[i] * x^i
//! b(x) = \sum_{i=0}^{N-1} input1[i] * β^i //! b(x) = \sum_{i=0}^{N-1} input1[i] * x^i
//! c(x) = \sum_{i=0}^{N-1} output[i] * β^i //! c(x) = \sum_{i=0}^{N-1} output[i] * x^i
//! m(x) = \sum_{i=0}^{N-1} modulus[i] * β^i //! m(x) = \sum_{i=0}^{N-1} modulus[i] * x^i
//! q(x) = \sum_{i=0}^{2N-1} quot[i] * β^i //! q(x) = \sum_{i=0}^{2N-1} quot[i] * x^i
//! s(x) = \sum_i^{2N-2} aux[i] * β^i //! s(x) = \sum_i^{2N-2} aux[i] * x^i
//! //!
//! Because A, B, M and C are 256-bit numbers, the degrees of a, b, m //! Because A, B, M and C are 256-bit numbers, the degrees of a, b, m
//! and c are (at most) N-1 = 15. If m = 1, then Q would be A*B which //! and c are (at most) N-1 = 15. If m = 1, then Q would be A*B which
@ -159,9 +160,9 @@ fn generate_modular_op<F: RichField>(
) { ) {
// Inputs are all range-checked in [0, 2^16), so the "as i64" // Inputs are all range-checked in [0, 2^16), so the "as i64"
// conversion is safe. // conversion is safe.
let input0_limbs = MODULAR_INPUT_0.map(|c| F::to_canonical_u64(&lv[c]) as i64); let input0_limbs = read_value_i64_limbs(lv, MODULAR_INPUT_0);
let input1_limbs = MODULAR_INPUT_1.map(|c| F::to_canonical_u64(&lv[c]) as i64); let input1_limbs = read_value_i64_limbs(lv, MODULAR_INPUT_1);
let mut modulus_limbs = MODULAR_MODULUS.map(|c| F::to_canonical_u64(&lv[c]) as i64); let mut modulus_limbs = read_value_i64_limbs(lv, MODULAR_MODULUS);
// The use of BigUints is just to avoid having to implement // The use of BigUints is just to avoid having to implement
// modular reduction. // modular reduction.
@ -174,12 +175,11 @@ fn generate_modular_op<F: RichField>(
let mut constr_poly = [0i64; 2 * N_LIMBS]; let mut constr_poly = [0i64; 2 * N_LIMBS];
constr_poly[..2 * N_LIMBS - 1].copy_from_slice(&operation(input0_limbs, input1_limbs)); constr_poly[..2 * N_LIMBS - 1].copy_from_slice(&operation(input0_limbs, input1_limbs));
let mut mod_is_zero = F::ZERO;
if modulus.is_zero() { if modulus.is_zero() {
modulus += 1u32; modulus += 1u32;
modulus_limbs[0] += 1i64; modulus_limbs[0] += 1i64;
lv[MODULAR_MOD_IS_ZERO] = F::ONE; mod_is_zero = F::ONE;
} else {
lv[MODULAR_MOD_IS_ZERO] = F::ZERO;
} }
let input = columns_to_biguint(&constr_poly); let input = columns_to_biguint(&constr_poly);
@ -211,21 +211,13 @@ fn generate_modular_op<F: RichField>(
// constr_poly must be zero when evaluated at x = β := // constr_poly must be zero when evaluated at x = β :=
// 2^LIMB_BITS, hence it's divisible by (x - β). `aux_limbs` is // 2^LIMB_BITS, hence it's divisible by (x - β). `aux_limbs` is
// the result of removing that root. // the result of removing that root.
let aux_limbs = pol_remove_root_2exp::<LIMB_BITS, _>(constr_poly); let aux_limbs = pol_remove_root_2exp::<LIMB_BITS, _, { 2 * N_LIMBS }>(constr_poly);
for deg in 0..N_LIMBS { lv[MODULAR_OUTPUT].copy_from_slice(&output_limbs.map(|c| F::from_canonical_i64(c)));
lv[MODULAR_OUTPUT[deg]] = F::from_canonical_i64(output_limbs[deg]); lv[MODULAR_OUT_AUX_RED].copy_from_slice(&out_aux_red.map(|c| F::from_canonical_i64(c)));
lv[MODULAR_OUT_AUX_RED[deg]] = F::from_canonical_i64(out_aux_red[deg]); lv[MODULAR_QUO_INPUT].copy_from_slice(&quot_limbs.map(|c| F::from_canonical_i64(c)));
lv[MODULAR_QUO_INPUT[deg]] = F::from_canonical_i64(quot_limbs[deg]); lv[MODULAR_AUX_INPUT].copy_from_slice(&aux_limbs.map(|c| F::from_noncanonical_i64(c)));
lv[MODULAR_QUO_INPUT[deg + N_LIMBS]] = F::from_canonical_i64(quot_limbs[deg + N_LIMBS]); lv[MODULAR_MOD_IS_ZERO] = mod_is_zero;
lv[MODULAR_AUX_INPUT[deg]] = F::from_noncanonical_i64(aux_limbs[deg]);
// Don't overwrite MODULAR_MOD_IS_ZERO, which is at the last
// index of MODULAR_AUX_INPUT
if deg < N_LIMBS - 1 {
lv[MODULAR_AUX_INPUT[deg + N_LIMBS]] =
F::from_noncanonical_i64(aux_limbs[deg + N_LIMBS]);
}
}
} }
/// Generate the output and auxiliary values for modular operations. /// Generate the output and auxiliary values for modular operations.
@ -261,7 +253,7 @@ fn modular_constr_poly<P: PackedField>(
range_check_error!(MODULAR_AUX_INPUT, 20, signed); range_check_error!(MODULAR_AUX_INPUT, 20, signed);
range_check_error!(MODULAR_OUTPUT, 16); range_check_error!(MODULAR_OUTPUT, 16);
let mut modulus = MODULAR_MODULUS.map(|c| lv[c]); let mut modulus = read_value::<N_LIMBS, _>(lv, MODULAR_MODULUS);
let mod_is_zero = lv[MODULAR_MOD_IS_ZERO]; let mod_is_zero = lv[MODULAR_MOD_IS_ZERO];
// Check that mod_is_zero is zero or one // Check that mod_is_zero is zero or one
@ -276,22 +268,22 @@ fn modular_constr_poly<P: PackedField>(
// modulus = 0. // modulus = 0.
modulus[0] += mod_is_zero; modulus[0] += mod_is_zero;
let output = MODULAR_OUTPUT.map(|c| lv[c]); let output = &lv[MODULAR_OUTPUT];
// Verify that the output is reduced, i.e. output < modulus. // Verify that the output is reduced, i.e. output < modulus.
let out_aux_red = MODULAR_OUT_AUX_RED.map(|c| lv[c]); let out_aux_red = &lv[MODULAR_OUT_AUX_RED];
let is_less_than = P::ONES; let is_less_than = P::ONES;
eval_packed_generic_lt( eval_packed_generic_lt(
yield_constr, yield_constr,
filter, filter,
output, output,
modulus, &modulus,
out_aux_red, out_aux_red,
is_less_than, is_less_than,
); );
// prod = q(x) * m(x) // prod = q(x) * m(x)
let quot = MODULAR_QUO_INPUT.map(|c| lv[c]); let quot = read_value::<{ 2 * N_LIMBS }, _>(lv, MODULAR_QUO_INPUT);
let prod = pol_mul_wide2(quot, modulus); let prod = pol_mul_wide2(quot, modulus);
// higher order terms must be zero // higher order terms must be zero
for &x in prod[2 * N_LIMBS..].iter() { for &x in prod[2 * N_LIMBS..].iter() {
@ -300,10 +292,11 @@ fn modular_constr_poly<P: PackedField>(
// constr_poly = c(x) + q(x) * m(x) // constr_poly = c(x) + q(x) * m(x)
let mut constr_poly: [_; 2 * N_LIMBS] = prod[0..2 * N_LIMBS].try_into().unwrap(); let mut constr_poly: [_; 2 * N_LIMBS] = prod[0..2 * N_LIMBS].try_into().unwrap();
pol_add_assign(&mut constr_poly, &output); pol_add_assign(&mut constr_poly, output);
// constr_poly = c(x) + q(x) * m(x) + (x - β) * s(x) // constr_poly = c(x) + q(x) * m(x) + (x - β) * s(x)
let aux = MODULAR_AUX_INPUT.map(|c| lv[c]); let mut aux = read_value::<{ 2 * N_LIMBS }, _>(lv, MODULAR_AUX_INPUT);
aux[2 * N_LIMBS - 1] = P::ZEROS; // zero out the MOD_IS_ZERO flag
let base = P::Scalar::from_canonical_u64(1 << LIMB_BITS); let base = P::Scalar::from_canonical_u64(1 << LIMB_BITS);
pol_add_assign(&mut constr_poly, &pol_adjoin_root(aux, base)); pol_add_assign(&mut constr_poly, &pol_adjoin_root(aux, base));
@ -322,8 +315,8 @@ pub(crate) fn eval_packed_generic<P: PackedField>(
// constr_poly has 2*N_LIMBS limbs // constr_poly has 2*N_LIMBS limbs
let constr_poly = modular_constr_poly(lv, yield_constr, filter); let constr_poly = modular_constr_poly(lv, yield_constr, filter);
let input0 = MODULAR_INPUT_0.map(|c| lv[c]); let input0 = read_value(lv, MODULAR_INPUT_0);
let input1 = MODULAR_INPUT_1.map(|c| lv[c]); let input1 = read_value(lv, MODULAR_INPUT_1);
let add_input = pol_add(input0, input1); let add_input = pol_add(input0, input1);
let mul_input = pol_mul_wide(input0, input1); let mul_input = pol_mul_wide(input0, input1);
@ -360,7 +353,7 @@ fn modular_constr_poly_ext_circuit<F: RichField + Extendable<D>, const D: usize>
yield_constr: &mut RecursiveConstraintConsumer<F, D>, yield_constr: &mut RecursiveConstraintConsumer<F, D>,
filter: ExtensionTarget<D>, filter: ExtensionTarget<D>,
) -> [ExtensionTarget<D>; 2 * N_LIMBS] { ) -> [ExtensionTarget<D>; 2 * N_LIMBS] {
let mut modulus = MODULAR_MODULUS.map(|c| lv[c]); let mut modulus = read_value::<N_LIMBS, _>(lv, MODULAR_MODULUS);
let mod_is_zero = lv[MODULAR_MOD_IS_ZERO]; let mod_is_zero = lv[MODULAR_MOD_IS_ZERO];
let t = builder.mul_sub_extension(mod_is_zero, mod_is_zero, mod_is_zero); let t = builder.mul_sub_extension(mod_is_zero, mod_is_zero, mod_is_zero);
@ -374,20 +367,20 @@ fn modular_constr_poly_ext_circuit<F: RichField + Extendable<D>, const D: usize>
modulus[0] = builder.add_extension(modulus[0], mod_is_zero); modulus[0] = builder.add_extension(modulus[0], mod_is_zero);
let output = MODULAR_OUTPUT.map(|c| lv[c]); let output = &lv[MODULAR_OUTPUT];
let out_aux_red = MODULAR_OUT_AUX_RED.map(|c| lv[c]); let out_aux_red = &lv[MODULAR_OUT_AUX_RED];
let is_less_than = builder.one_extension(); let is_less_than = builder.one_extension();
eval_ext_circuit_lt( eval_ext_circuit_lt(
builder, builder,
yield_constr, yield_constr,
filter, filter,
output, output,
modulus, &modulus,
out_aux_red, out_aux_red,
is_less_than, is_less_than,
); );
let quot = MODULAR_QUO_INPUT.map(|c| lv[c]); let quot = read_value::<{ 2 * N_LIMBS }, _>(lv, MODULAR_QUO_INPUT);
let prod = pol_mul_wide2_ext_circuit(builder, quot, modulus); let prod = pol_mul_wide2_ext_circuit(builder, quot, modulus);
for &x in prod[2 * N_LIMBS..].iter() { for &x in prod[2 * N_LIMBS..].iter() {
let t = builder.mul_extension(filter, x); let t = builder.mul_extension(filter, x);
@ -395,9 +388,10 @@ fn modular_constr_poly_ext_circuit<F: RichField + Extendable<D>, const D: usize>
} }
let mut constr_poly: [_; 2 * N_LIMBS] = prod[0..2 * N_LIMBS].try_into().unwrap(); let mut constr_poly: [_; 2 * N_LIMBS] = prod[0..2 * N_LIMBS].try_into().unwrap();
pol_add_assign_ext_circuit(builder, &mut constr_poly, &output); pol_add_assign_ext_circuit(builder, &mut constr_poly, output);
let aux = MODULAR_AUX_INPUT.map(|c| lv[c]); let mut aux = read_value::<{ 2 * N_LIMBS }, _>(lv, MODULAR_AUX_INPUT);
aux[2 * N_LIMBS - 1] = builder.zero_extension();
let base = builder.constant_extension(F::Extension::from_canonical_u64(1u64 << LIMB_BITS)); let base = builder.constant_extension(F::Extension::from_canonical_u64(1u64 << LIMB_BITS));
let t = pol_adjoin_root_ext_circuit(builder, aux, base); let t = pol_adjoin_root_ext_circuit(builder, aux, base);
pol_add_assign_ext_circuit(builder, &mut constr_poly, &t); pol_add_assign_ext_circuit(builder, &mut constr_poly, &t);
@ -418,8 +412,8 @@ pub(crate) fn eval_ext_circuit<F: RichField + Extendable<D>, const D: usize>(
let constr_poly = modular_constr_poly_ext_circuit(lv, builder, yield_constr, filter); let constr_poly = modular_constr_poly_ext_circuit(lv, builder, yield_constr, filter);
let input0 = MODULAR_INPUT_0.map(|c| lv[c]); let input0 = read_value(lv, MODULAR_INPUT_0);
let input1 = MODULAR_INPUT_1.map(|c| lv[c]); let input1 = read_value(lv, MODULAR_INPUT_1);
let add_input = pol_add_ext_circuit(builder, input0, input1); let add_input = pol_add_ext_circuit(builder, input0, input1);
let mul_input = pol_mul_wide_ext_circuit(builder, input0, input1); let mul_input = pol_mul_wide_ext_circuit(builder, input0, input1);
@ -495,11 +489,7 @@ mod tests {
for i in 0..N_RND_TESTS { for i in 0..N_RND_TESTS {
// set inputs to random values // set inputs to random values
for (&ai, &bi, &mi) in izip!( for (ai, bi, mi) in izip!(MODULAR_INPUT_0, MODULAR_INPUT_1, MODULAR_MODULUS) {
MODULAR_INPUT_0.iter(),
MODULAR_INPUT_1.iter(),
MODULAR_MODULUS.iter()
) {
lv[ai] = F::from_canonical_u16(rng.gen()); lv[ai] = F::from_canonical_u16(rng.gen());
lv[bi] = F::from_canonical_u16(rng.gen()); lv[bi] = F::from_canonical_u16(rng.gen());
lv[mi] = F::from_canonical_u16(rng.gen()); lv[mi] = F::from_canonical_u16(rng.gen());
@ -511,7 +501,7 @@ mod tests {
if i > N_RND_TESTS / 2 { if i > N_RND_TESTS / 2 {
// 1 <= start < N_LIMBS // 1 <= start < N_LIMBS
let start = (rng.gen::<usize>() % (N_LIMBS - 1)) + 1; let start = (rng.gen::<usize>() % (N_LIMBS - 1)) + 1;
for &mi in &MODULAR_MODULUS[start..N_LIMBS] { for mi in MODULAR_MODULUS.skip(start) {
lv[mi] = F::ZERO; lv[mi] = F::ZERO;
} }
} }
@ -549,11 +539,7 @@ mod tests {
for _i in 0..N_RND_TESTS { for _i in 0..N_RND_TESTS {
// set inputs to random values and the modulus to zero; // set inputs to random values and the modulus to zero;
// the output is defined to be zero when modulus is zero. // the output is defined to be zero when modulus is zero.
for (&ai, &bi, &mi) in izip!( for (ai, bi, mi) in izip!(MODULAR_INPUT_0, MODULAR_INPUT_1, MODULAR_MODULUS) {
MODULAR_INPUT_0.iter(),
MODULAR_INPUT_1.iter(),
MODULAR_MODULUS.iter()
) {
lv[ai] = F::from_canonical_u16(rng.gen()); lv[ai] = F::from_canonical_u16(rng.gen());
lv[bi] = F::from_canonical_u16(rng.gen()); lv[bi] = F::from_canonical_u16(rng.gen());
lv[mi] = F::ZERO; lv[mi] = F::ZERO;
@ -562,7 +548,7 @@ mod tests {
generate(&mut lv, op_filter); generate(&mut lv, op_filter);
// check that the correct output was generated // check that the correct output was generated
assert!(MODULAR_OUTPUT.iter().all(|&oi| lv[oi] == F::ZERO)); assert!(lv[MODULAR_OUTPUT].iter().all(|&c| c == F::ZERO));
let mut constraint_consumer = ConstraintConsumer::new( let mut constraint_consumer = ConstraintConsumer::new(
vec![GoldilocksField(2), GoldilocksField(3), GoldilocksField(5)], vec![GoldilocksField(2), GoldilocksField(3), GoldilocksField(5)],
@ -577,7 +563,7 @@ mod tests {
.all(|&acc| acc == F::ZERO)); .all(|&acc| acc == F::ZERO));
// Corrupt one output limb by setting it to a non-zero value // Corrupt one output limb by setting it to a non-zero value
let random_oi = MODULAR_OUTPUT[rng.gen::<usize>() % N_LIMBS]; let random_oi = MODULAR_OUTPUT.start + rng.gen::<usize>() % N_LIMBS;
lv[random_oi] = F::from_canonical_u16(rng.gen_range(1..u16::MAX)); lv[random_oi] = F::from_canonical_u16(rng.gen_range(1..u16::MAX));
eval_packed_generic(&lv, &mut constraint_consumer); eval_packed_generic(&lv, &mut constraint_consumer);

View File

@ -3,30 +3,57 @@
//! This crate verifies an EVM MUL instruction, which takes two //! This crate verifies an EVM MUL instruction, which takes two
//! 256-bit inputs A and B, and produces a 256-bit output C satisfying //! 256-bit inputs A and B, and produces a 256-bit output C satisfying
//! //!
//! C = A*B (mod 2^256). //! C = A*B (mod 2^256),
//! //!
//! Inputs A and B, and output C, are given as arrays of 16-bit //! i.e. C is the lower half of the usual long multiplication
//! A*B. Inputs A and B, and output C, are given as arrays of 16-bit
//! limbs. For example, if the limbs of A are a[0]...a[15], then //! limbs. For example, if the limbs of A are a[0]...a[15], then
//! //!
//! A = \sum_{i=0}^15 a[i] β^i, //! A = \sum_{i=0}^15 a[i] β^i,
//! //!
//! where β = 2^16. To verify that A, B and C satisfy the equation we //! where β = 2^16 = 2^LIMB_BITS. To verify that A, B and C satisfy
//! proceed as follows. Define a(x) = \sum_{i=0}^15 a[i] x^i (so A = a(β)) //! the equation we proceed as follows. Define
//! and similarly for b(x) and c(x). Then A*B = C (mod 2^256) if and only
//! if there exist polynomials q and m such that
//! //!
//! a(x)*b(x) - c(x) - m(x)*x^16 - (β - x)*q(x) == 0. //! a(x) = \sum_{i=0}^15 a[i] x^i
//!
//! (so A = a(β)) and similarly for b(x) and c(x). Then A*B = C (mod
//! 2^256) if and only if there exists q such that the polynomial
//!
//! a(x) * b(x) - c(x) - x^16 * q(x)
//!
//! is zero when evaluated at x = β, i.e. it is divisible by (x - β);
//! equivalently, there exists a polynomial s (representing the
//! carries from the long multiplication) such that
//!
//! a(x) * b(x) - c(x) - x^16 * q(x) - (x - β) * s(x) == 0
//!
//! As we only need the lower half of the product, we can omit q(x)
//! since it is multiplied by the modulus β^16 = 2^256. Thus we only
//! need to verify
//!
//! a(x) * b(x) - c(x) - (x - β) * s(x) == 0
//!
//! In the code below, this "constraint polynomial" is constructed in
//! the variable `constr_poly`. It must be identically zero for the
//! multiplication operation to be verified, or, equivalently, each of
//! its coefficients must be zero. The variable names of the
//! constituent polynomials are (writing N for N_LIMBS=16):
//!
//! a(x) = \sum_{i=0}^{N-1} input0[i] * x^i
//! b(x) = \sum_{i=0}^{N-1} input1[i] * x^i
//! c(x) = \sum_{i=0}^{N-1} output[i] * x^i
//! s(x) = \sum_i^{2N-3} aux[i] * x^i
//! //!
//! Because A, B and C are 256-bit numbers, the degrees of a, b and c //! Because A, B and C are 256-bit numbers, the degrees of a, b and c
//! are (at most) 15. Thus deg(a*b) <= 30, so deg(m) <= 14 and deg(q) //! are (at most) 15. Thus deg(a*b) <= 30 and deg(s) <= 29; however,
//! <= 29. However, the fact that we're verifying the equality modulo //! as we're only verifying the lower half of A*B, we only need to
//! 2^256 means that we can ignore terms of degree >= 16, since for //! know s(x) up to degree 14 (so that (x - β)*s(x) has degree 15). On
//! them evaluating at β gives a factor of β^16 = 2^256 which is 0. //! the other hand, the coefficients of s(x) can be as large as
//! 16*(β-2) or 20 bits.
//! //!
//! Hence, to verify the equality, we don't need m(x) at all, and we //! Note that, unlike for the general modular multiplication (see the
//! only need to know q(x) up to degree 14 (so that (β - x)*q(x) has //! file `modular.rs`), we don't need to check that output is reduced,
//! degree 15). On the other hand, the coefficients of q(x) can be as //! since any value of output is less than β^16 and is hence reduced.
//! large as 16*(β-2) or 20 bits.
use plonky2::field::extension::Extendable; use plonky2::field::extension::Extendable;
use plonky2::field::packed::PackedField; use plonky2::field::packed::PackedField;
@ -35,65 +62,41 @@ use plonky2::hash::hash_types::RichField;
use plonky2::iop::ext_target::ExtensionTarget; use plonky2::iop::ext_target::ExtensionTarget;
use crate::arithmetic::columns::*; use crate::arithmetic::columns::*;
use crate::arithmetic::utils::{pol_mul_lo, pol_sub_assign}; use crate::arithmetic::utils::*;
use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer};
use crate::range_check_error; use crate::range_check_error;
pub fn generate<F: RichField>(lv: &mut [F; NUM_ARITH_COLUMNS]) { pub fn generate<F: RichField>(lv: &mut [F; NUM_ARITH_COLUMNS]) {
let input0_limbs = MUL_INPUT_0.map(|c| lv[c].to_canonical_u64()); let input0 = read_value_i64_limbs(lv, MUL_INPUT_0);
let input1_limbs = MUL_INPUT_1.map(|c| lv[c].to_canonical_u64()); let input1 = read_value_i64_limbs(lv, MUL_INPUT_1);
const MASK: u64 = (1u64 << LIMB_BITS) - 1u64; const MASK: i64 = (1i64 << LIMB_BITS) - 1i64;
// Input and output have 16-bit limbs // Input and output have 16-bit limbs
let mut aux_in_limbs = [0u64; N_LIMBS]; let mut output_limbs = [0i64; N_LIMBS];
let mut output_limbs = [0u64; N_LIMBS];
// Column-wise pen-and-paper long multiplication on 16-bit limbs. // Column-wise pen-and-paper long multiplication on 16-bit limbs.
// First calculate the coefficients of a(x)*b(x) (in unreduced_prod), // First calculate the coefficients of a(x)*b(x) (in unreduced_prod),
// then do carry propagation to obtain C = c(β) = a(β)*b(β). // then do carry propagation to obtain C = c(β) = a(β)*b(β).
let mut cy = 0u64; let mut cy = 0i64;
let mut unreduced_prod = pol_mul_lo(input0_limbs, input1_limbs); let mut unreduced_prod = pol_mul_lo(input0, input1);
for col in 0..N_LIMBS { for col in 0..N_LIMBS {
let t = unreduced_prod[col] + cy; let t = unreduced_prod[col] + cy;
cy = t >> LIMB_BITS; cy = t >> LIMB_BITS;
output_limbs[col] = t & MASK; output_limbs[col] = t & MASK;
} }
// In principle, the last cy could be dropped because this is // In principle, the last cy could be dropped because this is
// multiplication modulo 2^256. However, we need it below for // multiplication modulo 2^256. However, we need it below for
// aux_in_limbs to handle the fact that unreduced_prod will // aux_limbs to handle the fact that unreduced_prod will
// inevitably contain a one digit's worth that is > 2^256. // inevitably contain one digit's worth that is > 2^256.
for (&c, output_limb) in MUL_OUTPUT.iter().zip(output_limbs) { lv[MUL_OUTPUT].copy_from_slice(&output_limbs.map(|c| F::from_canonical_i64(c)));
lv[c] = F::from_canonical_u64(output_limb);
}
pol_sub_assign(&mut unreduced_prod, &output_limbs); pol_sub_assign(&mut unreduced_prod, &output_limbs);
// unreduced_prod is the coefficients of the polynomial a(x)*b(x) - c(x). let mut aux_limbs = pol_remove_root_2exp::<LIMB_BITS, _, N_LIMBS>(unreduced_prod);
// This must be zero when evaluated at x = β = 2^LIMB_BITS, hence it's aux_limbs[N_LIMBS - 1] = -cy;
// divisible by (β - x). If we write unreduced_prod as
//
// a(x)*b(x) - c(x) = \sum_{i=0}^n p_i x^i + terms of degree > n
// = (β - x) \sum_{i=0}^{n-1} q_i x^i + terms of degree > n
//
// then by comparing coefficients it is easy to see that
//
// q_0 = p_0 / β and q_i = (p_i + q_{i-1}) / β
//
// for 0 < i < n-1 (and the divisions are exact). Because we're
// only calculating the result modulo 2^256, we can ignore the
// terms of degree > n = 15.
aux_in_limbs[0] = unreduced_prod[0] >> LIMB_BITS;
for deg in 1..N_LIMBS - 1 {
aux_in_limbs[deg] = (unreduced_prod[deg] + aux_in_limbs[deg - 1]) >> LIMB_BITS;
}
aux_in_limbs[N_LIMBS - 1] = cy;
for deg in 0..N_LIMBS { lv[MUL_AUX_INPUT].copy_from_slice(&aux_limbs.map(|c| F::from_noncanonical_i64(c)));
let c = MUL_AUX_INPUT[deg];
lv[c] = F::from_canonical_u64(aux_in_limbs[deg]);
}
} }
pub fn eval_packed_generic<P: PackedField>( pub fn eval_packed_generic<P: PackedField>(
@ -106,38 +109,35 @@ pub fn eval_packed_generic<P: PackedField>(
range_check_error!(MUL_AUX_INPUT, 20); range_check_error!(MUL_AUX_INPUT, 20);
let is_mul = lv[IS_MUL]; let is_mul = lv[IS_MUL];
let input0_limbs = MUL_INPUT_0.map(|c| lv[c]); let input0_limbs = read_value::<N_LIMBS, _>(lv, MUL_INPUT_0);
let input1_limbs = MUL_INPUT_1.map(|c| lv[c]); let input1_limbs = read_value::<N_LIMBS, _>(lv, MUL_INPUT_1);
let output_limbs = MUL_OUTPUT.map(|c| lv[c]); let output_limbs = read_value::<N_LIMBS, _>(lv, MUL_OUTPUT);
let aux_limbs = MUL_AUX_INPUT.map(|c| lv[c]); let aux_limbs = read_value::<N_LIMBS, _>(lv, MUL_AUX_INPUT);
// Constraint poly holds the coefficients of the polynomial that // Constraint poly holds the coefficients of the polynomial that
// must be identically zero for this multiplication to be // must be identically zero for this multiplication to be
// verified. // verified.
// //
// These two lines set constr_poly to the polynomial A(x)B(x) - C(x), // These two lines set constr_poly to the polynomial a(x)b(x) - c(x),
// where A, B and C are the polynomials // where a, b and c are the polynomials
// //
// A(x) = \sum_i input0_limbs[i] * 2^LIMB_BITS // a(x) = \sum_i input0_limbs[i] * x^i
// B(x) = \sum_i input1_limbs[i] * 2^LIMB_BITS // b(x) = \sum_i input1_limbs[i] * x^i
// C(x) = \sum_i output_limbs[i] * 2^LIMB_BITS // c(x) = \sum_i output_limbs[i] * x^i
// //
// This polynomial should equal (2^LIMB_BITS - x) * Q(x) where Q is // This polynomial should equal (x - β)*s(x) where s is
// //
// Q(x) = \sum_i aux_limbs[i] * 2^LIMB_BITS // s(x) = \sum_i aux_limbs[i] * x^i
// //
let mut constr_poly = pol_mul_lo(input0_limbs, input1_limbs); let mut constr_poly = pol_mul_lo(input0_limbs, input1_limbs);
pol_sub_assign(&mut constr_poly, &output_limbs); pol_sub_assign(&mut constr_poly, &output_limbs);
// This subtracts (2^LIMB_BITS - x) * Q(x) from constr_poly. // This subtracts (x - β) * s(x) from constr_poly.
let base = P::Scalar::from_canonical_u64(1 << LIMB_BITS); let base = P::Scalar::from_canonical_u64(1 << LIMB_BITS);
constr_poly[0] -= base * aux_limbs[0]; pol_sub_assign(&mut constr_poly, &pol_adjoin_root(aux_limbs, base));
for deg in 1..N_LIMBS {
constr_poly[deg] -= (base * aux_limbs[deg]) - aux_limbs[deg - 1];
}
// At this point constr_poly holds the coefficients of the // At this point constr_poly holds the coefficients of the
// polynomial A(x)B(x) - C(x) - (2^LIMB_BITS - x)*Q(x). The // polynomial a(x)b(x) - c(x) - (x - β)*s(x). The
// multiplication is valid if and only if all of those // multiplication is valid if and only if all of those
// coefficients are zero. // coefficients are zero.
for &c in &constr_poly { for &c in &constr_poly {
@ -151,40 +151,17 @@ pub fn eval_ext_circuit<F: RichField + Extendable<D>, const D: usize>(
yield_constr: &mut RecursiveConstraintConsumer<F, D>, yield_constr: &mut RecursiveConstraintConsumer<F, D>,
) { ) {
let is_mul = lv[IS_MUL]; let is_mul = lv[IS_MUL];
let input0_limbs = MUL_INPUT_0.map(|c| lv[c]); let input0_limbs = read_value::<N_LIMBS, _>(lv, MUL_INPUT_0);
let input1_limbs = MUL_INPUT_1.map(|c| lv[c]); let input1_limbs = read_value::<N_LIMBS, _>(lv, MUL_INPUT_1);
let output_limbs = MUL_OUTPUT.map(|c| lv[c]); let output_limbs = read_value::<N_LIMBS, _>(lv, MUL_OUTPUT);
let aux_in_limbs = MUL_AUX_INPUT.map(|c| lv[c]); let aux_limbs = read_value::<N_LIMBS, _>(lv, MUL_AUX_INPUT);
let zero = builder.zero_extension(); let mut constr_poly = pol_mul_lo_ext_circuit(builder, input0_limbs, input1_limbs);
let mut constr_poly = [zero; N_LIMBS]; // pointless init pol_sub_assign_ext_circuit(builder, &mut constr_poly, &output_limbs);
// Invariant: i + j = deg let base = builder.constant_extension(F::Extension::from_canonical_u64(1 << LIMB_BITS));
for col in 0..N_LIMBS { let rhs = pol_adjoin_root_ext_circuit(builder, aux_limbs, base);
let mut acc = zero; pol_sub_assign_ext_circuit(builder, &mut constr_poly, &rhs);
for i in 0..=col {
let j = col - i;
acc = builder.mul_add_extension(input0_limbs[i], input1_limbs[j], acc);
}
constr_poly[col] = builder.sub_extension(acc, output_limbs[col]);
}
let base = F::from_canonical_u64(1 << LIMB_BITS);
let one = builder.one_extension();
// constr_poly[0] = constr_poly[0] - base * aux_in_limbs[0]
constr_poly[0] =
builder.arithmetic_extension(F::ONE, -base, constr_poly[0], one, aux_in_limbs[0]);
for deg in 1..N_LIMBS {
// constr_poly[deg] -= (base*aux_in_limbs[deg] - aux_in_limbs[deg-1])
let t = builder.arithmetic_extension(
base,
F::NEG_ONE,
aux_in_limbs[deg],
one,
aux_in_limbs[deg - 1],
);
constr_poly[deg] = builder.sub_extension(constr_poly[deg], t);
}
for &c in &constr_poly { for &c in &constr_poly {
let filter = builder.mul_extension(is_mul, c); let filter = builder.mul_extension(is_mul, c);
@ -241,7 +218,7 @@ mod tests {
for _i in 0..N_RND_TESTS { for _i in 0..N_RND_TESTS {
// set inputs to random values // set inputs to random values
for (&ai, bi) in MUL_INPUT_0.iter().zip(MUL_INPUT_1) { for (ai, bi) in MUL_INPUT_0.zip(MUL_INPUT_1) {
lv[ai] = F::from_canonical_u16(rng.gen()); lv[ai] = F::from_canonical_u16(rng.gen());
lv[bi] = F::from_canonical_u16(rng.gen()); lv[bi] = F::from_canonical_u16(rng.gen());
} }

View File

@ -6,6 +6,7 @@ use plonky2::iop::ext_target::ExtensionTarget;
use crate::arithmetic::add::{eval_ext_circuit_are_equal, eval_packed_generic_are_equal}; use crate::arithmetic::add::{eval_ext_circuit_are_equal, eval_packed_generic_are_equal};
use crate::arithmetic::columns::*; use crate::arithmetic::columns::*;
use crate::arithmetic::utils::read_value_u64_limbs;
use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer};
use crate::range_check_error; use crate::range_check_error;
@ -28,14 +29,12 @@ pub(crate) fn u256_sub_br(input0: [u64; N_LIMBS], input1: [u64; N_LIMBS]) -> ([u
} }
pub fn generate<F: RichField>(lv: &mut [F; NUM_ARITH_COLUMNS]) { pub fn generate<F: RichField>(lv: &mut [F; NUM_ARITH_COLUMNS]) {
let input0_limbs = SUB_INPUT_0.map(|c| lv[c].to_canonical_u64()); let input0 = read_value_u64_limbs(lv, SUB_INPUT_0);
let input1_limbs = SUB_INPUT_1.map(|c| lv[c].to_canonical_u64()); let input1 = read_value_u64_limbs(lv, SUB_INPUT_1);
let (output_limbs, _) = u256_sub_br(input0_limbs, input1_limbs); let (output_limbs, _) = u256_sub_br(input0, input1);
for (&c, output_limb) in SUB_OUTPUT.iter().zip(output_limbs) { lv[SUB_OUTPUT].copy_from_slice(&output_limbs.map(|c| F::from_canonical_u64(c)));
lv[c] = F::from_canonical_u64(output_limb);
}
} }
pub fn eval_packed_generic<P: PackedField>( pub fn eval_packed_generic<P: PackedField>(
@ -47,13 +46,18 @@ pub fn eval_packed_generic<P: PackedField>(
range_check_error!(SUB_OUTPUT, 16); range_check_error!(SUB_OUTPUT, 16);
let is_sub = lv[IS_SUB]; let is_sub = lv[IS_SUB];
let input0_limbs = SUB_INPUT_0.iter().map(|&c| lv[c]); let input0_limbs = &lv[SUB_INPUT_0];
let input1_limbs = SUB_INPUT_1.iter().map(|&c| lv[c]); let input1_limbs = &lv[SUB_INPUT_1];
let output_limbs = SUB_OUTPUT.iter().map(|&c| lv[c]); let output_limbs = &lv[SUB_OUTPUT];
let output_computed = input0_limbs.zip(input1_limbs).map(|(a, b)| a - b); let output_computed = input0_limbs.iter().zip(input1_limbs).map(|(&a, &b)| a - b);
eval_packed_generic_are_equal(yield_constr, is_sub, output_limbs, output_computed); eval_packed_generic_are_equal(
yield_constr,
is_sub,
output_limbs.iter().copied(),
output_computed,
);
} }
#[allow(clippy::needless_collect)] #[allow(clippy::needless_collect)]
@ -63,24 +67,25 @@ pub fn eval_ext_circuit<F: RichField + Extendable<D>, const D: usize>(
yield_constr: &mut RecursiveConstraintConsumer<F, D>, yield_constr: &mut RecursiveConstraintConsumer<F, D>,
) { ) {
let is_sub = lv[IS_SUB]; let is_sub = lv[IS_SUB];
let input0_limbs = SUB_INPUT_0.iter().map(|&c| lv[c]); let input0_limbs = &lv[SUB_INPUT_0];
let input1_limbs = SUB_INPUT_1.iter().map(|&c| lv[c]); let input1_limbs = &lv[SUB_INPUT_1];
let output_limbs = SUB_OUTPUT.iter().map(|&c| lv[c]); let output_limbs = &lv[SUB_OUTPUT];
// Since `map` is lazy and the closure passed to it borrows // Since `map` is lazy and the closure passed to it borrows
// `builder`, we can't then borrow builder again below in the call // `builder`, we can't then borrow builder again below in the call
// to `eval_ext_circuit_are_equal`. The solution is to force // to `eval_ext_circuit_are_equal`. The solution is to force
// evaluation with `collect`. // evaluation with `collect`.
let output_computed = input0_limbs let output_computed = input0_limbs
.iter()
.zip(input1_limbs) .zip(input1_limbs)
.map(|(a, b)| builder.sub_extension(a, b)) .map(|(&a, &b)| builder.sub_extension(a, b))
.collect::<Vec<ExtensionTarget<D>>>(); .collect::<Vec<ExtensionTarget<D>>>();
eval_ext_circuit_are_equal( eval_ext_circuit_are_equal(
builder, builder,
yield_constr, yield_constr,
is_sub, is_sub,
output_limbs, output_limbs.iter().copied(),
output_computed.into_iter(), output_computed.into_iter(),
); );
} }
@ -134,7 +139,7 @@ mod tests {
for _ in 0..N_RND_TESTS { for _ in 0..N_RND_TESTS {
// set inputs to random values // set inputs to random values
for (&ai, bi) in SUB_INPUT_0.iter().zip(SUB_INPUT_1) { for (ai, bi) in SUB_INPUT_0.zip(SUB_INPUT_1) {
lv[ai] = F::from_canonical_u16(rng.gen()); lv[ai] = F::from_canonical_u16(rng.gen());
lv[bi] = F::from_canonical_u16(rng.gen()); lv[bi] = F::from_canonical_u16(rng.gen());
} }

View File

@ -1,4 +1,4 @@
use std::ops::{Add, AddAssign, Mul, Neg, Shr, Sub, SubAssign}; use std::ops::{Add, AddAssign, Mul, Neg, Range, Shr, Sub, SubAssign};
use log::error; use log::error;
use plonky2::field::extension::Extendable; use plonky2::field::extension::Extendable;
@ -6,7 +6,7 @@ use plonky2::hash::hash_types::RichField;
use plonky2::iop::ext_target::ExtensionTarget; use plonky2::iop::ext_target::ExtensionTarget;
use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2::plonk::circuit_builder::CircuitBuilder;
use crate::arithmetic::columns::N_LIMBS; use crate::arithmetic::columns::{NUM_ARITH_COLUMNS, N_LIMBS};
/// Emit an error message regarding unchecked range assumptions. /// Emit an error message regarding unchecked range assumptions.
/// Assumes the values in `cols` are `[cols[0], cols[0] + 1, ..., /// Assumes the values in `cols` are `[cols[0], cols[0] + 1, ...,
@ -14,7 +14,7 @@ use crate::arithmetic::columns::N_LIMBS;
pub(crate) fn _range_check_error<const RC_BITS: u32>( pub(crate) fn _range_check_error<const RC_BITS: u32>(
file: &str, file: &str,
line: u32, line: u32,
cols: &[usize], cols: Range<usize>,
signedness: &str, signedness: &str,
) { ) {
error!( error!(
@ -23,8 +23,8 @@ pub(crate) fn _range_check_error<const RC_BITS: u32>(
file, file,
RC_BITS, RC_BITS,
signedness, signedness,
cols[0], cols.start,
cols[0] + cols.len() - 1 cols.end - 1,
); );
} }
@ -34,7 +34,7 @@ macro_rules! range_check_error {
$crate::arithmetic::utils::_range_check_error::<$rc_bits>( $crate::arithmetic::utils::_range_check_error::<$rc_bits>(
file!(), file!(),
line!(), line!(),
&$cols, $cols,
"unsigned", "unsigned",
); );
}; };
@ -42,7 +42,7 @@ macro_rules! range_check_error {
$crate::arithmetic::utils::_range_check_error::<$rc_bits>( $crate::arithmetic::utils::_range_check_error::<$rc_bits>(
file!(), file!(),
line!(), line!(),
&$cols, $cols,
"signed", "signed",
); );
}; };
@ -225,6 +225,22 @@ where
res res
} }
pub(crate) fn pol_mul_lo_ext_circuit<F: RichField + Extendable<D>, const D: usize>(
builder: &mut CircuitBuilder<F, D>,
a: [ExtensionTarget<D>; N_LIMBS],
b: [ExtensionTarget<D>; N_LIMBS],
) -> [ExtensionTarget<D>; N_LIMBS] {
let zero = builder.zero_extension();
let mut res = [zero; N_LIMBS];
for deg in 0..N_LIMBS {
for i in 0..=deg {
let j = deg - i;
res[deg] = builder.mul_add_extension(a[i], b[j], res[deg]);
}
}
res
}
/// Adjoin M - N zeros to a, returning [a[0], a[1], ..., a[N-1], 0, 0, ..., 0]. /// Adjoin M - N zeros to a, returning [a[0], a[1], ..., a[N-1], 0, 0, ..., 0].
pub(crate) fn pol_extend<T, const N: usize, const M: usize>(a: [T; N]) -> [T; M] pub(crate) fn pol_extend<T, const N: usize, const M: usize>(a: [T; N]) -> [T; M]
where where
@ -248,11 +264,9 @@ pub(crate) fn pol_extend_ext_circuit<F: RichField + Extendable<D>, const D: usiz
zero_extend zero_extend
} }
/// Given polynomial a(x) = \sum_{i=0}^{2N-2} a[i] x^i and an element /// Given polynomial a(x) = \sum_{i=0}^{N-2} a[i] x^i and an element
/// `root`, return b = (x - root) * a(x). /// `root`, return b = (x - root) * a(x).
/// pub(crate) fn pol_adjoin_root<T, U, const N: usize>(a: [T; N], root: U) -> [T; N]
/// NB: Ignores element a[2 * N_LIMBS - 1], treating it as if it's 0.
pub(crate) fn pol_adjoin_root<T, U>(a: [T; 2 * N_LIMBS], root: U) -> [T; 2 * N_LIMBS]
where where
T: Add<Output = T> + Copy + Default + Mul<Output = T> + Sub<Output = T>, T: Add<Output = T> + Copy + Default + Mul<Output = T> + Sub<Output = T>,
U: Copy + Mul<T, Output = T> + Neg<Output = U>, U: Copy + Mul<T, Output = T> + Neg<Output = U>,
@ -261,67 +275,96 @@ where
// coefficients, res[0] = -root*a[0] and // coefficients, res[0] = -root*a[0] and
// res[i] = a[i-1] - root * a[i] // res[i] = a[i-1] - root * a[i]
let mut res = [T::default(); 2 * N_LIMBS]; let mut res = [T::default(); N];
res[0] = -root * a[0]; res[0] = -root * a[0];
for deg in 1..(2 * N_LIMBS - 1) { for deg in 1..N {
res[deg] = a[deg - 1] - (root * a[deg]); res[deg] = a[deg - 1] - (root * a[deg]);
} }
// NB: We assume that a[2 * N_LIMBS - 1] = 0, so the last
// iteration has no "* root" term.
res[2 * N_LIMBS - 1] = a[2 * N_LIMBS - 2];
res res
} }
pub(crate) fn pol_adjoin_root_ext_circuit<F: RichField + Extendable<D>, const D: usize>( pub(crate) fn pol_adjoin_root_ext_circuit<
F: RichField + Extendable<D>,
const D: usize,
const N: usize,
>(
builder: &mut CircuitBuilder<F, D>, builder: &mut CircuitBuilder<F, D>,
a: [ExtensionTarget<D>; 2 * N_LIMBS], a: [ExtensionTarget<D>; N],
root: ExtensionTarget<D>, root: ExtensionTarget<D>,
) -> [ExtensionTarget<D>; 2 * N_LIMBS] { ) -> [ExtensionTarget<D>; N] {
let zero = builder.zero_extension(); let zero = builder.zero_extension();
let mut res = [zero; 2 * N_LIMBS]; let mut res = [zero; N];
// res[deg] = NEG_ONE * root * a[0] + ZERO * zero // res[deg] = NEG_ONE * root * a[0] + ZERO * zero
res[0] = builder.arithmetic_extension(F::NEG_ONE, F::ZERO, root, a[0], zero); res[0] = builder.arithmetic_extension(F::NEG_ONE, F::ZERO, root, a[0], zero);
for deg in 1..(2 * N_LIMBS - 1) { for deg in 1..N {
// res[deg] = NEG_ONE * root * a[deg] + ONE * a[deg - 1] // res[deg] = NEG_ONE * root * a[deg] + ONE * a[deg - 1]
res[deg] = builder.arithmetic_extension(F::NEG_ONE, F::ONE, root, a[deg], a[deg - 1]); res[deg] = builder.arithmetic_extension(F::NEG_ONE, F::ONE, root, a[deg], a[deg - 1]);
} }
// NB: We assumes that a[2 * N_LIMBS - 1] = 0, so the last
// iteration has no "* root" term.
res[2 * N_LIMBS - 1] = a[2 * N_LIMBS - 2];
res res
} }
/// Given polynomial a(x) = \sum_{i=0}^{2N-1} a[i] x^i and a root of `a` /// Given polynomial a(x) = \sum_{i=0}^{N-1} a[i] x^i and a root of `a`
/// of the form 2^EXP, return q(x) satisfying a(x) = (x - root) * q(x). /// of the form 2^EXP, return q(x) satisfying a(x) = (x - root) * q(x).
/// ///
/// NB: We do not verify that a(2^EXP) = 0; if this doesn't hold the /// NB: We do not verify that a(2^EXP) = 0; if this doesn't hold the
/// result is basically junk. /// result is basically junk.
/// ///
/// NB: The result could be returned in 2*N-1 elements, but we return /// NB: The result could be returned in N-1 elements, but we return
/// 2*N and set the last element to zero since the calling code /// N and set the last element to zero since the calling code
/// happens to require a result zero-extended to 2*N elements. /// happens to require a result zero-extended to N elements.
pub(crate) fn pol_remove_root_2exp<const EXP: usize, T>(a: [T; 2 * N_LIMBS]) -> [T; 2 * N_LIMBS] pub(crate) fn pol_remove_root_2exp<const EXP: usize, T, const N: usize>(a: [T; N]) -> [T; N]
where where
T: Copy + Default + Neg<Output = T> + Shr<usize, Output = T> + Sub<Output = T>, T: Copy + Default + Neg<Output = T> + Shr<usize, Output = T> + Sub<Output = T>,
{ {
// By assumption β := 2^EXP is a root of `a`, i.e. (x - β) divides // By assumption β := 2^EXP is a root of `a`, i.e. (x - β) divides
// `a`; if we write // `a`; if we write
// //
// a(x) = \sum_{i=0}^{2N-1} a[i] x^i // a(x) = \sum_{i=0}^{N-1} a[i] x^i
// = (x - β) \sum_{i=0}^{2N-2} q[i] x^i // = (x - β) \sum_{i=0}^{N-2} q[i] x^i
// //
// then by comparing coefficients it is easy to see that // then by comparing coefficients it is easy to see that
// //
// q[0] = -a[0] / β and q[i] = (q[i-1] - a[i]) / β // q[0] = -a[0] / β and q[i] = (q[i-1] - a[i]) / β
// //
// for 0 < i <= 2N-1 (and the divisions are exact). // for 0 < i <= N-1 (and the divisions are exact).
let mut q = [T::default(); 2 * N_LIMBS]; let mut q = [T::default(); N];
q[0] = -(a[0] >> EXP); q[0] = -(a[0] >> EXP);
// NB: Last element of q is deliberately left equal to zero. // NB: Last element of q is deliberately left equal to zero.
for deg in 1..2 * N_LIMBS - 1 { for deg in 1..N - 1 {
q[deg] = (q[deg - 1] - a[deg]) >> EXP; q[deg] = (q[deg - 1] - a[deg]) >> EXP;
} }
q q
} }
/// Read the range `value_idxs` of values from `lv` into an array of
/// length `N`. Panics if the length of the range is not `N`.
pub(crate) fn read_value<const N: usize, T: Copy>(
lv: &[T; NUM_ARITH_COLUMNS],
value_idxs: Range<usize>,
) -> [T; N] {
lv[value_idxs].try_into().unwrap()
}
/// Read the range `value_idxs` of values from `lv` into an array of
/// length `N`, interpreting the values as `u64`s. Panics if the
/// length of the range is not `N`.
pub(crate) fn read_value_u64_limbs<const N: usize, F: RichField>(
lv: &[F; NUM_ARITH_COLUMNS],
value_idxs: Range<usize>,
) -> [u64; N] {
let limbs: [_; N] = lv[value_idxs].try_into().unwrap();
limbs.map(|c| F::to_canonical_u64(&c))
}
/// Read the range `value_idxs` of values from `lv` into an array of
/// length `N`, interpreting the values as `i64`s. Panics if the
/// length of the range is not `N`.
pub(crate) fn read_value_i64_limbs<const N: usize, F: RichField>(
lv: &[F; NUM_ARITH_COLUMNS],
value_idxs: Range<usize>,
) -> [i64; N] {
let limbs: [_; N] = lv[value_idxs].try_into().unwrap();
limbs.map(|c| F::to_canonical_u64(&c) as i64)
}

View File

@ -19,6 +19,9 @@ pub struct OpsColumnsView<T> {
pub mulmod: T, pub mulmod: T,
pub exp: T, pub exp: T,
pub signextend: T, pub signextend: T,
pub addfp254: T,
pub mulfp254: T,
pub subfp254: T,
pub lt: T, pub lt: T,
pub gt: T, pub gt: T,
pub slt: T, pub slt: T,

View File

@ -9,7 +9,7 @@ use crate::cpu::columns::{CpuColumnsView, COL_MAP};
use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::aggregator::KERNEL;
// TODO: This list is incomplete. // TODO: This list is incomplete.
const NATIVE_INSTRUCTIONS: [usize; 25] = [ const NATIVE_INSTRUCTIONS: [usize; 28] = [
COL_MAP.op.add, COL_MAP.op.add,
COL_MAP.op.mul, COL_MAP.op.mul,
COL_MAP.op.sub, COL_MAP.op.sub,
@ -20,6 +20,9 @@ const NATIVE_INSTRUCTIONS: [usize; 25] = [
COL_MAP.op.addmod, COL_MAP.op.addmod,
COL_MAP.op.mulmod, COL_MAP.op.mulmod,
COL_MAP.op.signextend, COL_MAP.op.signextend,
COL_MAP.op.addfp254,
COL_MAP.op.mulfp254,
COL_MAP.op.subfp254,
COL_MAP.op.lt, COL_MAP.op.lt,
COL_MAP.op.gt, COL_MAP.op.gt,
COL_MAP.op.slt, COL_MAP.op.slt,

View File

@ -11,7 +11,7 @@ use plonky2::hash::hash_types::RichField;
use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer};
use crate::cpu::columns::{CpuColumnsView, COL_MAP, NUM_CPU_COLUMNS}; use crate::cpu::columns::{CpuColumnsView, COL_MAP, NUM_CPU_COLUMNS};
use crate::cpu::{ use crate::cpu::{
bootstrap_kernel, control_flow, decode, dup_swap, jumps, membus, simple_logic, stack, bootstrap_kernel, control_flow, decode, dup_swap, jumps, membus, modfp254, simple_logic, stack,
stack_bounds, syscalls, stack_bounds, syscalls,
}; };
use crate::cross_table_lookup::Column; use crate::cross_table_lookup::Column;
@ -150,6 +150,7 @@ impl<F: RichField + Extendable<D>, const D: usize> Stark<F, D> for CpuStark<F, D
dup_swap::eval_packed(local_values, yield_constr); dup_swap::eval_packed(local_values, yield_constr);
jumps::eval_packed(local_values, next_values, yield_constr); jumps::eval_packed(local_values, next_values, yield_constr);
membus::eval_packed(local_values, yield_constr); membus::eval_packed(local_values, yield_constr);
modfp254::eval_packed(local_values, yield_constr);
simple_logic::eval_packed(local_values, yield_constr); simple_logic::eval_packed(local_values, yield_constr);
stack::eval_packed(local_values, yield_constr); stack::eval_packed(local_values, yield_constr);
stack_bounds::eval_packed(local_values, yield_constr); stack_bounds::eval_packed(local_values, yield_constr);
@ -170,6 +171,7 @@ impl<F: RichField + Extendable<D>, const D: usize> Stark<F, D> for CpuStark<F, D
dup_swap::eval_ext_circuit(builder, local_values, yield_constr); dup_swap::eval_ext_circuit(builder, local_values, yield_constr);
jumps::eval_ext_circuit(builder, local_values, next_values, yield_constr); jumps::eval_ext_circuit(builder, local_values, next_values, yield_constr);
membus::eval_ext_circuit(builder, local_values, yield_constr); membus::eval_ext_circuit(builder, local_values, yield_constr);
modfp254::eval_ext_circuit(builder, local_values, yield_constr);
simple_logic::eval_ext_circuit(builder, local_values, yield_constr); simple_logic::eval_ext_circuit(builder, local_values, yield_constr);
stack::eval_ext_circuit(builder, local_values, yield_constr); stack::eval_ext_circuit(builder, local_values, yield_constr);
stack_bounds::eval_ext_circuit(builder, local_values, yield_constr); stack_bounds::eval_ext_circuit(builder, local_values, yield_constr);

View File

@ -22,7 +22,7 @@ use crate::cpu::columns::{CpuColumnsView, COL_MAP};
/// behavior. /// behavior.
/// Note: invalid opcodes are not represented here. _Any_ opcode is permitted to decode to /// Note: invalid opcodes are not represented here. _Any_ opcode is permitted to decode to
/// `is_invalid`. The kernel then verifies that the opcode was _actually_ invalid. /// `is_invalid`. The kernel then verifies that the opcode was _actually_ invalid.
const OPCODES: [(u8, usize, bool, usize); 93] = [ const OPCODES: [(u8, usize, bool, usize); 96] = [
// (start index of block, number of top bits to check (log2), kernel-only, flag column) // (start index of block, number of top bits to check (log2), kernel-only, flag column)
(0x00, 0, false, COL_MAP.op.stop), (0x00, 0, false, COL_MAP.op.stop),
(0x01, 0, false, COL_MAP.op.add), (0x01, 0, false, COL_MAP.op.add),
@ -36,6 +36,9 @@ const OPCODES: [(u8, usize, bool, usize); 93] = [
(0x09, 0, false, COL_MAP.op.mulmod), (0x09, 0, false, COL_MAP.op.mulmod),
(0x0a, 0, false, COL_MAP.op.exp), (0x0a, 0, false, COL_MAP.op.exp),
(0x0b, 0, false, COL_MAP.op.signextend), (0x0b, 0, false, COL_MAP.op.signextend),
(0x0c, 0, true, COL_MAP.op.addfp254),
(0x0d, 0, true, COL_MAP.op.mulfp254),
(0x0e, 0, true, COL_MAP.op.subfp254),
(0x10, 0, false, COL_MAP.op.lt), (0x10, 0, false, COL_MAP.op.lt),
(0x11, 0, false, COL_MAP.op.gt), (0x11, 0, false, COL_MAP.op.gt),
(0x12, 0, false, COL_MAP.op.slt), (0x12, 0, false, COL_MAP.op.slt),

View File

@ -39,15 +39,20 @@ pub(crate) fn combined_kernel() -> Kernel {
include_str!("asm/memory/metadata.asm"), include_str!("asm/memory/metadata.asm"),
include_str!("asm/memory/packing.asm"), include_str!("asm/memory/packing.asm"),
include_str!("asm/memory/txn_fields.asm"), include_str!("asm/memory/txn_fields.asm"),
include_str!("asm/mpt/delete.asm"),
include_str!("asm/mpt/hash.asm"), include_str!("asm/mpt/hash.asm"),
include_str!("asm/mpt/hash_trie_specific.asm"), include_str!("asm/mpt/hash_trie_specific.asm"),
include_str!("asm/mpt/hex_prefix.asm"), include_str!("asm/mpt/hex_prefix.asm"),
include_str!("asm/mpt/insert.asm"),
include_str!("asm/mpt/insert_extension.asm"),
include_str!("asm/mpt/insert_leaf.asm"),
include_str!("asm/mpt/insert_trie_specific.asm"),
include_str!("asm/mpt/load.asm"), include_str!("asm/mpt/load.asm"),
include_str!("asm/mpt/load_trie_specific.asm"),
include_str!("asm/mpt/read.asm"), include_str!("asm/mpt/read.asm"),
include_str!("asm/mpt/storage_read.asm"), include_str!("asm/mpt/storage_read.asm"),
include_str!("asm/mpt/storage_write.asm"), include_str!("asm/mpt/storage_write.asm"),
include_str!("asm/mpt/util.asm"), include_str!("asm/mpt/util.asm"),
include_str!("asm/mpt/write.asm"),
include_str!("asm/ripemd/box.asm"), include_str!("asm/ripemd/box.asm"),
include_str!("asm/ripemd/compression.asm"), include_str!("asm/ripemd/compression.asm"),
include_str!("asm/ripemd/constants.asm"), include_str!("asm/ripemd/constants.asm"),

View File

@ -22,7 +22,7 @@ count_zeros_loop:
// stack: zeros', i, retdest // stack: zeros', i, retdest
SWAP1 SWAP1
// stack: i, zeros', retdest // stack: i, zeros', retdest
%add_const(1) %increment
// stack: i', zeros', retdest // stack: i', zeros', retdest
%jump(count_zeros_loop) %jump(count_zeros_loop)

View File

@ -14,7 +14,7 @@
%macro next_context_id %macro next_context_id
// stack: (empty) // stack: (empty)
%mload_global_metadata(@GLOBAL_METADATA_LARGEST_CONTEXT) %mload_global_metadata(@GLOBAL_METADATA_LARGEST_CONTEXT)
%add_const(1) %increment
// stack: new_ctx // stack: new_ctx
DUP1 DUP1
%mstore_global_metadata(@GLOBAL_METADATA_LARGEST_CONTEXT) %mstore_global_metadata(@GLOBAL_METADATA_LARGEST_CONTEXT)

View File

@ -132,7 +132,7 @@ pubkey_to_addr:
// stack: PKx, PKy, retdest // stack: PKx, PKy, retdest
PUSH 0 PUSH 0
// stack: 0, PKx, PKy, retdest // stack: 0, PKx, PKy, retdest
MSTORE // TODO: switch to kernel memory (like `%mstore_current(@SEGMENT_KERNEL_GENERAL)`). MSTORE // TODO: switch to kernel memory (like `%mstore_kernel(@SEGMENT_KERNEL_GENERAL)`).
// stack: PKy, retdest // stack: PKy, retdest
PUSH 0x20 PUSH 0x20
// stack: 0x20, PKy, retdest // stack: 0x20, PKy, retdest

View File

@ -55,6 +55,19 @@
// stack: (empty) // stack: (empty)
%endmacro %endmacro
// Store a single value from the given segment of kernel (context 0) memory.
%macro mstore_kernel(segment, offset)
// stack: value
PUSH $offset
// stack: offset, value
PUSH $segment
// stack: segment, offset, value
PUSH 0 // kernel has context 0
// stack: context, segment, offset, value
MSTORE_GENERAL
// stack: (empty)
%endmacro
// Load from the kernel a big-endian u32, consisting of 4 bytes (c_3, c_2, c_1, c_0) // Load from the kernel a big-endian u32, consisting of 4 bytes (c_3, c_2, c_1, c_0)
%macro mload_kernel_u32(segment) %macro mload_kernel_u32(segment)
// stack: offset // stack: offset
@ -64,7 +77,7 @@
%shl_const(8) %shl_const(8)
// stack: c_3 << 8, offset // stack: c_3 << 8, offset
DUP2 DUP2
%add_const(1) %increment
%mload_kernel($segment) %mload_kernel($segment)
OR OR
// stack: (c_3 << 8) | c_2, offset // stack: (c_3 << 8) | c_2, offset
@ -91,7 +104,7 @@
%mload_kernel($segment) %mload_kernel($segment)
// stack: c0 , offset // stack: c0 , offset
DUP2 DUP2
%add_const(1) %increment
%mload_kernel($segment) %mload_kernel($segment)
%shl_const(8) %shl_const(8)
OR OR
@ -208,7 +221,7 @@
// stack: c_2, c_1, c_0, offset // stack: c_2, c_1, c_0, offset
DUP4 DUP4
// stack: offset, c_2, c_1, c_0, offset // stack: offset, c_2, c_1, c_0, offset
%add_const(1) %increment
%mstore_kernel($segment) %mstore_kernel($segment)
// stack: c_1, c_0, offset // stack: c_1, c_0, offset
DUP3 DUP3

View File

@ -28,15 +28,15 @@ global memcpy:
// Increment dst_addr. // Increment dst_addr.
SWAP2 SWAP2
%add_const(1) %increment
SWAP2 SWAP2
// Increment src_addr. // Increment src_addr.
SWAP5 SWAP5
%add_const(1) %increment
SWAP5 SWAP5
// Decrement count. // Decrement count.
SWAP6 SWAP6
%sub_const(1) %decrement
SWAP6 SWAP6
// Continue the loop. // Continue the loop.

View File

@ -71,9 +71,9 @@ mstore_unpacking_loop:
// stack: i, context, segment, offset, value, len, retdest // stack: i, context, segment, offset, value, len, retdest
// Increment offset. // Increment offset.
SWAP3 %add_const(1) SWAP3 SWAP3 %increment SWAP3
// Increment i. // Increment i.
%add_const(1) %increment
%jump(mstore_unpacking_loop) %jump(mstore_unpacking_loop)

View File

@ -0,0 +1,6 @@
// Return a copy of the given node with the given key deleted.
//
// Pre stack: node_ptr, num_nibbles, key, retdest
// Post stack: updated_node_ptr
global mpt_delete:
PANIC // TODO

View File

@ -47,7 +47,29 @@ mpt_hash_hash_rlp_after_unpacking:
// Pre stack: node_ptr, encode_value, retdest // Pre stack: node_ptr, encode_value, retdest
// Post stack: result, result_len // Post stack: result, result_len
global encode_or_hash_node: global encode_or_hash_node:
%stack (node_ptr, encode_value) -> (node_ptr, encode_value, maybe_hash_node) // stack: node_ptr, encode_value, retdest
DUP1 %mload_trie_data
// Check if we're dealing with a concrete node, i.e. not a hash node.
// stack: node_type, node_ptr, encode_value, retdest
DUP1
PUSH @MPT_NODE_HASH
SUB
%jumpi(encode_or_hash_concrete_node)
// If we got here, node_type == @MPT_NODE_HASH.
// Load the hash and return (hash, 32).
// stack: node_type, node_ptr, encode_value, retdest
POP
// stack: node_ptr, encode_value, retdest
%increment // Skip over node type prefix
// stack: hash_ptr, encode_value, retdest
%mload_trie_data
// stack: hash, encode_value, retdest
%stack (hash, encode_value, retdest) -> (retdest, hash, 32)
JUMP
encode_or_hash_concrete_node:
%stack (node_type, node_ptr, encode_value) -> (node_type, node_ptr, encode_value, maybe_hash_node)
%jump(encode_node) %jump(encode_node)
maybe_hash_node: maybe_hash_node:
// stack: result_ptr, result_len, retdest // stack: result_ptr, result_len, retdest
@ -75,22 +97,22 @@ after_packed_small_rlp:
// RLP encode the given trie node, and return an (pointer, length) pair // RLP encode the given trie node, and return an (pointer, length) pair
// indicating where the data lives within @SEGMENT_RLP_RAW. // indicating where the data lives within @SEGMENT_RLP_RAW.
// //
// Pre stack: node_ptr, encode_value, retdest // Pre stack: node_type, node_ptr, encode_value, retdest
// Post stack: result_ptr, result_len // Post stack: result_ptr, result_len
global encode_node: encode_node:
// stack: node_ptr, encode_value, retdest
DUP1 %mload_trie_data
// stack: node_type, node_ptr, encode_value, retdest // stack: node_type, node_ptr, encode_value, retdest
// Increment node_ptr, so it points to the node payload instead of its type. // Increment node_ptr, so it points to the node payload instead of its type.
SWAP1 %add_const(1) SWAP1 SWAP1 %increment SWAP1
// stack: node_type, node_payload_ptr, encode_value, retdest // stack: node_type, node_payload_ptr, encode_value, retdest
DUP1 %eq_const(@MPT_NODE_EMPTY) %jumpi(encode_node_empty) DUP1 %eq_const(@MPT_NODE_EMPTY) %jumpi(encode_node_empty)
DUP1 %eq_const(@MPT_NODE_HASH) %jumpi(encode_node_hash)
DUP1 %eq_const(@MPT_NODE_BRANCH) %jumpi(encode_node_branch) DUP1 %eq_const(@MPT_NODE_BRANCH) %jumpi(encode_node_branch)
DUP1 %eq_const(@MPT_NODE_EXTENSION) %jumpi(encode_node_extension) DUP1 %eq_const(@MPT_NODE_EXTENSION) %jumpi(encode_node_extension)
DUP1 %eq_const(@MPT_NODE_LEAF) %jumpi(encode_node_leaf) DUP1 %eq_const(@MPT_NODE_LEAF) %jumpi(encode_node_leaf)
PANIC // Invalid node type? Shouldn't get here.
// If we got here, node_type is either @MPT_NODE_HASH, which should have
// been handled earlier in encode_or_hash_node, or something invalid.
PANIC
global encode_node_empty: global encode_node_empty:
// stack: node_type, node_payload_ptr, encode_value, retdest // stack: node_type, node_payload_ptr, encode_value, retdest
@ -105,27 +127,27 @@ global encode_node_empty:
%stack (retdest) -> (retdest, 0, 1) %stack (retdest) -> (retdest, 0, 1)
JUMP JUMP
global encode_node_hash:
// stack: node_type, node_payload_ptr, encode_value, retdest
POP
// stack: node_payload_ptr, encode_value, retdest
%mload_trie_data
%stack (hash, encode_value, retdest) -> (retdest, hash, 32)
JUMP
encode_node_branch: encode_node_branch:
// stack: node_type, node_payload_ptr, encode_value, retdest // stack: node_type, node_payload_ptr, encode_value, retdest
POP POP
// stack: node_payload_ptr, encode_value, retdest // stack: node_payload_ptr, encode_value, retdest
// Get the next unused offset within the encoded child buffers.
// Then immediately increment the next unused offset by 16, so any
// recursive calls will use nonoverlapping offsets.
%mload_global_metadata(@TRIE_ENCODED_CHILD_SIZE)
DUP1 %add_const(16)
%mstore_global_metadata(@TRIE_ENCODED_CHILD_SIZE)
// stack: base_offset, node_payload_ptr, encode_value, retdest
// We will call encode_or_hash_node on each child. For the i'th child, we // We will call encode_or_hash_node on each child. For the i'th child, we
// will store the result in SEGMENT_KERNEL_GENERAL[i], and its length in // will store the result in SEGMENT_TRIE_ENCODED_CHILD[base + i], and its length in
// SEGMENT_KERNEL_GENERAL_2[i]. // SEGMENT_TRIE_ENCODED_CHILD_LEN[base + i].
%encode_child(0) %encode_child(1) %encode_child(2) %encode_child(3) %encode_child(0) %encode_child(1) %encode_child(2) %encode_child(3)
%encode_child(4) %encode_child(5) %encode_child(6) %encode_child(7) %encode_child(4) %encode_child(5) %encode_child(6) %encode_child(7)
%encode_child(8) %encode_child(9) %encode_child(10) %encode_child(11) %encode_child(8) %encode_child(9) %encode_child(10) %encode_child(11)
%encode_child(12) %encode_child(13) %encode_child(14) %encode_child(15) %encode_child(12) %encode_child(13) %encode_child(14) %encode_child(15)
// stack: node_payload_ptr, encode_value, retdest // stack: base_offset, node_payload_ptr, encode_value, retdest
// Now, append each child to our RLP tape. // Now, append each child to our RLP tape.
PUSH 9 // rlp_pos; we start at 9 to leave room to prepend a list prefix PUSH 9 // rlp_pos; we start at 9 to leave room to prepend a list prefix
@ -133,25 +155,28 @@ encode_node_branch:
%append_child(4) %append_child(5) %append_child(6) %append_child(7) %append_child(4) %append_child(5) %append_child(6) %append_child(7)
%append_child(8) %append_child(9) %append_child(10) %append_child(11) %append_child(8) %append_child(9) %append_child(10) %append_child(11)
%append_child(12) %append_child(13) %append_child(14) %append_child(15) %append_child(12) %append_child(13) %append_child(14) %append_child(15)
// stack: rlp_pos', base_offset, node_payload_ptr, encode_value, retdest
// We no longer need base_offset.
SWAP1
POP
// stack: rlp_pos', node_payload_ptr, encode_value, retdest // stack: rlp_pos', node_payload_ptr, encode_value, retdest
SWAP1 SWAP1
%add_const(16) %add_const(16)
// stack: value_len_ptr, rlp_pos', encode_value, retdest // stack: value_ptr_ptr, rlp_pos', encode_value, retdest
DUP1 %mload_trie_data %mload_trie_data
// stack: value_len, value_len_ptr, rlp_pos', encode_value, retdest // stack: value_ptr, rlp_pos', encode_value, retdest
%jumpi(encode_node_branch_with_value) DUP1 %jumpi(encode_node_branch_with_value)
// No value; append the empty string (0x80). // No value; append the empty string (0x80).
// stack: value_len_ptr, rlp_pos', encode_value, retdest // stack: value_ptr, rlp_pos', encode_value, retdest
%stack (value_len_ptr, rlp_pos, encode_value) -> (rlp_pos, 0x80, rlp_pos) %stack (value_ptr, rlp_pos, encode_value) -> (rlp_pos, 0x80, rlp_pos)
%mstore_rlp %mstore_rlp
// stack: rlp_pos', retdest // stack: rlp_pos', retdest
%increment %increment
// stack: rlp_pos'', retdest // stack: rlp_pos'', retdest
%jump(encode_node_branch_prepend_prefix) %jump(encode_node_branch_prepend_prefix)
encode_node_branch_with_value: encode_node_branch_with_value:
// stack: value_len_ptr, rlp_pos', encode_value, retdest
%increment
// stack: value_ptr, rlp_pos', encode_value, retdest // stack: value_ptr, rlp_pos', encode_value, retdest
%stack (value_ptr, rlp_pos, encode_value) %stack (value_ptr, rlp_pos, encode_value)
-> (encode_value, rlp_pos, value_ptr, encode_node_branch_prepend_prefix) -> (encode_value, rlp_pos, value_ptr, encode_node_branch_prepend_prefix)
@ -163,43 +188,44 @@ encode_node_branch_prepend_prefix:
JUMP JUMP
// Part of the encode_node_branch function. Encodes the i'th child. // Part of the encode_node_branch function. Encodes the i'th child.
// Stores the result in SEGMENT_KERNEL_GENERAL[i], and its length in // Stores the result in SEGMENT_TRIE_ENCODED_CHILD[base + i], and its length in
// SEGMENT_KERNEL_GENERAL_2[i]. // SEGMENT_TRIE_ENCODED_CHILD_LEN[base + i].
%macro encode_child(i) %macro encode_child(i)
// stack: node_payload_ptr, encode_value, retdest // stack: base_offset, node_payload_ptr, encode_value, retdest
PUSH %%after_encode PUSH %%after_encode
DUP3 DUP3 DUP4 DUP4
// stack: node_payload_ptr, encode_value, %%after_encode, node_payload_ptr, encode_value, retdest // stack: node_payload_ptr, encode_value, %%after_encode, base_offset, node_payload_ptr, encode_value, retdest
%add_const($i) %mload_trie_data %add_const($i) %mload_trie_data
// stack: child_i_ptr, encode_value, %%after_encode, node_payload_ptr, encode_value, retdest // stack: child_i_ptr, encode_value, %%after_encode, base_offset, node_payload_ptr, encode_value, retdest
%jump(encode_or_hash_node) %jump(encode_or_hash_node)
%%after_encode: %%after_encode:
// stack: result, result_len, node_payload_ptr, encode_value, retdest // stack: result, result_len, base_offset, node_payload_ptr, encode_value, retdest
%mstore_kernel_general($i) DUP3 %add_const($i) %mstore_kernel(@SEGMENT_TRIE_ENCODED_CHILD)
%mstore_kernel_general_2($i) // stack: result_len, base_offset, node_payload_ptr, encode_value, retdest
// stack: node_payload_ptr, encode_value, retdest DUP2 %add_const($i) %mstore_kernel(@SEGMENT_TRIE_ENCODED_CHILD_LEN)
// stack: base_offset, node_payload_ptr, encode_value, retdest
%endmacro %endmacro
// Part of the encode_node_branch function. Appends the i'th child's RLP. // Part of the encode_node_branch function. Appends the i'th child's RLP.
%macro append_child(i) %macro append_child(i)
// stack: rlp_pos, node_payload_ptr, encode_value, retdest // stack: rlp_pos, base_offset, node_payload_ptr, encode_value, retdest
%mload_kernel_general($i) // load result DUP2 %add_const($i) %mload_kernel(@SEGMENT_TRIE_ENCODED_CHILD) // load result
%mload_kernel_general_2($i) // load result_len DUP3 %add_const($i) %mload_kernel(@SEGMENT_TRIE_ENCODED_CHILD_LEN) // load result_len
// stack: result_len, result, rlp_pos, node_payload_ptr, encode_value, retdest // stack: result_len, result, rlp_pos, base_offset, node_payload_ptr, encode_value, retdest
// If result_len != 32, result is raw RLP, with an appropriate RLP prefix already. // If result_len != 32, result is raw RLP, with an appropriate RLP prefix already.
DUP1 %sub_const(32) %jumpi(%%unpack) DUP1 %sub_const(32) %jumpi(%%unpack)
// Otherwise, result is a hash, and we need to add the prefix 0x80 + 32 = 160. // Otherwise, result is a hash, and we need to add the prefix 0x80 + 32 = 160.
// stack: result_len, result, rlp_pos, node_payload_ptr, encode_value, retdest // stack: result_len, result, rlp_pos, base_offset, node_payload_ptr, encode_value, retdest
PUSH 160 PUSH 160
DUP4 // rlp_pos DUP4 // rlp_pos
%mstore_rlp %mstore_rlp
SWAP2 %increment SWAP2 // rlp_pos += 1 SWAP2 %increment SWAP2 // rlp_pos += 1
%%unpack: %%unpack:
%stack (result_len, result, rlp_pos, node_payload_ptr, encode_value, retdest) %stack (result_len, result, rlp_pos, base_offset, node_payload_ptr, encode_value, retdest)
-> (rlp_pos, result, result_len, %%after_unpacking, node_payload_ptr, encode_value, retdest) -> (rlp_pos, result, result_len, %%after_unpacking, base_offset, node_payload_ptr, encode_value, retdest)
%jump(mstore_unpacking_rlp) %jump(mstore_unpacking_rlp)
%%after_unpacking: %%after_unpacking:
// stack: rlp_pos', node_payload_ptr, encode_value, retdest // stack: rlp_pos', base_offset, node_payload_ptr, encode_value, retdest
%endmacro %endmacro
encode_node_extension: encode_node_extension:
@ -214,7 +240,7 @@ encode_node_extension_after_encode_child:
PUSH encode_node_extension_after_hex_prefix // retdest PUSH encode_node_extension_after_hex_prefix // retdest
PUSH 0 // terminated PUSH 0 // terminated
// stack: terminated, encode_node_extension_after_hex_prefix, result, result_len, node_payload_ptr, retdest // stack: terminated, encode_node_extension_after_hex_prefix, result, result_len, node_payload_ptr, retdest
DUP5 %add_const(1) %mload_trie_data // Load the packed_nibbles field, which is at index 1. DUP5 %increment %mload_trie_data // Load the packed_nibbles field, which is at index 1.
// stack: packed_nibbles, terminated, encode_node_extension_after_hex_prefix, result, result_len, node_payload_ptr, retdest // stack: packed_nibbles, terminated, encode_node_extension_after_hex_prefix, result, result_len, node_payload_ptr, retdest
DUP6 %mload_trie_data // Load the num_nibbles field, which is at index 0. DUP6 %mload_trie_data // Load the num_nibbles field, which is at index 0.
// stack: num_nibbles, packed_nibbles, terminated, encode_node_extension_after_hex_prefix, result, result_len, node_payload_ptr, retdest // stack: num_nibbles, packed_nibbles, terminated, encode_node_extension_after_hex_prefix, result, result_len, node_payload_ptr, retdest
@ -247,7 +273,7 @@ encode_node_leaf:
PUSH encode_node_leaf_after_hex_prefix // retdest PUSH encode_node_leaf_after_hex_prefix // retdest
PUSH 1 // terminated PUSH 1 // terminated
// stack: terminated, encode_node_leaf_after_hex_prefix, node_payload_ptr, encode_value, retdest // stack: terminated, encode_node_leaf_after_hex_prefix, node_payload_ptr, encode_value, retdest
DUP3 %add_const(1) %mload_trie_data // Load the packed_nibbles field, which is at index 1. DUP3 %increment %mload_trie_data // Load the packed_nibbles field, which is at index 1.
// stack: packed_nibbles, terminated, encode_node_leaf_after_hex_prefix, node_payload_ptr, encode_value, retdest // stack: packed_nibbles, terminated, encode_node_leaf_after_hex_prefix, node_payload_ptr, encode_value, retdest
DUP4 %mload_trie_data // Load the num_nibbles field, which is at index 0. DUP4 %mload_trie_data // Load the num_nibbles field, which is at index 0.
// stack: num_nibbles, packed_nibbles, terminated, encode_node_leaf_after_hex_prefix, node_payload_ptr, encode_value, retdest // stack: num_nibbles, packed_nibbles, terminated, encode_node_leaf_after_hex_prefix, node_payload_ptr, encode_value, retdest
@ -257,7 +283,9 @@ encode_node_leaf:
encode_node_leaf_after_hex_prefix: encode_node_leaf_after_hex_prefix:
// stack: rlp_pos, node_payload_ptr, encode_value, retdest // stack: rlp_pos, node_payload_ptr, encode_value, retdest
SWAP1 SWAP1
%add_const(3) // The value starts at index 3, after num_nibbles, packed_nibbles, and value_len. %add_const(2) // The value pointer starts at index 3, after num_nibbles and packed_nibbles.
// stack: value_ptr_ptr, rlp_pos, encode_value, retdest
%mload_trie_data
// stack: value_ptr, rlp_pos, encode_value, retdest // stack: value_ptr, rlp_pos, encode_value, retdest
%stack (value_ptr, rlp_pos, encode_value, retdest) %stack (value_ptr, rlp_pos, encode_value, retdest)
-> (encode_value, rlp_pos, value_ptr, encode_node_leaf_after_encode_value, retdest) -> (encode_value, rlp_pos, value_ptr, encode_node_leaf_after_encode_value, retdest)

View File

@ -39,7 +39,7 @@ global mpt_hash_receipt_trie:
%%after: %%after:
%endmacro %endmacro
encode_account: global encode_account:
// stack: rlp_pos, value_ptr, retdest // stack: rlp_pos, value_ptr, retdest
// First, we compute the length of the RLP data we're about to write. // First, we compute the length of the RLP data we're about to write.
// The nonce and balance fields are variable-length, so we need to load them // The nonce and balance fields are variable-length, so we need to load them
@ -48,7 +48,7 @@ encode_account:
DUP2 %mload_trie_data // nonce = value[0] DUP2 %mload_trie_data // nonce = value[0]
%rlp_scalar_len %rlp_scalar_len
// stack: nonce_rlp_len, rlp_pos, value_ptr, retdest // stack: nonce_rlp_len, rlp_pos, value_ptr, retdest
DUP3 %add_const(1) %mload_trie_data // balance = value[1] DUP3 %increment %mload_trie_data // balance = value[1]
%rlp_scalar_len %rlp_scalar_len
// stack: balance_rlp_len, nonce_rlp_len, rlp_pos, value_ptr, retdest // stack: balance_rlp_len, nonce_rlp_len, rlp_pos, value_ptr, retdest
PUSH 66 // storage_root and code_hash fields each take 1 + 32 bytes PUSH 66 // storage_root and code_hash fields each take 1 + 32 bytes
@ -68,12 +68,17 @@ encode_account:
// stack: nonce, rlp_pos_3, value_ptr, retdest // stack: nonce, rlp_pos_3, value_ptr, retdest
SWAP1 %encode_rlp_scalar SWAP1 %encode_rlp_scalar
// stack: rlp_pos_4, value_ptr, retdest // stack: rlp_pos_4, value_ptr, retdest
DUP2 %add_const(1) %mload_trie_data // balance = value[1] DUP2 %increment %mload_trie_data // balance = value[1]
// stack: balance, rlp_pos_4, value_ptr, retdest // stack: balance, rlp_pos_4, value_ptr, retdest
SWAP1 %encode_rlp_scalar SWAP1 %encode_rlp_scalar
// stack: rlp_pos_5, value_ptr, retdest // stack: rlp_pos_5, value_ptr, retdest
DUP2 %add_const(2) %mload_trie_data // storage_root = value[2] PUSH encode_account_after_hash_storage_trie
// stack: storage_root, rlp_pos_5, value_ptr, retdest PUSH encode_storage_value
DUP4 %add_const(2) %mload_trie_data // storage_root_ptr = value[2]
// stack: storage_root_ptr, encode_storage_value, encode_account_after_hash_storage_trie, rlp_pos_5, value_ptr, retdest
%jump(mpt_hash)
encode_account_after_hash_storage_trie:
// stack: storage_root_digest, rlp_pos_5, value_ptr, retdest
SWAP1 %encode_rlp_256 SWAP1 %encode_rlp_256
// stack: rlp_pos_6, value_ptr, retdest // stack: rlp_pos_6, value_ptr, retdest
SWAP1 %add_const(3) %mload_trie_data // code_hash = value[3] SWAP1 %add_const(3) %mload_trie_data // code_hash = value[3]
@ -88,3 +93,6 @@ encode_txn:
encode_receipt: encode_receipt:
PANIC // TODO PANIC // TODO
encode_storage_value:
PANIC // TODO

View File

@ -15,7 +15,7 @@ global hex_prefix_rlp:
// Compute the length of the hex-prefix string, in bytes: // Compute the length of the hex-prefix string, in bytes:
// hp_len = num_nibbles / 2 + 1 = i + 1 // hp_len = num_nibbles / 2 + 1 = i + 1
DUP1 %add_const(1) DUP1 %increment
// stack: hp_len, i, rlp_pos, num_nibbles, packed_nibbles, terminated, retdest // stack: hp_len, i, rlp_pos, num_nibbles, packed_nibbles, terminated, retdest
// Write the RLP header. // Write the RLP header.
@ -35,7 +35,7 @@ rlp_header_medium:
%mstore_rlp %mstore_rlp
// rlp_pos += 1 // rlp_pos += 1
SWAP2 %add_const(1) SWAP2 SWAP2 %increment SWAP2
%jump(start_loop) %jump(start_loop)
@ -49,7 +49,7 @@ rlp_header_large:
%mstore_rlp %mstore_rlp
DUP1 // value = hp_len DUP1 // value = hp_len
DUP4 %add_const(1) // offset = rlp_pos + 1 DUP4 %increment // offset = rlp_pos + 1
%mstore_rlp %mstore_rlp
// rlp_pos += 2 // rlp_pos += 2
@ -74,7 +74,7 @@ loop:
%mstore_rlp %mstore_rlp
// stack: i, hp_len, rlp_pos, num_nibbles, packed_nibbles, terminated, retdest // stack: i, hp_len, rlp_pos, num_nibbles, packed_nibbles, terminated, retdest
%sub_const(1) %decrement
SWAP4 %shr_const(8) SWAP4 // packed_nibbles >>= 8 SWAP4 %shr_const(8) SWAP4 // packed_nibbles >>= 8
%jump(loop) %jump(loop)

View File

@ -0,0 +1,107 @@
// Return a copy of the given node, with the given key set to the given value.
//
// Pre stack: node_ptr, num_nibbles, key, value_ptr, retdest
// Post stack: updated_node_ptr
global mpt_insert:
// stack: node_ptr, num_nibbles, key, value_ptr, retdest
DUP1 %mload_trie_data
// stack: node_type, node_ptr, num_nibbles, key, value_ptr, retdest
// Increment node_ptr, so it points to the node payload instead of its type.
SWAP1 %increment SWAP1
// stack: node_type, node_payload_ptr, num_nibbles, key, value_ptr, retdest
DUP1 %eq_const(@MPT_NODE_EMPTY) %jumpi(mpt_insert_empty)
DUP1 %eq_const(@MPT_NODE_BRANCH) %jumpi(mpt_insert_branch)
DUP1 %eq_const(@MPT_NODE_EXTENSION) %jumpi(mpt_insert_extension)
DUP1 %eq_const(@MPT_NODE_LEAF) %jumpi(mpt_insert_leaf)
// There's still the MPT_NODE_HASH case, but if we hit a hash node,
// it means the prover failed to provide necessary Merkle data, so panic.
PANIC
mpt_insert_empty:
// stack: node_type, node_payload_ptr, num_nibbles, key, value_ptr, retdest
%pop2
// stack: num_nibbles, key, value_ptr, retdest
// We will append a new leaf node to our MPT tape and return a pointer to it.
%get_trie_data_size
// stack: leaf_ptr, num_nibbles, key, value_ptr, retdest
PUSH @MPT_NODE_LEAF %append_to_trie_data
// stack: leaf_ptr, num_nibbles, key, value_ptr, retdest
SWAP1 %append_to_trie_data
// stack: leaf_ptr, key, value_ptr, retdest
SWAP1 %append_to_trie_data
// stack: leaf_ptr, value_ptr, retdest
SWAP1 %append_to_trie_data
// stack: leaf_ptr, retdest
SWAP1
JUMP
mpt_insert_branch:
// stack: node_type, node_payload_ptr, num_nibbles, key, value_ptr, retdest
%get_trie_data_size
// stack: updated_branch_ptr, node_type, node_payload_ptr, num_nibbles, key, value_ptr, retdest
SWAP1
%append_to_trie_data
// stack: updated_branch_ptr, node_payload_ptr, num_nibbles, key, value_ptr, retdest
SWAP1
// stack: node_payload_ptr, updated_branch_ptr, num_nibbles, key, value_ptr, retdest
// Copy the original node's data to our updated node.
DUP1 %mload_trie_data %append_to_trie_data // Copy child[0]
DUP1 %add_const(1) %mload_trie_data %append_to_trie_data // ...
DUP1 %add_const(2) %mload_trie_data %append_to_trie_data
DUP1 %add_const(3) %mload_trie_data %append_to_trie_data
DUP1 %add_const(4) %mload_trie_data %append_to_trie_data
DUP1 %add_const(5) %mload_trie_data %append_to_trie_data
DUP1 %add_const(6) %mload_trie_data %append_to_trie_data
DUP1 %add_const(7) %mload_trie_data %append_to_trie_data
DUP1 %add_const(8) %mload_trie_data %append_to_trie_data
DUP1 %add_const(9) %mload_trie_data %append_to_trie_data
DUP1 %add_const(10) %mload_trie_data %append_to_trie_data
DUP1 %add_const(11) %mload_trie_data %append_to_trie_data
DUP1 %add_const(12) %mload_trie_data %append_to_trie_data
DUP1 %add_const(13) %mload_trie_data %append_to_trie_data
DUP1 %add_const(14) %mload_trie_data %append_to_trie_data
DUP1 %add_const(15) %mload_trie_data %append_to_trie_data // Copy child[15]
%add_const(16) %mload_trie_data %append_to_trie_data // Copy value_ptr
// At this point, we branch based on whether the key terminates with this branch node.
// stack: updated_branch_ptr, num_nibbles, key, value_ptr, retdest
DUP2 %jumpi(mpt_insert_branch_nonterminal)
// The key terminates here, so the value will be placed right in our (updated) branch node.
// stack: updated_branch_ptr, num_nibbles, key, value_ptr, retdest
SWAP3
// stack: value_ptr, num_nibbles, key, updated_branch_ptr, retdest
DUP4 %add_const(17)
// stack: updated_branch_value_ptr_ptr, value_ptr, num_nibbles, key, updated_branch_ptr, retdest
%mstore_trie_data
// stack: num_nibbles, key, updated_branch_ptr, retdest
%pop2
// stack: updated_branch_ptr, retdest
SWAP1
JUMP
mpt_insert_branch_nonterminal:
// The key continues, so we split off the first (most significant) nibble,
// and recursively insert into the child associated with that nibble.
// stack: updated_branch_ptr, num_nibbles, key, value_ptr, retdest
%stack (updated_branch_ptr, num_nibbles, key) -> (num_nibbles, key, updated_branch_ptr)
%split_first_nibble
// stack: first_nibble, num_nibbles, key, updated_branch_ptr, value_ptr, retdest
DUP4 %increment ADD
// stack: child_ptr_ptr, num_nibbles, key, updated_branch_ptr, value_ptr, retdest
%stack (child_ptr_ptr, num_nibbles, key, updated_branch_ptr, value_ptr)
-> (child_ptr_ptr, num_nibbles, key, value_ptr,
mpt_insert_branch_nonterminal_after_recursion,
child_ptr_ptr, updated_branch_ptr)
%mload_trie_data // Deref child_ptr_ptr, giving child_ptr
%jump(mpt_insert)
mpt_insert_branch_nonterminal_after_recursion:
// stack: updated_child_ptr, child_ptr_ptr, updated_branch_ptr, retdest
SWAP1 %mstore_trie_data // Store the pointer to the updated child.
// stack: updated_branch_ptr, retdest
SWAP1
JUMP

View File

@ -0,0 +1,201 @@
/*
Insert into an extension node.
The high-level logic can be expressed with the following pseudocode:
common_len, common_key, node_len, node_key, insert_len, insert_key =
split_common_prefix(node_len, node_key, insert_len, insert_key)
if node_len == 0:
new_node = insert(node_child, insert_len, insert_key, insert_value)
else:
new_node = [MPT_TYPE_BRANCH] + [0] * 17
// Process the node's child.
if node_len > 1:
// The node key continues with multiple nibbles left, so we can't place
// node_child directly in the branch, but need an extension for it.
node_key_first, node_len, node_key = split_first_nibble(node_len, node_key)
new_node[node_key_first + 1] = [MPT_TYPE_EXTENSION, node_len, node_key, node_child]
else:
// The remaining node_key is a single nibble, so we can place node_child directly in the branch.
new_node[node_key + 1] = node_child
// Process the inserted entry.
if insert_len > 0:
// The insert key continues. Add a leaf node for it.
insert_key_first, insert_len, insert_key = split_first_nibble(insert_len, insert_key)
new_node[insert_key_first + 1] = [MPT_TYPE_LEAF, insert_len, insert_key, insert_value]
else:
new_node[17] = insert_value
if common_len > 0:
return [MPT_TYPE_EXTENSION, common_len, common_key, new_node]
else:
return new_node
*/
global mpt_insert_extension:
// stack: node_type, node_payload_ptr, insert_len, insert_key, insert_value_ptr, retdest
POP
// stack: node_payload_ptr, insert_len, insert_key, insert_value_ptr, retdest
// We start by loading the extension node's three fields: node_len, node_key, node_child_ptr
DUP1 %add_const(2) %mload_trie_data
// stack: node_child_ptr, node_payload_ptr, insert_len, insert_key, insert_value_ptr, retdest
%stack (node_child_ptr, node_payload_ptr, insert_len, insert_key)
-> (node_payload_ptr, insert_len, insert_key, node_child_ptr)
// stack: node_payload_ptr, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest
DUP1 %increment %mload_trie_data
// stack: node_key, node_payload_ptr, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest
SWAP1 %mload_trie_data
// stack: node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest
// Next, we split off any key prefix which is common to the node's key and the inserted key.
%split_common_prefix
// stack: common_len, common_key, node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest
// Now we branch based on whether the node key continues beyond the common prefix.
DUP3 %jumpi(node_key_continues)
// The node key does not continue. In this case we recurse. Pseudocode:
// new_node = insert(node_child, insert_len, insert_key, insert_value)
// and then proceed to maybe_add_extension_for_common_key.
// stack: common_len, common_key, node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest
PUSH maybe_add_extension_for_common_key
DUP9 // insert_value_ptr
DUP8 // insert_key
DUP8 // insert_len
DUP11 // node_child_ptr
%jump(mpt_insert)
node_key_continues:
// stack: common_len, common_key, node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest
// Allocate new_node, a branch node which is initially empty
// Pseudocode: new_node = [MPT_TYPE_BRANCH] + [0] * 17
%get_trie_data_size // pointer to the branch node we're about to create
PUSH @MPT_NODE_BRANCH %append_to_trie_data
%rep 17
PUSH 0 %append_to_trie_data
%endrep
process_node_child:
// stack: new_node_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest
// We want to check if node_len > 1. We already know node_len > 0 since we're in node_key_continues,
// so it suffices to check 1 - node_len != 0
DUP4 // node_len
PUSH 1 SUB
%jumpi(node_key_continues_multiple_nibbles)
// If we got here, node_len = 1.
// Pseudocode: new_node[node_key + 1] = node_child
// stack: new_node_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest
DUP8 // node_child_ptr
DUP2 // new_node_ptr
%increment
DUP7 // node_key
ADD
%mstore_trie_data
// stack: new_node_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest
%jump(process_inserted_entry)
node_key_continues_multiple_nibbles:
// stack: new_node_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest
// Pseudocode: node_key_first, node_len, node_key = split_first_nibble(node_len, node_key)
// To minimize stack manipulation, we won't actually mutate the node_len, node_key variables in our stack.
// Instead we will duplicate them, and leave the old ones alone; they won't be used.
DUP5 DUP5
// stack: node_len, node_key, new_node_ptr, ...
%split_first_nibble
// stack: node_key_first, node_len, node_key, new_node_ptr, ...
// Pseudocode: new_node[node_key_first + 1] = [MPT_TYPE_EXTENSION, node_len, node_key, node_child]
%get_trie_data_size // pointer to the extension node we're about to create
// stack: ext_node_ptr, node_key_first, node_len, node_key, new_node_ptr, ...
PUSH @MPT_NODE_EXTENSION %append_to_trie_data
// stack: ext_node_ptr, node_key_first, node_len, node_key, new_node_ptr, ...
SWAP2 %append_to_trie_data // Append node_len
// stack: node_key_first, ext_node_ptr, node_key, new_node_ptr, ...
SWAP2 %append_to_trie_data // Append node_key
// stack: ext_node_ptr, node_key_first, new_node_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest
DUP10 %append_to_trie_data // Append node_child_ptr
SWAP1
// stack: node_key_first, ext_node_ptr, new_node_ptr, ...
DUP3 // new_node_ptr
ADD
%increment
// stack: new_node_ptr + node_key_first + 1, ext_node_ptr, new_node_ptr, ...
%mstore_trie_data
%jump(process_inserted_entry)
process_inserted_entry:
// stack: new_node_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest
DUP6 // insert_len
%jumpi(insert_key_continues)
// If we got here, insert_len = 0, so we store the inserted value directly in our new branch node.
// Pseudocode: new_node[17] = insert_value
DUP9 // insert_value_ptr
DUP2 // new_node_ptr
%add_const(17)
%mstore_trie_data
%jump(maybe_add_extension_for_common_key)
insert_key_continues:
// stack: new_node_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest
// Pseudocode: insert_key_first, insert_len, insert_key = split_first_nibble(insert_len, insert_key)
// To minimize stack manipulation, we won't actually mutate the node_len, node_key variables in our stack.
// Instead we will duplicate them, and leave the old ones alone; they won't be used.
DUP7 DUP7
// stack: insert_len, insert_key, new_node_ptr, ...
%split_first_nibble
// stack: insert_key_first, insert_len, insert_key, new_node_ptr, ...
// Pseudocode: new_node[insert_key_first + 1] = [MPT_TYPE_LEAF, insert_len, insert_key, insert_value]
%get_trie_data_size // pointer to the leaf node we're about to create
// stack: leaf_node_ptr, insert_key_first, insert_len, insert_key, new_node_ptr, ...
PUSH @MPT_NODE_LEAF %append_to_trie_data
// stack: leaf_node_ptr, insert_key_first, insert_len, insert_key, new_node_ptr, ...
SWAP2 %append_to_trie_data // Append insert_len
// stack: insert_key_first, leaf_node_ptr, insert_key, new_node_ptr, ...
SWAP2 %append_to_trie_data // Append insert_key
// stack: leaf_node_ptr, insert_key_first, new_node_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest
DUP11 %append_to_trie_data // Append insert_value_ptr
SWAP1
// stack: insert_key_first, leaf_node_ptr, new_node_ptr, ...
DUP3 // new_node_ptr
ADD
%increment
// stack: new_node_ptr + insert_key_first + 1, leaf_node_ptr, new_node_ptr, ...
%mstore_trie_data
%jump(maybe_add_extension_for_common_key)
maybe_add_extension_for_common_key:
// stack: new_node_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest
// If common_len > 0, we need to add an extension node.
DUP2 %jumpi(add_extension_for_common_key)
// Otherwise, we simply return new_node_ptr.
SWAP8
%pop8
// stack: new_node_ptr, retdest
SWAP1
JUMP
add_extension_for_common_key:
// stack: new_node_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest
// Pseudocode: return [MPT_TYPE_EXTENSION, common_len, common_key, new_node]
%get_trie_data_size // pointer to the extension node we're about to create
// stack: extension_ptr, new_node_ptr, common_len, common_key, ...
PUSH @MPT_NODE_EXTENSION %append_to_trie_data
SWAP2 %append_to_trie_data // Append common_len to our node
// stack: new_node_ptr, extension_ptr, common_key, ...
SWAP2 %append_to_trie_data // Append common_key to our node
// stack: extension_ptr, new_node_ptr, ...
SWAP1 %append_to_trie_data // Append new_node_ptr to our node
// stack: extension_ptr, node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest
SWAP6
%pop6
// stack: extension_ptr, retdest
SWAP1
JUMP

View File

@ -0,0 +1,193 @@
/*
Insert into a leaf node.
The high-level logic can be expressed with the following pseudocode:
if node_len == insert_len && node_key == insert_key:
return Leaf[node_key, insert_value]
common_len, common_key, node_len, node_key, insert_len, insert_key =
split_common_prefix(node_len, node_key, insert_len, insert_key)
branch = [MPT_TYPE_BRANCH] + [0] * 17
// Process the node's entry.
if node_len > 0:
node_key_first, node_len, node_key = split_first_nibble(node_len, node_key)
branch[node_key_first + 1] = [MPT_TYPE_LEAF, node_len, node_key, node_value]
else:
branch[17] = node_value
// Process the inserted entry.
if insert_len > 0:
insert_key_first, insert_len, insert_key = split_first_nibble(insert_len, insert_key)
branch[insert_key_first + 1] = [MPT_TYPE_LEAF, insert_len, insert_key, insert_value]
else:
branch[17] = insert_value
// Add an extension node if there is a common prefix.
if common_len > 0:
return [MPT_TYPE_EXTENSION, common_len, common_key, branch]
else:
return branch
*/
global mpt_insert_leaf:
// stack: node_type, node_payload_ptr, insert_len, insert_key, insert_value_ptr, retdest
POP
// stack: node_payload_ptr, insert_len, insert_key, insert_value_ptr, retdest
%stack (node_payload_ptr, insert_len, insert_key) -> (insert_len, insert_key, node_payload_ptr)
// stack: insert_len, insert_key, node_payload_ptr, insert_value_ptr, retdest
DUP3 %increment %mload_trie_data
// stack: node_key, insert_len, insert_key, node_payload_ptr, insert_value_ptr, retdest
DUP4 %mload_trie_data
// stack: node_len, node_key, insert_len, insert_key, node_payload_ptr, insert_value_ptr, retdest
// If the keys match, i.e. node_len == insert_len && node_key == insert_key,
// then we're simply replacing the leaf node's value. Since this is a common
// case, it's best to detect it early. Calling %split_common_prefix could be
// expensive as leaf keys tend to be long.
DUP1 DUP4 EQ // node_len == insert_len
DUP3 DUP6 EQ // node_key == insert_key
MUL // Cheaper than AND
// stack: keys_match, node_len, node_key, insert_len, insert_key, node_payload_ptr, insert_value_ptr, retdest
%jumpi(keys_match)
// Replace node_payload_ptr with node_value, which is node_payload[2].
// stack: node_len, node_key, insert_len, insert_key, node_payload_ptr, insert_value_ptr, retdest
SWAP4
%add_const(2)
%mload_trie_data
SWAP4
// stack: node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest
// Split off any common prefix between the node key and the inserted key.
%split_common_prefix
// stack: common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest
// For the remaining cases, we will need a new branch node since the two keys diverge.
// We may also need an extension node above it (if common_len > 0); we will handle that later.
// For now, we allocate the branch node, initially with no children or value.
%get_trie_data_size // pointer to the branch node we're about to create
PUSH @MPT_NODE_BRANCH %append_to_trie_data
%rep 17
PUSH 0 %append_to_trie_data
%endrep
// stack: branch_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest
// Now, we branch based on whether each key continues beyond the common
// prefix, starting with the node key.
process_node_entry:
DUP4 // node_len
%jumpi(node_key_continues)
// stack: branch_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest
// branch[17] = node_value_ptr
DUP8 // node_value_ptr
DUP2 // branch_ptr
%add_const(17)
%mstore_trie_data
process_inserted_entry:
DUP6 // insert_len
%jumpi(insert_key_continues)
// stack: branch_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest
// branch[17] = insert_value_ptr
DUP9 // insert_value_ptr
DUP2 // branch_ptr
%add_const(17)
%mstore_trie_data
maybe_add_extension_for_common_key:
// stack: branch_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest
// If common_len > 0, we need to add an extension node.
DUP2 %jumpi(add_extension_for_common_key)
// Otherwise, we simply return branch_ptr.
SWAP8
%pop8
// stack: branch_ptr, retdest
SWAP1
JUMP
add_extension_for_common_key:
// stack: branch_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest
// Pseudocode: return [MPT_TYPE_EXTENSION, common_len, common_key, branch]
%get_trie_data_size // pointer to the extension node we're about to create
// stack: extension_ptr, branch_ptr, common_len, common_key, ...
PUSH @MPT_NODE_EXTENSION %append_to_trie_data
SWAP2 %append_to_trie_data // Append common_len to our node
// stack: branch_ptr, extension_ptr, common_key, ...
SWAP2 %append_to_trie_data // Append common_key to our node
// stack: extension_ptr, branch_ptr, ...
SWAP1 %append_to_trie_data // Append branch_ptr to our node
// stack: extension_ptr, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest
SWAP6
%pop6
// stack: extension_ptr, retdest
SWAP1
JUMP
node_key_continues:
// stack: branch_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest
// branch[node_key_first + 1] = Leaf[node_len, node_key, node_value]
// To minimize stack manipulation, we won't actually mutate the node_len, node_key variables in our stack.
// Instead we will duplicate them, and leave the old ones alone; they won't be used.
DUP5 DUP5
// stack: node_len, node_key, branch_ptr, ...
%split_first_nibble
// stack: node_key_first, node_len, node_key, branch_ptr, ...
%get_trie_data_size // pointer to the leaf node we're about to create
// stack: leaf_ptr, node_key_first, node_len, node_key, branch_ptr, ...
SWAP1
DUP5 // branch_ptr
%increment // Skip over node type field
ADD // Add node_key_first
%mstore_trie_data
// stack: node_len, node_key, branch_ptr, ...
PUSH @MPT_NODE_LEAF %append_to_trie_data
%append_to_trie_data // Append node_len to our leaf node
%append_to_trie_data // Append node_key to our leaf node
// stack: branch_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest
DUP8 %append_to_trie_data // Append node_value_ptr to our leaf node
%jump(process_inserted_entry)
insert_key_continues:
// stack: branch_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest
// branch[insert_key_first + 1] = Leaf[insert_len, insert_key, insert_value]
// To minimize stack manipulation, we won't actually mutate the insert_len, insert_key variables in our stack.
// Instead we will duplicate them, and leave the old ones alone; they won't be used.
DUP7 DUP7
// stack: insert_len, insert_key, branch_ptr, ...
%split_first_nibble
// stack: insert_key_first, insert_len, insert_key, branch_ptr, ...
%get_trie_data_size // pointer to the leaf node we're about to create
// stack: leaf_ptr, insert_key_first, insert_len, insert_key, branch_ptr, ...
SWAP1
DUP5 // branch_ptr
%increment // Skip over node type field
ADD // Add insert_key_first
%mstore_trie_data
// stack: insert_len, insert_key, branch_ptr, ...
PUSH @MPT_NODE_LEAF %append_to_trie_data
%append_to_trie_data // Append insert_len to our leaf node
%append_to_trie_data // Append insert_key to our leaf node
// stack: branch_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest
DUP9 %append_to_trie_data // Append insert_value_ptr to our leaf node
%jump(maybe_add_extension_for_common_key)
keys_match:
// The keys match exactly, so we simply create a new leaf node with the new value.xs
// stack: node_len, node_key, insert_len, insert_key, node_payload_ptr, insert_value_ptr, retdest
%stack (node_len, node_key, insert_len, insert_key, node_payload_ptr, insert_value_ptr)
-> (node_len, node_key, insert_value_ptr)
// stack: common_len, common_key, insert_value_ptr, retdest
%get_trie_data_size // pointer to the leaf node we're about to create
// stack: updated_leaf_ptr, common_len, common_key, insert_value_ptr, retdest
PUSH @MPT_NODE_LEAF %append_to_trie_data
SWAP1 %append_to_trie_data // Append common_len to our leaf node
SWAP1 %append_to_trie_data // Append common_key to our leaf node
SWAP1 %append_to_trie_data // Append insert_value_ptr to our leaf node
// stack: updated_leaf_ptr, retdestx
SWAP1
JUMP

View File

@ -0,0 +1,14 @@
// Insertion logic specific to a particular trie.
// Mutate the state trie, inserting the given key-value pair.
global mpt_insert_state_trie:
// stack: num_nibbles, key, value_ptr, retdest
%stack (num_nibbles, key, value_ptr)
-> (num_nibbles, key, value_ptr, mpt_insert_state_trie_save)
%mload_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT)
// stack: state_root_ptr, num_nibbles, key, value_ptr, mpt_insert_state_trie_save, retdest
%jump(mpt_insert)
mpt_insert_state_trie_save:
// stack: updated_node_ptr, retdest
%mstore_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT)
JUMP

View File

@ -1,6 +1,3 @@
// TODO: Receipt trie leaves are variable-length, so we need to be careful not
// to permit buffer over-reads.
// Load all partial trie data from prover inputs. // Load all partial trie data from prover inputs.
global load_all_mpts: global load_all_mpts:
// stack: retdest // stack: retdest
@ -9,49 +6,20 @@ global load_all_mpts:
PUSH 1 PUSH 1
%set_trie_data_size %set_trie_data_size
%load_mpt_and_return_root_ptr %mstore_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) %load_mpt(mpt_load_state_trie_value) %mstore_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT)
%load_mpt_and_return_root_ptr %mstore_global_metadata(@GLOBAL_METADATA_TXN_TRIE_ROOT) %load_mpt(mpt_load_txn_trie_value) %mstore_global_metadata(@GLOBAL_METADATA_TXN_TRIE_ROOT)
%load_mpt_and_return_root_ptr %mstore_global_metadata(@GLOBAL_METADATA_RECEIPT_TRIE_ROOT) %load_mpt(mpt_load_receipt_trie_value) %mstore_global_metadata(@GLOBAL_METADATA_RECEIPT_TRIE_ROOT)
PROVER_INPUT(mpt)
// stack: num_storage_tries, retdest
DUP1 %mstore_global_metadata(@GLOBAL_METADATA_NUM_STORAGE_TRIES)
// stack: num_storage_tries, retdest
PUSH 0 // i = 0
// stack: i, num_storage_tries, retdest
storage_trie_loop:
DUP2 DUP2 EQ
// stack: i == num_storage_tries, i, num_storage_tries, retdest
%jumpi(storage_trie_loop_end)
// stack: i, num_storage_tries, retdest
PROVER_INPUT(mpt)
// stack: storage_trie_addr, i, num_storage_tries, retdest
DUP2
// stack: i, storage_trie_addr, i, num_storage_tries, retdest
%mstore_kernel(@SEGMENT_STORAGE_TRIE_ADDRS)
// stack: i, num_storage_tries, retdest
%load_mpt_and_return_root_ptr
// stack: root_ptr, i, num_storage_tries, retdest
DUP2
// stack: i, root_ptr, i, num_storage_tries, retdest
%mstore_kernel(@SEGMENT_STORAGE_TRIE_PTRS)
// stack: i, num_storage_tries, retdest
%jump(storage_trie_loop)
storage_trie_loop_end:
// stack: i, num_storage_tries, retdest
%pop2
// stack: retdest // stack: retdest
JUMP JUMP
// Load an MPT from prover inputs. // Load an MPT from prover inputs.
// Pre stack: retdest // Pre stack: load_value, retdest
// Post stack: (empty) // Post stack: node_ptr
load_mpt: global load_mpt:
// stack: retdest // stack: load_value, retdest
PROVER_INPUT(mpt) PROVER_INPUT(mpt)
// stack: node_type, retdest // stack: node_type, load_value, retdest
DUP1 %append_to_trie_data
// stack: node_type, retdest
DUP1 %eq_const(@MPT_NODE_EMPTY) %jumpi(load_mpt_empty) DUP1 %eq_const(@MPT_NODE_EMPTY) %jumpi(load_mpt_empty)
DUP1 %eq_const(@MPT_NODE_BRANCH) %jumpi(load_mpt_branch) DUP1 %eq_const(@MPT_NODE_BRANCH) %jumpi(load_mpt_branch)
@ -61,121 +29,145 @@ load_mpt:
PANIC // Invalid node type PANIC // Invalid node type
load_mpt_empty: load_mpt_empty:
// stack: node_type, retdest // TRIE_DATA[0] = 0, and an empty node has type 0, so we can simply return the null pointer.
POP %stack (node_type, load_value, retdest) -> (retdest, 0)
// stack: retdest
JUMP JUMP
load_mpt_branch: load_mpt_branch:
// stack: node_type, retdest // stack: node_type, load_value, retdest
POP %get_trie_data_size
// stack: retdest // stack: node_ptr, node_type, load_value, retdest
SWAP1 %append_to_trie_data
// stack: node_ptr, load_value, retdest
// Save the offset of our 16 child pointers so we can write them later. // Save the offset of our 16 child pointers so we can write them later.
// Then advance out current trie pointer beyond them, so we can load the // Then advance our current trie pointer beyond them, so we can load the
// value and have it placed after our child pointers. // value and have it placed after our child pointers.
%get_trie_data_size %get_trie_data_size
// stack: ptr_children, retdest // stack: children_ptr, node_ptr, load_value, retdest
DUP1 %add_const(16) DUP1 %add_const(17) // Skip over 16 children plus the value pointer
// stack: ptr_leaf, ptr_children, retdest // stack: end_of_branch_ptr, children_ptr, node_ptr, load_value, retdest
%set_trie_data_size DUP1 %set_trie_data_size
// stack: ptr_children, retdest // Now the top of the stack points to where the branch node will end and the
%load_leaf_value // value will begin, if there is a value. But we need to ask the prover if a
// value is present, and point to null if not.
// stack: end_of_branch_ptr, children_ptr, node_ptr, load_value, retdest
PROVER_INPUT(mpt)
// stack: is_value_present, end_of_branch_ptr, children_ptr, node_ptr, load_value, retdest
%jumpi(load_mpt_branch_value_present)
// There is no value present, so value_ptr = null.
%stack (end_of_branch_ptr) -> (0)
// stack: value_ptr, children_ptr, node_ptr, load_value, retdest
%jump(load_mpt_branch_after_load_value)
load_mpt_branch_value_present:
// stack: value_ptr, children_ptr, node_ptr, load_value, retdest
PUSH load_mpt_branch_after_load_value
DUP5 // load_value
JUMP
load_mpt_branch_after_load_value:
// stack: value_ptr, children_ptr, node_ptr, load_value, retdest
SWAP1
// stack: children_ptr, value_ptr, node_ptr, load_value, retdest
// Load the 16 children. // Load the 16 children.
%rep 16 %rep 16
%load_mpt_and_return_root_ptr DUP4 // load_value
// stack: child_ptr, ptr_next_child, retdest %load_mpt
// stack: child_ptr, next_child_ptr_ptr, value_ptr, node_ptr, load_value, retdest
DUP2 DUP2
// stack: ptr_next_child, child_ptr, ptr_next_child, retdest // stack: next_child_ptr_ptr, child_ptr, next_child_ptr_ptr, value_ptr, node_ptr, load_value, retdest
%mstore_trie_data %mstore_trie_data
// stack: ptr_next_child, retdest // stack: next_child_ptr_ptr, value_ptr, node_ptr, load_value, retdest
%increment %increment
// stack: ptr_next_child, retdest // stack: next_child_ptr_ptr, value_ptr, node_ptr, load_value, retdest
%endrep %endrep
// stack: ptr_next_child, retdest // stack: value_ptr_ptr, value_ptr, node_ptr, load_value, retdest
POP %mstore_trie_data
%stack (node_ptr, load_value, retdest) -> (retdest, node_ptr)
JUMP JUMP
load_mpt_extension: load_mpt_extension:
// stack: node_type, retdest // stack: node_type, load_value, retdest
POP %get_trie_data_size
// stack: retdest // stack: node_ptr, node_type, load_value, retdest
SWAP1 %append_to_trie_data
// stack: node_ptr, load_value, retdest
PROVER_INPUT(mpt) // read num_nibbles PROVER_INPUT(mpt) // read num_nibbles
%append_to_trie_data %append_to_trie_data
PROVER_INPUT(mpt) // read packed_nibbles PROVER_INPUT(mpt) // read packed_nibbles
%append_to_trie_data %append_to_trie_data
// stack: retdest // stack: node_ptr, load_value, retdest
// Let i be the current trie data size. We still need to expand this node by
// one element, appending our child pointer. Thus our child node will start
// at i + 1. So we will set our child pointer to i + 1.
%get_trie_data_size %get_trie_data_size
%add_const(1) // stack: child_ptr_ptr, node_ptr, load_value, retdest
%append_to_trie_data // Increment trie_data_size, to leave room for child_ptr_ptr, before we load our child.
// stack: retdest DUP1 %increment %set_trie_data_size
%stack (child_ptr_ptr, node_ptr, load_value, retdest)
%load_mpt -> (load_value, load_mpt_extension_after_load_mpt,
// stack: retdest child_ptr_ptr, retdest, node_ptr)
%jump(load_mpt)
load_mpt_extension_after_load_mpt:
// stack: child_ptr, child_ptr_ptr, retdest, node_ptr
SWAP1 %mstore_trie_data
// stack: retdest, node_ptr
JUMP JUMP
load_mpt_leaf: load_mpt_leaf:
// stack: node_type, retdest // stack: node_type, load_value, retdest
POP %get_trie_data_size
// stack: retdest // stack: node_ptr, node_type, load_value, retdest
SWAP1 %append_to_trie_data
// stack: node_ptr, load_value, retdest
PROVER_INPUT(mpt) // read num_nibbles PROVER_INPUT(mpt) // read num_nibbles
%append_to_trie_data %append_to_trie_data
PROVER_INPUT(mpt) // read packed_nibbles PROVER_INPUT(mpt) // read packed_nibbles
%append_to_trie_data %append_to_trie_data
// stack: retdest // stack: node_ptr, load_value, retdest
%load_leaf_value // We save value_ptr_ptr = get_trie_data_size, then increment trie_data_size
// stack: retdest // to skip over the slot for value_ptr_ptr. We will write to value_ptr_ptr
// after the load_value call.
%get_trie_data_size
// stack: value_ptr_ptr, node_ptr, load_value, retdest
DUP1 %increment
// stack: value_ptr, value_ptr_ptr, node_ptr, load_value, retdest
DUP1 %set_trie_data_size
// stack: value_ptr, value_ptr_ptr, node_ptr, load_value, retdest
%stack (value_ptr, value_ptr_ptr, node_ptr, load_value, retdest)
-> (load_value, load_mpt_leaf_after_load_value,
value_ptr_ptr, value_ptr, retdest, node_ptr)
JUMP
load_mpt_leaf_after_load_value:
// stack: value_ptr_ptr, value_ptr, retdest, node_ptr
%mstore_trie_data
// stack: retdest, node_ptr
JUMP JUMP
load_mpt_digest: load_mpt_digest:
// stack: node_type, retdest // stack: node_type, load_value, retdest
POP %get_trie_data_size
// stack: retdest // stack: node_ptr, node_type, load_value, retdest
SWAP1 %append_to_trie_data
// stack: node_ptr, load_value, retdest
PROVER_INPUT(mpt) // read digest PROVER_INPUT(mpt) // read digest
%append_to_trie_data %append_to_trie_data
// stack: retdest %stack (node_ptr, load_value, retdest) -> (retdest, node_ptr)
JUMP JUMP
// Convenience macro to call load_mpt and return where we left off. // Convenience macro to call load_mpt and return where we left off.
// Pre stack: load_value
// Post stack: node_ptr
%macro load_mpt %macro load_mpt
PUSH %%after %stack (load_value) -> (load_value, %%after)
%jump(load_mpt) %jump(load_mpt)
%%after: %%after:
%endmacro %endmacro
%macro load_mpt_and_return_root_ptr // Convenience macro to call load_mpt and return where we left off.
// stack: (empty) // Pre stack: (empty)
%get_trie_data_size // Post stack: node_ptr
// stack: ptr %macro load_mpt(load_value)
%load_mpt PUSH %%after
// stack: ptr PUSH $load_value
%endmacro %jump(load_mpt)
%%after:
// Load a leaf from prover input, and append it to trie data.
%macro load_leaf_value
// stack: (empty)
PROVER_INPUT(mpt)
// stack: leaf_len
DUP1 %append_to_trie_data
// stack: leaf_len
%%loop:
DUP1 ISZERO
// stack: leaf_len == 0, leaf_len
%jumpi(%%finish)
// stack: leaf_len
PROVER_INPUT(mpt)
// stack: leaf_part, leaf_len
%append_to_trie_data
// stack: leaf_len
%sub_const(1)
// stack: leaf_len'
%jump(%%loop)
%%finish:
POP
// stack: (empty)
%endmacro %endmacro

View File

@ -0,0 +1,40 @@
global mpt_load_state_trie_value:
// stack: retdest
// Load and append the nonce and balance.
PROVER_INPUT(mpt) %append_to_trie_data
PROVER_INPUT(mpt) %append_to_trie_data
// Now increment the trie data size by 2, to leave room for our storage trie
// pointer and code hash fields, before calling load_mpt which will append
// our storage trie data.
%get_trie_data_size
// stack: storage_trie_ptr_ptr, retdest
DUP1 %add_const(2)
// stack: storage_trie_ptr, storage_trie_ptr_ptr, retdest
%set_trie_data_size
// stack: storage_trie_ptr_ptr, retdest
%load_mpt(mpt_load_storage_trie_value)
// stack: storage_trie_ptr, storage_trie_ptr_ptr, retdest
DUP2 %mstore_trie_data
// stack: storage_trie_ptr_ptr, retdest
%increment
// stack: code_hash_ptr, retdest
PROVER_INPUT(mpt)
// stack: code_hash, code_hash_ptr, retdest
SWAP1 %mstore_trie_data
// stack: retdest
JUMP
global mpt_load_txn_trie_value:
// stack: retdest
PANIC // TODO
global mpt_load_receipt_trie_value:
// stack: retdest
PANIC // TODO
global mpt_load_storage_trie_value:
// stack: retdest
PANIC // TODO

View File

@ -1,6 +1,6 @@
// Given an address, return a pointer to the associated account data, which // Given an address, return a pointer to the associated account data, which
// consists of four words (nonce, balance, storage_root, code_hash), in the // consists of four words (nonce, balance, storage_root, code_hash), in the
// state trie. Returns 0 if the address is not found. // state trie. Returns null if the address is not found.
global mpt_read_state_trie: global mpt_read_state_trie:
// stack: addr, retdest // stack: addr, retdest
// The key is the hash of the address. Since KECCAK_GENERAL takes input from // The key is the hash of the address. Since KECCAK_GENERAL takes input from
@ -24,14 +24,14 @@ mpt_read_state_trie_after_mstore:
// - the key, as a U256 // - the key, as a U256
// - the number of nibbles in the key (should start at 64) // - the number of nibbles in the key (should start at 64)
// //
// This function returns a pointer to the leaf, or 0 if the key is not found. // This function returns a pointer to the value, or 0 if the key is not found.
global mpt_read: global mpt_read:
// stack: node_ptr, num_nibbles, key, retdest // stack: node_ptr, num_nibbles, key, retdest
DUP1 DUP1
%mload_trie_data %mload_trie_data
// stack: node_type, node_ptr, num_nibbles, key, retdest // stack: node_type, node_ptr, num_nibbles, key, retdest
// Increment node_ptr, so it points to the node payload instead of its type. // Increment node_ptr, so it points to the node payload instead of its type.
SWAP1 %add_const(1) SWAP1 SWAP1 %increment SWAP1
// stack: node_type, node_payload_ptr, num_nibbles, key, retdest // stack: node_type, node_payload_ptr, num_nibbles, key, retdest
DUP1 %eq_const(@MPT_NODE_EMPTY) %jumpi(mpt_read_empty) DUP1 %eq_const(@MPT_NODE_EMPTY) %jumpi(mpt_read_empty)
@ -39,7 +39,7 @@ global mpt_read:
DUP1 %eq_const(@MPT_NODE_EXTENSION) %jumpi(mpt_read_extension) DUP1 %eq_const(@MPT_NODE_EXTENSION) %jumpi(mpt_read_extension)
DUP1 %eq_const(@MPT_NODE_LEAF) %jumpi(mpt_read_leaf) DUP1 %eq_const(@MPT_NODE_LEAF) %jumpi(mpt_read_leaf)
// There's still the MPT_NODE_HASH case, but if we hit a digest node, // There's still the MPT_NODE_HASH case, but if we hit a hash node,
// it means the prover failed to provide necessary Merkle data, so panic. // it means the prover failed to provide necessary Merkle data, so panic.
PANIC PANIC
@ -75,15 +75,8 @@ mpt_read_branch_end_of_key:
%stack (node_payload_ptr, num_nibbles, key, retdest) -> (node_payload_ptr, retdest) %stack (node_payload_ptr, num_nibbles, key, retdest) -> (node_payload_ptr, retdest)
// stack: node_payload_ptr, retdest // stack: node_payload_ptr, retdest
%add_const(16) // skip over the 16 child nodes %add_const(16) // skip over the 16 child nodes
// stack: value_len_ptr, retdest // stack: value_ptr_ptr, retdest
DUP1 %mload_trie_data %mload_trie_data
// stack: value_len, value_len_ptr, retdest
%jumpi(mpt_read_branch_found_value)
// This branch node contains no value, so return null.
%stack (value_len_ptr, retdest) -> (retdest, 0)
mpt_read_branch_found_value:
// stack: value_len_ptr, retdest
%increment
// stack: value_ptr, retdest // stack: value_ptr, retdest
SWAP1 SWAP1
JUMP JUMP
@ -103,7 +96,7 @@ mpt_read_extension:
%mul_const(4) SHR // key_part = key >> (future_nibbles * 4) %mul_const(4) SHR // key_part = key >> (future_nibbles * 4)
DUP1 DUP1
// stack: key_part, key_part, future_nibbles, key, node_payload_ptr, retdest // stack: key_part, key_part, future_nibbles, key, node_payload_ptr, retdest
DUP5 %add_const(1) %mload_trie_data DUP5 %increment %mload_trie_data
// stack: node_key, key_part, key_part, future_nibbles, key, node_payload_ptr, retdest // stack: node_key, key_part, key_part, future_nibbles, key, node_payload_ptr, retdest
EQ // does the first part of our key match the node's key? EQ // does the first part of our key match the node's key?
%jumpi(mpt_read_extension_found) %jumpi(mpt_read_extension_found)
@ -131,7 +124,7 @@ mpt_read_leaf:
// stack: node_payload_ptr, num_nibbles, key, retdest // stack: node_payload_ptr, num_nibbles, key, retdest
DUP1 %mload_trie_data DUP1 %mload_trie_data
// stack: node_num_nibbles, node_payload_ptr, num_nibbles, key, retdest // stack: node_num_nibbles, node_payload_ptr, num_nibbles, key, retdest
DUP2 %add_const(1) %mload_trie_data DUP2 %increment %mload_trie_data
// stack: node_key, node_num_nibbles, node_payload_ptr, num_nibbles, key, retdest // stack: node_key, node_num_nibbles, node_payload_ptr, num_nibbles, key, retdest
SWAP3 SWAP3
// stack: num_nibbles, node_num_nibbles, node_payload_ptr, node_key, key, retdest // stack: num_nibbles, node_num_nibbles, node_payload_ptr, node_key, key, retdest
@ -147,7 +140,9 @@ mpt_read_leaf:
JUMP JUMP
mpt_read_leaf_found: mpt_read_leaf_found:
// stack: node_payload_ptr, retdest // stack: node_payload_ptr, retdest
%add_const(3) // The value is located after num_nibbles, the key, and the value length. %add_const(2) // The value pointer is located after num_nibbles and the key.
// stack: value_ptr_ptr, retdest
%mload_trie_data
// stack: value_ptr, retdest // stack: value_ptr, retdest
SWAP1 SWAP1
JUMP JUMP

View File

@ -28,7 +28,7 @@
%get_trie_data_size %get_trie_data_size
// stack: trie_data_size, value // stack: trie_data_size, value
DUP1 DUP1
%add_const(1) %increment
// stack: trie_data_size', trie_data_size, value // stack: trie_data_size', trie_data_size, value
%set_trie_data_size %set_trie_data_size
// stack: trie_data_size, value // stack: trie_data_size, value
@ -45,7 +45,7 @@
// return (first_nibble, num_nibbles, key) // return (first_nibble, num_nibbles, key)
%macro split_first_nibble %macro split_first_nibble
// stack: num_nibbles, key // stack: num_nibbles, key
%sub_const(1) // num_nibbles -= 1 %decrement // num_nibbles -= 1
// stack: num_nibbles, key // stack: num_nibbles, key
DUP2 DUP2
// stack: key, num_nibbles, key // stack: key, num_nibbles, key
@ -72,3 +72,96 @@
POP POP
// stack: first_nibble, num_nibbles, key // stack: first_nibble, num_nibbles, key
%endmacro %endmacro
// Split off the common prefix among two key parts.
//
// Pre stack: len_1, key_1, len_2, key_2
// Post stack: len_common, key_common, len_1, key_1, len_2, key_2
//
// Roughly equivalent to
// def split_common_prefix(len_1, key_1, len_2, key_2):
// bits_1 = len_1 * 4
// bits_2 = len_2 * 4
// len_common = 0
// key_common = 0
// while True:
// if bits_1 * bits_2 == 0:
// break
// first_nib_1 = (key_1 >> (bits_1 - 4)) & 0xF
// first_nib_2 = (key_2 >> (bits_2 - 4)) & 0xF
// if first_nib_1 != first_nib_2:
// break
// len_common += 1
// key_common = key_common * 16 + first_nib_1
// bits_1 -= 4
// bits_2 -= 4
// key_1 -= (first_nib_1 << bits_1)
// key_2 -= (first_nib_2 << bits_2)
// len_1 = bits_1 // 4
// len_2 = bits_2 // 4
// return (len_common, key_common, len_1, key_1, len_2, key_2)
%macro split_common_prefix
// stack: len_1, key_1, len_2, key_2
%mul_const(4)
SWAP2 %mul_const(4) SWAP2
// stack: bits_1, key_1, bits_2, key_2
PUSH 0
PUSH 0
%%loop:
// stack: len_common, key_common, bits_1, key_1, bits_2, key_2
// if bits_1 * bits_2 == 0: break
DUP3 DUP6 MUL ISZERO %jumpi(%%return)
// first_nib_2 = (key_2 >> (bits_2 - 4)) & 0xF
DUP6 DUP6 %sub_const(4) SHR %and_const(0xF)
// first_nib_1 = (key_1 >> (bits_1 - 4)) & 0xF
DUP5 DUP5 %sub_const(4) SHR %and_const(0xF)
// stack: first_nib_1, first_nib_2, len_common, key_common, bits_1, key_1, bits_2, key_2
// if first_nib_1 != first_nib_2: break
DUP2 DUP2 SUB %jumpi(%%return_with_first_nibs)
// len_common += 1
SWAP2 %increment SWAP2
// key_common = key_common * 16 + first_nib_1
SWAP3
%mul_const(16)
DUP4 ADD
SWAP3
// stack: first_nib_1, first_nib_2, len_common, key_common, bits_1, key_1, bits_2, key_2
// bits_1 -= 4
SWAP4 %sub_const(4) SWAP4
// bits_2 -= 4
SWAP6 %sub_const(4) SWAP6
// stack: first_nib_1, first_nib_2, len_common, key_common, bits_1, key_1, bits_2, key_2
// key_1 -= (first_nib_1 << bits_1)
DUP5 SHL
// stack: first_nib_1 << bits_1, first_nib_2, len_common, key_common, bits_1, key_1, bits_2, key_2
DUP6 SUB
// stack: key_1, first_nib_2, len_common, key_common, bits_1, key_1_old, bits_2, key_2
SWAP5 POP
// stack: first_nib_2, len_common, key_common, bits_1, key_1, bits_2, key_2
// key_2 -= (first_nib_2 << bits_2)
DUP6 SHL
// stack: first_nib_2 << bits_2, len_common, key_common, bits_1, key_1, bits_2, key_2
DUP7 SUB
// stack: key_2, len_common, key_common, bits_1, key_1, bits_2, key_2_old
SWAP6 POP
// stack: len_common, key_common, bits_1, key_1, bits_2, key_2
%jump(%%loop)
%%return_with_first_nibs:
// stack: first_nib_1, first_nib_2, len_common, key_common, bits_1, key_1, bits_2, key_2
%pop2
%%return:
// stack: len_common, key_common, bits_1, key_1, bits_2, key_2
SWAP2 %div_const(4) SWAP2 // bits_1 -> len_1 (in nibbles)
SWAP4 %div_const(4) SWAP4 // bits_2 -> len_2 (in nibbles)
// stack: len_common, key_common, len_1, key_1, len_2, key_2
%endmacro

View File

@ -1,3 +0,0 @@
global mpt_write:
// stack: node_ptr, num_nibbles, key, retdest
// TODO

View File

@ -44,7 +44,7 @@ store_input_stack:
// stack: offset, byte, rem, length, REM_INP // stack: offset, byte, rem, length, REM_INP
%mstore_kernel_general %mstore_kernel_general
// stack: rem, length, REM_INP // stack: rem, length, REM_INP
%sub_const(1) %decrement
DUP1 DUP1
// stack: rem - 1, rem - 1, length, REM_INP // stack: rem - 1, rem - 1, length, REM_INP
%jumpi(store_input_stack) %jumpi(store_input_stack)
@ -66,10 +66,10 @@ store_input:
// stack: offset, byte, rem , ADDR , length // stack: offset, byte, rem , ADDR , length
%mstore_kernel_general %mstore_kernel_general
// stack: rem , ADDR , length // stack: rem , ADDR , length
%sub_const(1) %decrement
// stack: rem-1, ADDR , length // stack: rem-1, ADDR , length
SWAP3 SWAP3
%add_const(1) %increment
SWAP3 SWAP3
// stack: rem-1, ADDR+1, length // stack: rem-1, ADDR+1, length
DUP1 DUP1
@ -90,12 +90,12 @@ global buffer_update:
// stack: get, set, get , set , times , retdest // stack: get, set, get , set , times , retdest
%mupdate_kernel_general %mupdate_kernel_general
// stack: get , set , times , retdest // stack: get , set , times , retdest
%add_const(1) %increment
SWAP1 SWAP1
%add_const(1) %increment
SWAP1 SWAP1
SWAP2 SWAP2
%sub_const(1) %decrement
SWAP2 SWAP2
// stack: get+1, set+1, times-1, retdest // stack: get+1, set+1, times-1, retdest
DUP3 DUP3
@ -112,7 +112,7 @@ global buffer_update:
// stack: offset = N-i, 0, i // stack: offset = N-i, 0, i
%mstore_kernel_general %mstore_kernel_general
// stack: i // stack: i
%sub_const(1) %decrement
DUP1 DUP1
// stack: i-1, i-1 // stack: i-1, i-1
%jumpi($label) %jumpi($label)

View File

@ -14,7 +14,7 @@
global decode_rlp_string_len: global decode_rlp_string_len:
// stack: pos, retdest // stack: pos, retdest
DUP1 DUP1
%mload_current(@SEGMENT_RLP_RAW) %mload_kernel(@SEGMENT_RLP_RAW)
// stack: first_byte, pos, retdest // stack: first_byte, pos, retdest
DUP1 DUP1
%gt_const(0xb7) %gt_const(0xb7)
@ -36,7 +36,7 @@ decode_rlp_string_len_medium:
%sub_const(0x80) %sub_const(0x80)
// stack: len, pos, retdest // stack: len, pos, retdest
SWAP1 SWAP1
%add_const(1) %increment
// stack: pos', len, retdest // stack: pos', len, retdest
%stack (pos, len, retdest) -> (retdest, pos, len) %stack (pos, len, retdest) -> (retdest, pos, len)
JUMP JUMP
@ -47,7 +47,7 @@ decode_rlp_string_len_large:
%sub_const(0xb7) %sub_const(0xb7)
// stack: len_of_len, pos, retdest // stack: len_of_len, pos, retdest
SWAP1 SWAP1
%add_const(1) %increment
// stack: pos', len_of_len, retdest // stack: pos', len_of_len, retdest
%jump(decode_int_given_len) %jump(decode_int_given_len)
@ -89,10 +89,10 @@ global decode_rlp_scalar:
global decode_rlp_list_len: global decode_rlp_list_len:
// stack: pos, retdest // stack: pos, retdest
DUP1 DUP1
%mload_current(@SEGMENT_RLP_RAW) %mload_kernel(@SEGMENT_RLP_RAW)
// stack: first_byte, pos, retdest // stack: first_byte, pos, retdest
SWAP1 SWAP1
%add_const(1) // increment pos %increment // increment pos
SWAP1 SWAP1
// stack: first_byte, pos', retdest // stack: first_byte, pos', retdest
// If first_byte is >= 0xf8, it's a > 55 byte list, and // If first_byte is >= 0xf8, it's a > 55 byte list, and
@ -151,13 +151,13 @@ decode_int_given_len_loop:
// stack: acc << 8, pos, end_pos, retdest // stack: acc << 8, pos, end_pos, retdest
DUP2 DUP2
// stack: pos, acc << 8, pos, end_pos, retdest // stack: pos, acc << 8, pos, end_pos, retdest
%mload_current(@SEGMENT_RLP_RAW) %mload_kernel(@SEGMENT_RLP_RAW)
// stack: byte, acc << 8, pos, end_pos, retdest // stack: byte, acc << 8, pos, end_pos, retdest
ADD ADD
// stack: acc', pos, end_pos, retdest // stack: acc', pos, end_pos, retdest
// Increment pos. // Increment pos.
SWAP1 SWAP1
%add_const(1) %increment
SWAP1 SWAP1
// stack: acc', pos', end_pos, retdest // stack: acc', pos', end_pos, retdest
%jump(decode_int_given_len_loop) %jump(decode_int_given_len_loop)

View File

@ -14,7 +14,7 @@ global encode_rlp_scalar:
// stack: pos, scalar, pos, retdest // stack: pos, scalar, pos, retdest
%mstore_rlp %mstore_rlp
// stack: pos, retdest // stack: pos, retdest
%add_const(1) %increment
// stack: pos', retdest // stack: pos', retdest
SWAP1 SWAP1
JUMP JUMP
@ -76,7 +76,7 @@ encode_rlp_fixed:
%mstore_rlp %mstore_rlp
// stack: len, pos, string, retdest // stack: len, pos, string, retdest
SWAP1 SWAP1
%add_const(1) // increment pos %increment // increment pos
// stack: pos, len, string, retdest // stack: pos, len, string, retdest
%stack (pos, len, string) -> (pos, string, len, encode_rlp_fixed_finish) %stack (pos, len, string) -> (pos, string, len, encode_rlp_fixed_finish)
// stack: context, segment, pos, string, len, encode_rlp_fixed_finish, retdest // stack: context, segment, pos, string, len, encode_rlp_fixed_finish, retdest
@ -159,7 +159,7 @@ global encode_rlp_list_prefix:
// stack: pos, prefix, pos, retdest // stack: pos, prefix, pos, retdest
%mstore_rlp %mstore_rlp
// stack: pos, retdest // stack: pos, retdest
%add_const(1) %increment
SWAP1 SWAP1
JUMP JUMP
encode_rlp_list_prefix_large: encode_rlp_list_prefix_large:
@ -172,7 +172,7 @@ encode_rlp_list_prefix_large:
DUP3 // pos DUP3 // pos
%mstore_rlp %mstore_rlp
// stack: len_of_len, pos, payload_len, retdest // stack: len_of_len, pos, payload_len, retdest
SWAP1 %add_const(1) SWAP1 %increment
// stack: pos', len_of_len, payload_len, retdest // stack: pos', len_of_len, payload_len, retdest
%stack (pos, len_of_len, payload_len) %stack (pos, len_of_len, payload_len)
-> (pos, payload_len, len_of_len, -> (pos, payload_len, len_of_len,
@ -231,7 +231,7 @@ prepend_rlp_list_prefix_big:
SUB SUB
// stack: start_pos, len_of_len, payload_len, end_pos, retdest // stack: start_pos, len_of_len, payload_len, end_pos, retdest
DUP2 %add_const(0xf7) DUP2 %mstore_rlp // rlp[start_pos] = 0xf7 + len_of_len DUP2 %add_const(0xf7) DUP2 %mstore_rlp // rlp[start_pos] = 0xf7 + len_of_len
DUP1 %add_const(1) // start_len_pos = start_pos + 1 DUP1 %increment // start_len_pos = start_pos + 1
%stack (start_len_pos, start_pos, len_of_len, payload_len, end_pos, retdest) %stack (start_len_pos, start_pos, len_of_len, payload_len, end_pos, retdest)
-> (start_len_pos, payload_len, len_of_len, -> (start_len_pos, payload_len, len_of_len,
prepend_rlp_list_prefix_big_done_writing_len, prepend_rlp_list_prefix_big_done_writing_len,
@ -269,7 +269,7 @@ prepend_rlp_list_prefix_big_done_writing_len:
// stack: scalar // stack: scalar
%num_bytes %num_bytes
// stack: scalar_bytes // stack: scalar_bytes
%add_const(1) // Account for the length prefix. %increment // Account for the length prefix.
// stack: rlp_len // stack: rlp_len
%%finish: %%finish:
%endmacro %endmacro

View File

@ -23,9 +23,9 @@ read_rlp_to_memory_loop:
// stack: byte, pos, len, retdest // stack: byte, pos, len, retdest
DUP2 DUP2
// stack: pos, byte, pos, len, retdest // stack: pos, byte, pos, len, retdest
%mstore_current(@SEGMENT_RLP_RAW) %mstore_kernel(@SEGMENT_RLP_RAW)
// stack: pos, len, retdest // stack: pos, len, retdest
%add_const(1) %increment
// stack: pos', len, retdest // stack: pos', len, retdest
%jump(read_rlp_to_memory_loop) %jump(read_rlp_to_memory_loop)

View File

@ -18,14 +18,14 @@ read_txn_from_memory:
// first byte >= 0xc0, so there is no overlap. // first byte >= 0xc0, so there is no overlap.
PUSH 0 PUSH 0
%mload_current(@SEGMENT_RLP_RAW) %mload_kernel(@SEGMENT_RLP_RAW)
%eq_const(1) %eq_const(1)
// stack: first_byte == 1, retdest // stack: first_byte == 1, retdest
%jumpi(process_type_1_txn) %jumpi(process_type_1_txn)
// stack: retdest // stack: retdest
PUSH 0 PUSH 0
%mload_current(@SEGMENT_RLP_RAW) %mload_kernel(@SEGMENT_RLP_RAW)
%eq_const(2) %eq_const(2)
// stack: first_byte == 2, retdest // stack: first_byte == 2, retdest
%jumpi(process_type_2_txn) %jumpi(process_type_2_txn)

View File

@ -44,6 +44,12 @@
%endrep %endrep
%endmacro %endmacro
%macro pop8
%rep 8
POP
%endrep
%endmacro
%macro and_const(c) %macro and_const(c)
// stack: input, ... // stack: input, ...
PUSH $c PUSH $c

View File

@ -21,7 +21,7 @@ pub(crate) enum ContextMetadata {
/// prohibited. /// prohibited.
Static = 8, Static = 8,
/// Pointer to the initial version of the state trie, at the creation of this context. Used when /// Pointer to the initial version of the state trie, at the creation of this context. Used when
/// we need to revert a context. See also `StorageTrieCheckpointPointers`. /// we need to revert a context.
StateTrieCheckpointPointer = 9, StateTrieCheckpointPointer = 9,
} }

View File

@ -18,9 +18,6 @@ pub(crate) enum GlobalMetadata {
TransactionTrieRoot = 5, TransactionTrieRoot = 5,
/// A pointer to the root of the receipt trie within the `TrieData` buffer. /// A pointer to the root of the receipt trie within the `TrieData` buffer.
ReceiptTrieRoot = 6, ReceiptTrieRoot = 6,
/// The number of storage tries involved in these transactions. I.e. the number of values in
/// `StorageTrieAddresses`, `StorageTriePointers` and `StorageTrieCheckpointPointers`.
NumStorageTries = 7,
// The root digests of each Merkle trie before these transactions. // The root digests of each Merkle trie before these transactions.
StateTrieRootDigestBefore = 8, StateTrieRootDigestBefore = 8,
@ -31,6 +28,10 @@ pub(crate) enum GlobalMetadata {
StateTrieRootDigestAfter = 11, StateTrieRootDigestAfter = 11,
TransactionTrieRootDigestAfter = 12, TransactionTrieRootDigestAfter = 12,
ReceiptTrieRootDigestAfter = 13, ReceiptTrieRootDigestAfter = 13,
/// The sizes of the `TrieEncodedChild` and `TrieEncodedChildLen` buffers. In other words, the
/// next available offset in these buffers.
TrieEncodedChildSize = 14,
} }
impl GlobalMetadata { impl GlobalMetadata {
@ -45,13 +46,13 @@ impl GlobalMetadata {
Self::StateTrieRoot, Self::StateTrieRoot,
Self::TransactionTrieRoot, Self::TransactionTrieRoot,
Self::ReceiptTrieRoot, Self::ReceiptTrieRoot,
Self::NumStorageTries,
Self::StateTrieRootDigestBefore, Self::StateTrieRootDigestBefore,
Self::TransactionTrieRootDigestBefore, Self::TransactionTrieRootDigestBefore,
Self::ReceiptTrieRootDigestBefore, Self::ReceiptTrieRootDigestBefore,
Self::StateTrieRootDigestAfter, Self::StateTrieRootDigestAfter,
Self::TransactionTrieRootDigestAfter, Self::TransactionTrieRootDigestAfter,
Self::ReceiptTrieRootDigestAfter, Self::ReceiptTrieRootDigestAfter,
Self::TrieEncodedChildSize,
] ]
} }
@ -65,7 +66,6 @@ impl GlobalMetadata {
GlobalMetadata::StateTrieRoot => "GLOBAL_METADATA_STATE_TRIE_ROOT", GlobalMetadata::StateTrieRoot => "GLOBAL_METADATA_STATE_TRIE_ROOT",
GlobalMetadata::TransactionTrieRoot => "GLOBAL_METADATA_TXN_TRIE_ROOT", GlobalMetadata::TransactionTrieRoot => "GLOBAL_METADATA_TXN_TRIE_ROOT",
GlobalMetadata::ReceiptTrieRoot => "GLOBAL_METADATA_RECEIPT_TRIE_ROOT", GlobalMetadata::ReceiptTrieRoot => "GLOBAL_METADATA_RECEIPT_TRIE_ROOT",
GlobalMetadata::NumStorageTries => "GLOBAL_METADATA_NUM_STORAGE_TRIES",
GlobalMetadata::StateTrieRootDigestBefore => "GLOBAL_METADATA_STATE_TRIE_DIGEST_BEFORE", GlobalMetadata::StateTrieRootDigestBefore => "GLOBAL_METADATA_STATE_TRIE_DIGEST_BEFORE",
GlobalMetadata::TransactionTrieRootDigestBefore => { GlobalMetadata::TransactionTrieRootDigestBefore => {
"GLOBAL_METADATA_TXN_TRIE_DIGEST_BEFORE" "GLOBAL_METADATA_TXN_TRIE_DIGEST_BEFORE"
@ -80,6 +80,7 @@ impl GlobalMetadata {
GlobalMetadata::ReceiptTrieRootDigestAfter => { GlobalMetadata::ReceiptTrieRootDigestAfter => {
"GLOBAL_METADATA_RECEIPT_TRIE_DIGEST_AFTER" "GLOBAL_METADATA_RECEIPT_TRIE_DIGEST_AFTER"
} }
GlobalMetadata::TrieEncodedChildSize => "TRIE_ENCODED_CHILD_SIZE",
} }
} }
} }

View File

@ -1,3 +1,5 @@
//! An EVM interpreter for testing and debugging purposes.
use std::collections::HashMap; use std::collections::HashMap;
use anyhow::{anyhow, bail, ensure}; use anyhow::{anyhow, bail, ensure};
@ -75,6 +77,7 @@ pub struct Interpreter<'a> {
pub(crate) generation_state: GenerationState<F>, pub(crate) generation_state: GenerationState<F>,
prover_inputs_map: &'a HashMap<usize, ProverInputFn>, prover_inputs_map: &'a HashMap<usize, ProverInputFn>,
pub(crate) halt_offsets: Vec<usize>, pub(crate) halt_offsets: Vec<usize>,
pub(crate) debug_offsets: Vec<usize>,
running: bool, running: bool,
} }
@ -128,6 +131,7 @@ impl<'a> Interpreter<'a> {
prover_inputs_map: prover_inputs, prover_inputs_map: prover_inputs,
context: 0, context: 0,
halt_offsets: vec![DEFAULT_HALT_OFFSET], halt_offsets: vec![DEFAULT_HALT_OFFSET],
debug_offsets: vec![],
running: false, running: false,
} }
} }
@ -168,10 +172,19 @@ impl<'a> Interpreter<'a> {
self.memory.context_memory[0].segments[Segment::GlobalMetadata as usize].get(field as usize) self.memory.context_memory[0].segments[Segment::GlobalMetadata as usize].get(field as usize)
} }
pub(crate) fn set_global_metadata_field(&mut self, field: GlobalMetadata, value: U256) {
self.memory.context_memory[0].segments[Segment::GlobalMetadata as usize]
.set(field as usize, value)
}
pub(crate) fn get_trie_data(&self) -> &[U256] { pub(crate) fn get_trie_data(&self) -> &[U256] {
&self.memory.context_memory[0].segments[Segment::TrieData as usize].content &self.memory.context_memory[0].segments[Segment::TrieData as usize].content
} }
pub(crate) fn get_trie_data_mut(&mut self) -> &mut Vec<U256> {
&mut self.memory.context_memory[0].segments[Segment::TrieData as usize].content
}
pub(crate) fn get_rlp_memory(&self) -> Vec<u8> { pub(crate) fn get_rlp_memory(&self) -> Vec<u8> {
self.memory.context_memory[0].segments[Segment::RlpRaw as usize] self.memory.context_memory[0].segments[Segment::RlpRaw as usize]
.content .content
@ -205,7 +218,7 @@ impl<'a> Interpreter<'a> {
self.push(if x { U256::one() } else { U256::zero() }); self.push(if x { U256::one() } else { U256::zero() });
} }
fn pop(&mut self) -> U256 { pub(crate) fn pop(&mut self) -> U256 {
self.stack_mut().pop().expect("Pop on empty stack.") self.stack_mut().pop().expect("Pop on empty stack.")
} }
@ -225,6 +238,9 @@ impl<'a> Interpreter<'a> {
0x09 => self.run_mulmod(), // "MULMOD", 0x09 => self.run_mulmod(), // "MULMOD",
0x0a => self.run_exp(), // "EXP", 0x0a => self.run_exp(), // "EXP",
0x0b => todo!(), // "SIGNEXTEND", 0x0b => todo!(), // "SIGNEXTEND",
0x0c => todo!(), // "ADDFP254",
0x0d => todo!(), // "MULFP254",
0x0e => todo!(), // "SUBFP254",
0x10 => self.run_lt(), // "LT", 0x10 => self.run_lt(), // "LT",
0x11 => self.run_gt(), // "GT", 0x11 => self.run_gt(), // "GT",
0x12 => todo!(), // "SLT", 0x12 => todo!(), // "SLT",
@ -274,7 +290,7 @@ impl<'a> Interpreter<'a> {
0x55 => todo!(), // "SSTORE", 0x55 => todo!(), // "SSTORE",
0x56 => self.run_jump(), // "JUMP", 0x56 => self.run_jump(), // "JUMP",
0x57 => self.run_jumpi(), // "JUMPI", 0x57 => self.run_jumpi(), // "JUMPI",
0x58 => todo!(), // "GETPC", 0x58 => self.run_pc(), // "PC",
0x59 => self.run_msize(), // "MSIZE", 0x59 => self.run_msize(), // "MSIZE",
0x5a => todo!(), // "GAS", 0x5a => todo!(), // "GAS",
0x5b => self.run_jumpdest(), // "JUMPDEST", 0x5b => self.run_jumpdest(), // "JUMPDEST",
@ -309,9 +325,24 @@ impl<'a> Interpreter<'a> {
0xff => todo!(), // "SELFDESTRUCT", 0xff => todo!(), // "SELFDESTRUCT",
_ => bail!("Unrecognized opcode {}.", opcode), _ => bail!("Unrecognized opcode {}.", opcode),
}; };
if self.debug_offsets.contains(&self.offset) {
println!("At {}, stack={:?}", self.offset_name(), self.stack());
}
Ok(()) Ok(())
} }
/// Get a string representation of the current offset for debugging purposes.
fn offset_name(&self) -> String {
// TODO: Not sure we should use KERNEL? Interpreter is more general in other places.
let label = KERNEL
.global_labels
.iter()
.find_map(|(k, v)| (*v == self.offset).then(|| k.clone()));
label.unwrap_or_else(|| self.offset.to_string())
}
fn run_stop(&mut self) { fn run_stop(&mut self) {
self.running = false; self.running = false;
} }
@ -467,6 +498,7 @@ impl<'a> Interpreter<'a> {
let bytes = (offset..offset + size) let bytes = (offset..offset + size)
.map(|i| self.memory.mload_general(context, segment, i).byte(0)) .map(|i| self.memory.mload_general(context, segment, i).byte(0))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
println!("Hashing {:?}", &bytes);
let hash = keccak(bytes); let hash = keccak(bytes);
self.push(U256::from_big_endian(hash.as_bytes())); self.push(U256::from_big_endian(hash.as_bytes()));
} }
@ -535,6 +567,10 @@ impl<'a> Interpreter<'a> {
} }
} }
fn run_pc(&mut self) {
self.push((self.offset - 1).into());
}
fn run_msize(&mut self) { fn run_msize(&mut self) {
let num_bytes = self.memory.context_memory[self.context].segments let num_bytes = self.memory.context_memory[self.context].segments
[Segment::MainMemory as usize] [Segment::MainMemory as usize]
@ -600,7 +636,13 @@ impl<'a> Interpreter<'a> {
let segment = Segment::all()[self.pop().as_usize()]; let segment = Segment::all()[self.pop().as_usize()];
let offset = self.pop().as_usize(); let offset = self.pop().as_usize();
let value = self.pop(); let value = self.pop();
assert!(value.bits() <= segment.bit_range()); assert!(
value.bits() <= segment.bit_range(),
"Value {} exceeds {:?} range of {} bits",
value,
segment,
segment.bit_range()
);
self.memory.mstore_general(context, segment, offset, value); self.memory.mstore_general(context, segment, offset, value);
} }
} }

View File

@ -2,7 +2,7 @@
pub(crate) fn get_push_opcode(n: u8) -> u8 { pub(crate) fn get_push_opcode(n: u8) -> u8 {
assert!(n > 0); assert!(n > 0);
assert!(n <= 32); assert!(n <= 32);
0x60 + (n as u8 - 1) 0x60 + n - 1
} }
/// The opcode of a standard instruction (not a `PUSH`). /// The opcode of a standard instruction (not a `PUSH`).
@ -20,6 +20,9 @@ pub(crate) fn get_opcode(mnemonic: &str) -> u8 {
"MULMOD" => 0x09, "MULMOD" => 0x09,
"EXP" => 0x0a, "EXP" => 0x0a,
"SIGNEXTEND" => 0x0b, "SIGNEXTEND" => 0x0b,
"ADDFP254" => 0x0c,
"MULFP254" => 0x0d,
"SUBFP254" => 0x0e,
"LT" => 0x10, "LT" => 0x10,
"GT" => 0x11, "GT" => 0x11,
"SLT" => 0x12, "SLT" => 0x12,

View File

@ -1,11 +1,12 @@
use anyhow::Result; use anyhow::Result;
use eth_trie_utils::partial_trie::{Nibbles, PartialTrie}; use eth_trie_utils::partial_trie::PartialTrie;
use ethereum_types::{BigEndianHash, H256, U256}; use ethereum_types::{BigEndianHash, H256};
use super::nibbles;
use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::aggregator::KERNEL;
use crate::cpu::kernel::interpreter::Interpreter; use crate::cpu::kernel::interpreter::Interpreter;
use crate::cpu::kernel::tests::mpt::extension_to_leaf; use crate::cpu::kernel::tests::mpt::{extension_to_leaf, test_account_1_rlp, test_account_2_rlp};
use crate::generation::mpt::{all_mpt_prover_inputs_reversed, AccountRlp}; use crate::generation::mpt::all_mpt_prover_inputs_reversed;
use crate::generation::TrieInputs; use crate::generation::TrieInputs;
// TODO: Test with short leaf. Might need to be a storage trie. // TODO: Test with short leaf. Might need to be a storage trie.
@ -23,74 +24,70 @@ fn mpt_hash_empty() -> Result<()> {
} }
#[test] #[test]
fn mpt_hash_leaf() -> Result<()> { fn mpt_hash_empty_branch() -> Result<()> {
let account = AccountRlp { let children = std::array::from_fn(|_| PartialTrie::Empty.into());
nonce: U256::from(1111), let state_trie = PartialTrie::Branch {
balance: U256::from(2222), children,
storage_root: H256::from_uint(&U256::from(3333)), value: vec![],
code_hash: H256::from_uint(&U256::from(4444)),
}; };
let account_rlp = rlp::encode(&account);
let state_trie = PartialTrie::Leaf {
nibbles: Nibbles {
count: 3,
packed: 0xABC.into(),
},
value: account_rlp.to_vec(),
};
let trie_inputs = TrieInputs { let trie_inputs = TrieInputs {
state_trie, state_trie,
transactions_trie: Default::default(), transactions_trie: Default::default(),
receipts_trie: Default::default(), receipts_trie: Default::default(),
storage_tries: vec![], storage_tries: vec![],
}; };
test_state_trie(trie_inputs)
}
#[test]
fn mpt_hash_hash() -> Result<()> {
let hash = H256::random();
let trie_inputs = TrieInputs {
state_trie: PartialTrie::Hash(hash),
transactions_trie: Default::default(),
receipts_trie: Default::default(),
storage_tries: vec![],
};
test_state_trie(trie_inputs)
}
#[test]
fn mpt_hash_leaf() -> Result<()> {
let state_trie = PartialTrie::Leaf {
nibbles: nibbles(0xABC),
value: test_account_1_rlp(),
};
let trie_inputs = TrieInputs {
state_trie,
transactions_trie: Default::default(),
receipts_trie: Default::default(),
storage_tries: vec![],
};
test_state_trie(trie_inputs) test_state_trie(trie_inputs)
} }
#[test] #[test]
fn mpt_hash_extension_to_leaf() -> Result<()> { fn mpt_hash_extension_to_leaf() -> Result<()> {
let account = AccountRlp { let state_trie = extension_to_leaf(test_account_1_rlp());
nonce: U256::from(1111),
balance: U256::from(2222),
storage_root: H256::from_uint(&U256::from(3333)),
code_hash: H256::from_uint(&U256::from(4444)),
};
let account_rlp = rlp::encode(&account);
let state_trie = extension_to_leaf(account_rlp.to_vec());
let trie_inputs = TrieInputs { let trie_inputs = TrieInputs {
state_trie, state_trie,
transactions_trie: Default::default(), transactions_trie: Default::default(),
receipts_trie: Default::default(), receipts_trie: Default::default(),
storage_tries: vec![], storage_tries: vec![],
}; };
test_state_trie(trie_inputs) test_state_trie(trie_inputs)
} }
#[test] #[test]
fn mpt_hash_branch_to_leaf() -> Result<()> { fn mpt_hash_branch_to_leaf() -> Result<()> {
let account = AccountRlp {
nonce: U256::from(1111),
balance: U256::from(2222),
storage_root: H256::from_uint(&U256::from(3333)),
code_hash: H256::from_uint(&U256::from(4444)),
};
let account_rlp = rlp::encode(&account);
let leaf = PartialTrie::Leaf { let leaf = PartialTrie::Leaf {
nibbles: Nibbles { nibbles: nibbles(0xABC),
count: 3, value: test_account_2_rlp(),
packed: 0xABC.into(), }
}, .into();
value: account_rlp.to_vec(), let mut children = std::array::from_fn(|_| PartialTrie::Empty.into());
}; children[3] = leaf;
let mut children = std::array::from_fn(|_| Box::new(PartialTrie::Empty));
children[0] = Box::new(leaf);
let state_trie = PartialTrie::Branch { let state_trie = PartialTrie::Branch {
children, children,
value: vec![], value: vec![],

View File

@ -0,0 +1,208 @@
use anyhow::Result;
use eth_trie_utils::partial_trie::{Nibbles, PartialTrie};
use ethereum_types::{BigEndianHash, H256};
use super::nibbles;
use crate::cpu::kernel::aggregator::KERNEL;
use crate::cpu::kernel::constants::global_metadata::GlobalMetadata;
use crate::cpu::kernel::interpreter::Interpreter;
use crate::cpu::kernel::tests::mpt::{test_account_1_rlp, test_account_2};
use crate::generation::mpt::{all_mpt_prover_inputs_reversed, AccountRlp};
use crate::generation::TrieInputs;
#[test]
fn mpt_insert_empty() -> Result<()> {
test_state_trie(Default::default(), nibbles(0xABC), test_account_2())
}
#[test]
fn mpt_insert_leaf_identical_keys() -> Result<()> {
let key = nibbles(0xABC);
let state_trie = PartialTrie::Leaf {
nibbles: key,
value: test_account_1_rlp(),
};
test_state_trie(state_trie, key, test_account_2())
}
#[test]
fn mpt_insert_leaf_nonoverlapping_keys() -> Result<()> {
let state_trie = PartialTrie::Leaf {
nibbles: nibbles(0xABC),
value: test_account_1_rlp(),
};
test_state_trie(state_trie, nibbles(0x123), test_account_2())
}
#[test]
fn mpt_insert_leaf_overlapping_keys() -> Result<()> {
let state_trie = PartialTrie::Leaf {
nibbles: nibbles(0xABC),
value: test_account_1_rlp(),
};
test_state_trie(state_trie, nibbles(0xADE), test_account_2())
}
#[test]
fn mpt_insert_leaf_insert_key_extends_leaf_key() -> Result<()> {
let state_trie = PartialTrie::Leaf {
nibbles: nibbles(0xABC),
value: test_account_1_rlp(),
};
test_state_trie(state_trie, nibbles(0xABCDE), test_account_2())
}
#[test]
fn mpt_insert_leaf_leaf_key_extends_insert_key() -> Result<()> {
let state_trie = PartialTrie::Leaf {
nibbles: nibbles(0xABCDE),
value: test_account_1_rlp(),
};
test_state_trie(state_trie, nibbles(0xABC), test_account_2())
}
#[test]
fn mpt_insert_branch_replacing_empty_child() -> Result<()> {
let children = std::array::from_fn(|_| PartialTrie::Empty.into());
let state_trie = PartialTrie::Branch {
children,
value: vec![],
};
test_state_trie(state_trie, nibbles(0xABC), test_account_2())
}
#[test]
// TODO: Not a valid test because branches state trie cannot have branch values.
// We should change it to use a different trie.
#[ignore]
fn mpt_insert_extension_nonoverlapping_keys() -> Result<()> {
// Existing keys are 0xABC, 0xABCDEF; inserted key is 0x12345.
let mut children = std::array::from_fn(|_| PartialTrie::Empty.into());
children[0xD] = PartialTrie::Leaf {
nibbles: nibbles(0xEF),
value: test_account_1_rlp(),
}
.into();
let state_trie = PartialTrie::Extension {
nibbles: nibbles(0xABC),
child: PartialTrie::Branch {
children,
value: test_account_1_rlp(),
}
.into(),
};
test_state_trie(state_trie, nibbles(0x12345), test_account_2())
}
#[test]
// TODO: Not a valid test because branches state trie cannot have branch values.
// We should change it to use a different trie.
#[ignore]
fn mpt_insert_extension_insert_key_extends_node_key() -> Result<()> {
// Existing keys are 0xA, 0xABCD; inserted key is 0xABCDEF.
let mut children = std::array::from_fn(|_| PartialTrie::Empty.into());
children[0xB] = PartialTrie::Leaf {
nibbles: nibbles(0xCD),
value: test_account_1_rlp(),
}
.into();
let state_trie = PartialTrie::Extension {
nibbles: nibbles(0xA),
child: PartialTrie::Branch {
children,
value: test_account_1_rlp(),
}
.into(),
};
test_state_trie(state_trie, nibbles(0xABCDEF), test_account_2())
}
#[test]
fn mpt_insert_branch_to_leaf_same_key() -> Result<()> {
let leaf = PartialTrie::Leaf {
nibbles: nibbles(0xBCD),
value: test_account_1_rlp(),
}
.into();
let mut children = std::array::from_fn(|_| PartialTrie::Empty.into());
children[0xA] = leaf;
let state_trie = PartialTrie::Branch {
children,
value: vec![],
};
test_state_trie(state_trie, nibbles(0xABCD), test_account_2())
}
/// Note: The account's storage_root is ignored, as we can't insert a new storage_root without the
/// accompanying trie data. An empty trie's storage_root is used instead.
fn test_state_trie(state_trie: PartialTrie, k: Nibbles, mut account: AccountRlp) -> Result<()> {
account.storage_root = PartialTrie::Empty.calc_hash();
let trie_inputs = TrieInputs {
state_trie: state_trie.clone(),
transactions_trie: Default::default(),
receipts_trie: Default::default(),
storage_tries: vec![],
};
let load_all_mpts = KERNEL.global_labels["load_all_mpts"];
let mpt_insert_state_trie = KERNEL.global_labels["mpt_insert_state_trie"];
let mpt_hash_state_trie = KERNEL.global_labels["mpt_hash_state_trie"];
let initial_stack = vec![0xDEADBEEFu32.into()];
let mut interpreter = Interpreter::new_with_kernel(load_all_mpts, initial_stack);
interpreter.generation_state.mpt_prover_inputs = all_mpt_prover_inputs_reversed(&trie_inputs);
interpreter.run()?;
assert_eq!(interpreter.stack(), vec![]);
// Next, execute mpt_insert_state_trie.
interpreter.offset = mpt_insert_state_trie;
let trie_data = interpreter.get_trie_data_mut();
if trie_data.is_empty() {
// In the assembly we skip over 0, knowing trie_data[0] = 0 by default.
// Since we don't explicitly set it to 0, we need to do so here.
trie_data.push(0.into());
}
let value_ptr = trie_data.len();
trie_data.push(account.nonce);
trie_data.push(account.balance);
// In memory, storage_root gets interpreted as a pointer to a storage trie,
// so we have to ensure the pointer is valid. It's easiest to set it to 0,
// which works as an empty node, since trie_data[0] = 0 = MPT_TYPE_EMPTY.
trie_data.push(H256::zero().into_uint());
trie_data.push(account.code_hash.into_uint());
let trie_data_len = trie_data.len().into();
interpreter.set_global_metadata_field(GlobalMetadata::TrieDataSize, trie_data_len);
interpreter.push(0xDEADBEEFu32.into());
interpreter.push(value_ptr.into()); // value_ptr
interpreter.push(k.packed); // key
interpreter.push(k.count.into()); // num_nibbles
interpreter.run()?;
assert_eq!(
interpreter.stack().len(),
0,
"Expected empty stack after insert, found {:?}",
interpreter.stack()
);
// Now, execute mpt_hash_state_trie.
interpreter.offset = mpt_hash_state_trie;
interpreter.push(0xDEADBEEFu32.into());
interpreter.run()?;
assert_eq!(
interpreter.stack().len(),
1,
"Expected 1 item on stack after hashing, found {:?}",
interpreter.stack()
);
let hash = H256::from_uint(&interpreter.stack()[0]);
let updated_trie = state_trie.insert(k, rlp::encode(&account).to_vec());
let expected_state_trie_hash = updated_trie.calc_hash();
assert_eq!(hash, expected_state_trie_hash);
Ok(())
}

View File

@ -1,26 +1,19 @@
use anyhow::Result; use anyhow::Result;
use eth_trie_utils::partial_trie::PartialTrie;
use ethereum_types::{BigEndianHash, H256, U256}; use ethereum_types::{BigEndianHash, H256, U256};
use crate::cpu::kernel::aggregator::KERNEL;
use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; use crate::cpu::kernel::constants::global_metadata::GlobalMetadata;
use crate::cpu::kernel::constants::trie_type::PartialTrieType; use crate::cpu::kernel::constants::trie_type::PartialTrieType;
use crate::cpu::kernel::interpreter::Interpreter; use crate::cpu::kernel::interpreter::Interpreter;
use crate::cpu::kernel::tests::mpt::extension_to_leaf; use crate::cpu::kernel::tests::mpt::{extension_to_leaf, test_account_1, test_account_1_rlp};
use crate::generation::mpt::{all_mpt_prover_inputs_reversed, AccountRlp}; use crate::cpu::kernel::{aggregator::KERNEL, tests::mpt::nibbles};
use crate::generation::mpt::all_mpt_prover_inputs_reversed;
use crate::generation::TrieInputs; use crate::generation::TrieInputs;
#[test] #[test]
fn load_all_mpts() -> Result<()> { fn load_all_mpts_empty() -> Result<()> {
let account = AccountRlp {
nonce: U256::from(1111),
balance: U256::from(2222),
storage_root: H256::from_uint(&U256::from(3333)),
code_hash: H256::from_uint(&U256::from(4444)),
};
let account_rlp = rlp::encode(&account);
let trie_inputs = TrieInputs { let trie_inputs = TrieInputs {
state_trie: extension_to_leaf(account_rlp.to_vec()), state_trie: Default::default(),
transactions_trie: Default::default(), transactions_trie: Default::default(),
receipts_trie: Default::default(), receipts_trie: Default::default(),
storage_tries: vec![], storage_tries: vec![],
@ -28,13 +21,194 @@ fn load_all_mpts() -> Result<()> {
let load_all_mpts = KERNEL.global_labels["load_all_mpts"]; let load_all_mpts = KERNEL.global_labels["load_all_mpts"];
let initial_stack = vec![0xdeadbeefu32.into()]; let initial_stack = vec![0xDEADBEEFu32.into()];
let mut interpreter = Interpreter::new_with_kernel(load_all_mpts, initial_stack);
interpreter.generation_state.mpt_prover_inputs = all_mpt_prover_inputs_reversed(&trie_inputs);
interpreter.run()?;
assert_eq!(interpreter.stack(), vec![]);
assert_eq!(interpreter.get_trie_data(), vec![]);
assert_eq!(
interpreter.get_global_metadata_field(GlobalMetadata::StateTrieRoot),
0.into()
);
assert_eq!(
interpreter.get_global_metadata_field(GlobalMetadata::TransactionTrieRoot),
0.into()
);
assert_eq!(
interpreter.get_global_metadata_field(GlobalMetadata::ReceiptTrieRoot),
0.into()
);
Ok(())
}
#[test]
fn load_all_mpts_leaf() -> Result<()> {
let trie_inputs = TrieInputs {
state_trie: PartialTrie::Leaf {
nibbles: nibbles(0xABC),
value: test_account_1_rlp(),
},
transactions_trie: Default::default(),
receipts_trie: Default::default(),
storage_tries: vec![],
};
let load_all_mpts = KERNEL.global_labels["load_all_mpts"];
let initial_stack = vec![0xDEADBEEFu32.into()];
let mut interpreter = Interpreter::new_with_kernel(load_all_mpts, initial_stack);
interpreter.generation_state.mpt_prover_inputs = all_mpt_prover_inputs_reversed(&trie_inputs);
interpreter.run()?;
assert_eq!(interpreter.stack(), vec![]);
let type_leaf = U256::from(PartialTrieType::Leaf as u32);
assert_eq!(
interpreter.get_trie_data(),
vec![
0.into(),
type_leaf,
3.into(),
0xABC.into(),
5.into(), // value ptr
test_account_1().nonce,
test_account_1().balance,
9.into(), // pointer to storage trie root
test_account_1().code_hash.into_uint(),
// These last two elements encode the storage trie, which is a hash node.
(PartialTrieType::Hash as u32).into(),
test_account_1().storage_root.into_uint(),
]
);
assert_eq!(
interpreter.get_global_metadata_field(GlobalMetadata::TransactionTrieRoot),
0.into()
);
assert_eq!(
interpreter.get_global_metadata_field(GlobalMetadata::ReceiptTrieRoot),
0.into()
);
Ok(())
}
#[test]
fn load_all_mpts_hash() -> Result<()> {
let hash = H256::random();
let trie_inputs = TrieInputs {
state_trie: PartialTrie::Hash(hash),
transactions_trie: Default::default(),
receipts_trie: Default::default(),
storage_tries: vec![],
};
let load_all_mpts = KERNEL.global_labels["load_all_mpts"];
let initial_stack = vec![0xDEADBEEFu32.into()];
let mut interpreter = Interpreter::new_with_kernel(load_all_mpts, initial_stack);
interpreter.generation_state.mpt_prover_inputs = all_mpt_prover_inputs_reversed(&trie_inputs);
interpreter.run()?;
assert_eq!(interpreter.stack(), vec![]);
let type_hash = U256::from(PartialTrieType::Hash as u32);
assert_eq!(
interpreter.get_trie_data(),
vec![0.into(), type_hash, hash.into_uint(),]
);
assert_eq!(
interpreter.get_global_metadata_field(GlobalMetadata::TransactionTrieRoot),
0.into()
);
assert_eq!(
interpreter.get_global_metadata_field(GlobalMetadata::ReceiptTrieRoot),
0.into()
);
Ok(())
}
#[test]
fn load_all_mpts_empty_branch() -> Result<()> {
let children = std::array::from_fn(|_| PartialTrie::Empty.into());
let state_trie = PartialTrie::Branch {
children,
value: vec![],
};
let trie_inputs = TrieInputs {
state_trie,
transactions_trie: Default::default(),
receipts_trie: Default::default(),
storage_tries: vec![],
};
let load_all_mpts = KERNEL.global_labels["load_all_mpts"];
let initial_stack = vec![0xDEADBEEFu32.into()];
let mut interpreter = Interpreter::new_with_kernel(load_all_mpts, initial_stack);
interpreter.generation_state.mpt_prover_inputs = all_mpt_prover_inputs_reversed(&trie_inputs);
interpreter.run()?;
assert_eq!(interpreter.stack(), vec![]);
let type_branch = U256::from(PartialTrieType::Branch as u32);
assert_eq!(
interpreter.get_trie_data(),
vec![
0.into(), // First address is unused, so that 0 can be treated as a null pointer.
type_branch,
0.into(), // child 0
0.into(), // ...
0.into(),
0.into(),
0.into(),
0.into(),
0.into(),
0.into(),
0.into(),
0.into(),
0.into(),
0.into(),
0.into(),
0.into(),
0.into(),
0.into(), // child 16
0.into(), // value_ptr
]
);
assert_eq!(
interpreter.get_global_metadata_field(GlobalMetadata::TransactionTrieRoot),
0.into()
);
assert_eq!(
interpreter.get_global_metadata_field(GlobalMetadata::ReceiptTrieRoot),
0.into()
);
Ok(())
}
#[test]
fn load_all_mpts_ext_to_leaf() -> Result<()> {
let trie_inputs = TrieInputs {
state_trie: extension_to_leaf(test_account_1_rlp()),
transactions_trie: Default::default(),
receipts_trie: Default::default(),
storage_tries: vec![],
};
let load_all_mpts = KERNEL.global_labels["load_all_mpts"];
let initial_stack = vec![0xDEADBEEFu32.into()];
let mut interpreter = Interpreter::new_with_kernel(load_all_mpts, initial_stack); let mut interpreter = Interpreter::new_with_kernel(load_all_mpts, initial_stack);
interpreter.generation_state.mpt_prover_inputs = all_mpt_prover_inputs_reversed(&trie_inputs); interpreter.generation_state.mpt_prover_inputs = all_mpt_prover_inputs_reversed(&trie_inputs);
interpreter.run()?; interpreter.run()?;
assert_eq!(interpreter.stack(), vec![]); assert_eq!(interpreter.stack(), vec![]);
let type_empty = U256::from(PartialTrieType::Empty as u32);
let type_extension = U256::from(PartialTrieType::Extension as u32); let type_extension = U256::from(PartialTrieType::Extension as u32);
let type_leaf = U256::from(PartialTrieType::Leaf as u32); let type_leaf = U256::from(PartialTrieType::Leaf as u32);
assert_eq!( assert_eq!(
@ -48,20 +222,16 @@ fn load_all_mpts() -> Result<()> {
type_leaf, type_leaf,
3.into(), // 3 nibbles 3.into(), // 3 nibbles
0xDEF.into(), // key part 0xDEF.into(), // key part
4.into(), // value length 9.into(), // value pointer
account.nonce, test_account_1().nonce,
account.balance, test_account_1().balance,
account.storage_root.into_uint(), 13.into(), // pointer to storage trie root
account.code_hash.into_uint(), test_account_1().code_hash.into_uint(),
type_empty, // txn trie // These last two elements encode the storage trie, which is a hash node.
type_empty, // receipt trie (PartialTrieType::Hash as u32).into(),
test_account_1().storage_root.into_uint(),
] ]
); );
assert_eq!(
interpreter.get_global_metadata_field(GlobalMetadata::NumStorageTries),
trie_inputs.storage_tries.len().into()
);
Ok(()) Ok(())
} }

View File

@ -1,23 +1,62 @@
use eth_trie_utils::partial_trie::{Nibbles, PartialTrie}; use eth_trie_utils::partial_trie::{Nibbles, PartialTrie};
use ethereum_types::{BigEndianHash, H256, U256};
use crate::generation::mpt::AccountRlp;
mod hash; mod hash;
mod hex_prefix; mod hex_prefix;
mod insert;
mod load; mod load;
mod read; mod read;
/// Helper function to reduce code duplication.
/// Note that this preserves all nibbles (eg. `0x123` is not interpreted as `0x0123`).
pub(crate) fn nibbles<T: Into<U256>>(v: T) -> Nibbles {
let packed = v.into();
Nibbles {
count: Nibbles::get_num_nibbles_in_key(&packed),
packed,
}
}
pub(crate) fn test_account_1() -> AccountRlp {
AccountRlp {
nonce: U256::from(1111),
balance: U256::from(2222),
storage_root: H256::from_uint(&U256::from(3333)),
code_hash: H256::from_uint(&U256::from(4444)),
}
}
pub(crate) fn test_account_1_rlp() -> Vec<u8> {
rlp::encode(&test_account_1()).to_vec()
}
pub(crate) fn test_account_2() -> AccountRlp {
AccountRlp {
nonce: U256::from(5555),
balance: U256::from(6666),
storage_root: H256::from_uint(&U256::from(7777)),
code_hash: H256::from_uint(&U256::from(8888)),
}
}
pub(crate) fn test_account_2_rlp() -> Vec<u8> {
rlp::encode(&test_account_2()).to_vec()
}
/// A `PartialTrie` where an extension node leads to a leaf node containing an account. /// A `PartialTrie` where an extension node leads to a leaf node containing an account.
pub(crate) fn extension_to_leaf(value: Vec<u8>) -> PartialTrie { pub(crate) fn extension_to_leaf(value: Vec<u8>) -> PartialTrie {
PartialTrie::Extension { PartialTrie::Extension {
nibbles: Nibbles { nibbles: nibbles(0xABC),
count: 3, child: PartialTrie::Leaf {
packed: 0xABC.into(),
},
child: Box::new(PartialTrie::Leaf {
nibbles: Nibbles { nibbles: Nibbles {
count: 3, count: 3,
packed: 0xDEF.into(), packed: 0xDEF.into(),
}, },
value, value,
}), }
.into(),
} }
} }

View File

@ -1,25 +1,17 @@
use anyhow::Result; use anyhow::Result;
use ethereum_types::{BigEndianHash, H256, U256}; use ethereum_types::BigEndianHash;
use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::aggregator::KERNEL;
use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; use crate::cpu::kernel::constants::global_metadata::GlobalMetadata;
use crate::cpu::kernel::interpreter::Interpreter; use crate::cpu::kernel::interpreter::Interpreter;
use crate::cpu::kernel::tests::mpt::extension_to_leaf; use crate::cpu::kernel::tests::mpt::{extension_to_leaf, test_account_1, test_account_1_rlp};
use crate::generation::mpt::{all_mpt_prover_inputs_reversed, AccountRlp}; use crate::generation::mpt::all_mpt_prover_inputs_reversed;
use crate::generation::TrieInputs; use crate::generation::TrieInputs;
#[test] #[test]
fn mpt_read() -> Result<()> { fn mpt_read() -> Result<()> {
let account = AccountRlp {
nonce: U256::from(1111),
balance: U256::from(2222),
storage_root: H256::from_uint(&U256::from(3333)),
code_hash: H256::from_uint(&U256::from(4444)),
};
let account_rlp = rlp::encode(&account);
let trie_inputs = TrieInputs { let trie_inputs = TrieInputs {
state_trie: extension_to_leaf(account_rlp.to_vec()), state_trie: extension_to_leaf(test_account_1_rlp()),
transactions_trie: Default::default(), transactions_trie: Default::default(),
receipts_trie: Default::default(), receipts_trie: Default::default(),
storage_tries: vec![], storage_tries: vec![],
@ -45,10 +37,11 @@ fn mpt_read() -> Result<()> {
assert_eq!(interpreter.stack().len(), 1); assert_eq!(interpreter.stack().len(), 1);
let result_ptr = interpreter.stack()[0].as_usize(); let result_ptr = interpreter.stack()[0].as_usize();
let result = &interpreter.get_trie_data()[result_ptr..][..4]; let result = &interpreter.get_trie_data()[result_ptr..][..4];
assert_eq!(result[0], account.nonce); assert_eq!(result[0], test_account_1().nonce);
assert_eq!(result[1], account.balance); assert_eq!(result[1], test_account_1().balance);
assert_eq!(result[2], account.storage_root.into_uint()); // result[2] is the storage root pointer. We won't check that it matches a
assert_eq!(result[3], account.code_hash.into_uint()); // particular address, since that seems like over-specifying.
assert_eq!(result[3], test_account_1().code_hash.into_uint());
Ok(()) Ok(())
} }

View File

@ -46,7 +46,7 @@ fn test_ripemd_reference() -> Result<()> {
let kernel = combined_kernel(); let kernel = combined_kernel();
let initial_offset = kernel.global_labels["ripemd_stack"]; let initial_offset = kernel.global_labels["ripemd_stack"];
let initial_stack: Vec<U256> = input.iter().map(|&x| U256::from(x as u32)).rev().collect(); let initial_stack: Vec<U256> = input.iter().map(|&x| U256::from(x)).rev().collect();
let final_stack: Vec<U256> = run_with_kernel(&kernel, initial_offset, initial_stack)? let final_stack: Vec<U256> = run_with_kernel(&kernel, initial_offset, initial_stack)?
.stack() .stack()
.to_vec(); .to_vec();

View File

@ -7,6 +7,7 @@ mod dup_swap;
mod jumps; mod jumps;
pub mod kernel; pub mod kernel;
pub(crate) mod membus; pub(crate) mod membus;
mod modfp254;
mod simple_logic; mod simple_logic;
mod stack; mod stack;
mod stack_bounds; mod stack_bounds;

53
evm/src/cpu/modfp254.rs Normal file
View File

@ -0,0 +1,53 @@
use itertools::izip;
use plonky2::field::extension::Extendable;
use plonky2::field::packed::PackedField;
use plonky2::field::types::Field;
use plonky2::hash::hash_types::RichField;
use plonky2::iop::ext_target::ExtensionTarget;
use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer};
use crate::cpu::columns::CpuColumnsView;
// Python:
// >>> P = 21888242871839275222246405745257275088696311157297823662689037894645226208583
// >>> "[" + ", ".join(hex((P >> n) % 2**32) for n in range(0, 256, 32)) + "]"
const P_LIMBS: [u32; 8] = [
0xd87cfd47, 0x3c208c16, 0x6871ca8d, 0x97816a91, 0x8181585d, 0xb85045b6, 0xe131a029, 0x30644e72,
];
pub fn eval_packed<P: PackedField>(
lv: &CpuColumnsView<P>,
yield_constr: &mut ConstraintConsumer<P>,
) {
let filter = lv.is_cpu_cycle * (lv.op.addfp254 + lv.op.mulfp254 + lv.op.subfp254);
// We want to use all the same logic as the usual mod operations, but without needing to read
// the modulus from the stack. We simply constrain `mem_channels[2]` to be our prime (that's
// where the modulus goes in the generalized operations).
let channel_val = lv.mem_channels[2].value;
for (channel_limb, p_limb) in izip!(channel_val, P_LIMBS) {
let p_limb = P::Scalar::from_canonical_u32(p_limb);
yield_constr.constraint(filter * (channel_limb - p_limb));
}
}
pub fn eval_ext_circuit<F: RichField + Extendable<D>, const D: usize>(
builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder<F, D>,
lv: &CpuColumnsView<ExtensionTarget<D>>,
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
) {
let filter = {
let flag_sum = builder.add_many_extension([lv.op.addfp254, lv.op.mulfp254, lv.op.subfp254]);
builder.mul_extension(lv.is_cpu_cycle, flag_sum)
};
// We want to use all the same logic as the usual mod operations, but without needing to read
// the modulus from the stack. We simply constrain `mem_channels[2]` to be our prime (that's
// where the modulus goes in the generalized operations).
let channel_val = lv.mem_channels[2].value;
for (channel_limb, p_limb) in izip!(channel_val, P_LIMBS) {
let p_limb = F::from_canonical_u32(p_limb);
let constr = builder.arithmetic_extension(F::ONE, -p_limb, filter, channel_limb, filter);
yield_constr.constraint(builder, constr);
}
}

View File

@ -52,6 +52,9 @@ const STACK_BEHAVIORS: OpsColumnsView<Option<StackBehavior>> = OpsColumnsView {
mulmod: BASIC_TERNARY_OP, mulmod: BASIC_TERNARY_OP,
exp: None, // TODO exp: None, // TODO
signextend: BASIC_BINARY_OP, signextend: BASIC_BINARY_OP,
addfp254: BASIC_BINARY_OP,
mulfp254: BASIC_BINARY_OP,
subfp254: BASIC_BINARY_OP,
lt: BASIC_BINARY_OP, lt: BASIC_BINARY_OP,
gt: BASIC_BINARY_OP, gt: BASIC_BINARY_OP,
slt: BASIC_BINARY_OP, slt: BASIC_BINARY_OP,

View File

@ -1,5 +1,8 @@
use eth_trie_utils::partial_trie::PartialTrie; use std::collections::HashMap;
use eth_trie_utils::partial_trie::{Nibbles, PartialTrie};
use ethereum_types::{BigEndianHash, H256, U256}; use ethereum_types::{BigEndianHash, H256, U256};
use keccak_hash::keccak;
use rlp_derive::{RlpDecodable, RlpEncodable}; use rlp_derive::{RlpDecodable, RlpEncodable};
use crate::cpu::kernel::constants::trie_type::PartialTrieType; use crate::cpu::kernel::constants::trie_type::PartialTrieType;
@ -23,15 +26,18 @@ pub(crate) fn all_mpt_prover_inputs_reversed(trie_inputs: &TrieInputs) -> Vec<U2
pub(crate) fn all_mpt_prover_inputs(trie_inputs: &TrieInputs) -> Vec<U256> { pub(crate) fn all_mpt_prover_inputs(trie_inputs: &TrieInputs) -> Vec<U256> {
let mut prover_inputs = vec![]; let mut prover_inputs = vec![];
mpt_prover_inputs(&trie_inputs.state_trie, &mut prover_inputs, &|rlp| { let storage_tries_by_state_key = trie_inputs
let account: AccountRlp = rlp::decode(rlp).expect("Decoding failed"); .storage_tries
vec![ .iter()
account.nonce, .map(|(address, storage_trie)| (Nibbles::from(keccak(address)), storage_trie))
account.balance, .collect();
account.storage_root.into_uint(),
account.code_hash.into_uint(), mpt_prover_inputs_state_trie(
] &trie_inputs.state_trie,
}); empty_nibbles(),
&mut prover_inputs,
&storage_tries_by_state_key,
);
mpt_prover_inputs(&trie_inputs.transactions_trie, &mut prover_inputs, &|rlp| { mpt_prover_inputs(&trie_inputs.transactions_trie, &mut prover_inputs, &|rlp| {
rlp::decode_list(rlp) rlp::decode_list(rlp)
@ -42,14 +48,6 @@ pub(crate) fn all_mpt_prover_inputs(trie_inputs: &TrieInputs) -> Vec<U256> {
vec![] vec![]
}); });
prover_inputs.push(trie_inputs.storage_tries.len().into());
for (addr, storage_trie) in &trie_inputs.storage_tries {
prover_inputs.push(addr.0.as_ref().into());
mpt_prover_inputs(storage_trie, &mut prover_inputs, &|leaf_be| {
vec![U256::from_big_endian(leaf_be)]
});
}
prover_inputs prover_inputs
} }
@ -60,7 +58,7 @@ pub(crate) fn all_mpt_prover_inputs(trie_inputs: &TrieInputs) -> Vec<U256> {
pub(crate) fn mpt_prover_inputs<F>( pub(crate) fn mpt_prover_inputs<F>(
trie: &PartialTrie, trie: &PartialTrie,
prover_inputs: &mut Vec<U256>, prover_inputs: &mut Vec<U256>,
parse_leaf: &F, parse_value: &F,
) where ) where
F: Fn(&[u8]) -> Vec<U256>, F: Fn(&[u8]) -> Vec<U256>,
{ {
@ -70,28 +68,108 @@ pub(crate) fn mpt_prover_inputs<F>(
PartialTrie::Hash(h) => prover_inputs.push(U256::from_big_endian(h.as_bytes())), PartialTrie::Hash(h) => prover_inputs.push(U256::from_big_endian(h.as_bytes())),
PartialTrie::Branch { children, value } => { PartialTrie::Branch { children, value } => {
if value.is_empty() { if value.is_empty() {
// There's no value, so length=0. // There's no value, so value_len = 0.
prover_inputs.push(U256::zero()); prover_inputs.push(U256::zero());
} else { } else {
let leaf = parse_leaf(value); let parsed_value = parse_value(value);
prover_inputs.push(leaf.len().into()); prover_inputs.push(parsed_value.len().into());
prover_inputs.extend(leaf); prover_inputs.extend(parsed_value);
} }
for child in children { for child in children {
mpt_prover_inputs(child, prover_inputs, parse_leaf); mpt_prover_inputs(child, prover_inputs, parse_value);
} }
} }
PartialTrie::Extension { nibbles, child } => { PartialTrie::Extension { nibbles, child } => {
prover_inputs.push(nibbles.count.into()); prover_inputs.push(nibbles.count.into());
prover_inputs.push(nibbles.packed); prover_inputs.push(nibbles.packed);
mpt_prover_inputs(child, prover_inputs, parse_leaf); mpt_prover_inputs(child, prover_inputs, parse_value);
} }
PartialTrie::Leaf { nibbles, value } => { PartialTrie::Leaf { nibbles, value } => {
prover_inputs.push(nibbles.count.into()); prover_inputs.push(nibbles.count.into());
prover_inputs.push(nibbles.packed); prover_inputs.push(nibbles.packed);
let leaf = parse_leaf(value); let leaf = parse_value(value);
prover_inputs.push(leaf.len().into());
prover_inputs.extend(leaf); prover_inputs.extend(leaf);
} }
} }
} }
/// Like `mpt_prover_inputs`, but for the state trie, which is a bit unique since each value
/// leads to a storage trie which we recursively traverse.
pub(crate) fn mpt_prover_inputs_state_trie(
trie: &PartialTrie,
key: Nibbles,
prover_inputs: &mut Vec<U256>,
storage_tries_by_state_key: &HashMap<Nibbles, &PartialTrie>,
) {
prover_inputs.push((PartialTrieType::of(trie) as u32).into());
match trie {
PartialTrie::Empty => {}
PartialTrie::Hash(h) => prover_inputs.push(U256::from_big_endian(h.as_bytes())),
PartialTrie::Branch { children, value } => {
assert!(value.is_empty(), "State trie should not have branch values");
// There's no value, so value_len = 0.
prover_inputs.push(U256::zero());
for (i, child) in children.iter().enumerate() {
let extended_key = key.merge(&Nibbles {
count: 1,
packed: i.into(),
});
mpt_prover_inputs_state_trie(
child,
extended_key,
prover_inputs,
storage_tries_by_state_key,
);
}
}
PartialTrie::Extension { nibbles, child } => {
prover_inputs.push(nibbles.count.into());
prover_inputs.push(nibbles.packed);
let extended_key = key.merge(nibbles);
mpt_prover_inputs_state_trie(
child,
extended_key,
prover_inputs,
storage_tries_by_state_key,
);
}
PartialTrie::Leaf { nibbles, value } => {
let account: AccountRlp = rlp::decode(value).expect("Decoding failed");
let AccountRlp {
nonce,
balance,
storage_root,
code_hash,
} = account;
let storage_hash_only = PartialTrie::Hash(storage_root);
let storage_trie: &PartialTrie = storage_tries_by_state_key
.get(&key)
.copied()
.unwrap_or(&storage_hash_only);
assert_eq!(storage_trie.calc_hash(), storage_root,
"In TrieInputs, an account's storage_root didn't match the associated storage trie hash");
prover_inputs.push(nibbles.count.into());
prover_inputs.push(nibbles.packed);
prover_inputs.push(nonce);
prover_inputs.push(balance);
mpt_prover_inputs(storage_trie, prover_inputs, &parse_storage_value);
prover_inputs.push(code_hash.into_uint());
}
}
}
fn parse_storage_value(value_rlp: &[u8]) -> Vec<U256> {
let value: U256 = rlp::decode(value_rlp).expect("Decoding failed");
vec![value]
}
fn empty_nibbles() -> Nibbles {
Nibbles {
count: 0,
packed: U256::zero(),
}
}

View File

@ -29,19 +29,14 @@ pub(crate) enum Segment {
/// Contains all trie data. Tries are stored as immutable, copy-on-write trees, so this is an /// Contains all trie data. Tries are stored as immutable, copy-on-write trees, so this is an
/// append-only buffer. It is owned by the kernel, so it only lives on context 0. /// append-only buffer. It is owned by the kernel, so it only lives on context 0.
TrieData = 12, TrieData = 12,
/// The account address associated with the `i`th storage trie. Only lives on context 0. /// A buffer used to store the encodings of a branch node's children.
StorageTrieAddresses = 13, TrieEncodedChild = 13,
/// A pointer to the `i`th storage trie within the `TrieData` buffer. Only lives on context 0. /// A buffer used to store the lengths of the encodings of a branch node's children.
StorageTriePointers = 14, TrieEncodedChildLen = 14,
/// Like `StorageTriePointers`, except that these pointers correspond to the version of each
/// trie at the creation of a given context. This lets us easily revert a context by replacing
/// `StorageTriePointers` with `StorageTrieCheckpointPointers`.
/// See also `StateTrieCheckpointPointer`.
StorageTrieCheckpointPointers = 15,
} }
impl Segment { impl Segment {
pub(crate) const COUNT: usize = 16; pub(crate) const COUNT: usize = 15;
pub(crate) fn all() -> [Self; Self::COUNT] { pub(crate) fn all() -> [Self; Self::COUNT] {
[ [
@ -58,9 +53,8 @@ impl Segment {
Self::TxnData, Self::TxnData,
Self::RlpRaw, Self::RlpRaw,
Self::TrieData, Self::TrieData,
Self::StorageTrieAddresses, Self::TrieEncodedChild,
Self::StorageTriePointers, Self::TrieEncodedChildLen,
Self::StorageTrieCheckpointPointers,
] ]
} }
@ -80,9 +74,8 @@ impl Segment {
Segment::TxnData => "SEGMENT_TXN_DATA", Segment::TxnData => "SEGMENT_TXN_DATA",
Segment::RlpRaw => "SEGMENT_RLP_RAW", Segment::RlpRaw => "SEGMENT_RLP_RAW",
Segment::TrieData => "SEGMENT_TRIE_DATA", Segment::TrieData => "SEGMENT_TRIE_DATA",
Segment::StorageTrieAddresses => "SEGMENT_STORAGE_TRIE_ADDRS", Segment::TrieEncodedChild => "SEGMENT_TRIE_ENCODED_CHILD",
Segment::StorageTriePointers => "SEGMENT_STORAGE_TRIE_PTRS", Segment::TrieEncodedChildLen => "SEGMENT_TRIE_ENCODED_CHILD_LEN",
Segment::StorageTrieCheckpointPointers => "SEGMENT_STORAGE_TRIE_CHECKPOINT_PTRS",
} }
} }
@ -102,9 +95,8 @@ impl Segment {
Segment::TxnData => 256, Segment::TxnData => 256,
Segment::RlpRaw => 8, Segment::RlpRaw => 8,
Segment::TrieData => 256, Segment::TrieData => 256,
Segment::StorageTrieAddresses => 160, Segment::TrieEncodedChild => 256,
Segment::StorageTriePointers => 32, Segment::TrieEncodedChildLen => 6,
Segment::StorageTrieCheckpointPointers => 32,
} }
} }
} }

View File

@ -146,7 +146,7 @@ impl<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>
} }
// Verify the CTL checks. // Verify the CTL checks.
let degrees_bits = std::array::from_fn(|i| verifier_data[i].common.degree_bits); let degrees_bits = std::array::from_fn(|i| verifier_data[i].common.degree_bits());
verify_cross_table_lookups::<F, C, D>( verify_cross_table_lookups::<F, C, D>(
cross_table_lookups, cross_table_lookups,
pis.map(|p| p.ctl_zs_last), pis.map(|p| p.ctl_zs_last),
@ -221,7 +221,7 @@ impl<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>
} }
// Verify the CTL checks. // Verify the CTL checks.
let degrees_bits = std::array::from_fn(|i| verifier_data[i].common.degree_bits); let degrees_bits = std::array::from_fn(|i| verifier_data[i].common.degree_bits());
verify_cross_table_lookups_circuit::<F, C, D>( verify_cross_table_lookups_circuit::<F, C, D>(
builder, builder,
cross_table_lookups, cross_table_lookups,

View File

@ -455,7 +455,7 @@ pub trait PrimeField: Field {
let mut x = w * *self; let mut x = w * *self;
let mut b = x * w; let mut b = x * w;
let mut v = Self::TWO_ADICITY as usize; let mut v = Self::TWO_ADICITY;
while !b.is_one() { while !b.is_one() {
let mut k = 0usize; let mut k = 0usize;

View File

@ -191,7 +191,7 @@ fn benchmark(config: &CircuitConfig, log2_inner_size: usize) -> Result<()> {
info!( info!(
"Initial proof degree {} = 2^{}", "Initial proof degree {} = 2^{}",
cd.degree(), cd.degree(),
cd.degree_bits cd.degree_bits()
); );
// Recursively verify the proof // Recursively verify the proof
@ -200,7 +200,7 @@ fn benchmark(config: &CircuitConfig, log2_inner_size: usize) -> Result<()> {
info!( info!(
"Single recursion proof degree {} = 2^{}", "Single recursion proof degree {} = 2^{}",
cd.degree(), cd.degree(),
cd.degree_bits cd.degree_bits()
); );
// Add a second layer of recursion to shrink the proof size further // Add a second layer of recursion to shrink the proof size further
@ -209,7 +209,7 @@ fn benchmark(config: &CircuitConfig, log2_inner_size: usize) -> Result<()> {
info!( info!(
"Double recursion proof degree {} = 2^{}", "Double recursion proof degree {} = 2^{}",
cd.degree(), cd.degree(),
cd.degree_bits cd.degree_bits()
); );
test_serialization(proof, vd, cd)?; test_serialization(proof, vd, cd)?;

View File

@ -443,7 +443,7 @@ impl<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
let mut current = base; let mut current = base;
let mut product = self.one_extension(); let mut product = self.one_extension();
for j in 0..bits_u64(exponent as u64) { for j in 0..bits_u64(exponent) {
if j != 0 { if j != 0 {
current = self.square_extension(current); current = self.square_extension(current);
} }

View File

@ -818,7 +818,6 @@ impl<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
let common = CommonCircuitData { let common = CommonCircuitData {
config: self.config, config: self.config,
fri_params, fri_params,
degree_bits,
gates, gates,
selectors_info, selectors_info,
quotient_degree_factor, quotient_degree_factor,

View File

@ -296,8 +296,6 @@ pub struct CommonCircuitData<
pub(crate) fri_params: FriParams, pub(crate) fri_params: FriParams,
pub degree_bits: usize,
/// The types of gates used in this circuit, along with their prefixes. /// The types of gates used in this circuit, along with their prefixes.
pub(crate) gates: Vec<GateRef<C::F, D>>, pub(crate) gates: Vec<GateRef<C::F, D>>,
@ -325,16 +323,20 @@ pub struct CommonCircuitData<
impl<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize> impl<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>
CommonCircuitData<F, C, D> CommonCircuitData<F, C, D>
{ {
pub const fn degree_bits(&self) -> usize {
self.fri_params.degree_bits
}
pub fn degree(&self) -> usize { pub fn degree(&self) -> usize {
1 << self.degree_bits 1 << self.degree_bits()
} }
pub fn lde_size(&self) -> usize { pub fn lde_size(&self) -> usize {
1 << (self.degree_bits + self.config.fri_config.rate_bits) self.fri_params.lde_size()
} }
pub fn lde_generator(&self) -> F { pub fn lde_generator(&self) -> F {
F::primitive_root_of_unity(self.degree_bits + self.config.fri_config.rate_bits) F::primitive_root_of_unity(self.degree_bits() + self.config.fri_config.rate_bits)
} }
pub fn constraint_degree(&self) -> usize { pub fn constraint_degree(&self) -> usize {
@ -377,7 +379,7 @@ impl<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>
}; };
// The Z polynomials are also opened at g * zeta. // The Z polynomials are also opened at g * zeta.
let g = F::Extension::primitive_root_of_unity(self.degree_bits); let g = F::Extension::primitive_root_of_unity(self.degree_bits());
let zeta_next = g * zeta; let zeta_next = g * zeta;
let zeta_next_batch = FriBatchInfo { let zeta_next_batch = FriBatchInfo {
point: zeta_next, point: zeta_next,
@ -403,7 +405,7 @@ impl<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>
}; };
// The Z polynomials are also opened at g * zeta. // The Z polynomials are also opened at g * zeta.
let g = F::primitive_root_of_unity(self.degree_bits); let g = F::primitive_root_of_unity(self.degree_bits());
let zeta_next = builder.mul_const_extension(g, zeta); let zeta_next = builder.mul_const_extension(g, zeta);
let zeta_next_batch = FriBatchInfoTarget { let zeta_next_batch = FriBatchInfoTarget {
point: zeta_next, point: zeta_next,

View File

@ -62,7 +62,7 @@ fn get_challenges<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, cons
commit_phase_merkle_caps, commit_phase_merkle_caps,
final_poly, final_poly,
pow_witness, pow_witness,
common_data.degree_bits, common_data.degree_bits(),
&config.fri_config, &config.fri_config,
), ),
}) })
@ -181,7 +181,7 @@ impl<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>
&self.proof.openings.to_fri_openings(), &self.proof.openings.to_fri_openings(),
*fri_alpha, *fri_alpha,
); );
let log_n = common_data.degree_bits + common_data.config.fri_config.rate_bits; let log_n = common_data.degree_bits() + common_data.config.fri_config.rate_bits;
// Simulate the proof verification and collect the inferred elements. // Simulate the proof verification and collect the inferred elements.
// The content of the loop is basically the same as the `fri_verifier_query_round` function. // The content of the loop is basically the same as the `fri_verifier_query_round` function.
for &(mut x_index) in fri_query_indices { for &(mut x_index) in fri_query_indices {

View File

@ -172,9 +172,9 @@ where
// To avoid leaking witness data, we want to ensure that our opening locations, `zeta` and // To avoid leaking witness data, we want to ensure that our opening locations, `zeta` and
// `g * zeta`, are not in our subgroup `H`. It suffices to check `zeta` only, since // `g * zeta`, are not in our subgroup `H`. It suffices to check `zeta` only, since
// `(g * zeta)^n = zeta^n`, where `n` is the order of `g`. // `(g * zeta)^n = zeta^n`, where `n` is the order of `g`.
let g = F::Extension::primitive_root_of_unity(common_data.degree_bits); let g = F::Extension::primitive_root_of_unity(common_data.degree_bits());
ensure!( ensure!(
zeta.exp_power_of_2(common_data.degree_bits) != F::Extension::ONE, zeta.exp_power_of_2(common_data.degree_bits()) != F::Extension::ONE,
"Opening point is in the subgroup." "Opening point is in the subgroup."
); );
@ -342,10 +342,10 @@ fn compute_quotient_polys<
// steps away since we work on an LDE of degree `max_filtered_constraint_degree`. // steps away since we work on an LDE of degree `max_filtered_constraint_degree`.
let next_step = 1 << quotient_degree_bits; let next_step = 1 << quotient_degree_bits;
let points = F::two_adic_subgroup(common_data.degree_bits + quotient_degree_bits); let points = F::two_adic_subgroup(common_data.degree_bits() + quotient_degree_bits);
let lde_size = points.len(); let lde_size = points.len();
let z_h_on_coset = ZeroPolyOnCoset::new(common_data.degree_bits, quotient_degree_bits); let z_h_on_coset = ZeroPolyOnCoset::new(common_data.degree_bits(), quotient_degree_bits);
let points_batches = points.par_chunks(BATCH_SIZE); let points_batches = points.par_chunks(BATCH_SIZE);
let num_batches = ceil_div_usize(points.len(), BATCH_SIZE); let num_batches = ceil_div_usize(points.len(), BATCH_SIZE);

View File

@ -71,7 +71,7 @@ impl<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
let partial_products = &proof.openings.partial_products; let partial_products = &proof.openings.partial_products;
let zeta_pow_deg = let zeta_pow_deg =
self.exp_power_of_2_extension(challenges.plonk_zeta, inner_common_data.degree_bits); self.exp_power_of_2_extension(challenges.plonk_zeta, inner_common_data.degree_bits());
let vanishing_polys_zeta = with_context!( let vanishing_polys_zeta = with_context!(
self, self,
"evaluate the vanishing polynomial at our challenge point, zeta.", "evaluate the vanishing polynomial at our challenge point, zeta.",
@ -228,17 +228,17 @@ mod tests {
// Start with a degree 2^14 proof // Start with a degree 2^14 proof
let (proof, vd, cd) = dummy_proof::<F, C, D>(&config, 16_000)?; let (proof, vd, cd) = dummy_proof::<F, C, D>(&config, 16_000)?;
assert_eq!(cd.degree_bits, 14); assert_eq!(cd.degree_bits(), 14);
// Shrink it to 2^13. // Shrink it to 2^13.
let (proof, vd, cd) = let (proof, vd, cd) =
recursive_proof::<F, C, C, D>(proof, vd, cd, &config, Some(13), false, false)?; recursive_proof::<F, C, C, D>(proof, vd, cd, &config, Some(13), false, false)?;
assert_eq!(cd.degree_bits, 13); assert_eq!(cd.degree_bits(), 13);
// Shrink it to 2^12. // Shrink it to 2^12.
let (proof, vd, cd) = let (proof, vd, cd) =
recursive_proof::<F, C, C, D>(proof, vd, cd, &config, None, true, true)?; recursive_proof::<F, C, C, D>(proof, vd, cd, &config, None, true, true)?;
assert_eq!(cd.degree_bits, 12); assert_eq!(cd.degree_bits(), 12);
test_serialization(&proof, &vd, &cd)?; test_serialization(&proof, &vd, &cd)?;
@ -260,11 +260,11 @@ mod tests {
// An initial dummy proof. // An initial dummy proof.
let (proof, vd, cd) = dummy_proof::<F, C, D>(&standard_config, 4_000)?; let (proof, vd, cd) = dummy_proof::<F, C, D>(&standard_config, 4_000)?;
assert_eq!(cd.degree_bits, 12); assert_eq!(cd.degree_bits(), 12);
// A standard recursive proof. // A standard recursive proof.
let (proof, vd, cd) = recursive_proof(proof, vd, cd, &standard_config, None, false, false)?; let (proof, vd, cd) = recursive_proof(proof, vd, cd, &standard_config, None, false, false)?;
assert_eq!(cd.degree_bits, 12); assert_eq!(cd.degree_bits(), 12);
// A high-rate recursive proof, designed to be verifiable with fewer routed wires. // A high-rate recursive proof, designed to be verifiable with fewer routed wires.
let high_rate_config = CircuitConfig { let high_rate_config = CircuitConfig {
@ -278,7 +278,7 @@ mod tests {
}; };
let (proof, vd, cd) = let (proof, vd, cd) =
recursive_proof::<F, C, C, D>(proof, vd, cd, &high_rate_config, None, true, true)?; recursive_proof::<F, C, C, D>(proof, vd, cd, &high_rate_config, None, true, true)?;
assert_eq!(cd.degree_bits, 12); assert_eq!(cd.degree_bits(), 12);
// A final proof, optimized for size. // A final proof, optimized for size.
let final_config = CircuitConfig { let final_config = CircuitConfig {
@ -294,7 +294,7 @@ mod tests {
}; };
let (proof, vd, cd) = let (proof, vd, cd) =
recursive_proof::<F, KC, C, D>(proof, vd, cd, &final_config, None, true, true)?; recursive_proof::<F, KC, C, D>(proof, vd, cd, &final_config, None, true, true)?;
assert_eq!(cd.degree_bits, 12, "final proof too large"); assert_eq!(cd.degree_bits(), 12, "final proof too large");
test_serialization(&proof, &vd, &cd)?; test_serialization(&proof, &vd, &cd)?;

View File

@ -82,7 +82,7 @@ where
let quotient_polys_zeta = &proof.openings.quotient_polys; let quotient_polys_zeta = &proof.openings.quotient_polys;
let zeta_pow_deg = challenges let zeta_pow_deg = challenges
.plonk_zeta .plonk_zeta
.exp_power_of_2(common_data.degree_bits); .exp_power_of_2(common_data.degree_bits());
let z_h_zeta = zeta_pow_deg - F::Extension::ONE; let z_h_zeta = zeta_pow_deg - F::Extension::ONE;
// `quotient_polys_zeta` holds `num_challenges * quotient_degree_factor` evaluations. // `quotient_polys_zeta` holds `num_challenges * quotient_degree_factor` evaluations.
// Each chunk of `quotient_degree_factor` holds the evaluations of `t_0(zeta),...,t_{quotient_degree_factor-1}(zeta)` // Each chunk of `quotient_degree_factor` holds the evaluations of `t_0(zeta),...,t_{quotient_degree_factor-1}(zeta)`