mod constraint_builder;
mod lexicographic_ordering;
mod lookups;
mod multiple_precision_integer;
mod param;
#[cfg(any(test, feature = "test-circuits"))]
mod dev;
#[cfg(test)]
mod test;
use bus_mapping::operation::Target;
#[cfg(feature = "test-circuits")]
pub use dev::StateCircuit as TestStateCircuit;
use self::{
constraint_builder::{MptUpdateTableQueries, RwTableQueries},
lexicographic_ordering::LimbIndex,
};
use crate::{
table::{AccountFieldTag, LookupTable, MPTProofType, MptTable, RwTable, UXTable},
util::{word::WordLoHi, Challenges, Expr, SubCircuit, SubCircuitConfig},
witness::{
self,
rw::{RwFingerprints, ToVec},
Chunk, MptUpdates, Rw, RwMap,
},
};
use constraint_builder::{ConstraintBuilder, Queries};
use eth_types::{Address, Field, Word};
use gadgets::{
batched_is_zero::{BatchedIsZeroChip, BatchedIsZeroConfig},
binary_number::{BinaryNumberBits, BinaryNumberChip, BinaryNumberConfig},
permutation::{PermutationChip, PermutationChipConfig},
};
use halo2_proofs::{
circuit::{Layouter, Region, Value},
plonk::{
Advice, Column, ConstraintSystem, Error, Expression, FirstPhase, Fixed, Instance,
SecondPhase, VirtualCells,
},
poly::Rotation,
};
use lexicographic_ordering::Config as LexicographicOrderingConfig;
use lookups::{Chip as LookupsChip, Config as LookupsConfig, Queries as LookupsQueries};
use multiple_precision_integer::{Chip as MpiChip, Config as MpiConfig, Queries as MpiQueries};
use param::*;
use std::marker::PhantomData;
#[cfg(test)]
use std::collections::HashMap;
#[derive(Clone)]
pub struct StateCircuitConfig<F> {
selector: Column<Fixed>,
pub rw_table: RwTable,
sort_keys: SortKeysConfig,
initial_value: WordLoHi<Column<Advice>>,
is_non_exist: BatchedIsZeroConfig,
mpt_proof_type: Column<Advice>,
state_root: WordLoHi<Column<Advice>>,
lexicographic_ordering: LexicographicOrderingConfig,
not_first_access: Column<Advice>,
lookups: LookupsConfig,
mpt_table: MptTable,
pub rw_permutation_config: PermutationChipConfig<F>,
pi_chunk_continuity: Column<Instance>,
_marker: PhantomData<F>,
}
pub struct StateCircuitConfigArgs<F: Field> {
pub rw_table: RwTable,
pub mpt_table: MptTable,
pub u8_table: UXTable<8>,
pub u10_table: UXTable<10>,
pub u16_table: UXTable<16>,
pub challenges: Challenges<Expression<F>>,
}
impl<F: Field> SubCircuitConfig<F> for StateCircuitConfig<F> {
type ConfigArgs = StateCircuitConfigArgs<F>;
fn new(
meta: &mut ConstraintSystem<F>,
Self::ConfigArgs {
rw_table,
mpt_table,
u8_table,
u10_table,
u16_table,
challenges,
}: Self::ConfigArgs,
) -> Self {
let selector = meta.fixed_column();
let lookups = LookupsChip::configure(meta, u8_table, u10_table, u16_table);
let rw_counter = MpiChip::configure(meta, selector, [rw_table.rw_counter], lookups);
let bits = BinaryNumberBits::construct(meta);
let tag = BinaryNumberChip::configure(meta, bits, selector, Some(rw_table.tag));
let id = MpiChip::configure(meta, selector, [rw_table.id], lookups);
let address = MpiChip::configure(meta, selector, [rw_table.address], lookups);
let storage_key = MpiChip::configure(
meta,
selector,
[rw_table.storage_key.lo(), rw_table.storage_key.hi()],
lookups,
);
let initial_value = WordLoHi::new([meta.advice_column(), meta.advice_column()]);
let is_non_exist = BatchedIsZeroChip::configure(
meta,
(FirstPhase, FirstPhase),
|meta| meta.query_fixed(selector, Rotation::cur()),
|meta| {
[
meta.query_advice(initial_value.lo(), Rotation::cur()),
meta.query_advice(initial_value.hi(), Rotation::cur()),
meta.query_advice(rw_table.value.lo(), Rotation::cur()),
meta.query_advice(rw_table.value.hi(), Rotation::cur()),
]
},
);
let mpt_proof_type = meta.advice_column_in(SecondPhase);
let state_root = WordLoHi::new([meta.advice_column(), meta.advice_column()]);
let sort_keys = SortKeysConfig {
tag,
id,
field_tag: rw_table.field_tag,
address,
storage_key,
rw_counter,
};
let power_of_randomness: [Expression<F>; 31] = challenges.keccak_powers_of_randomness();
let lexicographic_ordering =
LexicographicOrderingConfig::configure(meta, sort_keys, lookups, power_of_randomness);
let rw_permutation_config = PermutationChip::configure(
meta,
<RwTable as LookupTable<F>>::advice_columns(&rw_table),
);
rw_table.annotate_columns(meta);
mpt_table.annotate_columns(meta);
u8_table.annotate_columns(meta);
u10_table.annotate_columns(meta);
u16_table.annotate_columns(meta);
let pi_chunk_continuity = meta.instance_column();
meta.enable_equality(pi_chunk_continuity);
let config = Self {
selector,
sort_keys,
initial_value,
is_non_exist,
mpt_proof_type,
state_root,
lexicographic_ordering,
not_first_access: meta.advice_column(),
lookups,
rw_table,
mpt_table,
rw_permutation_config,
pi_chunk_continuity,
_marker: PhantomData,
};
let mut constraint_builder = ConstraintBuilder::new();
meta.create_gate("state circuit constraints", |meta| {
let queries = queries(meta, &config);
constraint_builder.build(&queries);
constraint_builder.gate(queries.selector)
});
constraint_builder.lookups(meta, config.selector);
config
}
}
impl<F: Field> StateCircuitConfig<F> {
pub(crate) fn load_aux_tables(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
LookupsChip::construct(self.lookups).load(layouter)
}
pub fn assign(
&self,
layouter: &mut impl Layouter<F>,
rows: &[Rw],
n_rows: usize, prev_chunk_last_rw: Option<Rw>,
) -> Result<(), Error> {
let updates = MptUpdates::mock_from(rows);
layouter.assign_region(
|| "state circuit",
|mut region| {
self.assign_with_region(&mut region, rows, &updates, n_rows, prev_chunk_last_rw)
},
)
}
fn assign_with_region(
&self,
region: &mut Region<'_, F>,
rows: &[Rw],
updates: &MptUpdates,
n_rows: usize, prev_chunk_last_rw: Option<Rw>,
) -> Result<(), Error> {
let tag_chip = BinaryNumberChip::construct(self.sort_keys.tag);
let (rows, padding_length) =
RwMap::table_assignments_padding(rows, n_rows, prev_chunk_last_rw);
let rows_len = rows.len();
let mut state_root = updates.old_root();
self.annotate_circuit_in_region(region);
for (offset, row) in rows.iter().enumerate() {
if offset >= padding_length {
log::trace!("state circuit assign offset:{} row:{:#?}", offset, row);
}
region.assign_fixed(
|| "selector",
self.selector,
offset,
|| Value::known(if offset == 0 { F::ZERO } else { F::ONE }),
)?;
tag_chip.assign(region, offset, &row.tag())?;
self.sort_keys
.rw_counter
.assign(region, offset, row.rw_counter() as u32)?;
if let Some(id) = row.id() {
self.sort_keys.id.assign(region, offset, id as u32)?;
}
if let Some(address) = row.address() {
self.sort_keys.address.assign(region, offset, address)?;
}
if let Some(storage_key) = row.storage_key() {
self.sort_keys
.storage_key
.assign(region, offset, storage_key)?;
}
if offset > 0 {
let prev_row = &rows[offset - 1];
let index = self
.lexicographic_ordering
.assign(region, offset, row, prev_row)?;
let is_first_access =
!matches!(index, LimbIndex::RwCounter0 | LimbIndex::RwCounter1);
region.assign_advice(
|| "not_first_access",
self.not_first_access,
offset,
|| Value::known(if is_first_access { F::ZERO } else { F::ONE }),
)?;
if is_first_access {
if let Some(update) = updates.get(prev_row) {
let (new_root, old_root) = update.root_assignments();
assert_eq!(state_root, old_root);
state_root = new_root;
}
if matches!(row.tag(), Target::CallContext) && !row.is_write() {
assert_eq!(row.value_assignment(), 0.into(), "{:?}", row);
}
}
}
let initial_value = WordLoHi::<F>::from(
updates
.get(row)
.map(|u| u.value_assignments().1)
.unwrap_or_default(),
);
initial_value.into_value().assign_advice(
region,
|| "initial_value",
self.initial_value,
offset,
)?;
let (committed_value, value) = {
let (_, committed_value) = updates
.get(row)
.map(|u| u.value_assignments())
.unwrap_or_default();
let value = row.value_assignment();
(
WordLoHi::<F>::from(committed_value),
WordLoHi::<F>::from(value),
)
};
BatchedIsZeroChip::construct(self.is_non_exist.clone()).assign(
region,
offset,
Value::known([
committed_value.lo(),
committed_value.hi(),
value.lo(),
value.hi(),
]),
)?;
let mpt_proof_type = match row {
Rw::AccountStorage { .. } => {
if committed_value.is_zero_vartime() && value.is_zero_vartime() {
MPTProofType::StorageDoesNotExist as u64
} else {
MPTProofType::StorageChanged as u64
}
}
Rw::Account { field_tag, .. } => {
if committed_value.is_zero_vartime()
&& value.is_zero_vartime()
&& matches!(field_tag, AccountFieldTag::CodeHash)
{
MPTProofType::AccountDoesNotExist as u64
} else {
*field_tag as u64
}
}
_ => 0,
};
region.assign_advice(
|| "mpt_proof_type",
self.mpt_proof_type,
offset,
|| Value::known(F::from(mpt_proof_type)),
)?;
if offset != 0 {
WordLoHi::<F>::from(state_root).into_value().assign_advice(
region,
|| "state root",
self.state_root,
offset - 1,
)?;
}
if offset == rows_len - 1 {
if let Some(update) = updates.get(row) {
state_root = {
let (new_root, old_root) = update.root_assignments();
assert_eq!(state_root, old_root);
new_root
};
}
WordLoHi::<F>::from(state_root).into_value().assign_advice(
region,
|| "last row state_root",
self.state_root,
offset,
)?;
}
}
Ok(())
}
fn annotate_circuit_in_region(&self, region: &mut Region<F>) {
self.rw_table.annotate_columns_in_region(region);
self.mpt_table.annotate_columns_in_region(region);
self.is_non_exist
.annotate_columns_in_region(region, "STATE");
self.lexicographic_ordering
.annotate_columns_in_region(region, "STATE");
self.sort_keys.annotate_columns_in_region(region, "STATE");
region.name_column(|| "STATE_selector", self.selector);
region.name_column(|| "STATE_not_first_access", self.not_first_access);
region.name_column(|| "STATE_initial_value lo", self.initial_value.lo());
region.name_column(|| "STATE_initial_value hi", self.initial_value.hi());
region.name_column(|| "STATE_mpt_proof_type", self.mpt_proof_type);
region.name_column(|| "STATE_state_root lo", self.state_root.lo());
region.name_column(|| "STATE_state_root hi", self.state_root.hi());
region.name_column(|| "STATE_pi_chunk_continuity", self.pi_chunk_continuity);
}
}
#[derive(Clone, Copy)]
pub struct SortKeysConfig {
tag: BinaryNumberConfig<Target, 4>,
id: MpiConfig<u32, N_LIMBS_ID>,
address: MpiConfig<Address, N_LIMBS_ACCOUNT_ADDRESS>,
field_tag: Column<Advice>,
storage_key: MpiConfig<Word, N_LIMBS_WORD>,
rw_counter: MpiConfig<u32, N_LIMBS_RW_COUNTER>,
}
impl SortKeysConfig {
pub fn annotate_columns_in_region<F: Field>(&self, region: &mut Region<F>, prefix: &str) {
self.tag.annotate_columns_in_region(region, prefix);
self.address.annotate_columns_in_region(region, prefix);
self.id.annotate_columns_in_region(region, prefix);
self.storage_key.annotate_columns_in_region(region, prefix);
self.rw_counter.annotate_columns_in_region(region, prefix);
region.name_column(|| format!("{}_field_tag", prefix), self.field_tag);
}
}
#[derive(Default, Clone, Debug)]
pub struct StateCircuit<F: Field> {
pub rows: Vec<Rw>,
#[cfg(test)]
row_padding_and_overrides: Vec<Vec<Value<F>>>,
updates: MptUpdates,
pub(crate) n_rows: usize,
#[cfg(test)]
overrides: HashMap<(dev::AdviceColumn, isize), F>,
permu_alpha: F,
permu_gamma: F,
rw_fingerprints: RwFingerprints<F>,
prev_chunk_last_rw: Option<Rw>,
_marker: PhantomData<F>,
}
impl<F: Field> StateCircuit<F> {
pub fn new(chunk: &Chunk<F>) -> Self {
let rows = chunk.by_address_rws.table_assignments(false); let updates = MptUpdates::mock_from(&rows);
Self {
rows,
#[cfg(test)]
row_padding_and_overrides: Default::default(),
updates,
n_rows: chunk.fixed_param.max_rws,
#[cfg(test)]
overrides: HashMap::new(),
permu_alpha: chunk.permu_alpha,
permu_gamma: chunk.permu_gamma,
rw_fingerprints: chunk.by_address_rw_fingerprints.clone(),
prev_chunk_last_rw: chunk.prev_chunk_last_by_address_rw,
_marker: PhantomData,
}
}
}
impl<F: Field> SubCircuit<F> for StateCircuit<F> {
type Config = StateCircuitConfig<F>;
fn new_from_block(_block: &witness::Block<F>, chunk: &Chunk<F>) -> Self {
Self::new(chunk)
}
fn unusable_rows() -> usize {
6
}
fn min_num_rows_block(_block: &witness::Block<F>, chunk: &Chunk<F>) -> (usize, usize) {
(
chunk.by_address_rws.0.values().flatten().count() + 1,
chunk.fixed_param.max_rws,
)
}
fn synthesize_sub(
&self,
config: &Self::Config,
_challenges: &Challenges<Value<F>>,
layouter: &mut impl Layouter<F>,
) -> Result<(), Error> {
config.load_aux_tables(layouter)?;
let (
alpha_cell,
gamma_cell,
row_fingerprints_prev_cell,
row_fingerprints_next_cell,
acc_fingerprints_prev_cell,
acc_fingerprints_next_cell,
) = layouter.assign_region(
|| "state circuit",
|mut region| {
let padded_rows = config.rw_table.load_with_region(
&mut region,
&self.rows,
self.n_rows,
self.prev_chunk_last_rw,
)?;
config.assign_with_region(
&mut region,
&self.rows,
&self.updates,
self.n_rows,
self.prev_chunk_last_rw,
)?;
#[allow(unused_assignments, unused_mut)]
let rows = if cfg!(test) {
let mut row_padding_and_overridess = None;
#[cfg(test)]
{
row_padding_and_overridess = if self.row_padding_and_overrides.is_empty() {
debug_assert!(
self.overrides.is_empty(),
"overrides size > 0 but row_padding_and_overridess = 0"
);
Some(padded_rows.to2dvec())
} else {
Some(self.row_padding_and_overrides.clone())
};
}
row_padding_and_overridess.unwrap()
} else {
padded_rows.to2dvec()
};
let permutation_cells = config.rw_permutation_config.assign(
&mut region,
Value::known(self.permu_alpha),
Value::known(self.permu_gamma),
Value::known(self.rw_fingerprints.prev_mul_acc),
&rows,
"state_circuit",
)?;
#[cfg(test)]
{
let first_non_padding_index = 1;
for ((column, row_offset), &f) in &self.overrides {
let advice_column = column.value(config);
let offset = usize::try_from(
isize::try_from(first_non_padding_index).unwrap() + *row_offset,
)
.unwrap();
region.assign_advice(
|| "override",
advice_column,
offset,
|| Value::known(f),
)?;
}
}
Ok(permutation_cells)
},
)?;
[
alpha_cell,
gamma_cell,
row_fingerprints_prev_cell,
row_fingerprints_next_cell,
acc_fingerprints_prev_cell,
acc_fingerprints_next_cell,
]
.iter()
.enumerate()
.try_for_each(|(i, cell)| {
layouter.constrain_instance(cell.cell(), config.pi_chunk_continuity, i)
})?;
Ok(())
}
fn instance(&self) -> Vec<Vec<F>> {
vec![vec![
self.permu_alpha,
self.permu_gamma,
self.rw_fingerprints.prev_ending_row,
self.rw_fingerprints.ending_row,
self.rw_fingerprints.prev_mul_acc,
self.rw_fingerprints.mul_acc,
]]
}
}
fn queries<F: Field>(meta: &mut VirtualCells<'_, F>, c: &StateCircuitConfig<F>) -> Queries<F> {
let first_different_limb = c.lexicographic_ordering.first_different_limb;
let final_bits_sum = meta.query_advice(first_different_limb.bits[3], Rotation::cur())
+ meta.query_advice(first_different_limb.bits[4], Rotation::cur());
let mpt_update_table_expressions = c.mpt_table.table_exprs(meta);
assert_eq!(mpt_update_table_expressions.len(), 12);
let meta_query_word =
|metap: &mut VirtualCells<'_, F>, word_column: WordLoHi<Column<Advice>>, at: Rotation| {
WordLoHi::new([
metap.query_advice(word_column.lo(), at),
metap.query_advice(word_column.hi(), at),
])
};
Queries {
selector: meta.query_fixed(c.selector, Rotation::cur()),
rw_table: RwTableQueries {
rw_counter: meta.query_advice(c.rw_table.rw_counter, Rotation::cur()),
prev_rw_counter: meta.query_advice(c.rw_table.rw_counter, Rotation::prev()),
is_write: meta.query_advice(c.rw_table.is_write, Rotation::cur()),
tag: meta.query_advice(c.rw_table.tag, Rotation::cur()),
id: meta.query_advice(c.rw_table.id, Rotation::cur()),
prev_id: meta.query_advice(c.rw_table.id, Rotation::prev()),
address: meta.query_advice(c.rw_table.address, Rotation::cur()),
prev_address: meta.query_advice(c.rw_table.address, Rotation::prev()),
field_tag: meta.query_advice(c.rw_table.field_tag, Rotation::cur()),
storage_key: meta_query_word(meta, c.rw_table.storage_key, Rotation::cur()),
value: meta_query_word(meta, c.rw_table.value, Rotation::cur()),
value_prev: meta_query_word(meta, c.rw_table.value, Rotation::prev()),
value_prev_column: meta_query_word(meta, c.rw_table.value_prev, Rotation::cur()),
},
mpt_update_table: MptUpdateTableQueries {
address: mpt_update_table_expressions[0].clone(),
storage_key: WordLoHi::new([
mpt_update_table_expressions[1].clone(),
mpt_update_table_expressions[2].clone(),
]),
proof_type: mpt_update_table_expressions[3].clone(),
new_root: WordLoHi::new([
mpt_update_table_expressions[4].clone(),
mpt_update_table_expressions[5].clone(),
]),
old_root: WordLoHi::new([
mpt_update_table_expressions[6].clone(),
mpt_update_table_expressions[7].clone(),
]),
new_value: WordLoHi::new([
mpt_update_table_expressions[8].clone(),
mpt_update_table_expressions[9].clone(),
]),
old_value: WordLoHi::new([
mpt_update_table_expressions[10].clone(),
mpt_update_table_expressions[11].clone(),
]),
},
lexicographic_ordering_selector: meta
.query_fixed(c.lexicographic_ordering.selector, Rotation::cur()),
rw_counter: MpiQueries::new(meta, c.sort_keys.rw_counter),
tag_bits: c
.sort_keys
.tag
.bits
.map(|bit| meta.query_advice(bit, Rotation::cur())),
id: MpiQueries::new(meta, c.sort_keys.id),
is_tag_and_id_unchanged: 4.expr()
* (meta.query_advice(first_different_limb.bits[0], Rotation::cur())
+ meta.query_advice(first_different_limb.bits[1], Rotation::cur())
+ meta.query_advice(first_different_limb.bits[2], Rotation::cur()))
+ final_bits_sum.clone() * (1.expr() - final_bits_sum),
address: MpiQueries::new(meta, c.sort_keys.address),
storage_key: MpiQueries::new(meta, c.sort_keys.storage_key),
initial_value: meta_query_word(meta, c.initial_value, Rotation::cur()),
initial_value_prev: meta_query_word(meta, c.initial_value, Rotation::prev()),
is_non_exist: meta.query_advice(c.is_non_exist.is_zero, Rotation::cur()),
mpt_proof_type: meta.query_advice(c.mpt_proof_type, Rotation::cur()),
lookups: LookupsQueries::new(meta, c.lookups),
first_different_limb: [0, 1, 2, 3]
.map(|idx| meta.query_advice(first_different_limb.bits[idx], Rotation::cur())),
not_first_access: meta.query_advice(c.not_first_access, Rotation::cur()),
last_access: 1.expr() - meta.query_advice(c.not_first_access, Rotation::next()),
state_root: meta_query_word(meta, c.state_root, Rotation::cur()),
state_root_prev: meta_query_word(meta, c.state_root, Rotation::prev()),
}
}