use super::{constraint_builder::ConstrainBuilderCommon, CachedRegion, MemoryAddress, WordExpr};
use crate::{
evm_circuit::{
param::{N_BYTES_GAS, N_BYTES_MEMORY_ADDRESS, N_BYTES_MEMORY_WORD_SIZE, N_BYTES_U64},
util::{
and,
constraint_builder::EVMConstraintBuilder,
from_bytes,
math_gadget::{
AddWordsGadget, ConstantDivisionGadget, IsZeroGadget, LtGadget, MinMaxGadget,
RangeCheckGadget,
},
or, select, sum, Cell,
},
},
util::{
word::{WordLoHi, WordLoHiCell},
Expr,
},
};
use array_init::array_init;
use eth_types::{
evm_types::{GasCost, MAX_EXPANDED_MEMORY_ADDRESS},
Field, ToLittleEndian, U256,
};
use gadgets::util::not;
use halo2_proofs::{
circuit::Value,
plonk::{Error, Expression},
};
use itertools::Itertools;
pub(crate) mod address_low {
use crate::evm_circuit::param::N_BYTES_MEMORY_ADDRESS;
pub(crate) fn value(address: [u8; 32]) -> u64 {
let mut bytes = [0; 8];
bytes[..N_BYTES_MEMORY_ADDRESS].copy_from_slice(&address[..N_BYTES_MEMORY_ADDRESS]);
u64::from_le_bytes(bytes)
}
}
pub(crate) trait CommonMemoryAddressGadget<F: Field> {
fn construct_self(cb: &mut EVMConstraintBuilder<F>) -> Self;
fn assign(
&self,
region: &mut CachedRegion<'_, '_, F>,
offset: usize,
memory_offset: U256,
memory_length: U256,
) -> Result<u64, Error>;
fn offset_word(&self) -> WordLoHi<Expression<F>>;
fn length_word(&self) -> WordLoHi<Expression<F>>;
fn length(&self) -> Expression<F>;
fn address(&self) -> Expression<F>;
}
#[derive(Clone, Debug)]
pub(crate) struct MemoryAddressGadget<F> {
memory_offset_bytes: MemoryAddress<F>,
memory_offset: WordLoHiCell<F>,
memory_length: MemoryAddress<F>,
memory_length_is_zero: IsZeroGadget<F>,
}
impl<F: Field> MemoryAddressGadget<F> {
pub(crate) fn construct(
cb: &mut EVMConstraintBuilder<F>,
memory_offset: WordLoHiCell<F>,
memory_length: MemoryAddress<F>,
) -> Self {
let memory_length_is_zero = cb.is_zero(memory_length.sum_expr());
let memory_offset_bytes = cb.query_memory_address();
let has_length = 1.expr() - memory_length_is_zero.expr();
cb.condition(has_length, |cb| {
cb.require_equal_word(
"Offset decomposition into 5 bytes",
WordLoHi::from_lo_unchecked(memory_offset_bytes.expr()),
memory_offset.to_word(),
);
});
Self {
memory_offset_bytes,
memory_offset,
memory_length,
memory_length_is_zero,
}
}
pub(crate) fn has_length(&self) -> Expression<F> {
1.expr() - self.memory_length_is_zero.expr()
}
pub(crate) fn offset(&self) -> Expression<F> {
self.has_length() * self.memory_offset_bytes.expr()
}
}
impl<F: Field> CommonMemoryAddressGadget<F> for MemoryAddressGadget<F> {
fn construct_self(cb: &mut EVMConstraintBuilder<F>) -> Self {
let offset = cb.query_word_unchecked();
let length = cb.query_memory_address();
Self::construct(cb, offset, length)
}
fn assign(
&self,
region: &mut CachedRegion<'_, '_, F>,
offset: usize,
memory_offset: U256,
memory_length: U256,
) -> Result<u64, Error> {
let memory_offset_bytes = memory_offset.to_le_bytes();
let memory_length_bytes = memory_length.to_le_bytes();
let memory_length_is_zero = memory_length.is_zero();
self.memory_offset_bytes.assign(
region,
offset,
if memory_length_is_zero {
Some([0; 5])
} else {
memory_offset_bytes[..N_BYTES_MEMORY_ADDRESS]
.try_into()
.ok()
},
)?;
self.memory_offset
.assign_u256(region, offset, memory_offset)?;
self.memory_length
.assign_u256(region, offset, memory_length)?;
self.memory_length_is_zero
.assign(region, offset, sum::value(&memory_length_bytes))?;
Ok(if memory_length_is_zero {
0
} else {
address_low::value(memory_offset_bytes) + address_low::value(memory_length_bytes)
})
}
fn offset_word(&self) -> WordLoHi<Expression<F>> {
self.memory_offset.to_word()
}
fn length_word(&self) -> WordLoHi<Expression<F>> {
self.memory_length.to_word()
}
fn length(&self) -> Expression<F> {
self.memory_length.expr()
}
fn address(&self) -> Expression<F> {
self.offset() + self.length()
}
}
#[derive(Clone, Debug)]
pub(crate) struct MemoryExpandedAddressGadget<F> {
length_is_zero: IsZeroGadget<F>,
offset_length_sum: AddWordsGadget<F, 2, false>,
sum_lt_cap: LtGadget<F, N_BYTES_U64>,
sum_within_u64: IsZeroGadget<F>,
}
impl<F: Field> CommonMemoryAddressGadget<F> for MemoryExpandedAddressGadget<F> {
fn construct_self(cb: &mut EVMConstraintBuilder<F>) -> Self {
let offset = cb.query_word32();
let length = cb.query_word32();
let sum = cb.query_word32();
let sum_lt_cap = cb.is_lt(
from_bytes::expr(&sum.limbs[..N_BYTES_U64]),
(MAX_EXPANDED_MEMORY_ADDRESS + 1).expr(),
);
let sum_overflow_hi = sum::expr(&sum.limbs[N_BYTES_U64..]);
let sum_within_u64 = cb.is_zero(sum_overflow_hi);
let length_is_zero = cb.is_zero(sum::expr(&length.limbs));
let offset_length_sum = AddWordsGadget::construct(cb, [offset, length], sum);
Self {
length_is_zero,
offset_length_sum,
sum_lt_cap,
sum_within_u64,
}
}
fn assign(
&self,
region: &mut CachedRegion<'_, '_, F>,
offset: usize,
memory_offset: U256,
memory_length: U256,
) -> Result<u64, Error> {
let length_bytes = memory_length
.to_le_bytes()
.iter()
.fold(0, |acc, val| acc + u64::from(*val));
self.length_is_zero
.assign(region, offset, F::from(length_bytes))?;
let (sum, sum_word_overflow) = memory_offset.overflowing_add(memory_length);
self.offset_length_sum
.assign(region, offset, [memory_offset, memory_length], sum)?;
self.sum_lt_cap.assign(
region,
offset,
F::from(sum.low_u64()),
F::from(MAX_EXPANDED_MEMORY_ADDRESS + 1),
)?;
let sum_overflow_hi_bytes = sum.to_le_bytes()[N_BYTES_U64..]
.iter()
.fold(0, |acc, val| acc + u64::from(*val));
self.sum_within_u64
.assign(region, offset, F::from(sum_overflow_hi_bytes))?;
let address = if length_bytes == 0
|| sum_overflow_hi_bytes != 0
|| sum_word_overflow
|| sum.low_u64() > MAX_EXPANDED_MEMORY_ADDRESS
{
0
} else {
sum.low_u64()
};
Ok(address)
}
fn offset_word(&self) -> WordLoHi<Expression<F>> {
let addends = self.offset_length_sum.addends();
addends[0].to_word()
}
fn length_word(&self) -> WordLoHi<Expression<F>> {
let addends = self.offset_length_sum.addends();
addends[1].to_word()
}
fn length(&self) -> Expression<F> {
let addends = self.offset_length_sum.addends();
select::expr(
self.within_range(),
from_bytes::expr(&addends[1].limbs[..N_BYTES_U64]),
0.expr(),
)
}
fn address(&self) -> Expression<F> {
select::expr(
self.length_is_zero.expr(),
0.expr(),
select::expr(
self.within_range(),
from_bytes::expr(&self.offset_length_sum.sum().limbs[..N_BYTES_U64]),
0.expr(),
),
)
}
}
impl<F: Field> MemoryExpandedAddressGadget<F> {
pub(crate) fn length_value(memory_offset: U256, memory_length: U256) -> u64 {
if memory_length.is_zero() {
return 0;
}
memory_offset
.checked_add(memory_length)
.map_or(0, |address| {
if address > MAX_EXPANDED_MEMORY_ADDRESS.into() {
0
} else {
memory_length.as_u64()
}
})
}
pub(crate) fn overflow(&self) -> Expression<F> {
not::expr(self.within_range())
}
pub(crate) fn within_range(&self) -> Expression<F> {
or::expr([
self.length_is_zero.expr(),
and::expr([
self.sum_lt_cap.expr(),
self.sum_within_u64.expr(),
not::expr(self.offset_length_sum.carry().as_ref().unwrap()),
]),
])
}
}
#[derive(Clone, Debug)]
pub(crate) struct MemoryWordSizeGadget<F> {
memory_word_size: ConstantDivisionGadget<F, N_BYTES_MEMORY_WORD_SIZE>,
}
impl<F: Field> MemoryWordSizeGadget<F> {
pub(crate) fn construct(cb: &mut EVMConstraintBuilder<F>, address: Expression<F>) -> Self {
let memory_word_size = cb.div_by_const(address + 31.expr(), 32);
Self { memory_word_size }
}
pub(crate) fn expr(&self) -> Expression<F> {
self.memory_word_size.quotient()
}
pub(crate) fn assign(
&self,
region: &mut CachedRegion<'_, '_, F>,
offset: usize,
address: u64,
) -> Result<u64, Error> {
let (quotient, _) = self
.memory_word_size
.assign(region, offset, (address as u128) + 31)?;
Ok(quotient as u64)
}
}
#[derive(Clone, Debug)]
pub(crate) struct MemoryExpansionGadget<F, const N: usize, const N_BYTES_MEMORY_WORD_SIZE: usize> {
memory_word_sizes: [MemoryWordSizeGadget<F>; N],
max_memory_word_sizes: [MinMaxGadget<F, N_BYTES_MEMORY_WORD_SIZE>; N],
curr_quad_memory_cost: ConstantDivisionGadget<F, N_BYTES_GAS>,
next_quad_memory_cost: ConstantDivisionGadget<F, N_BYTES_GAS>,
next_memory_word_size: Expression<F>,
gas_cost: Expression<F>,
}
impl<F: Field, const N: usize, const N_BYTES_MEMORY_WORD_SIZE: usize>
MemoryExpansionGadget<F, N, N_BYTES_MEMORY_WORD_SIZE>
{
pub(crate) fn construct(
cb: &mut EVMConstraintBuilder<F>,
addresses: [Expression<F>; N],
) -> Self {
let memory_word_sizes =
addresses.map(|address| MemoryWordSizeGadget::construct(cb, address));
let curr_memory_word_size = cb.curr.state.memory_word_size.expr();
let mut next_memory_word_size = curr_memory_word_size.clone();
let max_memory_word_sizes = array_init(|idx| {
let max_memory_word_size = MinMaxGadget::construct(
cb,
next_memory_word_size.clone(),
memory_word_sizes[idx].expr(),
);
next_memory_word_size = max_memory_word_size.max();
max_memory_word_size
});
let curr_quad_memory_cost = ConstantDivisionGadget::construct(
cb,
curr_memory_word_size.clone() * curr_memory_word_size.clone(),
GasCost::MEMORY_EXPANSION_QUAD_DENOMINATOR,
);
let next_quad_memory_cost = ConstantDivisionGadget::construct(
cb,
next_memory_word_size.clone() * next_memory_word_size.clone(),
GasCost::MEMORY_EXPANSION_QUAD_DENOMINATOR,
);
let gas_cost = GasCost::MEMORY_EXPANSION_LINEAR_COEFF.expr()
* (next_memory_word_size.clone() - curr_memory_word_size)
+ (next_quad_memory_cost.quotient() - curr_quad_memory_cost.quotient());
Self {
memory_word_sizes,
max_memory_word_sizes,
curr_quad_memory_cost,
next_quad_memory_cost,
next_memory_word_size,
gas_cost,
}
}
pub(crate) fn next_memory_word_size(&self) -> Expression<F> {
self.next_memory_word_size.clone()
}
pub(crate) fn gas_cost(&self) -> Expression<F> {
self.gas_cost.clone()
}
pub(crate) fn assign(
&self,
region: &mut CachedRegion<'_, '_, F>,
offset: usize,
curr_memory_word_size: u64,
addresses: [u64; N],
) -> Result<(u64, u64), Error> {
let memory_word_sizes = self
.memory_word_sizes
.iter()
.zip(addresses.iter())
.map(|(memory_word_size, address)| memory_word_size.assign(region, offset, *address))
.collect::<Result<Vec<_>, _>>()?;
let mut next_memory_word_size = curr_memory_word_size;
for (max_memory_word_sizes, memory_word_size) in self
.max_memory_word_sizes
.iter()
.zip(memory_word_sizes.iter())
{
let (_, max) = max_memory_word_sizes.assign(
region,
offset,
F::from(next_memory_word_size),
F::from(*memory_word_size),
)?;
next_memory_word_size = max.get_lower_128() as u64;
}
let (curr_quad_memory_cost, _) = self.curr_quad_memory_cost.assign(
region,
offset,
(curr_memory_word_size as u128) * (curr_memory_word_size as u128),
)?;
let (next_quad_memory_cost, _) = self.next_quad_memory_cost.assign(
region,
offset,
(next_memory_word_size as u128) * (next_memory_word_size as u128),
)?;
let memory_cost = GasCost::MEMORY_EXPANSION_LINEAR_COEFF
* (next_memory_word_size - curr_memory_word_size)
+ (next_quad_memory_cost - curr_quad_memory_cost) as u64;
Ok((next_memory_word_size, memory_cost))
}
}
#[derive(Clone, Debug)]
pub(crate) struct MemoryCopierGasGadget<F, const GAS_COPY: u64> {
word_size: MemoryWordSizeGadget<F>,
gas_cost: Expression<F>,
gas_cost_range_check: RangeCheckGadget<F, N_BYTES_GAS>,
}
impl<F: Field, const GAS_COPY: u64> MemoryCopierGasGadget<F, GAS_COPY> {
pub(crate) fn construct(
cb: &mut EVMConstraintBuilder<F>,
num_bytes: Expression<F>,
memory_expansion_gas_cost: Expression<F>,
) -> Self {
let word_size = MemoryWordSizeGadget::construct(cb, num_bytes);
let gas_cost = word_size.expr() * GAS_COPY.expr() + memory_expansion_gas_cost;
let gas_cost_range_check = RangeCheckGadget::construct(cb, gas_cost.clone());
Self {
word_size,
gas_cost,
gas_cost_range_check,
}
}
pub(crate) fn gas_cost(&self) -> Expression<F> {
self.gas_cost.clone()
}
pub(crate) fn assign(
&self,
region: &mut CachedRegion<'_, '_, F>,
offset: usize,
num_bytes: u64,
memory_expansion_gas_cost: u64,
) -> Result<u64, Error> {
let word_size = self.word_size.assign(region, offset, num_bytes)?;
let gas_cost = word_size * GAS_COPY + memory_expansion_gas_cost;
self.gas_cost_range_check
.assign(region, offset, F::from(gas_cost))?;
Ok(gas_cost)
}
}
#[derive(Clone, Debug)]
pub(crate) struct BufferReaderGadget<F, const MAX_BYTES: usize, const N_BYTES_MEMORY_ADDRESS: usize>
{
bytes: [Cell<F>; MAX_BYTES],
selectors: [Cell<F>; MAX_BYTES],
min_gadget: MinMaxGadget<F, N_BYTES_MEMORY_ADDRESS>,
}
impl<F: Field, const MAX_BYTES: usize, const ADDR_SIZE_IN_BYTES: usize>
BufferReaderGadget<F, MAX_BYTES, ADDR_SIZE_IN_BYTES>
{
pub(crate) fn construct(
cb: &mut EVMConstraintBuilder<F>,
addr_start: Expression<F>,
addr_end: Expression<F>,
) -> Self {
let bytes = array_init(|_| cb.query_byte());
let selectors = array_init(|_| cb.query_bool());
for i in 0..MAX_BYTES {
let selector_prev = if i == 0 {
1.expr()
} else {
selectors[i - 1].expr()
};
cb.require_boolean(
"Constrain selectors can only transit from 1 to 0",
selector_prev - selectors[i].expr(),
);
cb.add_constraint(
"bytes[i] == 0 when selectors[i] == 0",
(1.expr() - selectors[i].expr()) * bytes[i].expr(),
);
}
let is_empty = not::expr(&selectors[0]);
let cap = select::expr(is_empty.expr(), 0.expr(), MAX_BYTES.expr());
let signed_len = addr_end - addr_start;
let min_gadget = cb.min_max(cap, signed_len);
cb.condition(is_empty.expr(), |cb| {
cb.require_zero("addr_end <= addr_start", min_gadget.max());
});
cb.condition(not::expr(is_empty), |cb| {
let buffer_len = sum::expr(&selectors);
let capped_len = min_gadget.min();
cb.require_equal(
"buffer length == end - start (capped)",
buffer_len,
capped_len,
);
});
BufferReaderGadget {
bytes,
selectors,
min_gadget,
}
}
pub(crate) fn assign(
&self,
region: &mut CachedRegion<'_, '_, F>,
offset: usize,
addr_start: u64,
addr_end: u64,
bytes: &[u8],
) -> Result<(), Error> {
assert_eq!(bytes.len(), MAX_BYTES);
for (idx, ((byte_col, &byte_val), selector_col)) in self
.bytes
.iter()
.zip_eq(bytes.iter())
.zip_eq(self.selectors.iter())
.enumerate()
{
let selector = (addr_start + idx as u64) < addr_end;
selector_col.assign(region, offset, Value::known(F::from(selector as u64)))?;
byte_col.assign(region, offset, Value::known(F::from(byte_val as u64)))?;
}
let is_empty = addr_start >= addr_end;
let cap = if is_empty { 0 } else { MAX_BYTES };
let signed_len = if is_empty {
-F::from(addr_start - addr_end)
} else {
F::from(addr_end - addr_start)
};
self.min_gadget
.assign(region, offset, F::from(cap as u64), signed_len)?;
Ok(())
}
pub(crate) fn byte(&self, idx: usize) -> Expression<F> {
self.bytes[idx].expr()
}
pub(crate) fn read_flag(&self, idx: usize) -> Expression<F> {
self.selectors[idx].expr()
}
}
#[cfg(test)]
mod test_util;
#[cfg(test)]
mod test {
use crate::evm_circuit::util::{constraint_builder::ConstrainBuilderCommon, Cell, U64Cell};
use eth_types::Word;
use halo2_proofs::{halo2curves::bn256::Fr, plonk::Error};
use super::{test_util::*, *};
#[derive(Clone)]
struct BufferReaderGadgetTestContainer<
F,
const MAX_BYTES: usize,
const ADDR_SIZE_IN_BYTES: usize,
> {
buffer_reader_gadget: BufferReaderGadget<F, MAX_BYTES, ADDR_SIZE_IN_BYTES>,
addr_start: U64Cell<F>,
addr_end: U64Cell<F>,
bytes: [Cell<F>; MAX_BYTES],
}
impl<F: Field, const MAX_BYTES: usize, const ADDR_SIZE_IN_BYTES: usize> MemoryGadgetContainer<F>
for BufferReaderGadgetTestContainer<F, MAX_BYTES, ADDR_SIZE_IN_BYTES>
{
fn configure_gadget_container(cb: &mut EVMConstraintBuilder<F>) -> Self {
let addr_start = cb.query_u64();
let addr_end = cb.query_u64();
let buffer_reader_gadget =
BufferReaderGadget::<F, MAX_BYTES, ADDR_SIZE_IN_BYTES>::construct(
cb,
addr_start.expr(),
addr_end.expr(),
);
let bytes = cb.query_bytes();
let bytes_expr: Vec<Expression<F>> = bytes
.iter()
.map(|e| e.expr())
.collect::<Vec<Expression<F>>>();
let buffer_reader_gadget_bytes_expr = (0..MAX_BYTES)
.map(|e| buffer_reader_gadget.byte(e))
.collect::<Vec<Expression<F>>>();
let buffer_reader_gadget_seletor = (0..MAX_BYTES)
.clone()
.map(|e| buffer_reader_gadget.read_flag(e))
.collect::<Vec<Expression<F>>>();
for (byte_expr, buffer_reader_gadget_byte_expr) in bytes_expr
.iter()
.zip(buffer_reader_gadget_bytes_expr.iter())
{
cb.require_equal(
"every byte equal",
byte_expr.expr(),
buffer_reader_gadget_byte_expr.expr(),
);
}
cb.require_equal(
"selector length equal",
sum::expr(buffer_reader_gadget_seletor),
addr_end.expr() - addr_start.expr(),
);
BufferReaderGadgetTestContainer {
buffer_reader_gadget,
addr_start,
addr_end,
bytes,
}
}
fn assign_gadget_container(
&self,
witnesses: &[Word],
region: &mut CachedRegion<'_, '_, F>,
) -> Result<(), Error> {
let offset = 0;
let addr_start =
u64::from_le_bytes(witnesses[0].to_le_bytes()[..8].try_into().unwrap());
let addr_end = u64::from_le_bytes(witnesses[1].to_le_bytes()[..8].try_into().unwrap());
let mut input_bytes: Vec<u8> = Vec::new();
input_bytes
.extend_from_slice(&witnesses[2].to_le_bytes()[0..required_bytes(witnesses[2])]);
self.addr_start
.assign(region, offset, Some(addr_start.to_le_bytes()))?;
self.addr_end
.assign(region, offset, Some(addr_end.to_le_bytes()))?;
self.bytes
.iter()
.zip(input_bytes.iter())
.map(|(byte, input_byte)| {
byte.assign(region, offset, Value::known(F::from(*input_byte as u64)))
})
.collect::<Result<Vec<_>, _>>()?;
self.buffer_reader_gadget.assign(
region,
offset,
addr_start,
addr_end,
&input_bytes[..],
)?;
Ok(())
}
}
#[test]
fn test_buffer_reader_gadget_completeness() {
try_test!(
BufferReaderGadgetTestContainer<Fr, 2, 10>,
[
Word::from(0),
Word::from(2),
Word::from(256),
],
true,
);
try_test!(
BufferReaderGadgetTestContainer<Fr, 2, 10>,
[
Word::from(0),
Word::from(2),
Word::from(256),
],
true,
);
}
#[test]
fn test_buffer_reader_gadget_soundness() {
try_test!(
BufferReaderGadgetTestContainer<Fr, 2, 10>,
[
Word::from(0),
Word::from(1),
Word::from(256),
],
false,
);
try_test!(
BufferReaderGadgetTestContainer<Fr, 2, 10>,
[
Word::from(1),
Word::from(0),
Word::from(256),
],
false,
);
try_test!(
BufferReaderGadgetTestContainer<Fr, 2, 10>,
[
Word::from(0),
Word::from(0),
Word::from(256),
],
false,
);
try_test!(
BufferReaderGadgetTestContainer<Fr, 2, 10>,
[
Word::from(0),
Word::from(31),
Word::from(256),
],
false,
);
}
}