diff --git a/Cargo.lock b/Cargo.lock index d4746e56aa..4062cc942f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5417,6 +5417,7 @@ dependencies = [ "mock", "num", "num-bigint", + "num_enum", "once_cell", "pretty_assertions", "rand", diff --git a/gadgets/src/util.rs b/gadgets/src/util.rs index e0bfcaa6f1..6a315375dd 100644 --- a/gadgets/src/util.rs +++ b/gadgets/src/util.rs @@ -141,6 +141,34 @@ pub mod select { } } +/// Trait that implements functionality to get a scalar from +/// commonly used types. +pub trait Scalar { + /// Returns a scalar for the type. + fn scalar(&self) -> F; +} + +/// Implementation trait `Scalar` for type able to be casted to u64 +#[macro_export] +macro_rules! impl_scalar { + ($type:ty) => { + impl $crate::util::Scalar for $type { + #[inline] + fn scalar(&self) -> F { + F::from(*self as u64) + } + } + }; + ($type:ty, $method:path) => { + impl $crate::util::Scalar for $type { + #[inline] + fn scalar(&self) -> F { + F::from($method(self) as u64) + } + } + }; +} + /// Trait that implements functionality to get a constant expression from /// commonly used types. pub trait Expr { @@ -152,6 +180,7 @@ pub trait Expr { #[macro_export] macro_rules! impl_expr { ($type:ty) => { + $crate::impl_scalar!($type); impl $crate::util::Expr for $type { #[inline] fn expr(&self) -> Expression { @@ -160,6 +189,7 @@ macro_rules! impl_expr { } }; ($type:ty, $method:path) => { + $crate::impl_scalar!($type, $method); impl $crate::util::Expr for $type { #[inline] fn expr(&self) -> Expression { @@ -173,9 +203,24 @@ impl_expr!(bool); impl_expr!(u8); impl_expr!(u64); impl_expr!(usize); +impl_expr!(isize); impl_expr!(OpcodeId, OpcodeId::as_u8); impl_expr!(GasCost, GasCost::as_u64); +impl Scalar for i32 { + #[inline] + fn scalar(&self) -> F { + F::from(self.unsigned_abs() as u64) * if self.is_negative() { -F::ONE } else { F::ONE } + } +} + +impl Scalar for &F { + #[inline] + fn scalar(&self) -> F { + *(*self) + } +} + impl Expr for Expression { #[inline] fn expr(&self) -> Expression { diff --git a/zkevm-circuits/Cargo.toml b/zkevm-circuits/Cargo.toml index 6eee0266c5..8d46cc82d3 100644 --- a/zkevm-circuits/Cargo.toml +++ b/zkevm-circuits/Cargo.toml @@ -33,6 +33,7 @@ maingate = { git = "https://github.com/privacy-scaling-explorations/halo2wrong" integer = { git = "https://github.com/privacy-scaling-explorations/halo2wrong", tag = "v2023_04_20" } libsecp256k1 = "0.7" num-bigint = { version = "0.4" } +num_enum = "0.5.7" rand_chacha = "0.3" snark-verifier = { git = "https://github.com/brechtpd/snark-verifier.git", branch = "feat/add-sdk", default-features = false, features = ["loader_halo2", "system_halo2", "loader_evm", "parallel"] } snark-verifier-sdk = { git = "https://github.com/brechtpd/snark-verifier.git", branch = "feat/add-sdk", default-features = false, features = ["loader_halo2", "loader_evm", "parallel", "display", "halo2_circuit_params"] } diff --git a/zkevm-circuits/src/circuit_tools.rs b/zkevm-circuits/src/circuit_tools.rs new file mode 100644 index 0000000000..2c8535d22e --- /dev/null +++ b/zkevm-circuits/src/circuit_tools.rs @@ -0,0 +1,8 @@ +//! Circuit utilities +#![allow(missing_docs)] +#[macro_use] +pub mod constraint_builder; +pub mod cached_region; +pub mod cell_manager; +pub mod gadgets; +pub mod memory; diff --git a/zkevm-circuits/src/circuit_tools/cached_region.rs b/zkevm-circuits/src/circuit_tools/cached_region.rs new file mode 100644 index 0000000000..fe2eadbb79 --- /dev/null +++ b/zkevm-circuits/src/circuit_tools/cached_region.rs @@ -0,0 +1,241 @@ +use crate::circuit_tools::cell_manager::Cell; +use eth_types::Field; +use halo2_proofs::{ + circuit::{AssignedCell, Region, Value}, + plonk::{Advice, Any, Assigned, Column, Error, Expression, Fixed}, + poly::Rotation, +}; +use std::{ + collections::HashMap, + hash::{Hash, Hasher}, +}; + +use super::{ + cell_manager::{CellColumn, CellType}, + constraint_builder::ConstraintBuilder, +}; + +pub trait ChallengeSet { + fn indexed(&self) -> Vec<&Value>; +} + +impl]>> ChallengeSet for V { + fn indexed(&self) -> Vec<&Value> { + self.as_ref().iter().collect() + } +} + +pub struct CachedRegion<'r, 'b, F: Field> { + region: &'r mut Region<'b, F>, + pub advice: HashMap<(usize, usize), F>, + pub fixed: HashMap<(usize, usize), F>, + regions: Vec<(usize, usize)>, +} + +impl<'r, 'b, F: Field> CachedRegion<'r, 'b, F> { + pub(crate) fn new(region: &'r mut Region<'b, F>) -> Self { + Self { + region, + advice: HashMap::new(), + fixed: HashMap::new(), + regions: Vec::new(), + } + } + + pub(crate) fn inner(&mut self) -> &mut Region<'b, F> { + self.region + } + + pub(crate) fn annotate_columns(&mut self, col_configs: &[CellColumn]) { + for c in col_configs { + self.region.name_column( + || { + format!( + "{:?} {:?}: {:?} queried", + c.cell_type.clone(), + c.index, + c.height + ) + }, + c.column, + ); + } + } + + pub(crate) fn push_region(&mut self, offset: usize, region_id: usize) { + self.regions.push((offset, region_id)); + } + + pub(crate) fn pop_region(&mut self) { + // Nothing to do + } + + pub(crate) fn assign_stored_expressions>( + &mut self, + cb: &ConstraintBuilder, + challenges: &S, + ) -> Result<(), Error> { + for (offset, region_id) in self.regions.clone() { + for stored_expression in cb.get_stored_expressions(region_id).iter() { + stored_expression.assign(self, challenges, offset)?; + } + } + Ok(()) + } + + /// Assign an advice column value (witness). + pub fn assign_advice<'v, V, VR, A, AR>( + &'v mut self, + annotation: A, + column: Column, + offset: usize, + to: V, + ) -> Result, Error> + where + V: Fn() -> Value + 'v, + for<'vr> Assigned: From<&'vr VR>, + A: Fn() -> AR, + AR: Into, + { + // Actually set the value + let res = self.region.assign_advice(annotation, column, offset, &to); + // Cache the value + // Note that the `value_field` in `AssignedCell` might be `Value::unkonwn` if + // the column has different phase than current one, so we call to `to` + // again here to cache the value. + if res.is_ok() { + to().map(|f: VR| { + let existing = self + .advice + .insert((column.index(), offset), Assigned::from(&f).evaluate()); + assert!(existing.is_none()); + existing + }); + } + res + } + + pub fn name_column(&mut self, annotation: A, column: T) + where + A: Fn() -> AR, + AR: Into, + T: Into>, + { + self.region + .name_column(|| annotation().into(), column.into()); + } + + pub fn assign_fixed<'v, V, VR, A, AR>( + &'v mut self, + annotation: A, + column: Column, + offset: usize, + to: V, + ) -> Result, Error> + where + V: Fn() -> Value + 'v, + for<'vr> Assigned: From<&'vr VR>, + A: Fn() -> AR, + AR: Into, + { + // Actually set the value + let res = self.region.assign_fixed(annotation, column, offset, &to); + // Cache the value + // Note that the `value_field` in `AssignedCell` might be `Value::unkonwn` if + // the column has different phase than current one, so we call to `to` + // again here to cache the value. + if res.is_ok() { + to().map(|f: VR| { + let existing = self + .fixed + .insert((column.index(), offset), Assigned::from(&f).evaluate()); + assert!(existing.is_none()); + existing + }); + } + res + } + + pub fn get_fixed(&self, row_index: usize, column_index: usize, rotation: Rotation) -> F { + let zero = F::ZERO; + *self + .fixed + .get(&(column_index, row_index + rotation.0 as usize)) + .unwrap_or(&zero) + } + + pub fn get_advice(&self, row_index: usize, column_index: usize, rotation: Rotation) -> F { + let zero = F::ZERO; + *self + .advice + .get(&(column_index, row_index + rotation.0 as usize)) + .unwrap_or(&zero) + } + + /// Constrains a cell to have a constant value. + /// + /// Returns an error if the cell is in a column where equality has not been + /// enabled. + pub fn constrain_constant( + &mut self, + cell: AssignedCell, + constant: VR, + ) -> Result<(), Error> + where + VR: Into>, + { + self.region.constrain_constant(cell.cell(), constant.into()) + } +} + +#[derive(Debug, Clone)] +pub struct StoredExpression { + pub(crate) name: String, + pub(crate) cell: Cell, + pub(crate) cell_type: C, + pub(crate) expr: Expression, + pub(crate) expr_id: String, +} + +impl Hash for StoredExpression { + fn hash(&self, state: &mut H) { + self.expr_id.hash(state); + self.cell_type.hash(state); + } +} + +impl StoredExpression { + pub fn assign>( + &self, + region: &mut CachedRegion<'_, '_, F>, + challenges: &S, + offset: usize, + ) -> Result, Error> { + let value = self.expr.evaluate( + &|scalar| Value::known(scalar), + &|_| unimplemented!("selector column"), + &|fixed_query| { + Value::known(region.get_fixed( + offset, + fixed_query.column_index(), + fixed_query.rotation(), + )) + }, + &|advice_query| { + Value::known(region.get_advice( + offset, + advice_query.column_index(), + advice_query.rotation(), + )) + }, + &|_| unimplemented!("instance column"), + &|challenge| *challenges.indexed()[challenge.index()], + &|a| -a, + &|a, b| a + b, + &|a, b| a * b, + &|a, scalar| a * Value::known(scalar), + ); + self.cell.assign_value(region, offset, value)?; + Ok(value) + } +} diff --git a/zkevm-circuits/src/circuit_tools/cell_manager.rs b/zkevm-circuits/src/circuit_tools/cell_manager.rs new file mode 100644 index 0000000000..cdcafd3541 --- /dev/null +++ b/zkevm-circuits/src/circuit_tools/cell_manager.rs @@ -0,0 +1,411 @@ +//! Cell manager +use crate::{ + circuit_tools::cached_region::CachedRegion, + util::{query_expression, Expr}, +}; + +use crate::table::LookupTable; +use eth_types::Field; +use halo2_proofs::{ + circuit::{AssignedCell, Value}, + plonk::{ + Advice, Any, Column, ConstraintSystem, Error, Expression, FirstPhase, SecondPhase, + ThirdPhase, VirtualCells, + }, + poly::Rotation, +}; +use std::{cmp::Ordering, collections::BTreeMap, fmt::Debug, hash::Hash}; + +#[derive(Clone, Debug, Default)] +pub(crate) struct Cell { + // expression for constraint + expression: Option>, + pub column: Option>, + // relative position to selector for synthesis + pub rotation: usize, +} + +impl Cell { + pub(crate) fn new(meta: &mut VirtualCells, column: Column, rotation: usize) -> Self { + Self { + expression: Some(meta.query_advice(column, Rotation(rotation as i32))), + column: Some(column), + rotation, + } + } + + pub(crate) fn assign( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + value: F, + ) -> Result, Error> { + region.assign_advice( + || { + format!( + "Cell column: {:?} and rotation: {}", + self.column, self.rotation + ) + }, + self.column.unwrap(), + offset + self.rotation, + || Value::known(value), + ) + } + + pub(crate) fn assign_value( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + value: Value, + ) -> Result, Error> { + region.assign_advice( + || { + format!( + "Cell column: {:?} and rotation: {}", + self.column.unwrap(), + self.rotation + ) + }, + self.column.unwrap(), + offset + self.rotation, + || value, + ) + } + + pub(crate) fn column(&self) -> Column { + self.column.unwrap() + } + + pub(crate) fn rotation(&self) -> usize { + self.rotation + } + + pub(crate) fn rot(&self, meta: &mut VirtualCells, rot: usize) -> Expression { + meta.query_advice(self.column.unwrap(), Rotation((self.rotation + rot) as i32)) + } + + pub(crate) fn identifier(&self) -> String { + self.expr().identifier() + } +} + +impl Expr for Cell { + fn expr(&self) -> Expression { + self.expression.as_ref().unwrap().clone() + } +} + +impl Expr for &Cell { + fn expr(&self) -> Expression { + self.expression.as_ref().unwrap().clone() + } +} + +#[derive(Clone, Debug)] +pub struct CellConfig { + pub cell_type: C, + pub num_columns: usize, + pub phase: u8, + pub is_permute: bool, +} + +impl From<(C, usize, u8, bool)> for CellConfig { + fn from((cell_type, num_columns, phase, is_permute): (C, usize, u8, bool)) -> Self { + Self { + cell_type, + num_columns, + phase, + is_permute, + } + } +} + +impl CellConfig { + pub fn init_columns(&self, meta: &mut ConstraintSystem) -> Vec> { + let mut columns = Vec::with_capacity(self.num_columns); + for _ in 0..self.num_columns { + let tmp = match self.phase { + 1 => meta.advice_column_in(FirstPhase), + 2 => meta.advice_column_in(SecondPhase), + 3 => meta.advice_column_in(ThirdPhase), + _ => unreachable!(), + }; + columns.push(tmp); + } + if self.is_permute { + let _ = columns + .iter() + .map(|c| meta.enable_equality(*c)) + .collect::>(); + } + columns + } +} + +pub trait CellType: + Clone + Copy + Debug + PartialEq + Eq + PartialOrd + Ord + Hash + Default +{ + fn byte_type() -> Option; + + // The phase that given `Expression` becomes evaluateable. + fn expr_phase(expr: &Expression) -> u8 { + use Expression::*; + match expr { + Challenge(challenge) => challenge.phase() + 1, + Advice(query) => query.phase(), + Constant(_) | Selector(_) | Fixed(_) | Instance(_) => 0, + Negated(a) | Expression::Scaled(a, _) => Self::expr_phase(a), + Sum(a, b) | Product(a, b) => std::cmp::max(Self::expr_phase(a), Self::expr_phase(b)), + } + } + + /// Return the storage phase of phase + fn storage_for_phase(phase: u8) -> Self; + + /// Return the storage cell of the expression + fn storage_for_expr(expr: &Expression) -> Self { + Self::storage_for_phase(Self::expr_phase::(expr)) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum DefaultCellType { + StoragePhase1, + StoragePhase2, + StoragePhase3, +} + +impl Default for DefaultCellType { + fn default() -> Self { + Self::StoragePhase1 + } +} + +impl CellType for DefaultCellType { + fn byte_type() -> Option { + Some(DefaultCellType::StoragePhase1) + } + + fn storage_for_phase(phase: u8) -> Self { + match phase { + 1 => DefaultCellType::StoragePhase1, + 2 => DefaultCellType::StoragePhase2, + 3 => DefaultCellType::StoragePhase3, + _ => unreachable!(), + } + } +} + +#[derive(Clone, Debug)] +pub(crate) struct CellColumn { + pub(crate) column: Column, + pub(crate) cell_type: C, + pub(crate) cells: Vec>, + pub(crate) expr: Expression, + pub(crate) height: usize, + pub(crate) index: usize, +} + +impl PartialEq for CellColumn { + fn eq(&self, other: &Self) -> bool { + self.index == other.index + && self.cell_type == other.cell_type + && self.height == other.height + } +} + +impl Eq for CellColumn {} + +impl PartialOrd for CellColumn { + fn partial_cmp(&self, other: &Self) -> Option { + self.height.partial_cmp(&other.height) + } +} + +impl Ord for CellColumn { + fn cmp(&self, other: &Self) -> Ordering { + self.height.cmp(&other.height) + } +} + +impl Expr for CellColumn { + fn expr(&self) -> Expression { + self.expr.clone() + } +} + +#[derive(Clone, Debug, Default)] +pub struct CellManager { + configs: Vec>, + columns: Vec>, + height: usize, + height_limit: usize, +} + +impl CellManager { + pub(crate) fn new( + meta: &mut ConstraintSystem, + configs: Vec<(C, usize, u8, bool)>, + offset: usize, + max_height: usize, + ) -> Self { + let mut cm = CellManager::default(); + cm.height_limit = max_height; + configs + .into_iter() + .for_each(|c| cm.add_celltype(meta, c, offset)); + cm.height = max_height; + cm + } + + pub(crate) fn add_celltype( + &mut self, + meta: &mut ConstraintSystem, + config: (C, usize, u8, bool), + offset: usize, + ) { + if !self.get_typed_columns(config.0).is_empty() { + panic!("CellManager: cell type {:?} already exists", config.0); + } + let config = CellConfig::from(config); + for col in config.init_columns(meta).iter() { + let mut cells = Vec::new(); + for r in 0..self.height_limit { + query_expression(meta, |meta| { + cells.push(Cell::new(meta, *col, offset + r)); + }); + } + self.columns.push(CellColumn { + column: *col, + index: self.columns.len(), + cell_type: config.cell_type, + height: 0, + expr: cells[0].expr(), + cells, + }); + } + self.configs.push(config); + } + + pub(crate) fn restart(&mut self) { + self.height = self.height_limit; + for col in self.columns.iter_mut() { + col.height = 0; + } + } + + pub(crate) fn query_cells(&mut self, cell_type: C, count: usize) -> Vec> { + let mut cells = Vec::with_capacity(count); + while cells.len() < count { + let column_idx = self.next_column(cell_type); + let column = &mut self.columns[column_idx]; + cells.push(column.cells[column.height].clone()); + column.height += 1; + } + cells + } + + pub(crate) fn query_cell(&mut self, cell_type: C) -> Cell { + self.query_cells(cell_type, 1)[0].clone() + } + + pub(crate) fn reset(&mut self, height_limit: usize) { + assert!(height_limit <= self.height); + self.height_limit = height_limit; + for column in self.columns.iter_mut() { + column.height = 0; + } + } + + fn next_column(&self, cell_type: C) -> usize { + let mut best_index: Option = None; + let mut best_height = self.height; + for column in self.columns.iter() { + if column.cell_type == cell_type && column.height < best_height { + best_index = Some(column.index); + best_height = column.height; + } + } + if best_height >= self.height_limit { + best_index = None; + } + match best_index { + Some(index) => index, + None => unreachable!("not enough cells for query: {:?}", cell_type), + } + } + + pub(crate) fn get_height(&self) -> usize { + self.columns + .iter() + .map(|column| column.height) + .max() + .unwrap() + } + + /// Returns a map of CellType -> (width, height, num_cells) + pub(crate) fn get_stats(&self) -> BTreeMap { + let mut data = BTreeMap::new(); + for column in self.columns.iter() { + let (mut count, mut height, mut num_cells) = + data.get(&column.cell_type).unwrap_or(&(0, 0, 0)); + count += 1; + height = height.max(column.height); + num_cells += column.height; + data.insert(column.cell_type, (count, height, num_cells)); + } + data + } + + pub(crate) fn columns(&self) -> &[CellColumn] { + &self.columns + } + + pub(crate) fn get_typed_columns(&self, cell_type: C) -> Vec> { + let mut columns = Vec::new(); + for column in self.columns.iter() { + if column.cell_type == cell_type { + columns.push(column.clone()); + } + } + columns + } + + pub(crate) fn get_config(&self, cell_type: C) -> Option> { + for config in self.configs.iter() { + if config.cell_type == cell_type { + return Some(config.clone()); + } + } + None + } +} + +/// LookupTable created dynamically and stored in an advice column +#[derive(Clone, Debug)] +pub struct DynamicLookupTable { + /// Table + pub table: Column, +} + +impl DynamicLookupTable { + /// Construct a new BlockTable + pub fn from(cm: &CellManager, cell_type: C) -> Self { + let table_columns = cm.get_typed_columns(cell_type); + assert_eq!(table_columns.len(), 1); + Self { + table: table_columns[0].column, + } + } +} + +impl LookupTable for DynamicLookupTable { + fn columns(&self) -> Vec> { + vec![self.table.into()] + } + + fn annotations(&self) -> Vec { + vec![String::from("generated")] + } +} diff --git a/zkevm-circuits/src/circuit_tools/constraint_builder.rs b/zkevm-circuits/src/circuit_tools/constraint_builder.rs new file mode 100644 index 0000000000..6759a0f0e9 --- /dev/null +++ b/zkevm-circuits/src/circuit_tools/constraint_builder.rs @@ -0,0 +1,1626 @@ +//! Circuit utilities +use std::{ + collections::HashMap, + marker::PhantomData, + ops::{Add, Mul}, + vec, +}; + +use crate::{ + evm_circuit::util::rlc, + table::LookupTable, + util::{query_expression, Expr}, +}; +use eth_types::Field; +use gadgets::util::{and, sum, Scalar}; +use halo2_proofs::plonk::{Advice, Column, ConstraintSystem, Expression, Selector}; +use itertools::Itertools; + +use super::{ + cached_region::StoredExpression, + cell_manager::{Cell, CellManager, CellType}, +}; + +fn get_condition_expr(conditions: &Vec>) -> Expression { + if conditions.is_empty() { + 1.expr() + } else { + and::expr(conditions) + } +} + +/// Data for dynamic lookup +#[derive(Clone, Debug)] +pub struct LookupData { + /// Desciption + pub description: &'static str, + /// Condition under which the lookup needs to be done + pub regional_condition: Expression, + /// Need to store local condition for dyn table checks + pub local_condition: Expression, + /// The values to lookup + pub values: Vec>, + /// region + pub region_id: usize, + /// If the values are in rlc + pub compressed: bool, + /// If true lookup to fixed table + pub to_fixed: bool, +} + +/// Data for dynamic lookup +#[derive(Clone, Debug)] +pub struct TableData { + /// Condition under which the lookup needs to be done + pub regional_condition: Expression, + /// Need to store local condition for dyn table checks + pub local_condition: Expression, + /// The values to lookup + pub values: Vec>, + /// region + pub region_id: usize, +} + +impl TableData { + fn condition(&self) -> Expression { + self.regional_condition.expr() * self.local_condition.expr() + } +} + +struct TableMerger { + data: Vec>, + _phantom: PhantomData, +} + +impl TableMerger { + fn merge_check(&self, cb: &mut ConstraintBuilder) { + let selector = sum::expr(self.data.iter().map(|t| t.condition())); + crate::circuit!([meta, cb], { + require!(selector => bool); + }); + } + + fn merge_unsafe(&self) -> (Expression, Vec>) { + if self.data.is_empty() { + return (0.expr(), Vec::new()); + } + let selector = sum::expr(self.data.iter().map(|v| v.condition())); + // Merge + let max_length = self.data.iter().map(|t| t.values.len()).max().unwrap(); + let mut merged_values = vec![0.expr(); max_length]; + let default_value = 0.expr(); + merged_values.iter_mut().enumerate().for_each(|(idx, v)| { + *v = sum::expr( + self.data + .iter() + .map(|t| t.condition() * t.values.get(idx).unwrap_or(&default_value).expr()), + ); + }); + (selector, merged_values) + } + + fn check_and_merge( + &self, + cb: &mut ConstraintBuilder, + ) -> (Expression, Vec>) { + self.merge_check(cb); + self.merge_unsafe() + } + + fn merge_and_select(&self, _cb: &mut ConstraintBuilder) -> Vec> { + let (selector, v) = self.merge_unsafe(); + v.iter().map(|v| selector.expr() * v.expr()).collect() + } +} + +/// Constraint builder +#[derive(Clone)] +pub struct ConstraintBuilder { + /// Constraints to be returned to meta + constraints: Vec<(&'static str, Expression)>, + /// Max global degree of constraints + max_global_degree: usize, + /// Max local degree of constraints inside the current region + max_degree: usize, + /// conditions for constraints + conditions: Vec>, + /// Columns whoes equality constraints needed to be enable + equalities: Vec>, + /// The lookups generated during synthesis + /// assembles runtime access to RAM + pub lookups: HashMap>>, + /// The tables written during synthesis + /// write to RAM + pub dynamic_tables: HashMap>>, + /// The tables preloaded before configuration + /// Read-only memory + pub fixed_tables: HashMap>>, + /// All stored expressions + pub stored_expressions: HashMap>>, + /// CellManager + pub cell_manager: Option>, + /// Disable macro-generated description for constraints & lookups + /// for graph display + pub disable_description: bool, + /// region id + pub region_id: usize, + /// lookup input challenge + pub lookup_challenge: Option>, + /// state contect + pub state_context: Vec>, + /// state constraints start + pub state_constraints_start: usize, +} + +impl ConstraintBuilder { + pub(crate) fn new( + max_degree: usize, + cell_manager: Option>, + lookup_challenge: Option>, + ) -> Self { + ConstraintBuilder { + constraints: Vec::new(), + max_global_degree: max_degree, + max_degree, + conditions: Vec::new(), + equalities: Vec::new(), + lookups: HashMap::new(), + dynamic_tables: HashMap::new(), + fixed_tables: HashMap::new(), + cell_manager, + disable_description: false, + stored_expressions: HashMap::new(), + region_id: 0, + lookup_challenge, + state_context: Vec::new(), + state_constraints_start: 0, + } + } + + pub(crate) fn restart(&mut self) { + self.constraints.clear(); + self.conditions.clear(); + self.dynamic_tables.clear(); + self.stored_expressions.clear(); + self.region_id = 0; + self.state_context.clear(); + self.state_constraints_start = 0; + self.lookups.clear(); + if let Some(cell_manager) = &mut self.cell_manager { + cell_manager.restart(); + } + } + + pub(crate) fn set_cell_manager(&mut self, cell_manager: CellManager) { + self.cell_manager = Some(cell_manager); + } + + pub(crate) fn set_max_degree(&mut self, max_degree: usize) { + self.max_global_degree = max_degree; + } + + pub(crate) fn preload_tables( + &mut self, + meta: &mut ConstraintSystem, + tables: &[(C, &dyn LookupTable)], + ) { + query_expression(meta, |meta| { + for (tag, table) in tables { + self.fixed_tables.insert(*tag, table.table_exprs(meta)); + } + }) + } + + pub(crate) fn push_region(&mut self, region_id: usize) { + assert!(region_id != 0); + self.region_id = region_id; + self.state_context = self.conditions.clone(); + self.max_degree = self.max_global_degree - self.get_condition_expr().degree(); + self.conditions.clear(); + self.state_constraints_start = self.constraints.len(); + } + + pub(crate) fn pop_region(&mut self) { + let condition = get_condition_expr(&self.state_context); + for idx in self.state_constraints_start..self.constraints.len() { + self.constraints[idx].1 = condition.expr() * self.constraints[idx].1.clone(); + } + for (_, values) in self.lookups.iter_mut() { + for value in values { + if value.region_id == self.region_id { + value.regional_condition = value.regional_condition.expr() * condition.expr(); + } + } + } + for (_key, values) in self.dynamic_tables.iter_mut() { + for value in values { + if value.region_id == self.region_id { + value.regional_condition = value.regional_condition.expr() * condition.expr(); + } + } + } + self.conditions = self.state_context.clone(); + self.max_degree = self.max_global_degree - self.get_condition_expr().degree(); + self.region_id = 0; + } + + pub(crate) fn set_disable_description(&mut self, disable_description: bool) { + self.disable_description = disable_description; + } + + pub(crate) fn require_zero(&mut self, name: &'static str, constraint: Expression) { + self.add_constraint(name, constraint); + } + + pub(crate) fn require_equal( + &mut self, + name: &'static str, + lhs: Expression, + rhs: Expression, + ) { + self.add_constraint(name, lhs - rhs); + } + + pub(crate) fn require_boolean(&mut self, name: &'static str, value: Expression) { + self.add_constraint(name, value.clone() * (1.expr() - value)); + } + + pub(crate) fn require_in_set( + &mut self, + name: &'static str, + value: Expression, + set: Vec>, + ) { + self.add_constraint( + name, + set.iter() + .fold(1.expr(), |acc, item| acc * (value.clone() - item.clone())), + ); + } + + pub(crate) fn condition( + &mut self, + condition: Expression, + constraint: impl FnOnce(&mut Self) -> R, + ) -> R { + self.push_condition(condition); + let ret = constraint(self); + self.pop_condition(); + ret + } + + pub(crate) fn push_condition(&mut self, condition: Expression) { + self.conditions.push(condition); + } + + pub(crate) fn pop_condition(&mut self) { + self.conditions.pop(); + } + + pub(crate) fn add_constraints(&mut self, constraints: Vec<(&'static str, Expression)>) { + for (name, constraint) in constraints { + self.add_constraint(name, constraint); + } + } + + pub(crate) fn add_constraint(&mut self, name: &'static str, constraint: Expression) { + if self.max_global_degree == 0 { + return; + } + let constraint = match self.get_condition() { + Some(condition) => condition * constraint, + None => constraint, + }; + let constraint = self.split_expression(name, constraint); + self.validate_degree(constraint.degree(), name); + self.constraints.push((name, constraint)); + } + + pub(crate) fn get_condition(&self) -> Option> { + if self.conditions.is_empty() { + None + } else { + Some(and::expr(self.conditions.iter())) + } + } + + pub(crate) fn get_condition_expr(&self) -> Expression { + self.get_condition().unwrap_or_else(|| 1.expr()) + } + + pub(crate) fn enable_equality(&mut self, column: Column) { + self.equalities.push(column); + } + + // Query + + pub(crate) fn query_bool(&mut self) -> Cell { + let cell = self.query_default(); + self.require_boolean("Constrain cell to be a bool", cell.expr()); + cell + } + + pub(crate) fn query_default(&mut self) -> Cell { + self.query_cells_dyn(C::default(), 1) + .get(0) + .expect("No cell found") + .clone() + } + + pub(crate) fn query_one(&mut self, cell_type: C) -> Cell { + let res = self.query_cells_dyn(cell_type, 1).first().unwrap().clone(); + res + } + + pub(crate) fn query_bytes(&mut self) -> [Cell; N] { + self.query_cells_dyn( + C::byte_type().expect("No byte type for this CellManager"), + N, + ) + .try_into() + .unwrap() + } + + pub(crate) fn query_cells_dyn(&mut self, cell_type: C, count: usize) -> Vec> { + self.cell_manager + .as_mut() + .expect("Cell manager not set") + .query_cells(cell_type, count) + } + + pub(crate) fn query_cell_with_type(&mut self, cell_type: C) -> Cell { + self.query_cells_dyn(cell_type, 1).first().unwrap().clone() + } + + pub(crate) fn validate_degree(&self, degree: usize, name: &'static str) { + if self.max_global_degree > 0 && self.region_id != 0 { + debug_assert!( + degree <= self.max_degree, + "Expression {} degree too high: {} > {}", + name, + degree, + self.max_degree, + ); + } + } + + pub(crate) fn build_constraints( + &self, + selector: Option>, + ) -> Vec<(&'static str, Expression)> { + if self.constraints.is_empty() { + return vec![("No constraints", 0.expr())]; + } + selector.map_or(self.constraints.clone(), |s| { + self.constraints + .iter() + .map(|(name, c)| (*name, s.expr() * c.clone())) + .collect() + }) + } + + pub(crate) fn build_equalities(&self, meta: &mut ConstraintSystem) { + self.equalities + .iter() + .for_each(|c| meta.enable_equality(*c)); + } + + pub(crate) fn build_fixed_path( + &mut self, + meta: &mut ConstraintSystem, + cell_managers: &[CellManager], + tag: &(C, C), + selector: Option, + ) { + let (data_tag, table_tag) = tag; + let challenge = self.lookup_challenge.clone().unwrap(); + if let Some(table) = self.fixed_tables.get(table_tag) { + let table_expr = rlc::expr(table, challenge.expr()); + for cm in cell_managers { + for col in cm.get_typed_columns(*data_tag) { + meta.lookup_any(format!("{:?}", data_tag), |meta| { + let s = selector.map_or_else(|| 1.expr(), |s| meta.query_selector(s)); + vec![(col.expr() * s, table_expr.clone())] + }); + } + } + } + } + + pub(crate) fn build_dynamic_path( + &mut self, + meta: &mut ConstraintSystem, + tag: &(C, C), + selector: Option, + ) { + let (data_tag, table_tag) = tag; + if let Some(lookups) = self.lookups.clone().get(data_tag) { + for data in lookups.iter() { + let LookupData { + description, + values, + compressed, + regional_condition, + to_fixed, + .. + } = data.clone(); + let mut table = if to_fixed { + // (v1, v2, v3) => (t1, t2, t3) + // Direct lookup into the pre-difined fixed tables, vanilla lookup of + // Halo2. + self.fixed_tables + .get(table_tag) + .unwrap_or_else(|| { + panic!("Fixed table {:?} not found for dynamic lookup", table_tag) + }) + .clone() + } else { + // (v1, v2, v3) => cond * (t1, t2, t3) + // Applies condition to the advice values stored at configuration time + self.dynamic_table_merged(*table_tag) + }; + if compressed { + let challenge = self.lookup_challenge.clone().unwrap(); + table = vec![rlc::expr(&table, challenge)]; + } + // Apply the conditions added from popping regions + let mut values: Vec<_> = values + .iter() + .map(|value| value.expr() * regional_condition.clone()) + .collect(); + // align the length of values and table + assert!(table.len() >= values.len()); + while values.len() < table.len() { + values.push(0.expr()); + } + meta.lookup_any(description, |meta| { + let s = selector.map_or_else(|| 1.expr(), |s| meta.query_selector(s)); + values + .iter() + .zip(table.iter()) + .map(|(v, t)| (v.expr() * s.clone(), t.expr())) + .collect() + }); + } + } + } + + pub(crate) fn build_lookups( + &mut self, + meta: &mut ConstraintSystem, + cell_managers: &[CellManager], + tags: &[(C, C)], + selector: Option, + ) { + let _challenge = self.lookup_challenge.clone().unwrap(); + for tag in tags { + self.build_fixed_path(meta, cell_managers, tag, selector); + self.build_dynamic_path(meta, tag, selector); + } + } + + pub(crate) fn store_table( + &mut self, + description: &'static str, + tag: C, + values: Vec>, + compress: bool, + reduce: bool, + dyn_path: bool, + ) { + let values = self.local_processing(description, &values, tag, None, compress, reduce); + if dyn_path { + let data = TableData { + regional_condition: 1.expr(), + local_condition: self.get_condition_expr(), + values, + region_id: self.region_id, + }; + if let Some(tables) = self.dynamic_tables.get_mut(&tag) { + tables.push(data); + } else { + self.dynamic_tables.insert(tag, vec![data]); + } + } else { + self.fixed_tables.insert(tag, values); + } + } + + pub(crate) fn add_lookup( + &mut self, + description: &'static str, + tag: C, + values: Vec>, + to_fixed: bool, + compress: bool, + reduce: bool, + dyn_path: bool, + ) { + // Process the value with conpression and reduction flags + // also apply the local condition + let values = self.local_processing(description, &values, tag, None, compress, reduce); + // Incase of fixed_path, =>> + // Buildig lookup from typed columns -> fixed table + // no need to store the lookup, also to_fixed flag become useless + if dyn_path { + let data = LookupData { + description, + local_condition: self.get_condition_expr(), + regional_condition: 1.expr(), + values, + region_id: self.region_id, + compressed: compress, + to_fixed, + }; + if let Some(lookups) = self.lookups.get_mut(&tag) { + lookups.push(data); + } else { + self.lookups.insert(tag, vec![data]); + } + } + } + + pub(crate) fn local_processing( + &mut self, + name: &str, + values: &[Expression], + cell_type: C, + target_cell: Option>, + compress: bool, + reduce: bool, + ) -> Vec> { + let local_condition = self.get_condition_expr(); + let challenge = self.lookup_challenge.clone().unwrap(); + + let mut local_compression = |values: &[Expression]| -> Expression { + let rlc = rlc::expr(values, challenge.expr()) * local_condition.expr(); + match reduce { + true => { + let reduced_rlc = self.split_expression("compression", rlc); + self.store_expression(name, reduced_rlc, cell_type, target_cell.clone()) + } + false => rlc, + } + }; + + match (compress, reduce) { + (true, true) => vec![local_compression(values)], + (true, false) => vec![local_compression(values)], + (false, true) => values.iter().map(|_v| local_compression(values)).collect(), + (false, false) => values + .iter() + .map(|v| v.expr() * local_condition.expr()) + .collect(), + } + } + + pub(crate) fn dynamic_table_merged(&mut self, tag: C) -> Vec> { + let data = self + .dynamic_tables + .get(&tag) + .unwrap_or_else(|| panic!("Dynamic table {:?} not found", tag)) + .clone(); + let table_merger = TableMerger { + data, + _phantom: PhantomData, + }; + table_merger.merge_and_select(self) + } + + pub(crate) fn store_expression( + &mut self, + name: &str, + expr: Expression, + cell_type: C, + target_cell: Option>, + ) -> Expression { + // Check if we already stored the expression somewhere + let stored_expression = self.find_stored_expression(&expr, cell_type); + match stored_expression { + Some(stored_expression) => stored_expression.cell.expr(), + None => { + // Require the stored value to equal the value of the expression + let cell = if let Some(tc) = target_cell { + tc + } else { + self.query_one(cell_type) + }; + let name = format!("{} (stored expression)", name); + self.constraints.push(( + Box::leak(name.clone().into_boxed_str()), + cell.expr() - expr.clone(), + )); + self.stored_expressions + .entry(self.region_id) + .or_insert_with(Vec::new) + .push(StoredExpression { + name, + cell: cell.clone(), + cell_type, + expr_id: expr.identifier(), + expr, + }); + cell.expr() + } + } + } + + pub(crate) fn get_stored_expressions(&self, region_id: usize) -> Vec> { + self.stored_expressions + .get(®ion_id) + .cloned() + .unwrap_or_default() + } + + pub(crate) fn find_stored_expression( + &self, + expr: &Expression, + cell_type: C, + ) -> Option<&StoredExpression> { + let expr_id = expr.identifier(); + if let Some(stored_expressions) = self.stored_expressions.get(&self.region_id) { + stored_expressions + .iter() + .find(|&e| e.cell_type == cell_type && e.expr_id == expr_id) + } else { + None + } + } + + pub(crate) fn split_expression( + &mut self, + name: &'static str, + expr: Expression, + ) -> Expression { + if expr.degree() > self.max_degree && self.region_id != 0 { + match expr { + Expression::Negated(poly) => { + Expression::Negated(Box::new(self.split_expression(name, *poly))) + } + Expression::Scaled(poly, v) => { + Expression::Scaled(Box::new(self.split_expression(name, *poly)), v) + } + Expression::Sum(a, b) => { + let a = self.split_expression(name, *a); + let b = self.split_expression(name, *b); + a + b + } + Expression::Product(a, b) => { + let (mut a, mut b) = (*a, *b); + while a.degree() + b.degree() > self.max_degree { + let mut split = |expr: Expression| { + if expr.degree() > self.max_degree { + self.split_expression(name, expr) + } else { + let cell_type = C::storage_for_expr(&expr); + self.store_expression(name, expr, cell_type, None) + } + }; + if a.degree() >= b.degree() { + a = split(a); + } else { + b = split(b); + } + } + a * b + } + _ => expr.clone(), + } + } else { + expr.clone() + } + } + + pub(crate) fn print_stats(&self) { + let mut expressions = self.constraints.clone(); + expressions.sort_by(|a, b| a.1.degree().cmp(&b.1.degree())); + for (name, expr) in expressions.iter() { + println!("'{}': {}", name, expr.degree()); + } + } +} + +#[derive(PartialEq)] +pub enum LookupOption { + ToFixed, + Compress, + Reduce, +} + +pub const TO_FIX: LookupOption = LookupOption::ToFixed; +pub const COMPRESS: LookupOption = LookupOption::Compress; +pub const REDUCE: LookupOption = LookupOption::Reduce; + +/// General trait to convert to a vec +pub trait ToVec { + /// Converts a tuple to a vector + fn to_vec(&self) -> Vec; +} + +impl ToVec for Vec { + fn to_vec(&self) -> Vec { + self.clone() + } +} + +impl ToVec for [T] { + fn to_vec(&self) -> Vec { + self.to_owned() + } +} + +impl ToVec for &[T] { + fn to_vec(&self) -> Vec { + <&[T]>::clone(self).to_owned() + } +} + +macro_rules! impl_to_vec { + (($($t:ty),*), ($($v:ident),*)) => { + impl ToVec for ($($t,)*) { + fn to_vec(&self) -> Vec { + let ($($v,)*) = self; + vec![$($v.clone()),*] + } + } + }; +} + +impl_to_vec!((T, T), (a, b)); +impl_to_vec!((T, T, T), (a, b, c)); +impl_to_vec!((T, T, T, T), (a, b, c, d)); +impl_to_vec!((T, T, T, T, T), (a, b, c, d, e)); +impl_to_vec!((T, T, T, T, T, T), (a, b, c, d, e, f)); +impl_to_vec!((T, T, T, T, T, T, T), (a, b, c, d, e, f, g)); +impl_to_vec!((T, T, T, T, T, T, T, T), (a, b, c, d, e, f, g, h)); + +/// Trait that generates a vector of expressions +pub trait ExprVec { + /// Returns a vector of the expressions from itself + fn to_expr_vec(&self) -> Vec>; +} + +impl ExprVec for std::ops::Range { + fn to_expr_vec(&self) -> Vec> { + self.clone().map(|e| e.expr()).collect::>() + } +} + +impl> ExprVec for Vec { + fn to_expr_vec(&self) -> Vec> { + self.iter().map(|e| e.expr()).collect::>() + } +} + +impl> ExprVec for [E] { + fn to_expr_vec(&self) -> Vec> { + self.iter().map(|e| e.expr()).collect::>() + } +} + +impl> ExprVec for &[E] { + fn to_expr_vec(&self) -> Vec> { + self.iter().map(|e| e.expr()).collect::>() + } +} + +/// Implementation trait `ExprVec` for type able to be casted to an +/// Expression +#[macro_export] +macro_rules! impl_expr_vec { + ($type:ty) => { + impl ExprVec for $type { + #[inline] + fn to_expr_vec(&self) -> Vec> { + vec![self.expr()] + } + } + }; +} + +impl_expr_vec!(bool); +impl_expr_vec!(u8); +impl_expr_vec!(i32); +impl_expr_vec!(u64); +impl_expr_vec!(usize); +impl_expr_vec!(isize); +impl_expr_vec!(Expression); +impl_expr_vec!(Cell); + +/// Newtype wrapper for `Vec>` +#[derive(Clone)] +pub struct ExpressionVec(pub Vec>); + +impl Add for ExpressionVec { + type Output = ExpressionVec; + + fn add(self, rhs: ExpressionVec) -> Self::Output { + ExpressionVec( + self.0 + .iter() + .zip(rhs.0.iter()) + .map(|(a, b)| a.expr() + b.expr()) + .collect(), + ) + } +} + +impl Mul for ExpressionVec { + type Output = ExpressionVec; + + fn mul(self, rhs: ExpressionVec) -> Self::Output { + ExpressionVec( + self.0 + .iter() + .zip(rhs.0.iter()) + .map(|(a, b)| a.expr() * b.expr()) + .collect(), + ) + } +} + +/// Trait for doing math on Expressions, no matter the type they are stored in +pub trait ExprResult { + /// Adds two values together + fn add(&self, other: &Self) -> Self; + /// Multiply with a scalar + fn mul(&self, other: &Expression) -> Self; +} + +impl ExprResult for () { + fn add(&self, _other: &Self) -> Self {} + fn mul(&self, _other: &Expression) -> Self {} +} + +impl ExprResult for Vec> { + fn add(&self, other: &Self) -> Self { + (ExpressionVec(self.clone()) + ExpressionVec(other.clone())).0 + } + fn mul(&self, other: &Expression) -> Self { + (ExpressionVec(self.clone()) * ExpressionVec(vec![other.clone(); self.len()])).0 + } +} + +impl ExprResult for Expression { + fn add(&self, other: &Self) -> Self { + vec![self.clone()].add(&vec![other.clone()])[0].clone() + } + fn mul(&self, other: &Expression) -> Self { + vec![self.clone()].mul(other)[0].clone() + } +} + +/// Implement `ExprResult` for tupples +#[macro_export] +macro_rules! impl_expr_result { + ($($type:ty),*) => { + impl ExprResult for ($($type),*) { + fn add(&self, other: &Self) -> Self { + self.to_vec().add(&other.to_vec()).into_iter().collect_tuple().unwrap() + } + fn mul(&self, other: &Expression) -> Self { + self.to_vec().mul(other).into_iter().collect_tuple().unwrap() + } + } + }; +} + +impl_expr_result!(Expression, Expression); +impl_expr_result!(Expression, Expression, Expression); +impl_expr_result!(Expression, Expression, Expression, Expression); +impl_expr_result!( + Expression, + Expression, + Expression, + Expression, + Expression +); +impl_expr_result!( + Expression, + Expression, + Expression, + Expression, + Expression, + Expression +); +impl_expr_result!( + Expression, + Expression, + Expression, + Expression, + Expression, + Expression, + Expression +); +impl_expr_result!( + Expression, + Expression, + Expression, + Expression, + Expression, + Expression, + Expression, + Expression +); + +/// Trait around RLC +pub trait RLCable { + /// Returns the RLC of itself + fn rlc(&self, r: &Expression) -> Expression; + /// Returns the RLC of the reverse of itself + fn rlc_rev(&self, r: &Expression) -> Expression; +} + +impl + ?Sized> RLCable for E { + fn rlc(&self, r: &Expression) -> Expression { + rlc::expr(&self.to_expr_vec(), r.expr()) + } + + fn rlc_rev(&self, r: &Expression) -> Expression { + rlc::expr( + &self.to_expr_vec().iter().rev().cloned().collect_vec(), + r.expr(), + ) + } +} + +/// Trait around RLC +pub trait RLCChainable { + /// Returns the RLC of itself with a starting rlc/multiplier + fn rlc_chain(&self, other: Expression) -> Expression; +} + +impl RLCChainable for (Expression, Expression) { + fn rlc_chain(&self, other: Expression) -> Expression { + self.0.expr() + self.1.expr() * other.expr() + } +} + +/// Trait around RLC +pub trait RLCChainable2 { + /// Returns the RLC of itself with a starting rlc/multiplier + fn rlc_chain2(&self, other: (Expression, Expression)) -> Expression; +} + +impl RLCChainable2 for Expression { + fn rlc_chain2(&self, other: (Expression, Expression)) -> Expression { + self.expr() * other.1.expr() + other.0.expr() + } +} + +/// Trait around RLC +pub trait RLCableValue { + /// Returns the RLC of itself + fn rlc_value(&self, r: F) -> F; + /// Returns the RLC of itself + fn rlc_rev_value(&self, r: F) -> F; +} + +impl RLCableValue for Vec { + fn rlc_value(&self, r: F) -> F { + rlc::value(self, r) + } + + fn rlc_rev_value(&self, r: F) -> F { + rlc::value(self.iter().rev(), r) + } +} + +impl RLCableValue for [u8] { + fn rlc_value(&self, r: F) -> F { + rlc::value(self, r) + } + + fn rlc_rev_value(&self, r: F) -> F { + rlc::value(self.iter().rev(), r) + } +} + +/// Trait around RLC +pub trait RLCChainableValue { + /// Returns the RLC of itself with a starting rlc/multiplier + fn rlc_chain_value(&self, values: I, r: F) -> (F, F); +} + +impl, I: IntoIterator> RLCChainableValue for (F, F) { + fn rlc_chain_value(&self, values: I, r: F) -> (F, F) { + let mut rlc = self.0; + let mut mult = self.1; + for value in values.into_iter().map(|byte| byte.scalar()) { + rlc += value * mult; + mult *= r; + } + (rlc, mult) + } +} +/// require_parser +#[macro_export] +macro_rules! require_parser { + { + $cb:expr, + lhs = ($($lhs:tt)*) + rest = (== $($rhs:tt)*) + } => { + let description = $crate::concat_with_preamble!( + stringify!($($lhs)*), + " == ", + stringify!($($rhs)*) + ); + $crate::_require!($cb, description, $($lhs)* => $($rhs)*) + }; + + { + $cb:expr, + lhs = ($($lhs:tt)*) + rest = ($next:tt $($rest:tt)*) + } => { + $crate::require_parser! { + $cb, + lhs = ($($lhs)* $next) + rest = ($($rest)*) + } + }; +} + +/// _require2 +#[macro_export] +macro_rules! _require2 { + ($cb:expr, $($rest:tt)*) => {{ + $crate::require_parser! { + $cb, + lhs = () + rest = ($($rest)*) + } + }}; +} + +/// Creates a dummy constraint builder that cannot be used to add constraints. +#[macro_export] +macro_rules! _cb { + () => {{ + use $crate::circuit_tools::cell_manager::DefaultCellType; + ConstraintBuilder::::new(0, None, None) + }}; +} + +/// Concats arguments with preamble consisting of the originating file and line. +#[macro_export] +macro_rules! concat_with_preamble { + ($($args:expr),* $(,)?) => {{ + concat!( + file!(), + ":", + line!(), + ": ", + $( + $args, + )* + ) + }}; +} + +/// Can be used to mark a specific branch as unreachable +#[macro_export] +macro_rules! _unreachablex { + ($cb:expr $(,$descr:expr)?) => {{ + let descr = concat_with_preamble!( + "unreachable executed", + $( + ": ", + $descr, + )* + ); + _require!($cb, descr, true => false) + }}; +} + +/// _require +#[macro_export] +macro_rules! _require { + ($cb:expr, $lhs:expr => bool) => {{ + let description = concat_with_preamble!( + stringify!($lhs), + " => ", + "bool", + ); + $cb.require_boolean(description, $lhs.expr()); + }}; + + ($cb:expr, $lhs:expr => $rhs:expr) => {{ + let description = concat_with_preamble!( + stringify!($lhs), + " => ", + stringify!($rhs) + ); + _require!($cb, description, $lhs => $rhs) + }}; + + ($cb:expr, $descr:expr, $lhs:expr => $rhs:expr) => {{ + let rhs = $rhs.to_expr_vec(); + if rhs.len() == 1 { + $cb.require_equal( + Box::leak($descr.to_string().into_boxed_str()), + $lhs.expr(), + rhs[0].expr(), + ); + } else { + $cb.require_in_set( + Box::leak($descr.to_string().into_boxed_str()), + $lhs.expr(), + rhs.clone(), + ); + } + }}; + + // ----------------------------------------------------- + // Lookups build from table + // only reduce flag is allowed + + // Lookup using a array + ($cb:expr, $values:expr =>> @$tag:expr, $options:expr) => {{ + use $crate::circuit_tools::constraint_builder::REDUCE; + let description = concat_with_preamble!( + stringify!($values), + " =>> @", + stringify!($tag), + ); + $cb.add_lookup( + description, + $tag, + $values, + bool::default(), + $options.contains(&COMPRESS), + $options.contains(&REDUCE), + false + ); + }}; + // Lookup using a tuple + ($cb:expr, $descr:expr, $values:expr =>> @$tag:expr, $options:expr) => {{ + use $crate::circuit_tools::constraint_builder::REDUCE; + $cb.add_lookup( + Box::leak($descr.to_string().into_boxed_str()), + $tag, + $values, + bool::default(), + $options.contains(&COMPRESS), + $options.contains(&REDUCE), + false + ); + }}; + + // ----------------------------------------------------- + // Lookup using a tuple + ($cb:expr, $values:expr => @$tag:expr, $options:expr) => {{ + use $crate::circuit_tools::constraint_builder::{REDUCE, COMPRESS, TO_FIX}; + let description = concat_with_preamble!( + stringify!($values), + " => @", + stringify!($tag), + ); + $cb.add_lookup( + description, + $tag, + $values, + $options.contains(&TO_FIX), + $options.contains(&COMPRESS), + $options.contains(&REDUCE), + true + ); + }}; + ($cb:expr, $descr:expr, $values:expr => @$tag:expr, $options:expr) => {{ + use $crate::circuit_tools::constraint_builder::{REDUCE, COMPRESS, TO_FIX}; + $cb.add_lookup( + Box::leak($descr.into_boxed_str()), + $tag, + $values, + $options.contains(&TO_FIX), + $options.contains(&COMPRESS), + $options.contains(&REDUCE), + true + ); + }}; + + // ----------------------------------------------------- + + + // Put values in a lookup table using a tuple + ($cb:expr, @$tag:expr, $options:expr => $values:expr) => {{ + let description = concat_with_preamble!( + "@", + stringify!($tag), + " => (", + stringify!($values), + ")", + ); + $cb.store_table( + description, + $tag, + $values, + $options.contains(&COMPRESS), + $options.contains(&REDUCE), + true + ); + }}; + // Put values in a lookup table using a tuple + ($cb:expr, @$tag:expr, $options:expr =>> $values:expr) => {{ + let description = concat_with_preamble!( + "@", + stringify!($tag), + " => (", + stringify!($values), + ")", + ); + $cb.store_table( + description, + $tag, + $values, + $options.contains(&COMPRESS), + $options.contains(&REDUCE), + false + ); + }}; +} + +/// matchx +/// Supports `_` which works the same as in the normal `match`: if none of the +/// other arms are active the `_` arm will be executed and so can be used to +/// return some default values or could also be marked as unreachable (using the +/// unreachablex! macro). +#[macro_export] +macro_rules! _matchx { + ($cb:expr, ($($condition:expr => $when:expr),* $(, _ => $catch_all:expr)? $(,)?)) => {{ + let mut conditions = Vec::new(); + let mut cases = Vec::new(); + $( + $cb.push_condition($condition.expr()); + let ret = $when.clone(); + $cb.pop_condition(); + cases.push(($condition.expr(), ret)); + conditions.push($condition.expr()); + )* + + $( + let catch_all_condition = not::expr(sum::expr(&conditions)); + $cb.push_condition(catch_all_condition.expr()); + let ret = $catch_all; + $cb.pop_condition(); + cases.push((catch_all_condition.expr(), ret)); + conditions.push(catch_all_condition.expr()); + )* + + // All conditions need to be boolean + for condition in conditions.iter() { + _require!($cb, condition => bool); + } + // Exactly 1 case needs to be enabled + _require!($cb, sum::expr(&conditions) => 1); + + // Apply the conditions to all corresponding values + let mut res = cases[0].1.mul(&cases[0].0.expr()); + for pair in cases.iter().skip(1) { + res = <_ as ExprResult>::add(&res, &pair.1.mul(&pair.0.expr())); + } + res + }}; +} + +#[macro_export] +macro_rules! _to_and { + (($($condition:expr),*)) => { + and::expr([$($condition.expr()),*]) + }; + ($condition:expr) => { + $condition.expr() + } +} +/// ifx +#[macro_export] +macro_rules! _ifx { + ($cb:expr,$condition:tt => $when_true:block $(elsex $when_false:block)?) => {{ + let condition = _to_and!($condition); + + $cb.push_condition(condition.expr()); + let ret_true = $when_true; + $cb.pop_condition(); + + #[allow(unused_assignments, unused_mut)] + let mut ret = ret_true.mul(&condition.expr()); + $( + // In if/else cases, the condition needs to be boolean + _require!($cb, condition => bool); + + $cb.push_condition(not::expr(condition.expr())); + let ret_false = $when_false; + $cb.pop_condition(); + + ret = <_ as ExprResult>::add(&ret_true.mul(&condition), &ret_false.mul(¬::expr(condition.expr()))); + )* + ret + }}; +} + +/// matchw - Resembles matchx so that the witness generation can look like the +/// circuit code. +#[macro_export] +macro_rules! matchw { + ($($condition:expr => $when:expr),* $(, _ => $catch_all:expr)? $(,)?) => {{ + if false { + unreachable!() + } + $(else if $condition { + $when + } + )* + else { + $( + $catch_all + )* + unreachable!() + } + }}; +} + +/// assign advice +#[macro_export] +macro_rules! assign { + // Column + ($region:expr, ($column:expr, $offset:expr) => $value:expr) => {{ + use halo2_proofs::circuit::Value; + let description = + $crate::concat_with_preamble!(stringify!($column), " => ", stringify!($value)); + let value: F = $value; + $region.assign_advice(|| description, $column, $offset, || Value::known(value)) + }}; + ($region:expr, ($column:expr, $offset:expr) => $annotation:expr, $value:expr) => {{ + use halo2_proofs::circuit::Value; + let value: F = $value; + $region.name_column(|| $annotation, $column); + $region.assign_advice(|| "", $column, $offset, || Value::known(value)) + }}; + // Cell + ($region:expr, $cell:expr, $offset:expr => $value:expr) => {{ + use halo2_proofs::circuit::Value; + let description = + $crate::concat_with_preamble!(stringify!($cell), " => ", stringify!($value)); + let value: F = $value; + $region.assign_advice( + || description, + $cell.column(), + $offset + $cell.rotation(), + || Value::known(value), + ) + }}; + ($region:expr, $cell:expr, $offset:expr => $annotation:expr, $value:expr) => {{ + use halo2_proofs::circuit::Value; + let value: F = $value; + $region.assign_advice( + || $annotation, + $cell.column(), + $offset + $cell.rotation(), + || Value::known(value), + ) + }}; +} + +/// assign fixed +#[macro_export] +macro_rules! assignf { + ($region:expr, ($column:expr, $offset:expr) => $value:expr) => {{ + let description = + $crate::concat_with_preamble!(stringify!($column), " => ", stringify!($value)); + let value: F = $value; + $region.assign_fixed(|| description, $column, $offset, || Value::known(value)) + }}; +} + +#[macro_export] +macro_rules! _to_values_vec { + (($($tts:expr), *)) => { + vec![$($tts.expr()), *] + }; + ($tts:expr)=> { + $tts + } +} + +#[macro_export] +macro_rules! _to_options_vec { + (($($tts:expr), *)) => { + vec![$($tts), *] + }; +} +/// Circuit builder macros +/// Nested macro's can't do repetition +/// so we expose a couple of permutations here manually. +#[macro_export] +macro_rules! circuit { + ([$meta:expr, $cb:expr], $content:block) => {{ + #[allow(unused_imports)] + use gadgets::util::{and, not, or, sum, Expr}; + #[allow(unused_imports)] + use $crate::circuit_tools::constraint_builder::{ExprResult, ExprVec}; + #[allow(unused_imports)] + use $crate::{ + _ifx, _matchx, _require, _to_and, _to_options_vec, _to_values_vec, _unreachablex, + concat_with_preamble, + }; + + #[allow(unused_macros)] + macro_rules! f { + ($column:expr, $rot:expr) => {{ + $meta.query_fixed($column.clone(), Rotation($rot as i32)) + }}; + ($column:expr) => {{ + $meta.query_fixed($column.clone(), Rotation::cur()) + }}; + } + + #[allow(unused_macros)] + macro_rules! a { + ($column:expr, $rot:expr) => {{ + $meta.query_advice($column.clone(), Rotation($rot as i32)) + }}; + ($column:expr) => {{ + $meta.query_advice($column.clone(), Rotation::cur()) + }}; + } + + #[allow(unused_macros)] + macro_rules! c { + ($column:expr) => {{ + $meta.query_challenge($column.clone()) + }}; + } + + #[allow(unused_macros)] + macro_rules! q { + ($column:expr) => {{ + $meta.query_selector($column.clone()) + }}; + } + + #[allow(unused_macros)] + macro_rules! x { + ($column:expr, $rot:expr) => {{ + $meta.query_any($column.clone(), Rotation($rot as i32)) + }}; + ($column:expr) => {{ + $meta.query_any($column.clone(), Rotation::cur()) + }}; + } + + #[allow(unused_macros)] + macro_rules! not { + ($expr:expr) => {{ + gadgets::util::not::expr($expr.expr()) + }}; + } + + #[allow(unused_macros)] + macro_rules! invert { + ($expr:expr) => {{ + Expression::Constant(F::from($expr as u64).invert().unwrap()) + }}; + } + + #[allow(unused_macros)] + macro_rules! require { + ($lhs:expr => bool) => {{ + _require!($cb, $lhs => bool); + }}; + + ($lhs:expr => $rhs:expr) => {{ + _require!($cb, $lhs => $rhs); + }}; + + ($name:expr, $lhs:expr => $rhs:expr) => {{ + _require!($cb, $name, $lhs => $rhs); + }}; + + // Lookups build from table + // only reduce flag is allowed + ($values:tt =>> @$tag:expr, $options:tt) => {{ + let values = _to_values_vec!($values); + let options = _to_options_vec!($options); + _require!($cb, values =>> @$tag, options); + }}; + ($values:tt =>> @$tag:expr) => {{ + let values = _to_values_vec!($values); + let options = Vec::new(); + _require!($cb, values =>> @$tag, options); + }}; + ($descr:expr, $values:tt =>> @$tag:expr, $options:tt) => {{ + let values = _to_values_vec!($values); + let options = _to_options_vec!($options); + _require!($cb, $descr, values =>> @$tag, options); + }}; + ($descr:expr, $values:tt =>> @$tag:expr) => {{ + let values = _to_values_vec!($values); + let options = Vec::new(); + _require!($cb, $descr, values =>> @$tag, options); + }}; + + + + ($values:tt => @$tag:expr, $options:tt) => {{ + let values = _to_values_vec!($values); + let options = _to_options_vec!($options); + _require!($cb, values => @$tag, options); + }}; + ($values:tt => @$tag:expr) => {{ + let values = _to_values_vec!($values); + let options = Vec::new(); + _require!($cb, values => @$tag, options); + }}; + ($descr:expr, $values:tt => @$tag:expr, $options:tt) => {{ + let values = _to_values_vec!($values); + let options = _to_options_vec!($options); + _require!($cb, $descr, values => @$tag, options); + }}; + ($descr:expr, $values:tt => @$tag:expr) => {{ + let values = _to_values_vec!($values); + let options = Vec::new(); + _require!($cb, $descr, values => @$tag, options); + }}; + + + (@$tag:expr, $options:tt => $values:tt) => {{ + let values = _to_values_vec!($values); + let options = _to_options_vec!($options); + _require!($cb, @$tag, options => values); + }}; + (@$tag:expr => $values:tt) => {{ + let values = _to_values_vec!($values); + let options = Vec::new(); + _require!($cb, @$tag, options => values); + }}; + (@$tag:expr, $options:tt =>> $values:tt) => {{ + let values = _to_values_vec!($values); + let options = _to_options_vec!($options); + _require!($cb, @$tag, options =>> values); + }}; + (@$tag:expr =>> $values:tt) => {{ + let values = _to_values_vec!($values); + let options = Vec::new(); + _require!($cb, @$tag, options =>> values); + }}; + + } + + #[allow(unused_macros)] + macro_rules! ifx { + ($condition:tt => $when_true:block elsex $when_false:block) => {{ + _ifx!($cb, ($condition) => $when_true elsex $when_false) + }}; + ($condition:expr => $when_true:block elsex $when_false:block) => {{ + _ifx!($cb, $condition => $when_true elsex $when_false) + }}; + + ($condition:tt => $when_true:block) => {{ + _ifx!($cb, $condition => $when_true) + }}; + ($condition:expr => $when_true:block) => {{ + _ifx!($cb, $condition => $when_true) + }}; + } + + #[allow(unused_macros)] + macro_rules! matchx { + ($condition_to_when:tt) => {{ + _matchx!($cb, $condition_to_when) + }}; + } + + #[allow(unused_macros)] + macro_rules! unreachablex { + () => {{ + _unreachablex!($cb) + }}; + ($arg:expr) => {{ + _unreachablex!($cb, $arg) + }}; + } + + $content + }}; +} diff --git a/zkevm-circuits/src/circuit_tools/gadgets.rs b/zkevm-circuits/src/circuit_tools/gadgets.rs new file mode 100644 index 0000000000..9299957d67 --- /dev/null +++ b/zkevm-circuits/src/circuit_tools/gadgets.rs @@ -0,0 +1,168 @@ +//! Circuit gadgets +use eth_types::Field; +use gadgets::util::Expr; +use halo2_proofs::plonk::{Error, Expression}; + +use crate::evm_circuit::util::{from_bytes, pow_of_two}; + +use super::{ + cached_region::CachedRegion, + cell_manager::{Cell, CellType}, + constraint_builder::ConstraintBuilder, +}; + +/// Returns `1` when `value == 0`, and returns `0` otherwise. +#[derive(Clone, Debug, Default)] +pub struct IsZeroGadget { + inverse: Option>, + is_zero: Option>, +} + +impl IsZeroGadget { + pub(crate) fn construct( + cb: &mut ConstraintBuilder, + value: Expression, + ) -> Self { + circuit!([meta, cb], { + let inverse = cb.query_default(); + + let is_zero = 1.expr() - (value.expr() * inverse.expr()); + // `value != 0` => check `inverse = a.invert()`: value * (1 - value * inverse) + require!(value * is_zero.clone() => 0); + // `value == 0` => check `inverse = 0`: `inverse ⋅ (1 - value * inverse)` + require!(inverse.expr() * is_zero.expr() => 0); + + Self { + inverse: Some(inverse), + is_zero: Some(is_zero), + } + }) + } + + pub(crate) fn expr(&self) -> Expression { + self.is_zero.as_ref().unwrap().clone() + } + + pub(crate) fn assign( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + value: F, + ) -> Result { + let inverse = value.invert().unwrap_or(F::ZERO); + self.inverse + .as_ref() + .unwrap() + .assign(region, offset, inverse)?; + Ok(if value.is_zero().into() { + F::ONE + } else { + F::ZERO + }) + } +} + +/// Returns `1` when `lhs == rhs`, and returns `0` otherwise. +#[derive(Clone, Debug, Default)] +pub struct IsEqualGadget { + is_zero: IsZeroGadget, +} + +impl IsEqualGadget { + pub(crate) fn construct( + cb: &mut ConstraintBuilder, + lhs: Expression, + rhs: Expression, + ) -> Self { + let is_zero = IsZeroGadget::construct(cb, lhs - rhs); + + Self { is_zero } + } + + pub(crate) fn expr(&self) -> Expression { + self.is_zero.expr() + } + + pub(crate) fn assign( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + lhs: F, + rhs: F, + ) -> Result { + self.is_zero.assign(region, offset, lhs - rhs) + } +} + +/// Returns `1` when `lhs < rhs`, and returns `0` otherwise. +/// lhs and rhs `< 256**N_BYTES` +/// `N_BYTES` is required to be `<= MAX_N_BYTES_INTEGER` to prevent overflow: +/// values are stored in a single field element and two of these are added +/// together. +/// The equation that is enforced is `lhs - rhs == diff - (lt * range)`. +/// Because all values are `<= 256**N_BYTES` and `lt` is boolean, `lt` can only +/// be `1` when `lhs < rhs`. +#[derive(Clone, Debug, Default)] +pub struct LtGadget { + lt: Option>, // `1` when `lhs < rhs`, `0` otherwise. + diff: Option<[Cell; N_BYTES]>, /* The byte values of `diff`. + * `diff` equals `lhs - rhs` if `lhs >= rhs`, + * `lhs - rhs + range` otherwise. */ + range: F, // The range of the inputs, `256**N_BYTES` +} + +impl LtGadget { + pub(crate) fn construct( + cb: &mut ConstraintBuilder, + lhs: Expression, + rhs: Expression, + ) -> Self { + let lt = cb.query_bool(); + let diff = cb.query_bytes(); + let range = pow_of_two(N_BYTES * 8); + + // The equation we require to hold: `lhs - rhs == diff - (lt * range)`. + cb.require_equal( + "lhs - rhs == diff - (lt ⋅ range)", + lhs - rhs, + from_bytes::expr(&diff) - (lt.expr() * range), + ); + + Self { + lt: Some(lt), + diff: Some(diff), + range, + } + } + + pub(crate) fn expr(&self) -> Expression { + self.lt.as_ref().unwrap().expr() + } + + pub(crate) fn assign( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + lhs: F, + rhs: F, + ) -> Result<(F, Vec), Error> { + // Set `lt` + let lt = lhs < rhs; + self.lt + .as_ref() + .unwrap() + .assign(region, offset, if lt { F::ONE } else { F::ZERO })?; + // Set the bytes of diff + let diff = (lhs - rhs) + (if lt { self.range } else { F::ZERO }); + let diff_bytes = diff.to_repr(); + for (idx, diff) in self.diff.as_ref().unwrap().iter().enumerate() { + diff.assign(region, offset, F::from(diff_bytes[idx] as u64))?; + } + + Ok((if lt { F::ONE } else { F::ZERO }, diff_bytes.to_vec())) + } + + pub(crate) fn diff_bytes(&self) -> Vec> { + self.diff.as_ref().unwrap().to_vec() + } +} diff --git a/zkevm-circuits/src/circuit_tools/memory.rs b/zkevm-circuits/src/circuit_tools/memory.rs new file mode 100644 index 0000000000..ebdf5da465 --- /dev/null +++ b/zkevm-circuits/src/circuit_tools/memory.rs @@ -0,0 +1,286 @@ +//! Memory +use crate::util::{query_expression, Expr}; +use eth_types::Field; +use halo2_proofs::{ + circuit::Value, + plonk::{Advice, Column, ConstraintSystem, Error, Expression}, + poly::Rotation, +}; +use itertools::Itertools; +use std::{ + collections::HashMap, + marker::PhantomData, + ops::{Index, IndexMut}, +}; + +use super::{ + cached_region::CachedRegion, + cell_manager::{CellManager, CellType}, + constraint_builder::ConstraintBuilder, +}; + +#[derive(Clone, Debug, Default)] +pub(crate) struct Memory> { + // TODO(Cecilia): want to use dynamic dispatch + // i.e. dyn MemoryBank but trait with generic param is not object safe + pub(crate) banks: HashMap, + _phantom: PhantomData, +} + +impl> Index for Memory { + type Output = MB; + + fn index(&self, tag: C) -> &Self::Output { + if let Some(bank) = self.banks.get(&tag) { + bank + } else { + unreachable!() + } + } +} + +impl> IndexMut for Memory { + fn index_mut(&mut self, tag: C) -> &mut Self::Output { + if let Some(bank) = self.banks.get_mut(&tag) { + bank + } else { + unreachable!() + } + } +} + +impl> Memory { + pub(crate) fn new( + cm: &mut CellManager, + meta: &mut ConstraintSystem, + tags: Vec<(C, C, u8)>, + offset: usize, + ) -> Self { + let mut banks = HashMap::new(); + tags.into_iter().for_each(|(data_tag, table_tag, phase)| { + banks.insert( + data_tag, + MB::new(meta, cm, (data_tag, table_tag), phase, offset), + ); + }); + Self { + banks, + _phantom: PhantomData, + } + } + + pub(crate) fn get_columns(&self) -> Vec> { + self.banks.values().fold(Vec::new(), |mut acc, bank| { + acc.extend(bank.columns().iter()); + acc + }) + } + + pub(crate) fn build_constraints( + &self, + cb: &mut ConstraintBuilder, + q_start: Expression, + ) { + for (_, bank) in self.banks.iter() { + bank.build_constraints(cb, q_start.expr()); + } + } + + pub(crate) fn assign( + &self, + region: &mut CachedRegion<'_, '_, F>, + height: usize, + ) -> Result<(), Error> { + for (_, bank) in self.banks.iter() { + bank.assign(region, height)?; + } + Ok(()) + } + + pub(crate) fn tags(&self) -> Vec { + self.banks.values().map(|bank| bank.tag().0).collect() + } +} + +pub(crate) trait MemoryBank: Clone { + fn new( + meta: &mut ConstraintSystem, + cm: &mut CellManager, + tag: (C, C), + phase: u8, + offset: usize, + ) -> Self; + fn store( + &mut self, + cb: &mut ConstraintBuilder, + values: &[Expression], + ) -> Expression; + fn load( + &mut self, + cb: &mut ConstraintBuilder, + load_offset: Expression, + values: &[Expression], + ); + fn columns(&self) -> Vec>; + fn tag(&self) -> (C, C); + fn witness_store(&mut self, offset: usize, values: &[F]); + fn witness_load(&self, offset: usize) -> Vec; + fn build_constraints(&self, cb: &mut ConstraintBuilder, q_start: Expression); + fn assign(&self, region: &mut CachedRegion<'_, '_, F>, height: usize) -> Result<(), Error>; +} + +#[derive(Clone, Debug)] +pub(crate) struct RwBank { + tag: (C, C), + key: Column, + reads: Column, + writes: Column, + store_offsets: Vec, + stored_values: Vec>, + cur: Expression, + next: Expression, + // TODO(Cecilia): get rid of this when we kill regions + local_conditions: Vec<(usize, Expression)>, +} + +impl RwBank { + pub(crate) fn prepend_key(&self, values: &[Expression]) -> Vec> { + [&[self.cur.expr() + 1.expr()], values].concat().to_vec() + } + + pub(crate) fn prepend_offset( + &self, + values: &[Expression], + offset: Expression, + ) -> Vec> { + [&[self.cur.expr() - offset], values].concat().to_vec() + } +} + +impl MemoryBank for RwBank { + fn new( + meta: &mut ConstraintSystem, + cm: &mut CellManager, + tag: (C, C), + phase: u8, + offset: usize, + ) -> Self { + let rw: Vec> = [tag.0, tag.1] + .iter() + .map(|t| { + let config = (*t, 1usize, phase, false); + cm.add_celltype(meta, config, offset); + cm.get_typed_columns(*t)[0].column + }) + .collect(); + let key = meta.advice_column(); + let (cur, next) = query_expression(meta, |meta| { + ( + meta.query_advice(key, Rotation(0)), + meta.query_advice(key, Rotation(1)), + ) + }); + Self { + tag, + key, + reads: rw[0], + writes: rw[1], + store_offsets: Vec::new(), + stored_values: Vec::new(), + cur, + next, + local_conditions: Vec::new(), + } + } + + fn store( + &mut self, + cb: &mut ConstraintBuilder, + values: &[Expression], + ) -> Expression { + let values = self.prepend_key(values); + cb.store_table( + Box::leak(format!("{:?} store", self.tag.1).into_boxed_str()), + self.tag.1, + values.clone(), + true, + true, + false, + ); + self.local_conditions + .push((cb.region_id, cb.get_condition_expr())); + values[0].expr() + } + + fn load( + &mut self, + cb: &mut ConstraintBuilder, + load_offset: Expression, + values: &[Expression], + ) { + let values = self.prepend_offset(values, load_offset); + cb.add_lookup( + Box::leak(format!("{:?} load", self.tag.0).into_boxed_str()), + self.tag.0, + values, + false, + true, + true, + false, + ); + } + + fn tag(&self) -> (C, C) { + self.tag + } + + fn columns(&self) -> Vec> { + vec![self.key, self.reads, self.writes] + } + + fn build_constraints(&self, cb: &mut ConstraintBuilder, q_start: Expression) { + let condition = self + .local_conditions + .iter() + .filter(|tc| tc.0 == cb.region_id) + .fold(0.expr(), |acc, tc| acc + tc.1.expr()); + crate::circuit!([meta, cb], { + ifx! {q_start => { + require!(self.cur.expr() => 0); + }} + let description = format!("Dynamic lookup table {:?}", self.tag()); + require!(condition => bool); + require!(description, self.next => self.cur.expr() + condition.expr()); + }); + } + + fn witness_store(&mut self, offset: usize, values: &[F]) { + self.stored_values.push(values.to_vec()); + self.store_offsets.push(offset); + } + + fn witness_load(&self, offset: usize) -> Vec { + self.stored_values[self.stored_values.len() - 1 - offset].clone() + } + + fn assign(&self, region: &mut CachedRegion<'_, '_, F>, height: usize) -> Result<(), Error> { + // Pad to the full circuit (necessary for reads) + let mut store_offsets = self.store_offsets.clone(); + store_offsets.push(height); + + // TODO(Brecht): partial updates + let mut offset = 0; + for (store_index, &stored_offset) in store_offsets.iter().enumerate() { + while offset <= stored_offset { + region.assign_advice( + || "assign memory index".to_string(), + self.key, + offset, + || Value::known(F::from(store_index as u64)), + )?; + offset += 1; + } + } + Ok(()) + } +} diff --git a/zkevm-circuits/src/keccak_circuit.rs b/zkevm-circuits/src/keccak_circuit.rs index 029ec196a6..16d4185abb 100644 --- a/zkevm-circuits/src/keccak_circuit.rs +++ b/zkevm-circuits/src/keccak_circuit.rs @@ -1,6 +1,6 @@ //! The keccak circuit implementation. mod cell_manager; -/// Keccak packed multi +/// Keccak packed multiz pub mod keccak_packed_multi; mod param; mod table; diff --git a/zkevm-circuits/src/lib.rs b/zkevm-circuits/src/lib.rs index 62c796821d..be7ad68289 100644 --- a/zkevm-circuits/src/lib.rs +++ b/zkevm-circuits/src/lib.rs @@ -27,9 +27,12 @@ pub mod root_circuit; pub mod state_circuit; pub mod super_circuit; pub mod table; + +#[macro_use] +#[allow(missing_docs)] pub mod taiko_pi_circuit; +pub mod circuit_tools; pub mod taiko_super_circuit; - #[cfg(any(feature = "test", test))] pub mod test_util; diff --git a/zkevm-circuits/src/table/keccak_table.rs b/zkevm-circuits/src/table/keccak_table.rs index 5eaff3f30d..2a283c504d 100644 --- a/zkevm-circuits/src/table/keccak_table.rs +++ b/zkevm-circuits/src/table/keccak_table.rs @@ -52,6 +52,7 @@ impl KeccakTable { let input_rlc = challenges .keccak_input() .map(|challenge| rlc::value(input.iter().rev(), challenge)); + let input_len = F::from(input.len() as u64); let mut keccak = Keccak::default(); keccak.update(input); @@ -62,7 +63,6 @@ impl KeccakTable { challenge, ) }); - vec![[ Value::known(F::ONE), input_rlc, diff --git a/zkevm-circuits/src/taiko_pi_circuit.rs b/zkevm-circuits/src/taiko_pi_circuit.rs index 125e35c1ff..97173b602c 100644 --- a/zkevm-circuits/src/taiko_pi_circuit.rs +++ b/zkevm-circuits/src/taiko_pi_circuit.rs @@ -1,191 +1,288 @@ -//! Use the hash value as public input. +use eth_types::{Field, ToBigEndian, ToWord, H160, U256}; +use ethers_core::abi::*; + +use ethers_core::utils::keccak256; +use halo2_proofs::circuit::{AssignedCell, Layouter, SimpleFloorPlanner, Value}; + +use gadgets::util::{Expr, Scalar}; +use halo2_proofs::plonk::{Circuit, Column, ConstraintSystem, Expression, Instance, Selector}; +use std::{convert::TryInto, marker::PhantomData}; use crate::{ - evm_circuit::util::constraint_builder::{BaseConstraintBuilder, ConstrainBuilderCommon}, + assign, circuit, + circuit_tools::{ + cached_region::CachedRegion, + cell_manager::{Cell, CellColumn, CellManager, CellType}, + constraint_builder::{ConstraintBuilder, ExprVec, RLCable, TO_FIX}, + }, + evm_circuit::{table::Table, util::rlc}, table::{byte_table::ByteTable, BlockContextFieldTag, BlockTable, KeccakTable, LookupTable}, - util::{random_linear_combine_word as rlc, Challenges, SubCircuit, SubCircuitConfig}, + util::{Challenges, SubCircuit, SubCircuitConfig}, witness::{self, BlockContext}, }; -use eth_types::{Address, Field, ToBigEndian, ToWord, Word, H256}; -use ethers_core::utils::keccak256; -use gadgets::util::{or, select, Expr}; -use halo2_proofs::{ - circuit::{AssignedCell, Layouter, Region, SimpleFloorPlanner, Value}, - plonk::{ - Advice, Circuit, Column, ConstraintSystem, Error, Expression, Fixed, Instance, SecondPhase, - Selector, - }, - poly::Rotation, -}; -use std::marker::PhantomData; +use core::result::Result; +use halo2_proofs::plonk::Error; -const MAX_DEGREE: usize = 9; -const RPI_CELL_IDX: usize = 0; -const RPI_RLC_ACC_CELL_IDX: usize = 1; const BYTE_POW_BASE: u64 = 1 << 8; -const RPI_BYTES_LEN: usize = 32 * 10; -// 10 fields * 32B + lo(16B) + hi(16B) + keccak(32B) -const USED_ROWS: usize = RPI_BYTES_LEN + 64; - -/// PublicData contains all the values that the PiCircuit receives as input +const PADDING_LEN: usize = 32; +const META_HASH: usize = 0; +const PARENT_HASH: usize = 1; +const BLOCK_HASH: usize = 2; +const SIGNAL_ROOT: usize = 3; +const GRAFFITI: usize = 4; +const PROVER: usize = 5; +const S1: PiCellType = PiCellType::StoragePhase1; +const S2: PiCellType = PiCellType::StoragePhase2; +/// #[derive(Debug, Clone, Default)] -pub struct PublicData { - /// l1 signal service address - pub l1_signal_service: Word, - /// l2 signal service address - pub l2_signal_service: Word, - /// l2 contract address - pub l2_contract: Word, - /// meta hash - pub meta_hash: Word, - /// block hash value - pub block_hash: Word, - /// the parent block hash - pub parent_hash: Word, - /// signal root - pub signal_root: Word, - /// extra message - pub graffiti: Word, - /// union field - pub field9: Word, // prover[96:256]+parentGasUsed[64:96]+gasUsed[32:64] - /// union field - pub field10: Word, /* blockMaxGasLimit[192:256]+maxTransactionsPerBlock[128: - * 192]+maxBytesPerTxList[64:128] */ - - // privates - // Prover address - prover: Address, - // parent block gas used - parent_gas_used: u32, - // block gas used - gas_used: u32, - // blockMaxGasLimit - block_max_gas_limit: u64, - // maxTransactionsPerBlock: u64, - max_transactions_per_block: u64, - // maxBytesPerTxList: u64, - max_bytes_per_tx_list: u64, - - block_context: BlockContext, - chain_id: Word, +pub struct FieldGadget { + field: Vec>, + len: usize, } -impl PublicData { - fn assignments(&self) -> [(&'static str, Option, [u8; 32]); 10] { +impl FieldGadget { + fn config(cb: &mut ConstraintBuilder, len: usize) -> Self { + Self { + field: cb.query_cells_dyn(PiCellType::Byte, len), + len, + } + } + + fn bytes_expr(&self) -> Vec> { + self.field.iter().map(|f| f.expr()).collect() + } + + fn rlc_acc(&self, r: Expression) -> Expression { + // 0.expr() + self.bytes_expr().rlc_rev(&r) + } + + pub(crate) fn hi_low_field(&self) -> [Expression; 2] { + assert!(self.len == 32); + let hi = self.bytes_expr()[..16].to_vec(); + let low = self.bytes_expr()[16..].to_vec(); [ - ( - "l1_signal_service", - None, - self.l1_signal_service.to_be_bytes(), - ), - ( - "l2_signal_service", - None, - self.l2_signal_service.to_be_bytes(), - ), - ("l2_contract", None, self.l2_contract.to_be_bytes()), - ("meta_hash", None, self.meta_hash.to_be_bytes()), - ( - "parent_hash", - Some(self.block_context.number - 1), - self.parent_hash.to_be_bytes(), - ), - ( - "block_hash", - Some(self.block_context.number), - self.block_hash.to_be_bytes(), - ), - ("signal_root", None, self.signal_root.to_be_bytes()), - ("graffiti", None, self.graffiti.to_be_bytes()), - ( - "prover+parentGasUsed+gasUsed", - None, - self.field9.to_be_bytes(), - ), - ( - "blockMaxGasLimit+maxTransactionsPerBlock+maxBytesPerTxList", - None, - self.field10.to_be_bytes(), - ), + hi.rlc_rev(&BYTE_POW_BASE.expr()), + low.rlc_rev(&BYTE_POW_BASE.expr()), ] } - /// get rpi bytes - pub fn rpi_bytes(&self) -> Vec { - self.assignments().iter().flat_map(|v| v.2).collect() - } - - fn default() -> Self { - Self::new::(&witness::Block::default()) - } - - /// create PublicData from block and taiko - pub fn new(block: &witness::Block) -> Self { - use witness::left_shift; - let field9 = left_shift(block.protocol_instance.prover, 96) - + left_shift(block.protocol_instance.parent_gas_used as u64, 64) - + left_shift(block.protocol_instance.gas_used as u64, 32); - - let field10 = left_shift(block.protocol_instance.block_max_gas_limit, 192) - + left_shift(block.protocol_instance.max_transactions_per_block, 128) - + left_shift(block.protocol_instance.max_bytes_per_tx_list, 64); - PublicData { - l1_signal_service: block.protocol_instance.l1_signal_service.to_word(), - l2_signal_service: block.protocol_instance.l2_signal_service.to_word(), - l2_contract: block.protocol_instance.l2_contract.to_word(), - meta_hash: block.protocol_instance.meta_hash.hash().to_word(), - block_hash: block.protocol_instance.block_hash.to_word(), - parent_hash: block.protocol_instance.parent_hash.to_word(), - signal_root: block.protocol_instance.signal_root.to_word(), - graffiti: block.protocol_instance.graffiti.to_word(), - prover: block.protocol_instance.prover, - parent_gas_used: block.protocol_instance.parent_gas_used, - gas_used: block.protocol_instance.gas_used, - block_max_gas_limit: block.protocol_instance.block_max_gas_limit, - max_transactions_per_block: block.protocol_instance.max_transactions_per_block, - max_bytes_per_tx_list: block.protocol_instance.max_bytes_per_tx_list, - field9, - field10, + fn assign( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + bytes: &[F], + ) -> Result>, Error> { + assert!(bytes.len() == self.len); + let cells = self + .field + .iter() + .zip(bytes.iter()) + .map(|(cell, byte)| assign!(region, cell, offset => *byte).unwrap()) + .collect(); + Ok(cells) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +enum PiCellType { + StoragePhase1, + StoragePhase2, + Byte, + LookupPi, + Lookup(Table), +} + +impl CellType for PiCellType { + fn byte_type() -> Option { + Some(Self::Byte) + } + fn storage_for_phase(phase: u8) -> Self { + match phase { + 1 => PiCellType::StoragePhase1, + 2 => PiCellType::StoragePhase2, + _ => unimplemented!(), + } + } +} +impl Default for PiCellType { + fn default() -> Self { + Self::StoragePhase1 + } +} + +#[derive(Debug, Clone)] +pub struct PublicData { + evidence: Token, + block_context: BlockContext, + _phantom: PhantomData, +} + +impl Default for PublicData { + fn default() -> Self { + // has to have at least one history hash, block number must start with at least one + let mut ret = Self::new(&witness::Block::default()); + ret.block_context.history_hashes = vec![U256::default()]; + ret + } +} + +impl PublicData { + fn new(block: &witness::Block) -> Self { + let meta_hash = Token::FixedBytes( + block + .protocol_instance + .meta_hash + .hash() + .to_word() + .to_be_bytes() + .to_vec(), + ); + let parent_hash = Token::FixedBytes( + block + .protocol_instance + .parent_hash + .to_word() + .to_be_bytes() + .to_vec(), + ); + let block_hash = Token::FixedBytes( + block + .protocol_instance + .block_hash + .to_word() + .to_be_bytes() + .to_vec(), + ); + let signal_root = Token::FixedBytes( + block + .protocol_instance + .signal_root + .to_word() + .to_be_bytes() + .to_vec(), + ); + let graffiti = Token::FixedBytes( + block + .protocol_instance + .graffiti + .to_word() + .to_be_bytes() + .to_vec(), + ); + let prover = Token::Address(block.protocol_instance.prover); + Self { + evidence: Token::FixedArray(vec![ + meta_hash, + parent_hash, + block_hash, + signal_root, + graffiti, + prover, + ]), block_context: block.context.clone(), - chain_id: block.context.chain_id, + _phantom: PhantomData, + } + } + + fn set_field(&mut self, idx: usize, bytes: Vec) { + match self.evidence { + Token::FixedArray(ref mut tokens) => { + tokens[idx] = match tokens[idx].clone() { + Token::Bytes(_) => Token::Bytes(bytes), + Token::FixedBytes(_) => Token::FixedBytes(bytes), + Token::Address(_) => Token::Address(H160::from( + &bytes.try_into().expect("Wrong number of bytes for address"), + )), + _ => unreachable!(), + }; + } + _ => unreachable!(), } } - fn get_pi(&self) -> H256 { - let rpi_bytes = self.rpi_bytes(); - let rpi_keccak = keccak256(rpi_bytes); - H256(rpi_keccak) + pub fn encode_raw(&self) -> Vec { + encode(&[self.evidence.clone()]) + } + + fn encode_field(&self, idx: usize) -> Vec { + let field = match self.evidence { + Token::FixedArray(ref tokens) => tokens[idx].clone(), + _ => unreachable!(), + }; + encode(&[field]) + } + + fn total_acc(&self, r: Value) -> F { + let mut rand = F::ZERO; + r.map(|r| rand = r); + rlc::value(self.encode_raw().iter().rev(), rand) + } + + fn assignment(&self, idx: usize) -> Vec { + self.encode_field(idx) + .iter() + .map(|b| F::from(*b as u64)) + .collect() + } + + fn assignment_acc(&self, idx: usize, r: Value) -> F { + let mut rand = F::ZERO; + r.map(|r| rand = r); + rlc::value(self.encode_field(idx).iter().rev(), rand) + } + + fn keccak_hi_low(&self) -> [F; 2] { + let keccaked_pi = keccak256(self.encode_raw()); + [ + rlc::value(keccaked_pi[0..16].iter().rev(), BYTE_POW_BASE.scalar()), + rlc::value(keccaked_pi[16..].iter().rev(), BYTE_POW_BASE.scalar()), + ] + } + + fn keccak(&self) -> Vec { + keccak256(self.encode_raw()).to_vec() + } + + fn keccak_assignment(&self) -> Vec { + self.keccak().iter().map(|b| F::from(*b as u64)).collect() + } + + fn total_len(&self) -> usize { + self.encode_raw().len() + } + + fn field_len(&self, idx: usize) -> usize { + self.encode_field(idx).len() } } -/// Config for PiCircuit #[derive(Clone, Debug)] pub struct TaikoPiCircuitConfig { - rpi_field_bytes: Column, - rpi_field_bytes_acc: Column, - rpi_rlc_acc: Column, - q_field_start: Selector, - q_field_step: Selector, - q_field_end: Selector, - is_field_rlc: Column, + q_enable: Selector, + keccak_instance: Column, // equality - byte_table: ByteTable, + meta_hash: FieldGadget, + parent_hash: (Cell, FieldGadget, Cell), + block_hash: (Cell, FieldGadget, Cell), + signal_root: FieldGadget, + graffiti: FieldGadget, + prover: FieldGadget, - pi: Column, // keccak_hi, keccak_lo + total_acc: Cell, + keccak_bytes: FieldGadget, + keccak_hi_lo: [Cell; 2], - q_keccak: Selector, - keccak_table: KeccakTable, - - // External tables - q_block_table: Selector, - block_index: Column, block_table: BlockTable, + keccak_table: KeccakTable, + byte_table: ByteTable, - _marker: PhantomData, + annotation_configs: Vec>, } -/// Circuit configuration arguments pub struct TaikoPiCircuitConfigArgs { + /// + pub evidence: PublicData, /// BlockTable pub block_table: BlockTable, /// KeccakTable @@ -198,334 +295,207 @@ pub struct TaikoPiCircuitConfigArgs { impl SubCircuitConfig for TaikoPiCircuitConfig { type ConfigArgs = TaikoPiCircuitConfigArgs; - /// Return a new TaikoPiCircuitConfig fn new( meta: &mut ConstraintSystem, Self::ConfigArgs { + evidence, block_table, keccak_table, byte_table, challenges, }: Self::ConfigArgs, ) -> Self { - let rpi_field_bytes = meta.advice_column(); - let rpi_field_bytes_acc = meta.advice_column_in(SecondPhase); - let rpi_rlc_acc = meta.advice_column_in(SecondPhase); - let q_field_start = meta.complex_selector(); - let q_field_step = meta.complex_selector(); - let q_field_end = meta.complex_selector(); - let is_field_rlc = meta.fixed_column(); - - let pi = meta.instance_column(); - - let q_keccak = meta.complex_selector(); - let q_block_table = meta.complex_selector(); - let block_index = meta.advice_column(); - - meta.enable_equality(rpi_field_bytes); - meta.enable_equality(rpi_field_bytes_acc); - meta.enable_equality(rpi_rlc_acc); - meta.enable_equality(block_table.value); - meta.enable_equality(pi); - - // field bytes - meta.create_gate( - "rpi_field_bytes_acc[i+1] = rpi_field_bytes_acc[i] * t + rpi_bytes[i+1]", - |meta| { - let mut cb = BaseConstraintBuilder::new(MAX_DEGREE); - - let q_field_step = meta.query_selector(q_field_step); - let rpi_field_bytes_acc_next = - meta.query_advice(rpi_field_bytes_acc, Rotation::next()); - let rpi_field_bytes_acc = meta.query_advice(rpi_field_bytes_acc, Rotation::cur()); - let rpi_field_bytes_next = meta.query_advice(rpi_field_bytes, Rotation::next()); - let is_field_rlc = meta.query_fixed(is_field_rlc, Rotation::next()); - let randomness = challenges.evm_word(); - let t = select::expr(is_field_rlc, randomness, BYTE_POW_BASE.expr()); - cb.require_equal( - "rpi_field_bytes_acc[i+1] = rpi_field_bytes_acc[i] * t + rpi_bytes[i+1]", - rpi_field_bytes_acc_next, - rpi_field_bytes_acc * t + rpi_field_bytes_next, - ); - cb.gate(q_field_step) - }, + let keccak_r = challenges.keccak_input(); + let evm_word = challenges.evm_word(); + let cm = CellManager::new( + meta, + vec![ + (PiCellType::Byte, 7, 1, false), + (PiCellType::StoragePhase1, 1, 1, true), + (PiCellType::StoragePhase2, 1, 2, true), + ], + 0, + 32, ); - meta.create_gate("rpi_field_bytes_acc[0] = rpi_field_bytes[0]", |meta| { - let mut cb = BaseConstraintBuilder::new(MAX_DEGREE); - - let q_field_start = meta.query_selector(q_field_start); - let rpi_field_bytes_acc = meta.query_advice(rpi_field_bytes_acc, Rotation::cur()); - let rpi_field_bytes = meta.query_advice(rpi_field_bytes, Rotation::cur()); - - cb.require_equal( - "rpi_field_bytes_acc[0] = rpi_field_bytes[0]", - rpi_field_bytes_acc, - rpi_field_bytes, - ); - cb.gate(q_field_start) - }); - - // keccak in rpi - meta.lookup_any("keccak(rpi)", |meta| { - let q_keccak = meta.query_selector(q_keccak); - let rpi_rlc = meta.query_advice(rpi_field_bytes_acc, Rotation::cur()); - let output = meta.query_advice(rpi_rlc_acc, Rotation::cur()); - [1.expr(), rpi_rlc, RPI_BYTES_LEN.expr(), output] - .into_iter() - .zip(keccak_table.table_exprs(meta).into_iter()) - .map(|(arg, table)| (q_keccak.expr() * arg, table)) - .collect::>() - }); - - // in block table - meta.lookup_any("in block table", |meta| { - let q_block_table = meta.query_selector(q_block_table); - let block_index = meta.query_advice(block_index, Rotation::cur()); - let block_hash = meta.query_advice(rpi_field_bytes_acc, Rotation::cur()); - [ - BlockContextFieldTag::BlockHash.expr(), - block_index, - block_hash, - ] - .into_iter() - .zip(block_table.table_exprs(meta).into_iter()) - .map(|(arg, table)| (q_block_table.expr() * arg, table)) - .collect::>() - }); - // is byte - meta.lookup_any("is_byte", |meta| { - let q_field_step = meta.query_selector(q_field_start); - let q_field_end = meta.query_selector(q_field_end); - let is_field = or::expr([q_field_step, q_field_end]); - let rpi_field_bytes = meta.query_advice(rpi_field_bytes, Rotation::cur()); - [rpi_field_bytes] - .into_iter() - .zip(byte_table.table_exprs(meta).into_iter()) - .map(|(arg, table)| (is_field.expr() * arg, table)) - .collect::>() + let mut cb: ConstraintBuilder = + ConstraintBuilder::new(4, Some(cm.clone()), Some(evm_word.expr())); + cb.preload_tables( + meta, + &[ + (PiCellType::Lookup(Table::Keccak), &keccak_table), + (PiCellType::Lookup(Table::Bytecode), &byte_table), + (PiCellType::Lookup(Table::Block), &block_table), + ], + ); + let q_enable = meta.complex_selector(); + let keccak_instance = meta.instance_column(); + meta.enable_equality(keccak_instance); + + let meta_hash = FieldGadget::config(&mut cb, evidence.field_len(META_HASH)); + let parent_hash = ( + cb.query_one(S1), + FieldGadget::config(&mut cb, evidence.field_len(PARENT_HASH)), + cb.query_one(S2), + ); + let block_hash = ( + cb.query_one(S1), + FieldGadget::config(&mut cb, evidence.field_len(BLOCK_HASH)), + cb.query_one(S2), + ); + let signal_root = FieldGadget::config(&mut cb, evidence.field_len(SIGNAL_ROOT)); + let graffiti = FieldGadget::config(&mut cb, evidence.field_len(GRAFFITI)); + let prover = FieldGadget::config(&mut cb, evidence.field_len(PROVER)); + + let total_acc = cb.query_one(S2); + let keccak_bytes = FieldGadget::config(&mut cb, PADDING_LEN); + let keccak_hi_lo = [cb.query_one(S1), cb.query_one(S1)]; + meta.create_gate("PI acc constraints", |meta| { + circuit!([meta, cb], { + for (block_number, block_hash, block_hash_rlc) in + [parent_hash.clone(), block_hash.clone()] + { + require!(block_hash_rlc.expr() => block_hash.rlc_acc(evm_word.expr())); + require!( + ( + BlockContextFieldTag::BlockHash.expr(), + block_number.expr(), + block_hash_rlc.expr() + ) => @PiCellType::Lookup(Table::Block), (TO_FIX) + ); + } + let acc_val = [ + meta_hash.clone(), + parent_hash.1.clone(), + block_hash.1.clone(), + signal_root.clone(), + graffiti.clone(), + prover.clone(), + ] + .iter() + .fold(0.expr(), |acc, gadget| { + let mult = (0..gadget.len).fold(1.expr(), |acc, _| acc * keccak_r.expr()); + acc * mult + gadget.rlc_acc(keccak_r.expr()) + }); + require!(total_acc.expr() => acc_val); + require!( + ( + 1.expr(), + total_acc.expr(), + evidence.total_len().expr(), + keccak_bytes.rlc_acc(evm_word.expr()) + ) + => @PiCellType::Lookup(Table::Keccak), (TO_FIX) + ); + let hi_lo = keccak_bytes.hi_low_field(); + keccak_hi_lo + .iter() + .zip(hi_lo.iter()) + .for_each(|(cell, epxr)| { + require!(cell.expr() => epxr); + cb.enable_equality(cell.column()); + }); + }); + cb.build_constraints(Some(meta.query_selector(q_enable))) }); - + cb.build_lookups( + meta, + &[cm.clone()], + &[ + (PiCellType::Byte, PiCellType::Lookup(Table::Bytecode)), + ( + PiCellType::Lookup(Table::Keccak), + PiCellType::Lookup(Table::Keccak), + ), + ( + PiCellType::Lookup(Table::Block), + PiCellType::Lookup(Table::Block), + ), + ], + Some(q_enable), + ); + let annotation_configs = cm.columns().to_vec(); Self { - rpi_field_bytes, - rpi_field_bytes_acc, - rpi_rlc_acc, - q_field_start, - q_field_step, - q_field_end, - - byte_table, - is_field_rlc, - - pi, // keccak_hi, keccak_lo - - q_keccak, - keccak_table, - - q_block_table, - block_index, + q_enable, + keccak_instance, + meta_hash, + parent_hash, + block_hash, + signal_root, + graffiti, + prover, + total_acc, + keccak_bytes, + keccak_hi_lo, block_table, - - _marker: PhantomData, + keccak_table, + byte_table, + annotation_configs, } } } impl TaikoPiCircuitConfig { - #[allow(clippy::too_many_arguments)] - fn assign_pi_field( - &self, - region: &mut Region<'_, F>, - offset: &mut usize, - _annotation: &'static str, - field_bytes: &[u8], - rpi_rlc_acc: &mut Value, - challenges: &Challenges>, - keccak_hi_lo: bool, - block_number: Option, - ) -> Result>, Error> { - let len = field_bytes.len(); - let mut field_rlc_acc = Value::known(F::ZERO); - let (use_rlc, t) = if len * 8 > F::CAPACITY as usize { - (F::ONE, challenges.evm_word()) - } else { - (F::ZERO, Value::known(F::from(BYTE_POW_BASE))) - }; - - let randomness = if keccak_hi_lo { - challenges.evm_word() - } else { - challenges.keccak_input() - }; - let mut cells = vec![None; field_bytes.len() + 2]; - for (i, byte) in field_bytes.iter().enumerate() { - let row_offset = *offset + i; - - region.assign_fixed( - || "is_field_rlc", - self.is_field_rlc, - row_offset, - || Value::known(use_rlc), - )?; - - // assign field bytes - let field_byte_cell = region.assign_advice( - || "field bytes", - self.rpi_field_bytes, - row_offset, - || Value::known(F::from(*byte as u64)), - )?; - - field_rlc_acc = field_rlc_acc * t + Value::known(F::from(*byte as u64)); - let rpi_cell = region.assign_advice( - || "field bytes acc", - self.rpi_field_bytes_acc, - row_offset, - || field_rlc_acc, - )?; - *rpi_rlc_acc = *rpi_rlc_acc * randomness + Value::known(F::from(*byte as u64)); - let rpi_rlc_acc_cell = region.assign_advice( - || "rpi_rlc_acc", - self.rpi_rlc_acc, - row_offset, - || *rpi_rlc_acc, - )?; - // setup selector - if i == 0 { - self.q_field_start.enable(region, row_offset)?; - } - // the last offset of field - if i == field_bytes.len() - 1 { - self.q_field_end.enable(region, row_offset)?; - cells[RPI_CELL_IDX] = Some(rpi_cell); - cells[RPI_RLC_ACC_CELL_IDX] = Some(rpi_rlc_acc_cell); - if let Some(block_number) = block_number { - self.q_block_table.enable(region, row_offset)?; - region.assign_advice( - || "block_index", - self.block_index, - row_offset, - || Value::known(F::from(block_number.as_u64())), - )?; - } - } else { - self.q_field_step.enable(region, row_offset)?; - } - cells[2 + i] = Some(field_byte_cell); - } - *offset += field_bytes.len(); - Ok(cells.into_iter().map(|cell| cell.unwrap()).collect()) - } - - fn assign( + pub(crate) fn assign( &self, layouter: &mut impl Layouter, - public_data: &PublicData, - challenges: &Challenges>, + challenge: &Challenges>, + evidence: &PublicData, ) -> Result<(), Error> { - let pi = layouter.assign_region( - || "region 0", - |ref mut region| { - let mut rpi_rlc_acc = Value::known(F::ZERO); - let mut offset = 0; - let mut rpi_rlc_acc_cell = None; - for (annotation, block_number, field_bytes) in public_data.assignments() { - let cells = self.assign_pi_field( - region, - &mut offset, - annotation, - &field_bytes, - &mut rpi_rlc_acc, - challenges, - false, - block_number, - )?; - rpi_rlc_acc_cell = Some(cells[RPI_RLC_ACC_CELL_IDX].clone()); - } - - // input_rlc in self.rpi_field_bytes_acc - // input_len in self.rpi_len_acc - // output_rlc in self.rpi_rlc_acc - let keccak_row = offset; - let rpi_rlc_acc_cell = rpi_rlc_acc_cell.unwrap(); - rpi_rlc_acc_cell.copy_advice( - || "keccak(rpi)_input", - region, - self.rpi_field_bytes_acc, - keccak_row, - )?; - let keccak = public_data.get_pi(); - let mut keccak_input = keccak.to_fixed_bytes(); - keccak_input.reverse(); - let keccak_rlc = challenges - .evm_word() - .map(|randomness| rlc(keccak_input, randomness)); - let keccak_output_cell = region.assign_advice( - || "keccak(rpi)_output", - self.rpi_rlc_acc, - keccak_row, - || keccak_rlc, - )?; - self.q_keccak.enable(region, keccak_row)?; - - rpi_rlc_acc = Value::known(F::ZERO); - offset += 1; - let mut pi = Vec::with_capacity(2); - - for (idx, (annotation, field_bytes)) in [ - ( - "high_16_bytes_of_keccak_rpi", - &keccak.to_fixed_bytes()[..16], - ), - ("low_16_bytes_of_keccak_rpi", &keccak.to_fixed_bytes()[16..]), - ] - .into_iter() - .enumerate() - { - let cells = self.assign_pi_field( - region, - &mut offset, - annotation, - field_bytes, - &mut rpi_rlc_acc, - challenges, - true, - None, - )?; - pi.push(cells[RPI_CELL_IDX].clone()); - if idx == 1 { - region.constrain_equal( - keccak_output_cell.cell(), - cells[RPI_RLC_ACC_CELL_IDX].cell(), - )?; - } - } - - Ok(pi) - }, - )?; - for (idx, cell) in pi.into_iter().enumerate() { - layouter.constrain_instance(cell.cell(), self.pi, idx)?; + let evm_word = challenge.evm_word(); + let keccak_r = challenge.keccak_input(); + let hi_lo_cells = layouter.assign_region( + || "Pi", + |mut region| { + self.q_enable.enable(&mut region, 0)?; + let mut region = CachedRegion::new(&mut region); + region.annotate_columns(&self.annotation_configs); + + assign!(region, self.parent_hash.0, 0 => (evidence.block_context.number - 1).as_u64().scalar()); + assign!(region, self.parent_hash.2, 0 => evidence.assignment_acc(PARENT_HASH, evm_word)); + assign!(region, self.block_hash.0, 0 => (evidence.block_context.number).as_u64().scalar()); + assign!(region, self.block_hash.2, 0 => evidence.assignment_acc(BLOCK_HASH, evm_word)); + + let _acc = F::ZERO; + let mut idx = 0; + [ + &self.meta_hash, + &self.parent_hash.1, + &self.block_hash.1, + &self.signal_root, + &self.graffiti, + &self.prover, + ].iter().for_each(|gadget| { + gadget.assign(&mut region, 0, &evidence.assignment(idx)) + .expect(&format!("FieldGadget assignment failed at {:?}", idx)); + idx += 1; + }); + self.keccak_bytes.assign(&mut region, 0, &evidence.keccak_assignment()) + .expect("Keccak bytes assignment failed"); + assign!(region, self.total_acc, 0 => evidence.total_acc(keccak_r))?; + let hi_low_assignment = evidence.keccak_hi_low(); + let hi = assign!(region, self.keccak_hi_lo[0], 0 => hi_low_assignment[0])?; + let lo = assign!(region, self.keccak_hi_lo[1], 0 => hi_low_assignment[1])?; + + Ok([hi, lo]) + })?; + for (i, cell) in hi_lo_cells.iter().enumerate() { + layouter.constrain_instance(cell.cell(), self.keccak_instance, i)?; } Ok(()) } } - /// Public Inputs Circuit -#[derive(Clone, Default, Debug)] +#[derive(Clone, Debug, Default)] pub struct TaikoPiCircuit { /// PublicInputs data known by the verifier - pub public_data: PublicData, - _marker: PhantomData, + pub evidence: PublicData, } impl TaikoPiCircuit { /// Creates a new TaikoPiCircuit - pub fn new(public_data: PublicData) -> Self { - Self { - public_data, - _marker: PhantomData, - } + pub fn new(evidence: PublicData) -> Self { + Self { evidence } } } @@ -535,11 +505,12 @@ impl SubCircuit for TaikoPiCircuit { fn unusable_rows() -> usize { // No column queried at more than 3 distinct rotations, so returns 6 as // minimum unusable rows. - 6 + PublicData::::default().total_len() + 3 } - fn min_num_rows_block(_block: &witness::Block) -> (usize, usize) { - (USED_ROWS, USED_ROWS) + fn min_num_rows_block(block: &witness::Block) -> (usize, usize) { + // TODO(Cecilia): what is the first field? + (0, PublicData::new(block).total_len()) } fn new_from_block(block: &witness::Block) -> Self { @@ -548,25 +519,7 @@ impl SubCircuit for TaikoPiCircuit { /// Compute the public inputs for this circuit. fn instance(&self) -> Vec> { - let keccak_rpi = self.public_data.get_pi(); - let keccak_hi = keccak_rpi - .to_fixed_bytes() - .iter() - .take(16) - .fold(F::ZERO, |acc, byte| { - acc * F::from(BYTE_POW_BASE) + F::from(*byte as u64) - }); - - let keccak_lo = keccak_rpi - .to_fixed_bytes() - .iter() - .skip(16) - .fold(F::ZERO, |acc, byte| { - acc * F::from(BYTE_POW_BASE) + F::from(*byte as u64) - }); - - let public_inputs = vec![keccak_hi, keccak_lo]; - vec![public_inputs] + vec![self.evidence.keccak_hi_low().to_vec()] } /// Make the assignments to the PiCircuit @@ -577,32 +530,29 @@ impl SubCircuit for TaikoPiCircuit { layouter: &mut impl Layouter, ) -> Result<(), Error> { config.byte_table.load(layouter)?; - config.assign(layouter, &self.public_data, challenges) + config.assign(layouter, challenges, &self.evidence) } } -// We define the PiTestCircuit as a wrapper over PiCircuit extended to take the -// generic const parameters MAX_TXS and MAX_CALLDATA. This is necessary because -// the trait Circuit requires an implementation of `configure` that doesn't take -// any circuit parameters, and the PiCircuit defines gates that use rotations -// that depend on MAX_TXS and MAX_CALLDATA, so these two values are required -// during the configuration. -/// Test Circuit for PiCircuit #[cfg(any(feature = "test", test))] -#[derive(Default, Clone)] -pub struct TaikoPiTestCircuit(pub TaikoPiCircuit); - -#[cfg(any(feature = "test", test))] -impl Circuit for TaikoPiTestCircuit { +impl Circuit for TaikoPiCircuit { type Config = (TaikoPiCircuitConfig, Challenges); type FloorPlanner = SimpleFloorPlanner; - type Params = (); + type Params = PublicData; fn without_witnesses(&self) -> Self { Self::default() } + fn params(&self) -> Self::Params { + self.evidence.clone() + } + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + Self::configure_with_params(meta, PublicData::default()) + } + + fn configure_with_params(meta: &mut ConstraintSystem, params: Self::Params) -> Self::Config { let block_table = BlockTable::construct(meta); let keccak_table = KeccakTable::construct(meta); let byte_table = ByteTable::construct(meta); @@ -612,6 +562,7 @@ impl Circuit for TaikoPiTestCircuit { TaikoPiCircuitConfig::new( meta, TaikoPiCircuitConfigArgs { + evidence: params, block_table, keccak_table, byte_table, @@ -628,28 +579,30 @@ impl Circuit for TaikoPiTestCircuit { mut layouter: impl Layouter, ) -> Result<(), Error> { let challenges = challenges.values(&mut layouter); - let public_data = &self.0.public_data; - // assign block table + let evidance = self.params(); let randomness = challenges.evm_word(); + // assign block table config .block_table - .load(&mut layouter, &public_data.block_context, randomness)?; + .load(&mut layouter, &evidance.block_context, randomness)?; // assign keccak table config .keccak_table - .dev_load(&mut layouter, vec![&public_data.rpi_bytes()], &challenges)?; + .dev_load(&mut layouter, vec![&evidance.encode_raw()], &challenges)?; config.byte_table.load(&mut layouter)?; - self.0.synthesize_sub(&config, &challenges, &mut layouter) + self.synthesize_sub(&config, &challenges, &mut layouter) } } #[cfg(test)] mod taiko_pi_circuit_test { + use std::vec; + use super::*; - use eth_types::ToScalar; + use eth_types::H256; use halo2_proofs::{ dev::{MockProver, VerifyFailure}, halo2curves::bn256::Fr, @@ -658,51 +611,57 @@ mod taiko_pi_circuit_test { use pretty_assertions::assert_eq; lazy_static! { - static ref OMMERS_HASH: H256 = H256::from_slice( + static ref LAST_HASH: H256 = H256::from_slice( &hex::decode("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347") .unwrap(), ); + static ref THIS_HASH: H256 = H256::from_slice( + &hex::decode("1dcc4de8dec751111b85b567b6cc12fea12451b9480000000a142fd40d493111") + .unwrap(), + ); + static ref PROVER_ADDR: H160 = + H160::from_slice(&hex::decode("8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199").unwrap(),); } fn run( k: u32, - public_data: PublicData, + evidence: PublicData, pi: Option>>, ) -> Result<(), Vec> { - let circuit = TaikoPiTestCircuit::(TaikoPiCircuit::new(public_data)); - let public_inputs = pi.unwrap_or_else(|| circuit.0.instance()); - - let prover = match MockProver::run(k, &circuit, public_inputs) { + let circuit = TaikoPiCircuit::new(evidence); + let keccak_instance = pi.unwrap_or_else(|| circuit.instance()); + let prover = match MockProver::run(k, &circuit, keccak_instance) { Ok(prover) => prover, Err(e) => panic!("{:#?}", e), }; prover.verify() } - fn mock_public_data() -> PublicData { - let mut public_data = PublicData::default::(); - public_data.meta_hash = OMMERS_HASH.to_word(); - public_data.block_hash = OMMERS_HASH.to_word(); - public_data.block_context.block_hash = OMMERS_HASH.to_word(); - public_data.block_context.history_hashes = vec![Default::default(); 256]; - public_data.block_context.number = 300.into(); - public_data + fn mock_public_data() -> PublicData { + let mut evidence = PublicData::default(); + evidence.set_field(PARENT_HASH, LAST_HASH.to_fixed_bytes().to_vec()); + evidence.set_field(BLOCK_HASH, THIS_HASH.to_fixed_bytes().to_vec()); + evidence.block_context.number = 300.into(); + evidence.block_context.block_hash = THIS_HASH.to_word(); + // has to have at least one history block + evidence.block_context.history_hashes = vec![LAST_HASH.to_word()]; + evidence } #[test] fn test_default_pi() { - let public_data = mock_public_data(); + let evidence = mock_public_data(); let k = 17; - assert_eq!(run::(k, public_data, None), Ok(())); + assert_eq!(run::(k, evidence, None), Ok(())); } #[test] - fn test_fail_pi_hash() { - let public_data = mock_public_data(); + fn test_fail_hi_lo() { + let evidence = mock_public_data(); let k = 17; - match run::(k, public_data, Some(vec![vec![Fr::zero(), Fr::one()]])) { + match run::(k, evidence, Some(vec![vec![Fr::zero(), Fr::one()]])) { Ok(_) => unreachable!("this case must fail"), Err(errs) => { assert_eq!(errs.len(), 4); @@ -717,27 +676,29 @@ mod taiko_pi_circuit_test { } #[test] - fn test_fail_pi_prover() { - let mut public_data = mock_public_data(); - let address_bytes = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, - ]; + fn test_fail_historical_hash() { + let mut block = witness::Block::::default(); - public_data.prover = Address::from_slice(&address_bytes); + block.eth_block.parent_hash = *LAST_HASH; + block.eth_block.hash = Some(*THIS_HASH); + block.protocol_instance.block_hash = *THIS_HASH; + block.protocol_instance.parent_hash = *LAST_HASH; + + // parent hash doesn't exist in table! + block.context.history_hashes = vec![THIS_HASH.to_word(), THIS_HASH.to_word()]; + block.context.block_hash = THIS_HASH.to_word(); + block.context.number = 300.into(); + + let evidence = PublicData::new(&block); - let prover: Fr = public_data.prover.to_scalar().unwrap(); let k = 17; - match run::( - k, - public_data, - Some(vec![vec![prover, Fr::zero(), Fr::one()]]), - ) { + match run::(k, evidence, None) { Ok(_) => unreachable!("this case must fail"), Err(errs) => { - assert_eq!(errs.len(), 4); + assert_eq!(errs.len(), 1); for err in errs { match err { - VerifyFailure::Permutation { .. } => return, + VerifyFailure::Lookup { .. } => return, _ => unreachable!("unexpected error"), } } @@ -747,30 +708,32 @@ mod taiko_pi_circuit_test { #[test] fn test_simple_pi() { - let mut public_data = mock_public_data(); - let chain_id = 1337u64; - public_data.chain_id = Word::from(chain_id); + let mut evidence = mock_public_data(); + let block_number = 1337u64; + evidence.block_context.number = block_number.into(); + evidence.block_context.history_hashes = vec![LAST_HASH.to_word()]; + evidence.set_field(PROVER, PROVER_ADDR.to_fixed_bytes().to_vec()); let k = 17; - assert_eq!(run::(k, public_data, None), Ok(())); + assert_eq!(run::(k, evidence, None), Ok(())); } #[test] fn test_verify() { let mut block = witness::Block::::default(); - block.eth_block.parent_hash = *OMMERS_HASH; - block.eth_block.hash = Some(*OMMERS_HASH); - block.protocol_instance.block_hash = *OMMERS_HASH; - block.protocol_instance.parent_hash = *OMMERS_HASH; - block.context.history_hashes = vec![OMMERS_HASH.to_word()]; - block.context.block_hash = OMMERS_HASH.to_word(); + block.eth_block.parent_hash = *LAST_HASH; + block.eth_block.hash = Some(*THIS_HASH); + block.protocol_instance.block_hash = *THIS_HASH; + block.protocol_instance.parent_hash = *LAST_HASH; + block.context.history_hashes = vec![LAST_HASH.to_word()]; + block.context.block_hash = THIS_HASH.to_word(); block.context.number = 300.into(); - let public_data = PublicData::new(&block); + let evidence = PublicData::new(&block); let k = 17; - assert_eq!(run::(k, public_data, None), Ok(())); + assert_eq!(run::(k, evidence, None), Ok(())); } } diff --git a/zkevm-circuits/src/taiko_super_circuit.rs b/zkevm-circuits/src/taiko_super_circuit.rs index 650700be56..a876a3d9ef 100644 --- a/zkevm-circuits/src/taiko_super_circuit.rs +++ b/zkevm-circuits/src/taiko_super_circuit.rs @@ -7,7 +7,9 @@ pub mod test; use crate::{ anchor_tx_circuit::{AnchorTxCircuit, AnchorTxCircuitConfig, AnchorTxCircuitConfigArgs}, table::{byte_table::ByteTable, BlockTable, KeccakTable, PiTable, TxTable}, - taiko_pi_circuit::{TaikoPiCircuit, TaikoPiCircuitConfig, TaikoPiCircuitConfigArgs}, + taiko_pi_circuit::{ + PublicData, TaikoPiCircuit, TaikoPiCircuitConfig, TaikoPiCircuitConfigArgs, + }, util::{log2_ceil, Challenges, SubCircuit, SubCircuitConfig}, witness::{block_convert, Block, ProtocolInstance}, }; @@ -59,6 +61,7 @@ impl SubCircuitConfig for SuperCircuitConfig { let pi_circuit = TaikoPiCircuitConfig::new( meta, TaikoPiCircuitConfigArgs { + evidence: PublicData::default(), block_table: block_table.clone(), keccak_table: keccak_table.clone(), byte_table: byte_table.clone(), @@ -206,7 +209,7 @@ impl Circuit for SuperCircuit { .load(&mut layouter, &self.block.context, randomness)?; config.keccak_table.dev_load( &mut layouter, - vec![&self.pi_circuit.public_data.rpi_bytes()], + vec![&self.pi_circuit.evidence.encode_raw()], &challenges, )?; config.byte_table.load(&mut layouter)?; diff --git a/zkevm-circuits/src/witness/block.rs b/zkevm-circuits/src/witness/block.rs index 73c4e99c57..40cc4c1d70 100644 --- a/zkevm-circuits/src/witness/block.rs +++ b/zkevm-circuits/src/witness/block.rs @@ -11,6 +11,7 @@ use bus_mapping::{ Error, }; use eth_types::{Address, Field, ToLittleEndian, ToScalar, ToWord, Word}; + use halo2_proofs::circuit::Value; use super::{tx::tx_convert, Bytecode, ExecStep, ProtocolInstance, Rw, RwMap, Transaction}; @@ -186,6 +187,7 @@ impl BlockContext { [ Value::known(F::from(BlockContextFieldTag::BlockHash as u64)), Value::known(self.number.to_scalar().unwrap()), + // Value::known(F::ZERO), randomness .map(|randomness| rlc::value(&self.block_hash.to_le_bytes(), randomness)), ],