diff --git a/Cargo.lock b/Cargo.lock index b00a460ff0..2886af4e21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1458,9 +1458,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.10.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" dependencies = [ "humantime", "is-terminal", @@ -2133,6 +2133,7 @@ version = "0.1.0" dependencies = [ "eth-types", "halo2_proofs", + "itertools 0.11.0", "rand", "rand_xorshift", "sha3 0.10.8", @@ -5904,3 +5905,8 @@ dependencies = [ "cc", "pkg-config", ] + +[[patch.unused]] +name = "maingate" +version = "0.1.0" +source = "git+https://github.com/scroll-tech/halo2wrong?branch=bus-auto#75464fd39a7b276316364deb5995f2c398231b9a" diff --git a/Cargo.toml b/Cargo.toml index 5b704861b3..6b2be49bc2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,6 +71,8 @@ gobuild = { git = "https://github.com/scroll-tech/gobuild.git" } halo2_proofs = { git = "https://github.com/scroll-tech/halo2.git", branch = "v1.0" } [patch."https://github.com/privacy-scaling-explorations/poseidon.git"] poseidon = { git = "https://github.com/scroll-tech/poseidon.git", branch = "main" } +[patch."https://github.com/privacy-scaling-explorations/halo2wrong.git"] +maingate = { git = "https://github.com/scroll-tech/halo2wrong", branch = "bus-auto" } # Definition of benchmarks profile to use. diff --git a/eth-types/src/lib.rs b/eth-types/src/lib.rs index e36841355c..64b10658e4 100644 --- a/eth-types/src/lib.rs +++ b/eth-types/src/lib.rs @@ -64,7 +64,11 @@ use crate::evm_types::Storage; /// Trait used to reduce verbosity with the declaration of the [`Field`] /// trait and its repr. pub trait Field: - PrimeField + hash_circuit::hash::Hashable + std::convert::From + ScalarField + PrimeField + + hash_circuit::hash::Hashable + + std::convert::From + + ScalarField + + std::hash::Hash { /// Re-expose zero element as a function fn zero() -> Self { diff --git a/gadgets/Cargo.toml b/gadgets/Cargo.toml index f773d511f2..1a20159a22 100644 --- a/gadgets/Cargo.toml +++ b/gadgets/Cargo.toml @@ -8,6 +8,7 @@ license.workspace = true halo2_proofs.workspace = true sha3.workspace = true eth-types = { path = "../eth-types" } +itertools.workspace = true strum.workspace = true [dev-dependencies] diff --git a/gadgets/src/bus.rs b/gadgets/src/bus.rs new file mode 100644 index 0000000000..3ff23314f2 --- /dev/null +++ b/gadgets/src/bus.rs @@ -0,0 +1,30 @@ +//! Bus module. + +/// Bus chip that check the integrity of all bus accesses. +pub mod bus_chip; + +/// The bus builder collects all the ports into a bus. +pub mod bus_builder; + +/// A chip to access the bus. +pub mod bus_port; + +/// A chip to access the bus, with batching. +pub mod port_multi; + +/// A chip to expose a lookup table on a bus. +pub mod bus_lookup; + +/// This module encodes messages into terms. +pub mod bus_codec; + +/// This module helps ports with their assignments. +mod batch_assigner; + +/// Utility functions. +mod util; + +#[cfg(test)] +mod tests; + +use eth_types::Field; diff --git a/gadgets/src/bus/batch_assigner.rs b/gadgets/src/bus/batch_assigner.rs new file mode 100644 index 0000000000..f4e873587e --- /dev/null +++ b/gadgets/src/bus/batch_assigner.rs @@ -0,0 +1,128 @@ +use super::{ + bus_builder::BusAssigner, bus_codec::BusMessageF, bus_port::BusOpF, util::HelperBatch, Field, +}; +use halo2_proofs::circuit::{Region, Value}; +use std::{ + collections::{ + hash_map::Entry::{Occupied, Vacant}, + HashMap, + }, + marker::PhantomData, +}; + +/// Assigners are used to delay the assignment until helper values are computed. +pub trait Assigner: Send + Sync { + /// Given the helper value, assign ports and return (offset, term). + #[must_use = "terms must be added to the bus"] + fn assign(&self, region: &mut Region<'_, F>, helper: F) -> (usize, F); +} + +/// BatchAssigner computes and assigns terms into helper cells and the bus. +pub struct BatchAssigner { + assigners: HelperBatch>>, + _marker: PhantomData, +} + +impl> Default for BatchAssigner { + fn default() -> Self { + Self::new() + } +} + +impl> BatchAssigner { + /// Create a new BatchAssigner. + pub fn new() -> Self { + Self { + assigners: HelperBatch::new(), + _marker: PhantomData, + } + } + + /// Execute an assignment later, with the inverse of `denom`. + pub fn assign_later(&mut self, cmd: Box>, denom: Value) { + self.assigners.add_denom(denom, cmd) + } + + /// Assign the helper cells and report the terms to the bus. + pub fn finish(self, region: &mut Region<'_, F>, bus_assigner: &mut BusAssigner) { + self.assigners.invert().map(|commands| { + for (helper, command) in commands { + let (offset, term) = command.assign(region, helper); + bus_assigner.add_term(region.global_offset(offset), Value::known(term)); + } + }); + } + + pub fn len(&self) -> usize { + self.assigners.len() + } +} + +/// OpCounter tracks the messages received, to help generating the corresponding sends. +#[derive(Clone, Debug)] +pub struct BusOpCounter { + counts: HashMap, + _marker: PhantomData<(F, M)>, +} + +impl> Default for BusOpCounter { + fn default() -> Self { + Self::new() + } +} + +impl> BusOpCounter { + /// Create a new BusOpCounter. + pub fn new() -> Self { + Self { + counts: HashMap::new(), + _marker: PhantomData, + } + } + + /// Record an operation that went on the bus. + pub fn track_op(&mut self, op: &BusOpF) { + if op.count() == 0 { + return; + } + match self.counts.entry(op.message()) { + Occupied(mut entry) => { + let count = entry.get_mut(); + *count += op.count(); + if *count == 0 { + entry.remove(); + } + } + Vacant(entry) => { + entry.insert(op.count()); + } + }; + } + + /// Count how many times a message was received (net of sends). + pub fn count_receives(&self, message: &M) -> isize { + (-self.count_ops(message)).max(0) + } + + /// Count how many times a message was sent (net of receives). + pub fn count_sent(&self, message: &M) -> isize { + self.count_ops(message).max(0) + } + + /// Count how many times a message was sent (net positive) or received (net negative). + fn count_ops(&self, message: &M) -> isize { + *self.counts.get(message).unwrap_or(&0) + } + + /// Return true if all messages received have been sent. + pub fn is_complete(&self) -> bool { + self.counts.is_empty() + } + + /// Merge another instance of BusOpCounter into self. The counts are accumulated. + pub fn merge(&mut self, other: Self) { + for (key, other_count) in other.counts { + *self.counts.entry(key).or_insert(0) += other_count; + } + } +} diff --git a/gadgets/src/bus/bus_builder.rs b/gadgets/src/bus/bus_builder.rs new file mode 100644 index 0000000000..4dfce86daf --- /dev/null +++ b/gadgets/src/bus/bus_builder.rs @@ -0,0 +1,191 @@ +use std::mem; + +use super::{ + batch_assigner::{BatchAssigner, BusOpCounter}, + bus_chip::BusTerm, + bus_codec::{BusCodecExpr, BusCodecVal, BusMessageF}, + Field, +}; +use halo2_proofs::circuit::{Region, Value}; + +/// BusBuilder +#[derive(Debug)] +pub struct BusBuilder { + codec: BusCodecExpr, + terms: Vec>, +} + +impl BusBuilder { + /// Create a new bus. + pub fn new(codec: BusCodecExpr) -> Self { + Self { + codec, + terms: vec![], + } + } + + /// Return the codec for messages on this bus. + pub fn codec(&self) -> &BusCodecExpr { + &self.codec + } + + /// Add a term to the bus. + pub fn add_term(&mut self, term: BusTerm) { + self.terms.push(term); + } + + /// Return the collected terms. + pub fn build(self) -> Vec> { + self.terms + } +} + +/// BusAssigner +pub struct BusAssigner { + codec: BusCodecVal, + term_adder: TermAdder, + bus_op_counter: BusOpCounter, + batch_assigner: BatchAssigner, +} + +impl> BusAssigner { + /// Create a new bus assigner with a maximum number of rows. + pub fn new(codec: BusCodecVal, n_rows: usize) -> Self { + Self { + batch_assigner: BatchAssigner::new(), + codec, + term_adder: TermAdder::new(0, n_rows), + bus_op_counter: BusOpCounter::new(), + } + } + + /// Return the first offset supported by this BusAssigner. + pub fn start_offset(&self) -> usize { + self.term_adder.start_offset + } + + /// Return the number of rows supported by this BusAssigner. + pub fn n_rows(&self) -> usize { + self.term_adder.terms.len() + } + + /// Return the codec for messages on this bus. + pub fn codec(&self) -> &BusCodecVal { + &self.codec + } + + /// Return the op counter. + pub fn op_counter(&mut self) -> &mut BusOpCounter { + &mut self.bus_op_counter + } + + /// Return the batch assigner. + pub fn batch_assigner(&mut self) -> &mut BatchAssigner { + &mut self.batch_assigner + } + + /// Finish pending assignments in a region. + pub fn finish_ports(&mut self, region: &mut Region<'_, F>) { + let old_batch_assigner = mem::replace(&mut self.batch_assigner, BatchAssigner::new()); + + old_batch_assigner.finish(region, self); + } + + fn assert_finished(&self) { + assert_eq!(self.batch_assigner.len(), 0, "finish_ports was not called"); + // TODO: better error handling. + } + + /// Add a term value to the bus. + pub fn add_term(&mut self, offset: usize, term: Value) { + self.term_adder.add_term(offset, term); + } + + /// Return the collected terms. + pub fn terms(&self) -> Value<&[F]> { + self.assert_finished(); + assert_eq!( + self.start_offset(), + 0, + "cannot use the terms of a BusAssigner fork" + ); + self.term_adder.terms() + } + + /// Fork this BusAssigner for parallel assignment. + pub fn fork(&self, start_offset: usize, n_rows: usize) -> Self { + Self { + batch_assigner: BatchAssigner::new(), + codec: self.codec.clone(), + term_adder: TermAdder::new(start_offset, n_rows), + bus_op_counter: BusOpCounter::new(), + } + } + + /// Merge a fork of this BusAssigner back into it. + pub fn merge(&mut self, fork: Self) { + fork.assert_finished(); + self.term_adder.merge(fork.term_adder); + self.bus_op_counter.merge(fork.bus_op_counter); + } +} + +struct TermAdder { + start_offset: usize, + terms: Vec, + unknown: bool, +} + +impl TermAdder { + /// Create a term adder with a maximum number of rows. + fn new(start_offset: usize, n_rows: usize) -> Self { + Self { + start_offset, + terms: vec![F::zero(); n_rows], + unknown: false, + } + } + + /// Add a term value to the bus. + fn add_term(&mut self, offset: usize, term: Value) { + let range = self.start_offset..self.start_offset + self.terms.len(); + assert!( + range.contains(&offset), + "offset={offset} out of bounds ({range:?})" + ); + if self.unknown { + return; + } + if term.is_none() { + self.unknown = true; + self.terms.clear(); + } else { + term.map(|t| self.terms[offset - self.start_offset] += t); + } + } + + /// Return the collected terms. + fn terms(&self) -> Value<&[F]> { + if self.unknown { + Value::unknown() + } else { + Value::known(&self.terms) + } + } + + /// Merge another TermAdder::terms() into self. + fn merge(&mut self, other: Self) { + if other.unknown { + self.unknown = true; + self.terms.clear(); + } else { + assert!(other.start_offset >= self.start_offset); + assert!(other.start_offset + other.terms.len() <= self.start_offset + self.terms.len()); + let start_index = other.start_offset - self.start_offset; + + for (index, term) in other.terms.into_iter().enumerate() { + self.terms[start_index + index] += term; + } + } + } +} diff --git a/gadgets/src/bus/bus_chip.rs b/gadgets/src/bus/bus_chip.rs new file mode 100644 index 0000000000..8f5454d4b5 --- /dev/null +++ b/gadgets/src/bus/bus_chip.rs @@ -0,0 +1,135 @@ +use super::{bus_builder::BusAssigner, bus_codec::BusMessageF, Field}; +use crate::util::{assign_global, Expr}; +use halo2_proofs::{ + circuit::{Layouter, Region, Value}, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, ThirdPhase}, + poly::Rotation, +}; + +/// A term of the bus sum check. +#[derive(Clone, Debug)] +pub struct BusTerm(Expression); + +impl BusTerm { + /// Wrap an expression to indicate that it was properly constructed as a bus term. + pub fn verified(term: Expression) -> Self { + Self(term) + } +} + +impl Expr for BusTerm { + fn expr(&self) -> Expression { + self.0.clone() + } +} + +/// BusConfig +#[derive(Clone, Debug)] +pub struct BusConfig { + enabled: Column, + is_first: Column, + acc: Column, +} + +impl BusConfig { + /// Create a new bus. + pub fn new(cs: &mut ConstraintSystem, terms: &[BusTerm]) -> Self { + let enabled = cs.fixed_column(); + let is_first = cs.fixed_column(); + let acc = cs.advice_column_in(ThirdPhase); + + cs.create_gate("bus sum check", |cs| { + let not_last = cs.query_fixed(enabled, Rotation::next()); + let enabled = cs.query_fixed(enabled, Rotation::cur()); + let is_first = cs.query_fixed(is_first, Rotation::cur()); + + let acc_next = cs.query_advice(acc, Rotation::next()); + let acc = cs.query_advice(acc, Rotation::cur()); + + // The sum of terms on the current row. + let sum = terms + .iter() + .fold(0.expr(), |acc, term| acc + term.0.clone()); + let next_or_zero = not_last * acc_next; + let diff_or_zero = enabled * (next_or_zero - acc.clone()); + + [ + // If is_first, then initialize: `acc = 0`. + is_first * acc, + // If not last, the terms go into accumulator: `∑terms + acc = acc_next` + // If last, the final accumulator is zero: `∑terms + acc = 0` + // If not enabled, the terms add up to zero: `∑terms = 0` + sum - diff_or_zero, + ] + }); + + cs.annotate_lookup_any_column(enabled, || "Bus_enabled"); + cs.annotate_lookup_any_column(is_first, || "Bus_is_first"); + cs.annotate_lookup_any_column(acc, || "Bus_acc"); + + Self { + enabled, + is_first, + acc, + } + } + + /// Assign the accumulator values from a BusAssigner. + pub fn finish_assigner>( + &self, + layouter: &mut impl Layouter, + bus_assigner: BusAssigner, + ) -> Result<(), Error> { + assign_global( + layouter, + || "Bus_accumulator", + |mut region| self.assign(&mut region, bus_assigner.n_rows(), bus_assigner.terms()), + ) + } + + /// Assign the accumulator values, from the sum of terms per row. + pub fn assign( + &self, + region: &mut Region<'_, F>, + n_rows: usize, + terms: Value<&[F]>, + ) -> Result<(), Error> { + /*assert_eq!( + region.global_offset(0), + 0, + "The bus requires a global region" + );*/ + + region.assign_fixed( + || "Bus_is_first", + self.is_first, + 0, + || Value::known(F::one()), + )?; + + for offset in 0..n_rows { + region.assign_fixed( + || "Bus_enable", + self.enabled, + offset, + || Value::known(F::one()), + )?; + } + + terms.map(|terms| { + assert!(terms.len() <= n_rows, "Bus terms out-of-bound"); + let mut acc = F::zero(); + + for offset in 0..n_rows { + region + .assign_advice(|| "Bus_acc", self.acc, offset, || Value::known(acc)) + .unwrap(); + + if let Some(term) = terms.get(offset) { + acc += term; + } + } + }); + Ok(()) + } +} diff --git a/gadgets/src/bus/bus_codec.rs b/gadgets/src/bus/bus_codec.rs new file mode 100644 index 0000000000..1c51d8e98a --- /dev/null +++ b/gadgets/src/bus/bus_codec.rs @@ -0,0 +1,136 @@ +use super::Field; +use crate::util::Expr; +use halo2_proofs::{circuit::Value, plonk::Expression}; +use std::{cmp::Eq, hash::Hash, marker::PhantomData}; + +/// The default message type for expressions. +pub type DefaultMsgExpr = Vec>; + +/// The default message type for values. +pub type DefaultMsgVal = Vec>; + +/// The codec for expressions. +pub type BusCodecExpr = BusCodec, M>; + +/// The codec for values. +pub type BusCodecVal = BusCodec, M>; + +/// A message codec that adds a random value to the message. +#[derive(Clone, Debug)] +pub struct BusCodec { + rand: T, + _marker: PhantomData, +} + +impl BusCodec { + /// Create a new message codec. + pub fn new(rand: T) -> Self { + Self { + rand, + _marker: PhantomData, + } + } +} + +impl BusCodec, M> +where + F: Field, + M: BusMessageExpr, +{ + /// Compress a message into a field element, such that: + /// - the map from message to elements is collision-resistant. + /// - the inverses of the elements are linearly independent. + /// - Elements are non-zero. + pub fn compress(&self, msg: M) -> Expression { + msg.into_items() + .fold(1.expr(), |acc, f| self.rand.clone() * acc + f) + } +} + +impl BusCodec, M> +where + F: Field, + M: BusMessage, +{ + /// Compress a message into a field element, such that: + /// - the map from message to elements is collision-resistant. + /// - the inverses of the elements are linearly independent. + /// - Elements are non-zero. + pub fn compress(&self, msg: M) -> Value { + self.rand + .map(|rand| msg.into_items().fold(F::one(), |acc, f| rand * acc + f)) + } +} + +/// A message as expressions to configure circuits. +pub trait BusMessageExpr: BusMessage> {} +impl BusMessageExpr for M where M: BusMessage> {} + +/// A message as values to be assigned. +pub trait BusMessageF: BusMessage + Eq + Hash {} +impl BusMessageF for M where M: BusMessage + Eq + Hash {} + +/// A trait for messages that can be encoded. +pub trait BusMessage: Clone { + /// The item iterator type. + type IntoIter: Iterator; + + /// Convert the message into an iterator. + fn into_items(self) -> Self::IntoIter; +} + +// The default implementation of `BusMessage` for iterators of compatible types. +impl BusMessage for I +where + I: IntoIterator + Clone, + I::Item: Into, +{ + type IntoIter = std::iter::Map T>; + + fn into_items(self) -> Self::IntoIter { + self.into_iter().map(Into::into) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use halo2_proofs::halo2curves::bn256::Fr; + + #[derive(Clone, Debug, Hash)] + struct TestMessage { + a: u64, + b: u64, + } + + impl> BusMessage for TestMessage { + type IntoIter = std::array::IntoIter; + + fn into_items(self) -> Self::IntoIter { + [self.a.into(), self.b.into()].into_iter() + } + } + + #[test] + fn test_codec() { + let rand = Value::known(Fr::from(10u64)); + { + // Using vectors as message type. + let codec = BusCodec::new(rand); + let msg = vec![2u64, 3u64]; + let compressed = codec.compress(msg); + + assert!(!compressed.is_none()); + compressed.map(|c| assert_eq!(c, Fr::from(123))); + } + { + // Using a custom message type. + let codec = BusCodec::new(rand); + let msg = TestMessage { a: 2, b: 3 }; + let compressed = codec.compress(msg); + + assert!(!compressed.is_none()); + compressed.map(|c| assert_eq!(c, Fr::from(123))); + } + } +} diff --git a/gadgets/src/bus/bus_lookup.rs b/gadgets/src/bus/bus_lookup.rs new file mode 100644 index 0000000000..4157242622 --- /dev/null +++ b/gadgets/src/bus/bus_lookup.rs @@ -0,0 +1,82 @@ +//! The BusLookup chip exposes entries from a table as messages on the bus. + +use crate::util::query_expression; + +use super::{ + bus_builder::{BusAssigner, BusBuilder}, + bus_codec::{BusMessageExpr, BusMessageF}, + bus_port::{BusOp, BusOpF, PortChip}, + util::from_isize, + Field, +}; +use halo2_proofs::{ + circuit::{Region, Value}, + plonk::{Advice, Column, ConstraintSystem, Error, Expression}, + poly::Rotation, +}; + +/// BusLookup exposes a table as a lookup through the bus. +#[derive(Clone, Debug)] +pub struct BusLookupChip { + port: PortChip, + count: Column, +} + +impl BusLookupChip { + /// Create and connect a new BusLookup circuit from the expressions of message and count. + pub fn connect>( + meta: &mut ConstraintSystem, + bus_builder: &mut BusBuilder, + enabled: Expression, + message: M, + ) -> Self { + let count = meta.advice_column(); + let count_expr = query_expression(meta, |meta| meta.query_advice(count, Rotation::cur())); + + let port = PortChip::connect( + meta, + bus_builder, + enabled, + BusOp::send_to_lookups(message, count_expr), + ); + + meta.annotate_lookup_any_column(count, || "BusLookup_count"); + + Self { port, count } + } + + /// Assign a lookup operation from message. The count is discovered from the BusAssigner. + pub fn assign>( + &self, + region: &mut Region<'_, F>, + bus_assigner: &mut BusAssigner, + offset: usize, + message: M, + ) -> Result<(), Error> { + let count = bus_assigner.op_counter().count_receives(&message); + self.assign_op( + region, + bus_assigner, + offset, + BusOp::send_to_lookups(message, count), + ) + } + + /// Assign a lookup operation from message and count. + pub fn assign_op>( + &self, + region: &mut Region<'_, F>, + bus_assigner: &mut BusAssigner, + offset: usize, + op: BusOpF, + ) -> Result<(), Error> { + region.assign_advice( + || "BusLookup", + self.count, + offset, + || Value::known(from_isize::(op.count())), + )?; + self.port.assign(bus_assigner, offset, op); + Ok(()) + } +} diff --git a/gadgets/src/bus/bus_port.rs b/gadgets/src/bus/bus_port.rs new file mode 100644 index 0000000000..3d329d0f8d --- /dev/null +++ b/gadgets/src/bus/bus_port.rs @@ -0,0 +1,308 @@ +use super::{ + batch_assigner::Assigner, + bus_builder::{BusAssigner, BusBuilder}, + bus_chip::BusTerm, + bus_codec::{BusCodecExpr, BusCodecVal, BusMessageExpr, BusMessageF}, + util::from_isize, + Field, +}; +use crate::util::query_expression; +use halo2_proofs::{ + circuit::{Region, Value}, + plonk::{Advice, Column, ConstraintSystem, Expression, ThirdPhase}, + poly::Rotation, +}; +use std::{marker::PhantomData, ops::Mul}; + +/// A bus operation, as expressions for circuit config. +pub type BusOpExpr = BusOp>; + +/// A bus operation, as values for circuit assignment. +pub type BusOpF = BusOp; + +/// A bus operation. +#[derive(Clone, Debug)] +pub struct BusOp { + message: M, + count: C, +} + +impl BusOp +where + M: Clone, + C: Count, +{ + /// Receive a message, with the expectation that it carries a true fact. This can be a + /// cross-circuit call answered by a `send`, or a lookup query answered by a `send_to_lookups`. + pub fn receive(message: M) -> Self { + Self { + message, + count: C::neg_one(), + } + } + + /// Send a message, with the responsibility to verify that it states a true fact, and the + /// expectation that it is received exactly once somewhere else. + pub fn send(message: M) -> Self { + Self { + message, + count: C::one(), + } + } + + /// Expose an entry of a lookup table as a bus message, with the responsibility that it is a + /// true fact. It can be received any number of times. This number is the `count` advice. + pub fn send_to_lookups(message: M, count: C) -> Self { + Self { message, count } + } + + /// Apply a boolean condition to this operation. + pub fn conditional(self, condition: C) -> Self { + Self { + message: self.message, + count: self.count * condition, + } + } + + /// The message to send or receive. + pub fn message(&self) -> M { + self.message.clone() + } + + /// The number of copies of the message to send (if positive) or receive (if negative). + pub fn count(&self) -> C { + self.count.clone() + } +} + +/// Trait usable as BusOp count (Expression or isize). +pub trait Count: Clone + Mul { + /// 1 + fn one() -> Self; + /// -1 + fn neg_one() -> Self; +} + +impl Count for Expression { + fn one() -> Self { + Self::Constant(F::one()) + } + fn neg_one() -> Self { + Self::Constant(-F::one()) + } +} + +impl Count for isize { + fn one() -> Self { + 1 + } + fn neg_one() -> Self { + -1 + } +} + +/// A chip to access the bus. It manages its own helper column and gives one access per row. +#[derive(Clone, Debug)] +pub struct PortChip { + helper: Column, + _marker: PhantomData, +} + +impl PortChip { + /// Create a new bus port with a single access. + pub fn connect>( + meta: &mut ConstraintSystem, + bus_builder: &mut BusBuilder, + enabled: Expression, + op: BusOpExpr, + ) -> Self { + let helper = meta.advice_column_in(ThirdPhase); + let helper_expr = query_expression(meta, |meta| meta.query_advice(helper, Rotation::cur())); + + Port::connect(meta, bus_builder, enabled, op, helper_expr); + + meta.annotate_lookup_any_column(helper, || "Port_helper"); + + Self { + helper, + _marker: PhantomData, + } + } + + /// Assign an operation. + pub fn assign>( + &self, + bus_assigner: &mut BusAssigner, + offset: usize, + op: BusOpF, + ) { + Port::assign(bus_assigner, offset, op, self.helper, 0); + } +} + +/// Functions to add an operation to the bus. +#[derive(Clone, Debug)] +pub struct Port; + +impl Port { + /// Create a new bus port with a single operation. + /// The helper cell can be used for something else when not enabled. + pub fn connect>( + meta: &mut ConstraintSystem, + bus_builder: &mut BusBuilder, + enabled: Expression, + op: BusOpExpr, + helper: Expression, + ) { + let term = Self::create_term(meta, bus_builder.codec(), enabled, op, helper); + bus_builder.add_term(term); + } + + /// Assign an operation. + pub fn assign>( + bus_assigner: &mut BusAssigner, + offset: usize, + op: BusOpF, + helper: Column, + rotation: isize, + ) { + if op.count() == 0 { + return; // Leave the helper cell at 0. + } + + let cmd = Box::new(PortAssigner { + offset, + helper, + rotation, + count: op.count(), + }); + let denom = bus_assigner.codec().compress(op.message()); + + bus_assigner.op_counter().track_op(&op); + bus_assigner.batch_assigner().assign_later(cmd, denom); + } + + /// Return the degree of the constraints given these inputs. + pub fn degree>( + codec: &BusCodecExpr, + enabled: Expression, + op: BusOpExpr, + helper: Expression, + ) -> usize { + let term = helper * enabled.clone(); + let [constraint] = Self::constraint(codec, enabled, op, term); + constraint.degree() + } + + /// Return the maximum (message_degree, count_degree), given the degrees of the constraint + /// system and `enabled`. + pub fn max_degrees(max_degree: usize, enabled_degree: usize) -> (usize, usize) { + let helper_degree = 1; + ( + max_degree - enabled_degree - helper_degree, + max_degree - enabled_degree, + ) + } + + fn create_term>( + meta: &mut ConstraintSystem, + codec: &BusCodecExpr, + enabled: Expression, + op: BusOpExpr, + helper: Expression, + ) -> BusTerm { + let term = helper * enabled.clone(); + + meta.create_gate("bus access", |_| { + Self::constraint(codec, enabled, op, term.clone()) + }); + + BusTerm::verified(term) + } + + fn constraint>( + codec: &BusCodecExpr, + enabled: Expression, + op: BusOpExpr, + term: Expression, + ) -> [Expression; 1] { + // Verify that `term = enabled * count / compress(message)`. + // + // With witness: helper = count / compress(message) + // + // If `enabled = 0`, then `term = 0` by definition. In that case, the helper cell is not + // constrained, so it can be used for something else. + [term * codec.compress(op.message()) - op.count() * enabled] + } + + /// Return the witness that must be assigned to the helper cell. + /// Very slow. Prefer `BatchAssigner::assign_later` instead. + pub fn helper_witness>( + codec: &BusCodecVal, + op: BusOpF, + ) -> Value { + codec + .compress(op.message()) + .map(|denom| from_isize::(op.count()) * denom.invert().unwrap_or(F::zero())) + } +} + +struct PortAssigner { + offset: usize, + helper: Column, + rotation: isize, + count: isize, +} + +impl Assigner for PortAssigner { + fn assign(&self, region: &mut Region<'_, F>, inversed_denom: F) -> (usize, F) { + let term = from_isize::(self.count) * inversed_denom; + + region + .assign_advice( + || "BusPort_helper", + self.helper, + (self.offset as isize + self.rotation) as usize, + || Value::known(term), + ) + .unwrap(); + + (self.offset, term) + } +} + +/// A chip to access the bus. It manages its own helper columns and gives multiple accesses per row. +#[derive(Clone, Debug)] +pub struct BusPortMulti { + // TODO: implement with as few helper columns as possible. + ports: Vec>, +} + +impl BusPortMulti { + /// Create and connect a new bus port with multiple accesses. + pub fn connect>( + meta: &mut ConstraintSystem, + bus_builder: &mut BusBuilder, + enabled: Expression, + ops: Vec>, + ) -> Self { + let ports = ops + .into_iter() + .map(|op| PortChip::connect(meta, bus_builder, enabled.clone(), op)) + .collect(); + Self { ports } + } + + /// Assign operations. + pub fn assign>( + &self, + bus_assigner: &mut BusAssigner, + offset: usize, + ops: Vec>, + ) { + assert_eq!(self.ports.len(), ops.len()); + for (port, op) in self.ports.iter().zip(ops) { + port.assign(bus_assigner, offset, op); + } + } +} diff --git a/gadgets/src/bus/port_multi.rs b/gadgets/src/bus/port_multi.rs new file mode 100644 index 0000000000..f6a349cd55 --- /dev/null +++ b/gadgets/src/bus/port_multi.rs @@ -0,0 +1,247 @@ +use super::{ + batch_assigner::Assigner, + bus_builder::{BusAssigner, BusBuilder}, + bus_chip::BusTerm, + bus_codec::{BusCodecExpr, BusCodecVal, BusMessageExpr, BusMessageF}, + bus_port::{BusOpExpr, BusOpF}, + util::from_isize, + Field, +}; +use crate::util::{query_expression, Expr}; +use halo2_proofs::{ + circuit::{Region, Value}, + plonk::{Advice, Column, ConstraintSystem, Expression, ThirdPhase}, + poly::Rotation, +}; +use itertools::Itertools; +use std::{marker::PhantomData, ops::Mul}; + +/// PortBatchedChip does multiple bus operations per row. It manages its own helper column. +#[derive(Clone, Debug)] +pub struct PortBatchedChip { + helper: Column, + _marker: PhantomData, +} + +impl PortBatchedChip { + /// Create a new bus port with multiple operations. + pub fn connect>( + meta: &mut ConstraintSystem, + bus_builder: &mut BusBuilder, + enabled: Expression, + ops: Vec>, + ) -> Self { + let helper = meta.advice_column_in(ThirdPhase); + let helper_expr = query_expression(meta, |meta| meta.query_advice(helper, Rotation::cur())); + + PortBatched::connect(meta, bus_builder, enabled, ops, helper_expr); + + meta.annotate_lookup_any_column(helper, || "PortBatched_helper"); + + Self { + helper, + _marker: PhantomData, + } + } + + /// Assign an operation. + pub fn assign>( + &self, + bus_assigner: &mut BusAssigner, + offset: usize, + ops: Vec>, + ) { + PortBatched::assign(bus_assigner, offset, ops, self.helper, 0); + } +} + +/// Functions to add multiple operations to the bus, using only one helper cell. However, the degree +/// of input expressions is more limited than with the simple Port. +pub struct PortBatched; + +impl PortBatched { + /// Create a new bus port with multiple operations. + /// The helper cell can be used for something else when not enabled. + pub fn connect>( + meta: &mut ConstraintSystem, + bus_builder: &mut BusBuilder, + enabled: Expression, + ops: Vec>, + helper: Expression, + ) { + let term = Self::create_term(meta, bus_builder.codec(), enabled, ops, helper); + bus_builder.add_term(term); + } + + /// Assign an operation. + pub fn assign>( + bus_assigner: &mut BusAssigner, + offset: usize, + ops: Vec>, + helper: Column, + rotation: isize, + ) { + let (numer, denom) = Self::helper_fraction(bus_assigner.codec(), &ops); + + let cmd = Box::new(PortBatchedAssigner { + offset, + helper, + rotation, + numer, + }); + + for op in &ops { + bus_assigner.op_counter().track_op(op); + } + bus_assigner.batch_assigner().assign_later(cmd, denom); + } + + /// Return the degree of the constraints given these inputs. + pub fn degree>( + codec: &BusCodecExpr, + enabled: Expression, + ops: Vec>, + helper: Expression, + ) -> usize { + let total_term = helper * enabled.clone(); + let [constraint] = Self::constraint(codec, enabled, ops, total_term); + constraint.degree() + } + + fn create_term>( + meta: &mut ConstraintSystem, + codec: &BusCodecExpr, + enabled: Expression, + ops: Vec>, + helper: Expression, + ) -> BusTerm { + // term = helper, or 0 when not enabled. + let total_term = helper * enabled.clone(); + + meta.create_gate("bus access (multi)", |_| { + Self::constraint(codec, enabled, ops, total_term.clone()) + }); + + BusTerm::verified(total_term) + } + + fn constraint>( + codec: &BusCodecExpr, + enabled: Expression, + ops: Vec>, + total_term: Expression, + ) -> [Expression; 1] { + // denoms[i] = compress(messages[i]) + let denoms = ops + .iter() + .map(|op| codec.compress(op.message())) + .collect::>(); + + // other_denoms[i] = ∏ denoms[j] for j!=i + // all_denoms = ∏ denoms[j] for all j + let (other_denoms, all_denoms) = Self::product_of_others(denoms, 1.expr()); + + // counts_times_others = ∑ counts[i] * other_denoms[i] + let counts_times_others = ops + .iter() + .zip_eq(other_denoms) + .map(|(op, other)| op.count() * other) + .reduce(|acc, term| acc + term) + .unwrap_or(0.expr()); + + // Verify that: term = enabled * ∑ counts[i] / compress(messages[i]) + // + // With witness: helper = ∑ counts[i] / compress(messages[i]) + // + // If `enabled = 0`, then `term = 0` by definition. In that case, the helper cell is not + // constrained, so it can be used for something else. + [total_term * all_denoms - counts_times_others * enabled] + } + + /// Return the witness that must be assigned to the helper cell, as (numerator, denominator). + fn helper_fraction>( + codec: &BusCodecVal, + ops: &[BusOpF], + ) -> (F, Value) { + // denoms[i] = compress(messages[i]) + let denoms = { + let mut denoms = Vec::with_capacity(ops.len()); + for op in ops { + let denom = codec.compress(op.message()); + if denom.is_none() { + return (F::zero(), Value::unknown()); + } else { + denom.map(|denom| denoms.push(denom)); + } + } + denoms + }; + + // other_denoms[i] = ∏ denoms[j] for j!=i + // all_denoms = ∏ denoms[j] for all j + let (other_denoms, all_denoms) = Self::product_of_others(denoms, F::one()); + + // helper = ∑ counts[i] / compress(messages[i]) + // = (∑ counts[i] * other_denoms[i]) / all_denoms + let numer = ops + .iter() + .zip_eq(other_denoms) + .map(|(op, others)| from_isize::(op.count()) * others) + .reduce(|sum, term| sum + term) + .unwrap_or(F::zero()); + + (numer, Value::known(all_denoms)) + } + + /// Return products such that `others[i] = ∏ values[j] for j!=i`, and the product of all values. + fn product_of_others(values: Vec, one: T) -> (Vec, T) + where + T: Mul + Clone, + { + // all_afters[i] contains the product of all values after values[i] (non-inclusive). + let all_afters = { + let mut all_after = one.clone(); + let mut all_afters = Vec::with_capacity(values.len()); + for value in values.iter().rev() { + all_afters.push(all_after.clone()); + all_after = all_after * value.clone(); + } + all_afters.reverse(); + all_afters + }; + + // all_before at step i contains the product of all values before vals[i] (non-inclusive). + let mut all_before = one; + let mut all_others = Vec::with_capacity(values.len()); + for (value, all_after) in values.into_iter().zip(all_afters) { + all_others.push(all_before.clone() * all_after); + all_before = all_before * value; + } + + (all_others, all_before) + } +} + +struct PortBatchedAssigner { + offset: usize, + helper: Column, + rotation: isize, + numer: F, +} + +impl Assigner for PortBatchedAssigner { + fn assign(&self, region: &mut Region<'_, F>, inversed_denom: F) -> (usize, F) { + let term = self.numer * inversed_denom; + + region + .assign_advice( + || "PortBatched_helper", + self.helper, + (self.offset as isize + self.rotation) as usize, + || Value::known(term), + ) + .unwrap(); + + (self.offset, term) + } +} diff --git a/gadgets/src/bus/tests.rs b/gadgets/src/bus/tests.rs new file mode 100644 index 0000000000..2537c8c6bd --- /dev/null +++ b/gadgets/src/bus/tests.rs @@ -0,0 +1,162 @@ +use crate::util::{query_expression, Expr}; +use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + dev::MockProver, + halo2curves::bn256::Fr, + plonk::{Challenge, Circuit, Column, ConstraintSystem, Error, Fixed, SecondPhase}, + poly::Rotation, +}; +use std::marker::PhantomData; + +use super::{ + bus_builder::*, + bus_chip::*, + bus_codec::{BusCodecExpr, BusCodecVal}, + bus_lookup::BusLookupChip, + bus_port::*, + Field, +}; + +#[test] +fn test_bus() { + test_circuit(); +} + +#[derive(Clone)] +struct TestCircuitConfig { + enabled: Column, + bus_config: BusConfig, + bus_lookup: BusLookupChip, + port2: PortChip, + rand: Challenge, + _marker: PhantomData, +} + +#[derive(Default, Clone)] +struct TestCircuit { + n_rows: usize, + _marker: PhantomData, +} + +impl Circuit for TestCircuit { + type Config = TestCircuitConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(cs: &mut ConstraintSystem) -> Self::Config { + cs.advice_column(); // Bypass illogical validation. + cs.advice_column_in(SecondPhase); + + let enabled = cs.fixed_column(); + let enabled_expr = query_expression(cs, |cs| cs.query_fixed(enabled, Rotation::cur())); + + let rand = cs.challenge_usable_after(SecondPhase); + let rand_expr = query_expression(cs, |cs| cs.query_challenge(rand)); + let mut bus_builder = BusBuilder::new(BusCodecExpr::new(rand_expr)); + + let message = vec![2.expr()]; + + // Circuit 1 sends values dynamically. + let bus_lookup = + BusLookupChip::connect(cs, &mut bus_builder, enabled_expr.clone(), message.clone()); + + // Circuit 2 receives one value per row. + let port2 = PortChip::connect(cs, &mut bus_builder, enabled_expr, BusOp::receive(message)); + + // Global bus connection. + let bus_config = BusConfig::new(cs, &bus_builder.build()); + + TestCircuitConfig { + enabled, + bus_config, + bus_lookup, + port2, + rand, + _marker: PhantomData, + } + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let rand = layouter.get_challenge(config.rand); + + layouter.assign_region( + || "witness", + |mut region| { + for offset in 0..self.n_rows { + region.assign_fixed( + || "Port_enable", + config.enabled, + offset, + || Value::known(F::one()), + )?; + } + + let mut bus_assigner = BusAssigner::new(BusCodecVal::new(rand), self.n_rows); + + // Circuit 2 receives one message per row. + { + // First pass: run circuit steps. + for offset in 0..self.n_rows { + // Do normal circuit assignment logic, and obtain a message. + let message = vec![F::from(2)]; + + // Assign an operation to the port of this circuit, and to the shared bus. + config + .port2 + .assign(&mut bus_assigner, offset, BusOp::receive(message)); + } + } + + // Circuit 1 sends a message on some row. + { + // Do normal circuit assignment logic, and obtain a message. + let message = vec![F::from(2)]; + + // The bus assigner counted `n_rows` messages from Circuit 2. + assert_eq!( + bus_assigner.op_counter().count_receives(&message), + self.n_rows as isize, + ); + + // Provide an entry of the table to the bus. + let offset = 3; // can be anywhere. + config.bus_lookup.assign( + &mut region, + &mut bus_assigner, + offset, + message.clone(), + )?; + + // The bus assigner counted that all lookups were satisfied. + assert_eq!(bus_assigner.op_counter().count_receives(&message), 0,); + } + + // Final pass: assign the bus witnesses. + bus_assigner.finish_ports(&mut region); + + config + .bus_config + .assign(&mut region, self.n_rows, bus_assigner.terms())?; + + Ok(()) + }, + ) + } +} + +fn test_circuit() { + let circuit = TestCircuit:: { + n_rows: 10, + _marker: PhantomData, + }; + let k = 10; + let prover = MockProver::::run(k, &circuit, vec![]).unwrap(); + prover.assert_satisfied_par() +} diff --git a/gadgets/src/bus/util.rs b/gadgets/src/bus/util.rs new file mode 100644 index 0000000000..e3572fb25f --- /dev/null +++ b/gadgets/src/bus/util.rs @@ -0,0 +1,58 @@ +use std::ops::Neg; + +use halo2_proofs::{ + circuit::Value, + halo2curves::group::{ff, ff::BatchInvert}, +}; + +/// Convert an isize to a field element. +pub fn from_isize + Neg>(x: isize) -> F { + if x < 0 { + -F::from((-x) as u64) + } else { + F::from(x as u64) + } +} + +/// TermBatch calculates helper witnesses, in batches for better performance. +pub struct HelperBatch { + denoms: Vec<(F, INFO)>, + unknown: bool, +} + +impl HelperBatch { + /// Create a new term batch. + pub fn new() -> Self { + Self { + denoms: vec![], + unknown: false, + } + } + + /// Add a helper denominator to the batch. Some `info` can be attached for later use. + pub fn add_denom(&mut self, denom: Value, info: INFO) { + if self.unknown { + return; + } + if denom.is_none() { + self.unknown = true; + self.denoms.clear(); + } else { + denom.map(|denom| self.denoms.push((denom, info))); + } + } + + /// Return the inverse of all denominators and their associated info. + pub fn invert(mut self) -> Value> { + if self.unknown { + Value::unknown() + } else { + self.denoms.iter_mut().map(|(d, _)| d).batch_invert(); + Value::known(self.denoms) + } + } + + pub fn len(&self) -> usize { + self.denoms.len() + } +} diff --git a/gadgets/src/lib.rs b/gadgets/src/lib.rs index c997babb15..83a5661e6a 100644 --- a/gadgets/src/lib.rs +++ b/gadgets/src/lib.rs @@ -13,6 +13,7 @@ pub mod batched_is_zero; pub mod binary_number; +pub mod bus; pub mod comparator; pub mod evm_word; pub mod is_equal; diff --git a/gadgets/src/util.rs b/gadgets/src/util.rs index 9799928c39..0d0eaf1cb8 100644 --- a/gadgets/src/util.rs +++ b/gadgets/src/util.rs @@ -3,7 +3,10 @@ use eth_types::{ evm_types::{GasCost, OpcodeId}, Field, U256, }; -use halo2_proofs::plonk::Expression; +use halo2_proofs::{ + circuit::{Layouter, Region}, + plonk::{ConstraintSystem, Error, Expression, VirtualCells}, +}; /// Returns the sum of the passed in cells pub mod sum { @@ -223,6 +226,45 @@ pub fn expr_from_u16>(u16s: &[E]) -> Expression { value } +/// Query an expression from the constraint system. +pub fn query_expression( + meta: &mut ConstraintSystem, + mut f: impl FnMut(&mut VirtualCells) -> T, +) -> T { + let mut expr = None; + meta.create_gate("Query expression", |meta| { + expr = Some(f(meta)); + Some(0.expr()) + }); + expr.unwrap() +} + +/// Assign into the global circuit. This is a wrapper around `Layouter::assign_region`, but the +/// closure is called only once, with a region spanning the entire circuit. +pub fn assign_global( + layouter: &mut impl Layouter, + name: N, + mut assignment: A, +) -> Result +where + F: Field, + AR: Default, + A: FnMut(Region<'_, F>) -> Result, + N: Fn() -> NR, + NR: Into, +{ + let mut closure_count = 0; + let ret = layouter.assign_region(name, |region| { + closure_count += 1; + if closure_count == 1 { + return Ok(AR::default()); + } + assignment(region) + }); + assert_eq!(closure_count, 2, "assign_region behavior changed"); + ret +} + /// Returns 2**by as Field pub fn pow_of_two(by: usize) -> F { F::from(2).pow([by as u64, 0, 0, 0]) diff --git a/zkevm-circuits/src/ecc_circuit.rs b/zkevm-circuits/src/ecc_circuit.rs index 16be9af4f9..a047c5dd24 100644 --- a/zkevm-circuits/src/ecc_circuit.rs +++ b/zkevm-circuits/src/ecc_circuit.rs @@ -310,6 +310,12 @@ impl EccCircuit { |mut region| { // handle EcAdd ops. for (idx, ec_add_assigned) in assigned_ec_ops.ec_adds_assigned.iter().enumerate() { + region.assign_fixed( + || "assign ecc_table enabled", + config.ecc_table.q_enable, + idx, + || Value::known(F::one()), + )?; region.assign_fixed( || "assign ecc_table op_type", config.ecc_table.op_type, @@ -369,6 +375,12 @@ impl EccCircuit { // handle EcMul ops. for (idx, ec_mul_assigned) in assigned_ec_ops.ec_muls_assigned.iter().enumerate() { let idx = idx + self.max_add_ops; + region.assign_fixed( + || "assign ecc_table enabled", + config.ecc_table.q_enable, + idx, + || Value::known(F::one()), + )?; region.assign_fixed( || "assign ecc_table op_type", config.ecc_table.op_type, @@ -426,6 +438,12 @@ impl EccCircuit { assigned_ec_ops.ec_pairings_assigned.iter().enumerate() { let idx = idx + self.max_add_ops + self.max_mul_ops; + region.assign_fixed( + || "assign ecc_table enabled", + config.ecc_table.q_enable, + idx, + || Value::known(F::one()), + )?; region.assign_fixed( || "assign ecc_table op_type", config.ecc_table.op_type, diff --git a/zkevm-circuits/src/evm_bus.rs b/zkevm-circuits/src/evm_bus.rs new file mode 100644 index 0000000000..6dedd97cda --- /dev/null +++ b/zkevm-circuits/src/evm_bus.rs @@ -0,0 +1,443 @@ +use crate::{ + evm_circuit::table::{Lookup, MsgExpr, MsgF, RwValues}, + table::{ + BlockTable, BytecodeTable, CopyTable, DualByteTable, EccTable, ExpTable, FixedTable, + KeccakTable, ModExpTable, PowOfRandTable, RwTable, SHA256Table, SigTable, TxTable, + }, + util::{assign_global, query_expression}, +}; +use eth_types::Field; +use gadgets::bus::{ + bus_builder::{BusAssigner, BusBuilder}, + bus_lookup::BusLookupChip, +}; +use halo2_proofs::{ + circuit::{Layouter, Region, Value}, + plonk::{ConstraintSystem, Error, Expression, VirtualCells}, + poly::Rotation, +}; + +/// EVMBusLookups makes all lookup tables available on the bus. +#[derive(Clone, Debug)] +pub struct EVMBusLookups { + bus_tables: [BusTable; 14], +} + +impl EVMBusLookups { + /// Connect all tables to the bus. + #[allow(clippy::too_many_arguments)] + pub fn configure( + meta: &mut ConstraintSystem, + bus_builder: &mut BusBuilder>, + dual_byte_table: &DualByteTable, + fixed_table: &FixedTable, + rw_table: &RwTable, + tx_table: &TxTable, + bytecode_table: &BytecodeTable, + block_table: &BlockTable, + copy_table: &CopyTable, + keccak_table: &KeccakTable, + sha256_table: &SHA256Table, + exp_table: &ExpTable, + sig_table: &SigTable, + modexp_table: &ModExpTable, + ecc_table: &EccTable, + pow_of_rand_table: &PowOfRandTable, + ) -> Self { + let tables: [&dyn QueryTable; 14] = [ + dual_byte_table, + fixed_table, + rw_table, + tx_table, + bytecode_table, + block_table, + copy_table, + keccak_table, + sha256_table, + exp_table, + sig_table, + modexp_table, + ecc_table, + pow_of_rand_table, + ]; + Self { + bus_tables: tables.map(|table| BusTable::configure(meta, bus_builder, table)), + } + } + + /// Assign the answers to all lookups on the bus. + /// + /// This must be called after all other circuits. The lookup queries and the content of the + /// tables must have been assigned already. + /// + /// This function reads the content of the tables, it detects how many times each entry has been + /// looked up, and it assigns the bus operations from the tables to balance these lookups. + pub fn assign( + &self, + layouter: &mut impl Layouter, + bus_assigner: &mut BusAssigner>, + ) -> Result<(), Error> { + assign_global( + layouter, + || "EVM Bus Tables", + |mut region| { + for table in &self.bus_tables { + table.assign(&mut region, bus_assigner)?; + } + + bus_assigner.finish_ports(&mut region); + Ok(()) + }, + ) + } +} + +#[derive(Clone, Debug)] +struct BusTable { + enabled: Expression, + message: MsgExpr, + chip: BusLookupChip, +} + +impl BusTable { + fn configure( + meta: &mut ConstraintSystem, + bus_builder: &mut BusBuilder>, + table: &dyn QueryTable, + ) -> Self { + let (enabled, message) = + query_expression(meta, |meta| (table.enabled(meta), table.message(meta))); + BusTable { + enabled: enabled.clone(), + message: message.clone(), + chip: BusLookupChip::connect(meta, bus_builder, enabled, message), + } + } + + fn assign( + &self, + region: &mut Region, + bus_assigner: &mut BusAssigner>, + ) -> Result<(), Error> { + for offset in 0..bus_assigner.n_rows() { + let enabled = eval(region, offset, self.enabled.clone()); + if enabled.is_zero_vartime() { + continue; + } + + let message = self + .message + .clone() + .map_values(|expr| eval(region, offset, expr)); + + self.chip + .assign(region, bus_assigner, offset, message.clone())?; + } + Ok(()) + } +} + +fn eval(region: &Region, offset: usize, expr: Expression) -> F { + // TODO: error handling. + let value = expr.evaluate( + &|scalar| Value::known(scalar), + &|_| unimplemented!("selector column"), + &|fixed_query| { + Value::known( + region + .query_fixed( + fixed_query.column(), + (offset as i32 + fixed_query.rotation().0) as usize, + ) + .unwrap(), + ) + }, + &|advice_query| { + Value::known( + region + .query_advice( + advice_query.column(), + (offset as i32 + advice_query.rotation().0) as usize, + ) + .unwrap(), + ) + }, + &|_| unimplemented!("instance column"), + &|_| unimplemented!("challenge"), + &|a| -a, + &|a, b| a + b, + &|a, b| a * b, + &|a, scalar| a * Value::known(scalar), + ); + let mut f = F::zero(); + value.map(|v| f = v); + f +} + +trait QueryTable { + fn enabled(&self, meta: &mut VirtualCells) -> Expression; + fn message(&self, meta: &mut VirtualCells) -> MsgExpr; +} + +impl QueryTable for DualByteTable { + fn enabled(&self, meta: &mut VirtualCells) -> Expression { + meta.query_fixed(self.q_enable, Rotation::cur()) + } + + fn message(&self, meta: &mut VirtualCells) -> MsgExpr { + MsgExpr::bytes(self.bytes.map(|col| meta.query_fixed(col, Rotation::cur()))) + } +} + +impl QueryTable for FixedTable { + fn enabled(&self, meta: &mut VirtualCells) -> Expression { + meta.query_fixed(self.q_enable, Rotation::cur()) + } + + fn message(&self, meta: &mut VirtualCells) -> MsgExpr { + let mut query = |col| meta.query_fixed(col, Rotation::cur()); + + MsgExpr::lookup(Lookup::Fixed { + tag: query(self.tag), + values: self.values.map(query), + }) + } +} + +impl QueryTable for RwTable { + fn enabled(&self, meta: &mut VirtualCells) -> Expression { + meta.query_fixed(self.q_enable, Rotation::cur()) + } + + fn message(&self, meta: &mut VirtualCells) -> MsgExpr { + let mut query = |col| meta.query_advice(col, Rotation::cur()); + + MsgExpr::lookup(Lookup::Rw { + counter: query(self.rw_counter), + is_write: query(self.is_write), + tag: query(self.tag), + values: RwValues { + id: query(self.id), + address: query(self.address), + field_tag: query(self.field_tag), + storage_key: query(self.storage_key), + value: query(self.value), + value_prev: query(self.value_prev), + aux1: query(self.aux1), + aux2: query(self.aux2), + }, + }) + } +} + +impl QueryTable for TxTable { + fn enabled(&self, meta: &mut VirtualCells) -> Expression { + meta.query_fixed(self.q_enable, Rotation::cur()) + } + + fn message(&self, meta: &mut VirtualCells) -> MsgExpr { + MsgExpr::lookup(Lookup::Tx { + id: meta.query_advice(self.tx_id, Rotation::cur()), + field_tag: meta.query_fixed(self.tag, Rotation::cur()), + index: meta.query_advice(self.index, Rotation::cur()), + value: meta.query_advice(self.value, Rotation::cur()), + }) + } +} + +impl QueryTable for BytecodeTable { + fn enabled(&self, meta: &mut VirtualCells) -> Expression { + meta.query_fixed(self.q_enable, Rotation::cur()) + } + + fn message(&self, meta: &mut VirtualCells) -> MsgExpr { + let mut query = |col| meta.query_advice(col, Rotation::cur()); + + MsgExpr::lookup(Lookup::Bytecode { + hash: query(self.code_hash), + tag: query(self.tag), + index: query(self.index), + is_code: query(self.is_code), + value: query(self.value), + push_rlc: query(self.push_rlc), + }) + } +} + +impl QueryTable for BlockTable { + fn enabled(&self, meta: &mut VirtualCells) -> Expression { + meta.query_fixed(self.q_enable, Rotation::cur()) + } + + fn message(&self, meta: &mut VirtualCells) -> MsgExpr { + MsgExpr::lookup(Lookup::Block { + field_tag: meta.query_fixed(self.tag, Rotation::cur()), + number: meta.query_advice(self.index, Rotation::cur()), + value: meta.query_advice(self.value, Rotation::cur()), + }) + } +} + +impl QueryTable for CopyTable { + fn enabled(&self, meta: &mut VirtualCells) -> Expression { + meta.query_fixed(self.q_enable, Rotation::cur()) + } + + fn message(&self, meta: &mut VirtualCells) -> MsgExpr { + MsgExpr::lookup(Lookup::CopyTable { + is_first: meta.query_advice(self.is_first, Rotation::cur()), + src_id: meta.query_advice(self.id, Rotation::cur()), + src_tag: self.tag.value(Rotation::cur())(meta), + dst_id: meta.query_advice(self.id, Rotation::next()), + dst_tag: self.tag.value(Rotation::next())(meta), + src_addr: meta.query_advice(self.addr, Rotation::cur()), + src_addr_end: meta.query_advice(self.src_addr_end, Rotation::cur()), + dst_addr: meta.query_advice(self.addr, Rotation::next()), + length: meta.query_advice(self.real_bytes_left, Rotation::cur()), + rlc_acc: meta.query_advice(self.rlc_acc, Rotation::cur()), + rw_counter: meta.query_advice(self.rw_counter, Rotation::cur()), + rwc_inc: meta.query_advice(self.rwc_inc_left, Rotation::cur()), + }) + } +} + +impl QueryTable for KeccakTable { + fn enabled(&self, meta: &mut VirtualCells) -> Expression { + // This is a boolean because of constraint "boolean is_final". + meta.query_fixed(self.q_enable, Rotation::cur()) + * meta.query_advice(self.is_final, Rotation::cur()) + } + + fn message(&self, meta: &mut VirtualCells) -> MsgExpr { + MsgExpr::lookup(Lookup::KeccakTable { + input_rlc: meta.query_advice(self.input_rlc, Rotation::cur()), + input_len: meta.query_advice(self.input_len, Rotation::cur()), + output_rlc: meta.query_advice(self.output_rlc, Rotation::cur()), + }) + } +} + +impl QueryTable for SHA256Table { + fn enabled(&self, meta: &mut VirtualCells) -> Expression { + // This is a boolean because of constraint "final_is_bool". + meta.query_fixed(self.q_enable, Rotation::cur()) + * meta.query_advice(self.is_final, Rotation::cur()) + } + + fn message(&self, meta: &mut VirtualCells) -> MsgExpr { + MsgExpr::lookup(Lookup::Sha256Table { + input_rlc: meta.query_advice(self.input_rlc, Rotation::cur()), + input_len: meta.query_advice(self.input_len, Rotation::cur()), + output_rlc: meta.query_advice(self.output_rlc, Rotation::cur()), + }) + } +} + +impl QueryTable for ExpTable { + fn enabled(&self, meta: &mut VirtualCells) -> Expression { + // is_step implies q_enable by fixed assignment. + meta.query_fixed(self.is_step, Rotation::cur()) + } + + fn message(&self, meta: &mut VirtualCells) -> MsgExpr { + MsgExpr::lookup(Lookup::ExpTable { + base_limbs: [ + meta.query_advice(self.base_limb, Rotation::cur()), + meta.query_advice(self.base_limb, Rotation::next()), + meta.query_advice(self.base_limb, Rotation(2)), + meta.query_advice(self.base_limb, Rotation(3)), + ], + exponent_lo_hi: [ + meta.query_advice(self.exponent_lo_hi, Rotation::cur()), + meta.query_advice(self.exponent_lo_hi, Rotation::next()), + ], + exponentiation_lo_hi: [ + meta.query_advice(self.exponentiation_lo_hi, Rotation::cur()), + meta.query_advice(self.exponentiation_lo_hi, Rotation::next()), + ], + }) + } +} + +impl QueryTable for SigTable { + fn enabled(&self, meta: &mut VirtualCells) -> Expression { + meta.query_fixed(self.q_enable, Rotation::cur()) + } + + fn message(&self, meta: &mut VirtualCells) -> MsgExpr { + let mut query = |col| meta.query_advice(col, Rotation::cur()); + + MsgExpr::lookup(Lookup::SigTable { + msg_hash_rlc: query(self.msg_hash_rlc), + sig_v: query(self.sig_v), + sig_r_rlc: query(self.sig_r_rlc), + sig_s_rlc: query(self.sig_s_rlc), + recovered_addr: query(self.recovered_addr), + is_valid: query(self.is_valid), + }) + } +} + +impl QueryTable for ModExpTable { + fn enabled(&self, meta: &mut VirtualCells) -> Expression { + meta.query_fixed(self.q_head, Rotation::cur()) + } + + fn message(&self, meta: &mut VirtualCells) -> MsgExpr { + MsgExpr::lookup(Lookup::ModExpTable { + base_limbs: [ + meta.query_advice(self.base, Rotation::cur()), + meta.query_advice(self.base, Rotation::next()), + meta.query_advice(self.base, Rotation(2)), + ], + exp_limbs: [ + meta.query_advice(self.exp, Rotation::cur()), + meta.query_advice(self.exp, Rotation::next()), + meta.query_advice(self.exp, Rotation(2)), + ], + modulus_limbs: [ + meta.query_advice(self.modulus, Rotation::cur()), + meta.query_advice(self.modulus, Rotation::next()), + meta.query_advice(self.modulus, Rotation(2)), + ], + result_limbs: [ + meta.query_advice(self.result, Rotation::cur()), + meta.query_advice(self.result, Rotation::next()), + meta.query_advice(self.result, Rotation(2)), + ], + }) + } +} + +impl QueryTable for EccTable { + fn enabled(&self, meta: &mut VirtualCells) -> Expression { + meta.query_fixed(self.q_enable, Rotation::cur()) + } + + fn message(&self, meta: &mut VirtualCells) -> MsgExpr { + MsgExpr::lookup(Lookup::EccTable { + op_type: meta.query_fixed(self.op_type, Rotation::cur()), + is_valid: meta.query_advice(self.is_valid, Rotation::cur()), + arg1_rlc: meta.query_advice(self.arg1_rlc, Rotation::cur()), + arg2_rlc: meta.query_advice(self.arg2_rlc, Rotation::cur()), + arg3_rlc: meta.query_advice(self.arg3_rlc, Rotation::cur()), + arg4_rlc: meta.query_advice(self.arg4_rlc, Rotation::cur()), + input_rlc: meta.query_advice(self.input_rlc, Rotation::cur()), + output1_rlc: meta.query_advice(self.output1_rlc, Rotation::cur()), + output2_rlc: meta.query_advice(self.output2_rlc, Rotation::cur()), + }) + } +} + +impl QueryTable for PowOfRandTable { + fn enabled(&self, meta: &mut VirtualCells) -> Expression { + meta.query_fixed(self.q_enable, Rotation::cur()) + } + + fn message(&self, meta: &mut VirtualCells) -> MsgExpr { + MsgExpr::lookup(Lookup::PowOfRandTable { + exponent: meta.query_fixed(self.exponent, Rotation::cur()), + pow_of_rand: meta.query_advice(self.pow_of_rand, Rotation::cur()), + }) + } +} diff --git a/zkevm-circuits/src/evm_circuit.rs b/zkevm-circuits/src/evm_circuit.rs index 1442c9a508..423b019a87 100644 --- a/zkevm-circuits/src/evm_circuit.rs +++ b/zkevm-circuits/src/evm_circuit.rs @@ -1,6 +1,11 @@ //! The EVM circuit implementation. #![allow(missing_docs)] +use gadgets::bus::{ + bus_builder::{BusAssigner, BusBuilder}, + bus_chip::BusConfig, + bus_codec::{BusCodecExpr, BusCodecVal}, +}; use halo2_proofs::{ circuit::{Cell, Layouter, SimpleFloorPlanner, Value}, plonk::*, @@ -20,68 +25,40 @@ pub use self::EvmCircuit as TestEvmCircuit; pub use crate::witness; use crate::{ + evm_bus::EVMBusLookups, evm_circuit::param::{MAX_STEP_HEIGHT, STEP_STATE_HEIGHT}, table::{ - BlockTable, BytecodeTable, CopyTable, EccTable, ExpTable, KeccakTable, LookupTable, - ModExpTable, PowOfRandTable, RwTable, SHA256Table, SigTable, TxTable, + BlockTable, BytecodeTable, CopyTable, DualByteTable, EccTable, ExpTable, FixedTable, + KeccakTable, LookupTable, ModExpTable, PowOfRandTable, RwTable, SHA256Table, SigTable, + TxTable, }, - util::{SubCircuit, SubCircuitConfig}, + util::{assign_global, SubCircuit, SubCircuitConfig}, }; use bus_mapping::evm::OpcodeId; use eth_types::Field; use execution::ExecutionConfig; use itertools::Itertools; use strum::IntoEnumIterator; -use table::FixedTableTag; +use table::{FixedTableTag, MsgExpr, MsgF}; use witness::Block; /// EvmCircuitConfig implements verification of execution trace of a block. #[derive(Clone, Debug)] pub struct EvmCircuitConfig { - fixed_table: [Column; 4], - byte_table: [Column; 1], - pub(crate) execution: Box>, - // External tables - tx_table: TxTable, - rw_table: RwTable, - bytecode_table: BytecodeTable, - block_table: BlockTable, - copy_table: CopyTable, - keccak_table: KeccakTable, - sha256_table: SHA256Table, - exp_table: ExpTable, - sig_table: SigTable, - modexp_table: ModExpTable, - ecc_table: EccTable, + dual_byte_table: DualByteTable, + fixed_table: FixedTable, pow_of_rand_table: PowOfRandTable, + pub(crate) execution: Box>, } /// Circuit configuration arguments pub struct EvmCircuitConfigArgs { /// Challenge pub challenges: crate::util::Challenges>, - /// TxTable - pub tx_table: TxTable, - /// RwTable - pub rw_table: RwTable, - /// BytecodeTable - pub bytecode_table: BytecodeTable, - /// BlockTable - pub block_table: BlockTable, - /// CopyTable - pub copy_table: CopyTable, - /// KeccakTable - pub keccak_table: KeccakTable, - /// SHA256Table - pub sha256_table: SHA256Table, - /// ExpTable - pub exp_table: ExpTable, - /// SigTable - pub sig_table: SigTable, - /// ModExpTable - pub modexp_table: ModExpTable, - /// Ecc Table. - pub ecc_table: EccTable, + /// The dual byte table. + pub dual_byte_table: DualByteTable, + /// Fixed Table. + pub fixed_table: FixedTable, // Power of Randomness Table. pub pow_of_rand_table: PowOfRandTable, } @@ -93,122 +70,111 @@ pub struct EvmCircuitExports { pub withdraw_root: (Cell, Value), } +// Implement this marker trait. Its method is never called. impl SubCircuitConfig for EvmCircuitConfig { - type ConfigArgs = EvmCircuitConfigArgs; + type ConfigArgs = Unreachable; + + #[allow(clippy::too_many_arguments)] + fn new(_: &mut ConstraintSystem, _: Self::ConfigArgs) -> Self { + unreachable!() + } +} +/// This type guarantees that SubCircuitConfig::new() is never called. +pub struct Unreachable { + _private: (), +} + +impl EvmCircuitConfig { /// Configure EvmCircuitConfig #[allow(clippy::too_many_arguments)] - fn new( + pub fn new( meta: &mut ConstraintSystem, - Self::ConfigArgs { + bus_builder: &mut BusBuilder>, + EvmCircuitConfigArgs { challenges, - tx_table, - rw_table, - bytecode_table, - block_table, - copy_table, - keccak_table, - sha256_table, - exp_table, - sig_table, - modexp_table, - ecc_table, + dual_byte_table, + fixed_table, pow_of_rand_table, - }: Self::ConfigArgs, + }: EvmCircuitConfigArgs, ) -> Self { - let fixed_table = [(); 4].map(|_| meta.fixed_column()); - let byte_table = [(); 1].map(|_| meta.fixed_column()); - let execution = Box::new(ExecutionConfig::configure( - meta, - challenges, - &fixed_table, - &byte_table, - &tx_table, - &rw_table, - &bytecode_table, - &block_table, - ©_table, - &keccak_table, - &sha256_table, - &exp_table, - &sig_table, - &modexp_table, - &ecc_table, - &pow_of_rand_table, - )); - - meta.annotate_lookup_any_column(byte_table[0], || "byte_range"); - fixed_table.iter().enumerate().for_each(|(idx, &col)| { - meta.annotate_lookup_any_column(col, || format!("fix_table_{idx}")) - }); - tx_table.annotate_columns(meta); - rw_table.annotate_columns(meta); - bytecode_table.annotate_columns(meta); - block_table.annotate_columns(meta); - copy_table.annotate_columns(meta); - keccak_table.annotate_columns(meta); - exp_table.annotate_columns(meta); - sig_table.annotate_columns(meta); - modexp_table.annotate_columns(meta); - ecc_table.annotate_columns(meta); - pow_of_rand_table.annotate_columns(meta); + let execution = Box::new(ExecutionConfig::configure(meta, challenges, bus_builder)); Self { + dual_byte_table, fixed_table, - byte_table, - execution, - tx_table, - rw_table, - bytecode_table, - block_table, - copy_table, - keccak_table, - sha256_table, - exp_table, - sig_table, - modexp_table, - ecc_table, pow_of_rand_table, + execution, } } } impl EvmCircuitConfig { /// Load fixed table - pub fn load_fixed_table( + fn load_fixed_table( &self, layouter: &mut impl Layouter, fixed_table_tags: Vec, ) -> Result<(), Error> { - layouter.assign_region( + assign_global( + layouter, || "fixed table", |mut region| { + let table: [Column; 4] = + >::fixed_columns(&self.fixed_table) + .try_into() + .unwrap(); + for (offset, row) in std::iter::once([F::zero(); 4]) .chain(fixed_table_tags.iter().flat_map(|tag| tag.build())) .enumerate() { - for (column, value) in self.fixed_table.iter().zip_eq(row) { + for (column, value) in table.iter().zip_eq(row) { region.assign_fixed(|| "", *column, offset, || Value::known(value))?; } + region.assign_fixed( + || "fixed table enabled", + self.fixed_table.q_enable, + offset, + || Value::known(F::one()), + )?; } - Ok(()) }, ) } - /// Load byte table - pub fn load_byte_table(&self, layouter: &mut impl Layouter) -> Result<(), Error> { - layouter.assign_region( + /// Load dual byte table + fn load_dual_byte_table(&self, layouter: &mut impl Layouter) -> Result<(), Error> { + assign_global( + layouter, || "byte table", |mut region| { - for offset in 0..256 { - region.assign_fixed( - || "", - self.byte_table[0], - offset, - || Value::known(F::from(offset as u64)), - )?; + for i in 0..256 { + let b0 = F::from(i); + for j in 0..256 { + let offset = (i * 256 + j) as usize; + let b1 = F::from(j); + + region.assign_fixed( + || "", + self.dual_byte_table.q_enable, + offset, + || Value::known(F::one()), + )?; + region.assign_fixed( + || "", + self.dual_byte_table.bytes[0], + offset, + || Value::known(b0), + )?; + region.assign_fixed( + || "", + self.dual_byte_table.bytes[1], + offset, + || Value::known(b1), + )?; + } } Ok(()) @@ -284,6 +250,10 @@ impl EvmCircuit { } } + // It must fit the dual byte table. + // TODO: Find a way to make this smaller in tests. + num_rows = num_rows.max(256 * 256); + // It must have one row for EndBlock and at least one unused one num_rows + 2 } @@ -323,20 +293,37 @@ impl SubCircuit for EvmCircuit { (num_rows_required_for_execution_steps, total_rows) } - /// Make the assignments to the EvmCircuit + // TODO: remove. fn synthesize_sub( &self, - config: &Self::Config, + _config: &Self::Config, + _challenges: &crate::util::Challenges>, + _layouter: &mut impl Layouter, + ) -> Result<(), Error> { + unimplemented!("use synthesize_sub2") + } +} + +impl EvmCircuit { + /// Make the assignments to the EvmCircuit + pub fn synthesize_sub2( + &self, + config: &EvmCircuitConfig, challenges: &crate::util::Challenges>, layouter: &mut impl Layouter, + bus_assigner: &mut BusAssigner>, ) -> Result<(), Error> { let block = self.block.as_ref().unwrap(); + config.load_dual_byte_table(layouter)?; config.load_fixed_table(layouter, self.fixed_table_tags.clone())?; - config.load_byte_table(layouter)?; config.pow_of_rand_table.assign(layouter, challenges)?; - let export = config.execution.assign_block(layouter, block, challenges)?; + + let export = config + .execution + .assign_block(layouter, bus_assigner, block, challenges)?; self.exports.borrow_mut().replace(export); + Ok(()) } } @@ -443,7 +430,23 @@ use crate::util::Challenges; use crate::util::MockChallenges as Challenges; impl Circuit for EvmCircuit { - type Config = (EvmCircuitConfig, Challenges); + type Config = ( + EvmCircuitConfig, + BusConfig, + EVMBusLookups, + Challenges, + RwTable, + TxTable, + BytecodeTable, + BlockTable, + CopyTable, + KeccakTable, + SHA256Table, + ExpTable, + SigTable, + ModExpTable, + EccTable, + ); type FloorPlanner = SimpleFloorPlanner; #[cfg(feature = "circuit-params")] type Params = (); @@ -455,39 +458,85 @@ impl Circuit for EvmCircuit { fn configure(meta: &mut ConstraintSystem) -> Self::Config { let challenges = Challenges::construct(meta); let challenges_expr = challenges.exprs(meta); + let mut bus_builder = BusBuilder::new(BusCodecExpr::new(challenges_expr.lookup_input())); + + let dual_byte_table = DualByteTable::construct(meta); + let fixed_table = FixedTable::construct(meta); + fixed_table.annotate_columns(meta); let rw_table = RwTable::construct(meta); + rw_table.annotate_columns(meta); let tx_table = TxTable::construct(meta); + tx_table.annotate_columns(meta); let bytecode_table = BytecodeTable::construct(meta); + bytecode_table.annotate_columns(meta); let block_table = BlockTable::construct(meta); + block_table.annotate_columns(meta); let q_copy_table = meta.fixed_column(); let copy_table = CopyTable::construct(meta, q_copy_table); + copy_table.annotate_columns(meta); let keccak_table = KeccakTable::construct(meta); + keccak_table.annotate_columns(meta); let sha256_table = SHA256Table::construct(meta); let exp_table = ExpTable::construct(meta); + exp_table.annotate_columns(meta); let sig_table = SigTable::construct(meta); + sig_table.annotate_columns(meta); let modexp_table = ModExpTable::construct(meta); + modexp_table.annotate_columns(meta); let ecc_table = EccTable::construct(meta); + ecc_table.annotate_columns(meta); let pow_of_rand_table = PowOfRandTable::construct(meta, &challenges_expr); + pow_of_rand_table.annotate_columns(meta); + + let config = EvmCircuitConfig::new( + meta, + &mut bus_builder, + EvmCircuitConfigArgs { + challenges: challenges_expr, + // Tables assigned by the EVM circuit. + dual_byte_table: dual_byte_table.clone(), + fixed_table: fixed_table.clone(), + pow_of_rand_table, + }, + ); + + let evm_lookups = EVMBusLookups::configure( + meta, + &mut bus_builder, + &dual_byte_table, + &fixed_table, + &rw_table, + &tx_table, + &bytecode_table, + &block_table, + ©_table, + &keccak_table, + &sha256_table, + &exp_table, + &sig_table, + &modexp_table, + &ecc_table, + &pow_of_rand_table, + ); + let bus = BusConfig::new(meta, &bus_builder.build()); + ( - EvmCircuitConfig::new( - meta, - EvmCircuitConfigArgs { - challenges: challenges_expr, - tx_table, - rw_table, - bytecode_table, - block_table, - copy_table, - keccak_table, - sha256_table, - exp_table, - sig_table, - modexp_table, - ecc_table, - pow_of_rand_table, - }, - ), + config, + bus, + evm_lookups, challenges, + // Tables assigned by external circuits. + rw_table, + tx_table, + bytecode_table, + block_table, + copy_table, + keccak_table, + sha256_table, + exp_table, + sig_table, + modexp_table, + ecc_table, ) } @@ -497,11 +546,31 @@ impl Circuit for EvmCircuit { mut layouter: impl Layouter, ) -> Result<(), Error> { let block = self.block.as_ref().unwrap(); + let num_rows = Self::get_num_rows_required(block); - let (config, challenges) = config; + let ( + config, + bus, + evm_lookups, + challenges, + rw_table, + tx_table, + bytecode_table, + block_table, + copy_table, + keccak_table, + sha256_table, + exp_table, + sig_table, + modexp_table, + ecc_table, + ) = config; let challenges = challenges.values(&layouter); - config.tx_table.load( + let mut bus_assigner = + BusAssigner::new(BusCodecVal::new(challenges.lookup_input()), num_rows); + + tx_table.load( &mut layouter, &block.txs, block.circuits_params.max_txs, @@ -509,26 +578,20 @@ impl Circuit for EvmCircuit { block.chain_id, &challenges, )?; + block.rws.check_rw_counter_sanity(); - config.rw_table.load( + rw_table.load( &mut layouter, &block.rws.table_assignments(), block.circuits_params.max_rws, challenges.evm_word(), )?; - config - .bytecode_table - .dev_load(&mut layouter, block.bytecodes.values(), &challenges)?; - config - .block_table - .dev_load(&mut layouter, &block.context, &block.txs, &challenges)?; - config - .copy_table - .dev_load(&mut layouter, block, &challenges)?; - config - .keccak_table - .dev_load(&mut layouter, &block.sha3_inputs, &challenges)?; - config.sha256_table.dev_load( + + bytecode_table.dev_load(&mut layouter, block.bytecodes.values(), &challenges)?; + block_table.dev_load(&mut layouter, &block.context, &block.txs, &challenges)?; + copy_table.dev_load(&mut layouter, block, &challenges)?; + keccak_table.dev_load(&mut layouter, &block.sha3_inputs, &challenges)?; + sha256_table.dev_load( &mut layouter, block .get_sha256() @@ -536,14 +599,10 @@ impl Circuit for EvmCircuit { .map(|evt| (&evt.input, &evt.digest)), &challenges, )?; - config.exp_table.dev_load(&mut layouter, block)?; - config - .sig_table - .dev_load(&mut layouter, block, &challenges)?; - config - .modexp_table - .dev_load(&mut layouter, &block.get_big_modexp())?; - config.ecc_table.dev_load( + exp_table.dev_load(&mut layouter, block)?; + sig_table.dev_load(&mut layouter, block, &challenges)?; + modexp_table.dev_load(&mut layouter, &block.get_big_modexp())?; + ecc_table.dev_load( &mut layouter, block.circuits_params.max_ec_ops, &block.get_ec_add_ops(), @@ -552,7 +611,17 @@ impl Circuit for EvmCircuit { &challenges, )?; - self.synthesize_sub(&config, &challenges, &mut layouter) + self.synthesize_sub2(&config, &challenges, &mut layouter, &mut bus_assigner)?; + + evm_lookups.assign(&mut layouter, &mut bus_assigner)?; + + if !bus_assigner.op_counter().is_complete() { + log::warn!("Incomplete bus assignment."); + log::debug!("Missing bus ops: {:?}", bus_assigner.op_counter()); + } + bus.finish_assigner(&mut layouter, bus_assigner)?; + + Ok(()) } } @@ -561,8 +630,8 @@ mod evm_circuit_stats { use crate::{ evm_circuit::{ param::{ - LOOKUP_CONFIG, N_BYTE_LOOKUPS, N_COPY_COLUMNS, N_PHASE1_COLUMNS, N_PHASE2_COLUMNS, - N_PHASE2_COPY_COLUMNS, + N_BYTE_LOOKUPS, N_COPY_COLUMNS, N_PHASE1_COLUMNS, N_PHASE2_COLUMNS, + N_PHASE2_COPY_COLUMNS, N_PHASE3_COLUMNS, }, step::ExecutionState, table::FixedTableTag, @@ -745,34 +814,14 @@ mod evm_circuit_stats { N_PHASE1_COLUMNS, storage_2, N_PHASE2_COLUMNS, + storage_3, + N_PHASE3_COLUMNS, storage_perm, N_COPY_COLUMNS, storage_perm_2, N_PHASE2_COPY_COLUMNS, byte_lookup, - N_BYTE_LOOKUPS, - fixed_table, - LOOKUP_CONFIG[0].1, - tx_table, - LOOKUP_CONFIG[1].1, - rw_table, - LOOKUP_CONFIG[2].1, - bytecode_table, - LOOKUP_CONFIG[3].1, - block_table, - LOOKUP_CONFIG[4].1, - copy_table, - LOOKUP_CONFIG[5].1, - keccak_table, - LOOKUP_CONFIG[6].1, - exp_table, - LOOKUP_CONFIG[7].1, - sig_table, - LOOKUP_CONFIG[8].1, - ecc_table, - LOOKUP_CONFIG[9].1, - pow_of_rand_table, - LOOKUP_CONFIG[10].1 + N_BYTE_LOOKUPS ); } diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index f2f1a2d212..9a0d2d7e6b 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -1,18 +1,16 @@ use super::{ - param::{ - BLOCK_TABLE_LOOKUPS, BYTECODE_TABLE_LOOKUPS, COPY_TABLE_LOOKUPS, ECC_TABLE_LOOKUPS, - EXP_TABLE_LOOKUPS, FIXED_TABLE_LOOKUPS, KECCAK_TABLE_LOOKUPS, MODEXP_TABLE_LOOKUPS, - N_BYTE_LOOKUPS, N_COPY_COLUMNS, N_PHASE1_COLUMNS, POW_OF_RAND_TABLE_LOOKUPS, - RW_TABLE_LOOKUPS, SHA256_TABLE_LOOKUPS, SIG_TABLE_LOOKUPS, TX_TABLE_LOOKUPS, + param::{N_BYTE_LOOKUPS, N_COPY_COLUMNS, N_PHASE1_COLUMNS}, + util::{ + instrumentation::Instrument, CachedRegion, CellManager, Inverter, StepBusOp, + StoredExpression, }, - util::{instrumentation::Instrument, CachedRegion, CellManager, Inverter, StoredExpression}, EvmCircuitExports, }; use crate::{ evm_circuit::{ - param::{EVM_LOOKUP_COLS, MAX_STEP_HEIGHT, N_PHASE2_COLUMNS, STEP_WIDTH}, + param::{MAX_STEP_HEIGHT, N_PHASE2_COLUMNS, N_PHASE3_COLUMNS, STEP_WIDTH}, step::{ExecutionState, Step}, - table::Table, + table::{MsgExpr, MsgF}, util::{ constraint_builder::{ BaseConstraintBuilder, ConstrainBuilderCommon, EVMConstraintBuilder, @@ -21,12 +19,18 @@ use crate::{ }, witness::{Block, Call, ExecStep, Transaction}, }, - table::{LookupTable, RwTableTag, TxReceiptFieldTag}, + table::{RwTableTag, TxReceiptFieldTag}, util::{query_expression, Challenges, Expr}, }; use bus_mapping::util::read_env_var; use eth_types::{Field, ToLittleEndian}; -use gadgets::util::not; +use gadgets::{ + bus::{ + bus_builder::{BusAssigner, BusBuilder}, + bus_port::{BusOp, BusPortMulti}, + }, + util::not, +}; use halo2_proofs::{ circuit::{Layouter, Region, Value}, plonk::{ @@ -262,8 +266,10 @@ pub(crate) struct ExecutionConfig { q_step_last: Selector, advices: [Column; STEP_WIDTH], step: Step, + bytes_port: BusPortMulti, pub(crate) height_map: HashMap, stored_expressions_map: HashMap>>, + bus_ops_map: HashMap>>, instrument: Instrument, // internal state gadgets begin_tx_gadget: Box>, @@ -369,25 +375,11 @@ pub(crate) struct ExecutionConfig { } impl ExecutionConfig { - #[allow(clippy::too_many_arguments)] #[allow(clippy::redundant_closure_call)] pub(crate) fn configure( meta: &mut ConstraintSystem, challenges: Challenges>, - fixed_table: &dyn LookupTable, - byte_table: &dyn LookupTable, - tx_table: &dyn LookupTable, - rw_table: &dyn LookupTable, - bytecode_table: &dyn LookupTable, - block_table: &dyn LookupTable, - copy_table: &dyn LookupTable, - keccak_table: &dyn LookupTable, - sha256_table: &dyn LookupTable, - exp_table: &dyn LookupTable, - sig_table: &dyn LookupTable, - modexp_table: &dyn LookupTable, - ecc_table: &dyn LookupTable, - pow_of_rand_table: &dyn LookupTable, + bus_builder: &mut BusBuilder>, ) -> Self { let mut instrument = Instrument::default(); let q_usable = meta.fixed_column(); @@ -403,9 +395,9 @@ impl ExecutionConfig { .iter() .enumerate() .map(|(n, _)| { - if n < EVM_LOOKUP_COLS { + if n < N_PHASE3_COLUMNS { meta.advice_column_in(ThirdPhase) - } else if n < EVM_LOOKUP_COLS + N_PHASE2_COLUMNS { + } else if n < N_PHASE3_COLUMNS + N_PHASE2_COLUMNS { meta.advice_column_in(SecondPhase) } else { meta.advice_column_in(FirstPhase) @@ -508,6 +500,7 @@ impl ExecutionConfig { }); let mut stored_expressions_map = HashMap::new(); + let mut bus_ops_map: HashMap>> = HashMap::new(); macro_rules! configure_gadget { () => { @@ -519,6 +512,7 @@ impl ExecutionConfig { (|| { Box::new(Self::configure_gadget( meta, + bus_builder, advices, q_usable, q_step, @@ -529,6 +523,7 @@ impl ExecutionConfig { &step_curr, &mut height_map, &mut stored_expressions_map, + &mut bus_ops_map, &mut instrument, )) })() @@ -537,7 +532,9 @@ impl ExecutionConfig { let cell_manager = step_curr.cell_manager.clone(); - let config = Self { + let bytes_port = Self::configure_bytes_port(meta, bus_builder, q_usable, &cell_manager); + + Self { q_usable, q_step, constants, @@ -545,6 +542,7 @@ impl ExecutionConfig { num_rows_inv, q_step_first, q_step_last, + bytes_port, advices, // internal states begin_tx_gadget: configure_gadget!(), @@ -650,29 +648,9 @@ impl ExecutionConfig { step: step_curr, height_map, stored_expressions_map, + bus_ops_map, instrument, - }; - - Self::configure_lookup( - meta, - fixed_table, - byte_table, - tx_table, - rw_table, - bytecode_table, - block_table, - copy_table, - keccak_table, - sha256_table, - exp_table, - sig_table, - modexp_table, - ecc_table, - pow_of_rand_table, - &challenges, - &cell_manager, - ); - config + } } pub(crate) fn instrument(&self) -> &Instrument { @@ -682,6 +660,7 @@ impl ExecutionConfig { #[allow(clippy::too_many_arguments)] fn configure_gadget>( meta: &mut ConstraintSystem, + bus_builder: &mut BusBuilder>, advices: [Column; STEP_WIDTH], q_usable: Column, q_step: Column, @@ -692,6 +671,7 @@ impl ExecutionConfig { step_curr: &Step, height_map: &mut HashMap, stored_expressions_map: &mut HashMap>>, + bus_ops_map: &mut HashMap>>, instrument: &mut Instrument, ) -> G { // Configure the gadget with the max height first so we can find out the actual @@ -705,7 +685,7 @@ impl ExecutionConfig { G::EXECUTION_STATE, ); cb.annotation(G::NAME, |cb| G::configure(cb)); - let (_, _, _, height) = cb.build(); + let (_, _, _, _, height) = cb.build(); height }; @@ -722,6 +702,7 @@ impl ExecutionConfig { Self::configure_gadget_impl( meta, + bus_builder, q_usable, q_step, num_rows_until_next_step, @@ -731,6 +712,7 @@ impl ExecutionConfig { step_next, height_map, stored_expressions_map, + bus_ops_map, instrument, G::NAME, G::EXECUTION_STATE, @@ -744,6 +726,7 @@ impl ExecutionConfig { #[allow(clippy::too_many_arguments)] fn configure_gadget_impl( meta: &mut ConstraintSystem, + bus_builder: &mut BusBuilder>, q_usable: Column, q_step: Column, num_rows_until_next_step: Column, @@ -753,6 +736,7 @@ impl ExecutionConfig { step_next: &Step, height_map: &mut HashMap, stored_expressions_map: &mut HashMap>>, + bus_ops_map: &mut HashMap>>, instrument: &mut Instrument, name: &'static str, execution_state: ExecutionState, @@ -771,7 +755,7 @@ impl ExecutionConfig { instrument.on_gadget_built(execution_state, &cb); - let (state_selector, constraints, stored_expressions, _) = cb.build(); + let (state_selector, constraints, stored_expressions, bus_ops, _) = cb.build(); debug_assert!( !height_map.contains_key(&execution_state), "execution state already configured" @@ -783,6 +767,18 @@ impl ExecutionConfig { "execution state already configured" ); stored_expressions_map.insert(execution_state, stored_expressions); + bus_ops_map.insert(execution_state, bus_ops.clone()); + + { + let step_enabled = query_expression(meta, |meta| { + let q_usable = meta.query_fixed(q_usable, Rotation::cur()); + let q_step = meta.query_advice(q_step, Rotation::cur()); + q_usable * q_step * state_selector.clone() + }); + for op in bus_ops { + op.connect(meta, bus_builder, step_enabled.clone()); + } + } // Enforce the logic for this opcode let sel_step: &dyn Fn(&mut VirtualCells) -> Expression = @@ -919,61 +915,36 @@ impl ExecutionConfig { }); } - #[allow(clippy::too_many_arguments)] - fn configure_lookup( + fn configure_bytes_port( meta: &mut ConstraintSystem, - fixed_table: &dyn LookupTable, - byte_table: &dyn LookupTable, - tx_table: &dyn LookupTable, - rw_table: &dyn LookupTable, - bytecode_table: &dyn LookupTable, - block_table: &dyn LookupTable, - copy_table: &dyn LookupTable, - keccak_table: &dyn LookupTable, - sha256_table: &dyn LookupTable, - exp_table: &dyn LookupTable, - sig_table: &dyn LookupTable, - modexp_table: &dyn LookupTable, - ecc_table: &dyn LookupTable, - pow_of_rand_table: &dyn LookupTable, - challenges: &Challenges>, + bus_builder: &mut BusBuilder>, + q_usable: Column, cell_manager: &CellManager, - ) { - for column in cell_manager.columns().iter() { - if let CellType::Lookup(table) = column.cell_type { - let name = format!("{table:?}"); - meta.lookup_any(Box::leak(name.into_boxed_str()), |meta| { - let table_expressions = match table { - Table::Fixed => fixed_table, - Table::Tx => tx_table, - Table::Rw => rw_table, - Table::Bytecode => bytecode_table, - Table::Block => block_table, - Table::Copy => copy_table, - Table::Keccak => keccak_table, - Table::Sha256 => sha256_table, - Table::Exp => exp_table, - Table::Sig => sig_table, - Table::ModExp => modexp_table, - Table::Ecc => ecc_table, - Table::PowOfRand => pow_of_rand_table, - } - .table_exprs(meta); - vec![( - column.expr(), - rlc::expr(&table_expressions, challenges.lookup_input()), - )] - }); - } - } - for column in cell_manager.columns().iter() { - if let CellType::LookupByte = column.cell_type { - meta.lookup_any("Byte lookup", |meta| { - let byte_table_expression = byte_table.table_exprs(meta)[0].clone(); - vec![(column.expr(), byte_table_expression)] - }); + ) -> BusPortMulti { + let q_usable = query_expression(meta, |meta| meta.query_fixed(q_usable, Rotation::cur())); + + let byte_columns = { + let mut cols = cell_manager + .columns() + .iter() + .filter(|column| column.cell_type == CellType::LookupByte) + .map(|column| column.expr()) + .collect::>(); + if cols.len() % 2 != 0 { + cols.push(0.expr()); } - } + cols + }; + + let ops = byte_columns + .chunks(2) + .map(|columns| { + let message = MsgExpr::bytes([columns[0].clone(), columns[1].clone()]); + BusOp::receive(message) + }) + .collect::>(); + + BusPortMulti::connect(meta, bus_builder, q_usable, ops) } pub fn get_num_rows_required_no_padding(&self, block: &Block) -> usize { @@ -1048,6 +1019,7 @@ impl ExecutionConfig { pub fn assign_block( &self, layouter: &mut impl Layouter, + bus_assigner: &mut BusAssigner>, block: &Block, challenges: &Challenges>, ) -> Result>, Error> { @@ -1088,8 +1060,7 @@ impl ExecutionConfig { self.q_step, height - 1, || Value::known(F::zero()), - )?; - Ok(height) + ) }; let dummy_tx = Transaction::default(); @@ -1189,13 +1160,15 @@ impl ExecutionConfig { }; // Step1: assign real steps - let (region1_chunk_size, region1_chunk_num) = - chunking_fn("region1", step_assignments.len(), 50); - let mut region1_is_first_time: Vec<(usize, bool)> = (0..region1_chunk_num) - .map(|chunk_idx| (chunk_idx, true)) - .collect(); - let region1_height_sum = layouter - .assign_regions( + { + // Prepare the thread states. + let (region1_chunk_size, region1_chunk_num) = + chunking_fn("region1", step_assignments.len(), 50); + let mut region1_is_first_time: Vec<(usize, bool)> = (0..region1_chunk_num) + .map(|chunk_idx| (chunk_idx, true)) + .collect(); + // Assign in parallel. + let bus_assigner_forks = layouter.assign_regions( || "Execution step region1", region1_is_first_time .iter_mut() @@ -1213,10 +1186,14 @@ impl ExecutionConfig { .sum::(); if *is_first_time { *is_first_time = false; - return assign_shape_fn(&mut region, total_height); + assign_shape_fn(&mut region, total_height)?; + return Ok(None); } let mut offset = 0; + let mut bus_assigner_fork = + bus_assigner.fork(region.global_offset(0), total_height); + // Annotate the EVMCircuit columns within it's single region. self.annotate_circuit(&mut region); @@ -1246,6 +1223,7 @@ impl ExecutionConfig { self.assign_exec_step( &mut region, + &mut bus_assigner_fork, offset, block, transaction, @@ -1260,57 +1238,90 @@ impl ExecutionConfig { offset += height; } + + bus_assigner_fork.finish_ports(&mut region); debug_assert_eq!(offset, total_height); - Ok(total_height) + Ok(Some(bus_assigner_fork)) } }) .collect_vec(), - )? - .into_iter() - .sum::(); + )?; + // Merge the thread results. + let mut actual_offset = 0; + for bus_assigner_fork in bus_assigner_forks { + let bus_assigner_fork = bus_assigner_fork.unwrap(); + + // Validate the offsets found by assign_regions. + assert_eq!(actual_offset, bus_assigner_fork.start_offset()); + actual_offset += bus_assigner_fork.n_rows(); - debug_assert_eq!(region1_height, region1_height_sum); + bus_assigner.merge(bus_assigner_fork); + } + assert_eq!(region1_height, actual_offset); + } // part2: assign non-last EndBlock steps when padding needed + { + // Prepare the thread states. + let (region2_chunk_size, region2_chunk_num) = + chunking_fn("region2", region2_height, 300); + let idxs: Vec = (0..region2_height).collect(); + let mut region2_is_first_time = vec![true; region2_chunk_num]; - let (region2_chunk_size, region2_chunk_num) = chunking_fn("region2", region2_height, 300); - let idxs: Vec = (0..region2_height).collect(); - let mut region2_is_first_time = vec![true; region2_chunk_num]; + log::trace!( + "assign non-last EndBlock in range [{},{})", + region1_height, + region1_height + region2_height + ); + let bus_assigner_forks = layouter.assign_regions( + || "Execution step region2", + idxs.chunks(region2_chunk_size) + .zip_eq(region2_is_first_time.iter_mut()) + .map(|(rows, is_first_time)| { + |mut region: Region<'_, F>| { + if *is_first_time { + *is_first_time = false; + assign_shape_fn(&mut region, rows.len())?; + return Ok(None); + } - log::trace!( - "assign non-last EndBlock in range [{},{})", - region1_height, - region1_height + region2_height - ); - layouter.assign_regions( - || "Execution step region2", - idxs.chunks(region2_chunk_size) - .zip_eq(region2_is_first_time.iter_mut()) - .map(|(rows, is_first_time)| { - |mut region: Region<'_, F>| { - if *is_first_time { - *is_first_time = false; - return assign_shape_fn(&mut region, rows.len()); - } - self.assign_same_exec_step_in_range( - &mut region, - 0, - rows.len(), - block, - &dummy_tx, - &last_call, - end_block_not_last, - 1, - challenges, - )?; - for row_idx in 0..rows.len() { - self.assign_q_step(&mut region, &inverter, row_idx, 1)?; + let mut bus_assigner_fork = + bus_assigner.fork(region.global_offset(0), rows.len()); + + self.assign_same_exec_step_in_range( + &mut region, + &mut bus_assigner_fork, + 0, + rows.len(), + block, + &dummy_tx, + &last_call, + end_block_not_last, + 1, + challenges, + )?; + for row_idx in 0..rows.len() { + self.assign_q_step(&mut region, &inverter, row_idx, 1)?; + } + bus_assigner_fork.finish_ports(&mut region); + Ok(Some(bus_assigner_fork)) } - Ok(rows.len()) - } - }) - .collect_vec(), - )?; + }) + .collect_vec(), + )?; + // Merge the thread results. + let mut actual_offset = region1_height; + for bus_assigner_fork in bus_assigner_forks { + let bus_assigner_fork = bus_assigner_fork.unwrap(); + + // Validate the offsets found by assign_regions. + assert_eq!(actual_offset, bus_assigner_fork.start_offset()); + actual_offset += bus_assigner_fork.n_rows(); + + bus_assigner.merge(bus_assigner_fork); + } + assert_eq!(region1_height + region2_height, actual_offset); + } // part3: assign the last EndBlock at offset `evm_rows - 1` // This region don't need to be parallelized @@ -1325,10 +1336,12 @@ impl ExecutionConfig { |mut region| { if region3_is_first_time { region3_is_first_time = false; - return assign_shape_fn(&mut region, region3_height); + assign_shape_fn(&mut region, region3_height)?; + return Ok(()); } self.assign_exec_step( &mut region, + bus_assigner, offset, block, &dummy_tx, @@ -1353,7 +1366,11 @@ impl ExecutionConfig { 1, || Value::known(F::zero()), )?; - Ok(2) // region height + + bus_assigner.finish_ports(&mut region); + + assert_eq!(region.global_offset(0), region1_height + region2_height); + Ok(()) }, )?; @@ -1377,20 +1394,8 @@ impl ExecutionConfig { fn annotate_circuit(&self, region: &mut Region) { let groups = [ - ("EVM_lookup_fixed", FIXED_TABLE_LOOKUPS), - ("EVM_lookup_tx", TX_TABLE_LOOKUPS), - ("EVM_lookup_rw", RW_TABLE_LOOKUPS), - ("EVM_lookup_bytecode", BYTECODE_TABLE_LOOKUPS), - ("EVM_lookup_block", BLOCK_TABLE_LOOKUPS), - ("EVM_lookup_copy", COPY_TABLE_LOOKUPS), - ("EVM_lookup_keccak", KECCAK_TABLE_LOOKUPS), - ("EVM_lookup_sha256", SHA256_TABLE_LOOKUPS), - ("EVM_lookup_exp", EXP_TABLE_LOOKUPS), - ("EVM_lookup_sig", SIG_TABLE_LOOKUPS), - ("EVM_lookup_modexp", MODEXP_TABLE_LOOKUPS), - ("EVM_lookup_ecc", ECC_TABLE_LOOKUPS), - ("EVM_lookup_pow_of_rand", POW_OF_RAND_TABLE_LOOKUPS), ("EVM_adv_phase2", N_PHASE2_COLUMNS), + ("EVM_adv_phase3", N_PHASE3_COLUMNS), ("EVM_copy", N_COPY_COLUMNS), ("EVM_lookup_byte", N_BYTE_LOOKUPS), ("EVM_adv_phase1", N_PHASE1_COLUMNS), @@ -1413,10 +1418,61 @@ impl ExecutionConfig { region.name_column(|| "Copy_Constr_const", self.constants); } + fn assign_byte_lookups( + &self, + region: &mut CachedRegion<'_, '_, F>, + bus_assigner: &mut BusAssigner>, + offset_begin: usize, + offset_end: usize, + ) { + let byte_columns = self + .step + .cell_manager + .columns() + .iter() + .filter(|column| column.cell_type == CellType::LookupByte) + .map(|column| self.advices[column.index].index()) + .collect::>(); + + for offset in offset_begin..offset_end { + let ops = byte_columns + .chunks(2) + .map(|columns| { + let byte_0 = region.get_advice(offset, columns[0], Rotation::cur()); + let byte_1 = if columns.len() == 2 { + region.get_advice(offset, columns[1], Rotation::cur()) + } else { + F::zero() + }; + BusOp::receive(MsgF::bytes([byte_0, byte_1])) + }) + .collect::>(); + + self.bytes_port.assign(bus_assigner, offset, ops); + } + } + + fn assign_bus_lookups( + &self, + region: &mut CachedRegion<'_, '_, F>, + bus_assigner: &mut BusAssigner>, + offset: usize, + step: &ExecStep, + ) { + let bus_ops = self + .bus_ops_map + .get(&step.execution_state) + .unwrap_or_else(|| panic!("Execution state unknown: {:?}", step.execution_state)); + for bus_op in bus_ops { + bus_op.assign(region, bus_assigner, offset); + } + } + #[allow(clippy::too_many_arguments)] fn assign_same_exec_step_in_range( &self, region: &mut Region<'_, F>, + bus_assigner: &mut BusAssigner>, offset_begin: usize, offset_end: usize, block: &Block, @@ -1450,6 +1506,13 @@ impl ExecutionConfig { offset_end, )?; + // TODO: accelerate repeated bus assignments. + self.assign_byte_lookups(region, bus_assigner, offset_begin, offset_end); + + for offset in offset_begin..offset_end { + self.assign_bus_lookups(region, bus_assigner, offset, step); + } + Ok(()) } @@ -1457,6 +1520,7 @@ impl ExecutionConfig { fn assign_exec_step( &self, region: &mut Region<'_, F>, + bus_assigner: &mut BusAssigner>, offset: usize, block: &Block, transaction: &Transaction, @@ -1494,7 +1558,13 @@ impl ExecutionConfig { )?; } - self.assign_exec_step_int(region, offset, block, transaction, call, step, true) + self.assign_exec_step_int(region, offset, block, transaction, call, step, true)?; + + self.assign_byte_lookups(region, bus_assigner, offset, offset + height); + + self.assign_bus_lookups(region, bus_assigner, offset, step); + + Ok(()) } #[allow(clippy::too_many_arguments)] diff --git a/zkevm-circuits/src/evm_circuit/param.rs b/zkevm-circuits/src/evm_circuit/param.rs index 4acd41c538..aa531f4463 100644 --- a/zkevm-circuits/src/evm_circuit/param.rs +++ b/zkevm-circuits/src/evm_circuit/param.rs @@ -1,4 +1,3 @@ -use super::table::Table; use crate::evm_circuit::{step::ExecutionState, EvmCircuit}; use halo2_proofs::{ halo2curves::bn256::Fr, @@ -7,7 +6,7 @@ use halo2_proofs::{ use std::{collections::HashMap, sync::LazyLock}; // Step dimension -pub(crate) const STEP_WIDTH: usize = 140; +pub(crate) const STEP_WIDTH: usize = 121; /// Step height pub const MAX_STEP_HEIGHT: usize = 21; /// The height of the state of a step, used by gates that connect two @@ -18,9 +17,12 @@ pub(crate) const STEP_STATE_HEIGHT: usize = 1; /// Number of Advice Phase2 columns in the EVM circuit pub(crate) const N_PHASE2_COLUMNS: usize = 7; +/// Number of Advice Phase3 columns, used by Bus ports. +pub const N_PHASE3_COLUMNS: usize = 12; + /// Number of Advice Phase1 columns in the EVM circuit pub(crate) const N_PHASE1_COLUMNS: usize = - STEP_WIDTH - EVM_LOOKUP_COLS - N_PHASE2_COLUMNS - N_COPY_COLUMNS - N_BYTE_LOOKUPS; + STEP_WIDTH - N_PHASE3_COLUMNS - N_PHASE2_COLUMNS - N_COPY_COLUMNS - N_BYTE_LOOKUPS; // Number of copy columns pub(crate) const N_COPY_COLUMNS: usize = 2; @@ -29,76 +31,6 @@ pub(crate) const N_PHASE2_COPY_COLUMNS: usize = 1; pub(crate) const N_BYTE_LOOKUPS: usize = 26; -/// Amount of lookup columns in the EVM circuit dedicated to lookups. -pub(crate) const EVM_LOOKUP_COLS: usize = FIXED_TABLE_LOOKUPS - + TX_TABLE_LOOKUPS - + RW_TABLE_LOOKUPS - + BYTECODE_TABLE_LOOKUPS - + BLOCK_TABLE_LOOKUPS - + COPY_TABLE_LOOKUPS - + KECCAK_TABLE_LOOKUPS - + SHA256_TABLE_LOOKUPS - + EXP_TABLE_LOOKUPS - + SIG_TABLE_LOOKUPS - + MODEXP_TABLE_LOOKUPS - + ECC_TABLE_LOOKUPS - + POW_OF_RAND_TABLE_LOOKUPS; - -/// Lookups done per row. -pub(crate) const LOOKUP_CONFIG: &[(Table, usize)] = &[ - (Table::Fixed, FIXED_TABLE_LOOKUPS), - (Table::Tx, TX_TABLE_LOOKUPS), - (Table::Rw, RW_TABLE_LOOKUPS), - (Table::Bytecode, BYTECODE_TABLE_LOOKUPS), - (Table::Block, BLOCK_TABLE_LOOKUPS), - (Table::Copy, COPY_TABLE_LOOKUPS), - (Table::Keccak, KECCAK_TABLE_LOOKUPS), - (Table::Sha256, SHA256_TABLE_LOOKUPS), - (Table::Exp, EXP_TABLE_LOOKUPS), - (Table::Sig, SIG_TABLE_LOOKUPS), - (Table::ModExp, MODEXP_TABLE_LOOKUPS), - (Table::Ecc, ECC_TABLE_LOOKUPS), - (Table::PowOfRand, POW_OF_RAND_TABLE_LOOKUPS), -]; - -/// Fixed Table lookups done in EVMCircuit -pub const FIXED_TABLE_LOOKUPS: usize = 10; - -/// Tx Table lookups done in EVMCircuit -pub const TX_TABLE_LOOKUPS: usize = 4; - -/// Rw Table lookups done in EVMCircuit -pub const RW_TABLE_LOOKUPS: usize = 8; - -/// Bytecode Table lookups done in EVMCircuit -pub const BYTECODE_TABLE_LOOKUPS: usize = 1; - -/// Block Table lookups done in EVMCircuit -pub const BLOCK_TABLE_LOOKUPS: usize = 1; - -/// Copy Table lookups done in EVMCircuit -pub const COPY_TABLE_LOOKUPS: usize = 1; - -/// Keccak Table lookups done in EVMCircuit -pub const KECCAK_TABLE_LOOKUPS: usize = 1; - -/// Keccak Table lookups done in EVMCircuit -pub const SHA256_TABLE_LOOKUPS: usize = 1; - -/// Exp Table lookups done in EVMCircuit -pub const EXP_TABLE_LOOKUPS: usize = 1; - -/// Sig Table lookups done in EVMCircuit -pub const SIG_TABLE_LOOKUPS: usize = 1; - -/// ModExp Table lookups done in EVMCircuit -pub const MODEXP_TABLE_LOOKUPS: usize = 1; -/// Ecc Table lookups done in EVMCircuit -pub const ECC_TABLE_LOOKUPS: usize = 1; - -/// Power of Randomness lookups done from EVM Circuit. -pub const POW_OF_RAND_TABLE_LOOKUPS: usize = 1; - /// Maximum number of bytes that an integer can fit in field without wrapping /// around. pub(crate) const MAX_N_BYTES_INTEGER: usize = 31; diff --git a/zkevm-circuits/src/evm_circuit/table.rs b/zkevm-circuits/src/evm_circuit/table.rs index 62f7da7426..80fb83cd7c 100644 --- a/zkevm-circuits/src/evm_circuit/table.rs +++ b/zkevm-circuits/src/evm_circuit/table.rs @@ -2,14 +2,134 @@ pub use crate::table::TxContextFieldTag; use crate::{ evm_circuit::step::{ExecutionState, ResponsibleOp}, impl_expr, + witness::RwRow, }; use bus_mapping::{evm::OpcodeId, precompile::PrecompileCalls}; use eth_types::Field; -use gadgets::util::Expr; +use gadgets::{bus::bus_codec::BusMessage, util::Expr}; use halo2_proofs::plonk::Expression; use strum::IntoEnumIterator; use strum_macros::EnumIter; +#[derive(Clone, Debug)] +pub enum MsgExpr { + Bytes([Expression; 2]), + Lookup(Table, Vec>), +} + +impl MsgExpr { + pub fn bytes(bytes: [Expression; 2]) -> Self { + Self::Bytes(bytes) + } + + pub fn lookup(lookup: Lookup) -> Self { + Self::Lookup(lookup.table(), lookup.input_exprs()) + } + + pub fn map_exprs(self, f: impl FnMut(Expression) -> Expression) -> Self { + match self { + Self::Bytes(bytes) => Self::Bytes(bytes.map(f)), + Self::Lookup(table, inputs) => Self::Lookup(table, inputs.into_iter().map(f).collect()), + } + } + + pub fn map_values(self, f: impl FnMut(Expression) -> F) -> MsgF { + match self { + Self::Bytes(exprs) => { + let values = exprs.map(f); + MsgF::Bytes(values) + } + Self::Lookup(table, exprs) => { + let values = exprs.into_iter().map(f).collect(); + MsgF::Lookup(table, values) + } + } + } +} + +impl BusMessage> for MsgExpr { + type IntoIter = std::vec::IntoIter>; + + fn into_items(self) -> Self::IntoIter { + match self { + Self::Bytes([byte0, byte1]) => { + let tag = 0.expr(); + vec![tag, byte0, byte1].into_iter() + } + Self::Lookup(table, mut inputs) => { + let tag = (1 + table as u64).expr(); + inputs.insert(0, tag); + inputs.into_iter() + } + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum MsgF { + Bytes([F; 2]), + Lookup(Table, Vec), +} + +impl MsgF { + pub fn bytes(bytes: [F; 2]) -> Self { + Self::Bytes(bytes) + } + + pub fn fixed(row: [F; 4]) -> Self { + Self::Lookup(Table::Fixed, row.to_vec()) + } + + pub fn rw(row: RwRow) -> Self { + // Same format as `Lookup::input_exprs()` + Self::Lookup( + Table::Rw, + vec![ + F::one(), // TODO: can remove the "enabled" field. + row.rw_counter, + row.is_write, + row.tag, + row.id, + row.address, + row.field_tag, + row.storage_key, + row.value, + row.value_prev, + row.aux1, + row.aux2, + ], + ) + } + + pub fn tx(id: F, field_tag: F, index: F, value: F) -> Self { + Self::Lookup( + Table::Tx, + vec![ + F::one(), // TODO: can remove the "enabled" field. + id, + field_tag, + index, + value, + ], + ) + } +} + +impl BusMessage for MsgF { + type IntoIter = std::vec::IntoIter; + + fn into_items(self) -> Self::IntoIter { + match self { + Self::Bytes([b0, b1]) => vec![F::zero(), b0, b1].into_iter(), + Self::Lookup(table, mut inputs) => { + let tag = F::from(1 + table as u64); + inputs.insert(0, tag); + inputs.into_iter() + } + } + } +} + #[derive(Clone, Copy, Debug, EnumIter)] pub enum FixedTableTag { Zero = 0, @@ -146,7 +266,7 @@ impl FixedTableTag { } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, EnumIter)] -pub(crate) enum Table { +pub enum Table { Fixed, Tx, Rw, @@ -200,7 +320,7 @@ impl RwValues { } #[derive(Clone, Debug)] -pub(crate) enum Lookup { +pub enum Lookup { /// Lookup to fixed table, which contains several pre-built tables such as /// range tables or bitwise tables. Fixed { @@ -377,6 +497,17 @@ impl Lookup { } } + /// Return the inner lookup (not Conditional), and the condition (or constant 1). + pub(crate) fn unconditional(self) -> (Self, Expression) { + match self { + Self::Conditional(cond, lookup) => { + let (lookup, cond2) = lookup.unconditional(); + (lookup, cond * cond2) + } + _ => (self, 1.expr()), + } + } + pub(crate) fn input_exprs(&self) -> Vec> { match self { Self::Fixed { tag, values } => [vec![tag.clone()], values.to_vec()].concat(), diff --git a/zkevm-circuits/src/evm_circuit/util.rs b/zkevm-circuits/src/evm_circuit/util.rs index aeb6c2db5a..6d50b597b1 100644 --- a/zkevm-circuits/src/evm_circuit/util.rs +++ b/zkevm-circuits/src/evm_circuit/util.rs @@ -1,10 +1,7 @@ use crate::{ - evm_circuit::{ - param::{ - LOOKUP_CONFIG, N_BYTES_MEMORY_ADDRESS, N_BYTES_U64, N_BYTE_LOOKUPS, N_COPY_COLUMNS, - N_PHASE2_COLUMNS, N_PHASE2_COPY_COLUMNS, - }, - table::Table, + evm_circuit::param::{ + N_BYTES_MEMORY_ADDRESS, N_BYTES_U64, N_BYTE_LOOKUPS, N_COPY_COLUMNS, N_PHASE2_COLUMNS, + N_PHASE2_COPY_COLUMNS, N_PHASE3_COLUMNS, }, table::RwTableTag, util::{query_expression, Challenges, Expr}, @@ -12,6 +9,10 @@ use crate::{ }; use bus_mapping::state_db::CodeDB; use eth_types::{Address, Field, ToLittleEndian, ToWord, U256}; +use gadgets::bus::{ + bus_builder::{BusAssigner, BusBuilder}, + bus_port::{BusOpExpr, BusOpF, Port}, +}; use halo2_proofs::{ circuit::{AssignedCell, Region, Value}, halo2curves::group::ff::BatchInvert, @@ -34,6 +35,8 @@ pub(crate) mod precompile_gadget; pub use gadgets::util::{and, not, or, select, sum}; +use super::table::{MsgExpr, MsgF}; + #[derive(Clone, Debug)] pub(crate) struct Cell { // expression for constraint @@ -90,6 +93,48 @@ impl Expr for &Cell { self.expression.clone() } } + +#[derive(Clone, Debug)] +pub struct StepBusOp { + op: BusOpExpr>, + helper: Cell, +} + +impl StepBusOp { + pub(crate) fn connect( + self, + meta: &mut ConstraintSystem, + bus_builder: &mut BusBuilder>, + enabled: Expression, + ) { + Port::connect(meta, bus_builder, enabled, self.op, self.helper.expr()); + } + + pub(crate) fn assign( + &self, + region: &mut CachedRegion<'_, '_, F>, + bus_assigner: &mut BusAssigner>, + offset: usize, + ) { + let count = region.eval(offset, self.op.count()); + if count.is_zero_vartime() { + return; + } + assert_eq!(count, -F::one(), "count must be 0 or -1"); + + let eval = |expr| region.eval(offset, expr); + let message = self.op.message().map_values(eval); + + Port::assign( + bus_assigner, + offset, + BusOpF::receive(message), + self.helper.column, + self.helper.rotation as isize, + ); + } +} + pub struct CachedRegion<'r, 'b, F: Field> { region: &'r mut Region<'b, F>, advice: Vec>, @@ -198,6 +243,36 @@ impl<'r, 'b, F: Field> CachedRegion<'r, 'b, F> { } } + pub fn eval(&self, offset: usize, expr: Expression) -> F { + let value = expr.evaluate( + &|scalar| Value::known(scalar), + &|_| unimplemented!("selector column"), + &|fixed_query| { + Value::known(self.get_fixed( + offset, + fixed_query.column_index(), + fixed_query.rotation(), + )) + }, + &|advice_query| { + Value::known(self.get_advice( + offset, + advice_query.column_index(), + advice_query.rotation(), + )) + }, + &|_| unimplemented!("instance column"), + &|challenge| *self.challenges().indexed()[challenge.index()], + &|a| -a, + &|a, b| a + b, + &|a, b| a * b, + &|a, scalar| a * Value::known(scalar), + ); + let mut f = F::zero(); + value.map(|v| f = v); + f + } + pub fn get_fixed(&self, _row_index: usize, _column_index: usize, _rotation: Rotation) -> F { unimplemented!("fixed column"); } @@ -309,10 +384,10 @@ impl StoredExpression { pub(crate) enum CellType { StoragePhase1, StoragePhase2, + StoragePhase3, StoragePermutation, StoragePermutationPhase2, LookupByte, - Lookup(Table), } impl CellType { @@ -333,6 +408,7 @@ impl CellType { match phase { 0 => CellType::StoragePhase1, 1 => CellType::StoragePhase2, + 2 => CellType::StoragePhase3, _ => unreachable!(), } } @@ -392,12 +468,10 @@ impl CellManager { let mut column_idx = 0; - // Mark columns used for lookups in Phase3 - for &(table, count) in LOOKUP_CONFIG { - for _ in 0usize..count { - columns[column_idx].cell_type = CellType::Lookup(table); - column_idx += 1; - } + // Mark columns used for Phase3. + for _ in 0..N_PHASE3_COLUMNS { + columns[column_idx].cell_type = CellType::StoragePhase3; + column_idx += 1; } // Mark columns used for copy constraints on phase2 diff --git a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs index 29aaf3e7d1..2658af9714 100644 --- a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs +++ b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs @@ -2,7 +2,7 @@ use crate::{ evm_circuit::{ param::STACK_CAPACITY, step::{ExecutionState, Step}, - table::{FixedTableTag, Lookup, RwValues}, + table::{FixedTableTag, Lookup, MsgExpr, RwValues}, util::{Cell, RandomLinearCombination, Word}, }, table::{ @@ -16,7 +16,10 @@ use bus_mapping::{ util::{KECCAK_CODE_HASH_EMPTY, POSEIDON_CODE_HASH_EMPTY}, }; use eth_types::{Field, ToLittleEndian, ToScalar, ToWord}; -use gadgets::util::{and, not}; +use gadgets::{ + bus::bus_port::{BusOpExpr, Port}, + util::{and, not}, +}; use halo2_proofs::{ circuit::Value, plonk::{ @@ -26,7 +29,7 @@ use halo2_proofs::{ }; use itertools::Itertools; -use super::{rlc, CachedRegion, CellType, StoredExpression}; +use super::{rlc, CachedRegion, CellType, StepBusOp, StoredExpression}; // Max degree allowed in all expressions passing through the ConstraintBuilder. // It aims to cap `extended_k` to 2, which allows constraint degree to 2^2+1, @@ -312,6 +315,7 @@ pub(crate) struct EVMConstraintBuilder<'a, F> { conditions: Vec>, constraints_location: ConstraintLocation, stored_expressions: Vec>, + bus_ops: Vec>, pub(crate) max_inner_degree: (&'static str, usize), #[cfg(feature = "debug-annotations")] annotations: Vec, @@ -360,6 +364,7 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> { conditions: Vec::new(), constraints_location: ConstraintLocation::Step, stored_expressions: Vec::new(), + bus_ops: Vec::new(), max_inner_degree: ("", 0), annotations: Vec::new(), } @@ -374,6 +379,7 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> { Expression, Constraints, Vec>, + Vec>, usize, ) { let exec_state_sel = self.curr.execution_state_selector([self.execution_state]); @@ -381,6 +387,7 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> { exec_state_sel, self.constraints, self.stored_expressions, + self.bus_ops, self.curr.cell_manager.get_height(), ) } @@ -467,6 +474,17 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> { cell } + #[allow(clippy::let_and_return)] + fn query_cell_phase3(&mut self) -> Cell { + let cell = self.query_cell_with_type(CellType::StoragePhase3); + #[cfg(not(feature = "onephase"))] + assert_eq!( + cell.column.column_type(), + &halo2_proofs::plonk::Advice::new(halo2_proofs::plonk::ThirdPhase) + ); + cell + } + pub(crate) fn query_copy_cell(&mut self) -> Cell { self.query_cell_with_type(CellType::StoragePermutation) } @@ -1630,17 +1648,45 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> { } } - pub(crate) fn add_lookup(&mut self, name: &str, lookup: Lookup) { + pub(crate) fn add_lookup(&mut self, _name: &str, lookup: Lookup) { let lookup = match self.condition_expr_opt() { Some(condition) => lookup.conditional(condition), None => lookup, }; - let compressed_expr = self.split_expression( - "Lookup compression", - rlc::expr(&lookup.input_exprs(), self.challenges.lookup_input()), - MAX_DEGREE - IMPLICIT_DEGREE, - ); - self.store_expression(name, compressed_expr, CellType::Lookup(lookup.table())); + self.add_bus_lookup(lookup); + } + + fn add_bus_lookup(&mut self, lookup: Lookup) { + let (lookup, condition) = lookup.unconditional(); + + // Reduce the degree of the inputs and condition. + let (msg_degree, count_degree) = Port::max_degrees(MAX_DEGREE, IMPLICIT_DEGREE); + + let message = MsgExpr::lookup(lookup) + .map_exprs(|expr| self.store_any_expression("Bus message", expr, msg_degree)); + + let condition = self.store_any_expression("Bus count", condition, count_degree); + + // Allocate an operation to an helper cell. + let op = BusOpExpr::receive(message).conditional(condition); + let helper = self.query_cell_phase3(); + self.bus_ops.push(StepBusOp { op, helper }); + } + + /// Reduce the degree of `expr` to `target_degree`. Split and store the expression if needed. + fn store_any_expression( + &mut self, + name: &'static str, + expr: Expression, + target_degree: usize, + ) -> Expression { + let expr = self.split_expression(name, expr, MAX_DEGREE - IMPLICIT_DEGREE); + if expr.degree() <= target_degree { + expr + } else { + let cell_type = CellType::storage_for_expr(&expr); + self.store_expression(name, expr, cell_type) + } } pub(crate) fn store_expression( @@ -1653,13 +1699,7 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> { let stored_expression = self.find_stored_expression(&expr, cell_type); match stored_expression { - Some(stored_expression) => { - debug_assert!( - !matches!(cell_type, CellType::Lookup(_)), - "The same lookup is done multiple times", - ); - stored_expression.cell.expr() - } + Some(stored_expression) => stored_expression.cell.expr(), None => { // Even if we're building expressions for the next step, // these intermediate values need to be stored in the current step. diff --git a/zkevm-circuits/src/evm_circuit/util/instrumentation.rs b/zkevm-circuits/src/evm_circuit/util/instrumentation.rs index 7de58deb45..c32fc7c02d 100644 --- a/zkevm-circuits/src/evm_circuit/util/instrumentation.rs +++ b/zkevm-circuits/src/evm_circuit/util/instrumentation.rs @@ -1,6 +1,5 @@ use crate::evm_circuit::{ step::ExecutionState, - table::Table, util::{constraint_builder::EVMConstraintBuilder, CellType}, }; use eth_types::Field; @@ -70,6 +69,9 @@ impl Instrument { CellType::StoragePhase2 => { report.storage_2 = data_entry; } + CellType::StoragePhase3 => { + report.storage_3 = data_entry; + } CellType::StoragePermutation => { report.storage_perm = data_entry; } @@ -79,45 +81,6 @@ impl Instrument { CellType::LookupByte => { report.byte_lookup = data_entry; } - CellType::Lookup(Table::Fixed) => { - report.fixed_table = data_entry; - } - CellType::Lookup(Table::Tx) => { - report.tx_table = data_entry; - } - CellType::Lookup(Table::Rw) => { - report.rw_table = data_entry; - } - CellType::Lookup(Table::Bytecode) => { - report.bytecode_table = data_entry; - } - CellType::Lookup(Table::Block) => { - report.block_table = data_entry; - } - CellType::Lookup(Table::Copy) => { - report.copy_table = data_entry; - } - CellType::Lookup(Table::Keccak) => { - report.keccak_table = data_entry; - } - CellType::Lookup(Table::Sha256) => { - report.sha256_table = data_entry; - } - CellType::Lookup(Table::Exp) => { - report.exp_table = data_entry; - } - CellType::Lookup(Table::Sig) => { - report.sig_table = data_entry; - } - CellType::Lookup(Table::ModExp) => { - report.modexp_table = data_entry; - } - CellType::Lookup(Table::Ecc) => { - report.ecc_table = data_entry; - } - CellType::Lookup(Table::PowOfRand) => { - report.pow_of_rand_table = data_entry; - } } } report_collection.push(report); @@ -133,6 +96,7 @@ pub(crate) struct ExecStateReport { pub(crate) state: ExecutionState, pub(crate) storage_1: StateReportRow, pub(crate) storage_2: StateReportRow, + pub(crate) storage_3: StateReportRow, pub(crate) storage_perm: StateReportRow, pub(crate) storage_perm_2: StateReportRow, pub(crate) byte_lookup: StateReportRow, diff --git a/zkevm-circuits/src/evm_circuit/util/math_gadget/test_util.rs b/zkevm-circuits/src/evm_circuit/util/math_gadget/test_util.rs index 9741e6cd02..2340e75b17 100644 --- a/zkevm-circuits/src/evm_circuit/util/math_gadget/test_util.rs +++ b/zkevm-circuits/src/evm_circuit/util/math_gadget/test_util.rs @@ -2,18 +2,12 @@ use itertools::Itertools; use std::marker::PhantomData; use strum::IntoEnumIterator; -use crate::{ - evm_circuit::{ - param::{MAX_STEP_HEIGHT, N_PHASE2_COLUMNS, STEP_WIDTH}, - step::{ExecutionState, Step}, - table::{FixedTableTag, Table}, - util::{ - constraint_builder::EVMConstraintBuilder, rlc, CachedRegion, CellType, Expr, - StoredExpression, LOOKUP_CONFIG, - }, - Advice, Column, Fixed, - }, - table::LookupTable, +use crate::evm_circuit::{ + param::{MAX_STEP_HEIGHT, N_PHASE2_COLUMNS, N_PHASE3_COLUMNS, STEP_WIDTH}, + step::{ExecutionState, Step}, + table::FixedTableTag, + util::{constraint_builder::EVMConstraintBuilder, CachedRegion, StoredExpression}, + Advice, Column, Fixed, }; #[cfg(not(feature = "onephase"))] @@ -120,14 +114,13 @@ impl> Circuit for UnitTestMathGadgetBaseC let q_usable = meta.selector(); let fixed_table = [(); 4].map(|_| meta.fixed_column()); - let lookup_column_count: usize = LOOKUP_CONFIG.iter().map(|(_, count)| *count).sum(); let advices = [(); STEP_WIDTH] .iter() .enumerate() .map(|(n, _)| { - if n < lookup_column_count { + if n < N_PHASE3_COLUMNS { meta.advice_column_in(ThirdPhase) - } else if n < lookup_column_count + N_PHASE2_COLUMNS { + } else if n < N_PHASE3_COLUMNS + N_PHASE2_COLUMNS { meta.advice_column_in(SecondPhase) } else { meta.advice_column_in(FirstPhase) @@ -146,7 +139,7 @@ impl> Circuit for UnitTestMathGadgetBaseC ExecutionState::STOP, ); let math_gadget_container = G::configure_gadget_container(&mut cb); - let (state_selector, constraints, stored_expressions, _) = cb.build(); + let (state_selector, constraints, stored_expressions, _bus_ops, _) = cb.build(); if !constraints.step.is_empty() { let step_constraints = constraints.step; @@ -158,22 +151,6 @@ impl> Circuit for UnitTestMathGadgetBaseC }); } - let cell_manager = step_curr.cell_manager.clone(); - for column in cell_manager.columns().iter() { - if let CellType::Lookup(table) = column.cell_type { - if table == Table::Fixed { - let name = format!("{table:?}"); - meta.lookup_any(Box::leak(name.into_boxed_str()), |meta| { - let table_expressions = fixed_table.table_exprs(meta); - vec![( - column.expr(), - rlc::expr(&table_expressions, challenges_exprs.lookup_input()), - )] - }); - } - } - } - ( UnitTestMathGadgetBaseCircuitConfig:: { q_usable, diff --git a/zkevm-circuits/src/lib.rs b/zkevm-circuits/src/lib.rs index 2aa7b7a560..a709b5f5be 100644 --- a/zkevm-circuits/src/lib.rs +++ b/zkevm-circuits/src/lib.rs @@ -37,6 +37,7 @@ pub mod rlp_circuit_fsm; pub mod sig_circuit; // we don't use this for aggregation //pub mod root_circuit; +mod evm_bus; pub mod modexp_circuit; pub mod sha256_circuit; pub mod state_circuit; diff --git a/zkevm-circuits/src/pi_circuit.rs b/zkevm-circuits/src/pi_circuit.rs index d77b41301c..0b9b55b384 100644 --- a/zkevm-circuits/src/pi_circuit.rs +++ b/zkevm-circuits/src/pi_circuit.rs @@ -1464,7 +1464,11 @@ impl PiCircuitConfig { let block_table_columns = >::advice_columns(&self.block_table); - for fixed in [self.q_block_tag, self.is_block_num_txs] { + for fixed in [ + self.q_block_tag, + self.is_block_num_txs, + self.block_table.q_enable, + ] { region.assign_fixed( || "block table all-zero row for fixed", fixed, @@ -1530,6 +1534,12 @@ impl PiCircuitConfig { .into_iter() .zip(tag.iter()) { + region.assign_fixed( + || format!("block table enabled {offset}"), + self.block_table.q_enable, + offset, + || Value::known(F::one()), + )?; region.assign_fixed( || format!("block table row {offset}"), self.block_table.tag, diff --git a/zkevm-circuits/src/state_circuit.rs b/zkevm-circuits/src/state_circuit.rs index 94f5427468..8c98c7cfd0 100644 --- a/zkevm-circuits/src/state_circuit.rs +++ b/zkevm-circuits/src/state_circuit.rs @@ -927,6 +927,11 @@ impl SubCircuit for StateCircuit { ) } + /// powers of randomness for instance columns + fn instance(&self) -> Vec> { + vec![] + } + /// Make the assignments to the StateCircuit fn synthesize_sub( &self, @@ -1026,11 +1031,6 @@ impl SubCircuit for StateCircuit { }, ) } - - /// powers of randomness for instance columns - fn instance(&self) -> Vec> { - vec![] - } } fn queries(meta: &mut VirtualCells<'_, F>, c: &StateCircuitConfig) -> Queries { diff --git a/zkevm-circuits/src/super_circuit.rs b/zkevm-circuits/src/super_circuit.rs index e2dc6c4882..784c808a84 100644 --- a/zkevm-circuits/src/super_circuit.rs +++ b/zkevm-circuits/src/super_circuit.rs @@ -63,6 +63,7 @@ use crate::{ bytecode_circuit::circuit::{BytecodeCircuit, BytecodeCircuitConfigArgs}, copy_circuit::{CopyCircuit, CopyCircuitConfig, CopyCircuitConfigArgs}, ecc_circuit::{EccCircuit, EccCircuitConfig, EccCircuitConfigArgs}, + evm_bus::EVMBusLookups, evm_circuit::{EvmCircuit, EvmCircuitConfig, EvmCircuitConfigArgs}, exp_circuit::{ExpCircuit, ExpCircuitArgs, ExpCircuitConfig}, keccak_circuit::{ @@ -80,9 +81,9 @@ use crate::{ sig_circuit::{SigCircuit, SigCircuitConfig, SigCircuitConfigArgs}, state_circuit::{StateCircuit, StateCircuitConfig, StateCircuitConfigArgs}, table::{ - BlockTable, BytecodeTable, CopyTable, EccTable, ExpTable, KeccakTable, ModExpTable, - MptTable, PoseidonTable, PowOfRandTable, RlpFsmRlpTable as RlpTable, RwTable, SHA256Table, - SigTable, TxTable, U16Table, U8Table, + BlockTable, BytecodeTable, CopyTable, DualByteTable, EccTable, ExpTable, FixedTable, + KeccakTable, LookupTable, ModExpTable, MptTable, PoseidonTable, PowOfRandTable, + RlpFsmRlpTable as RlpTable, RwTable, SHA256Table, SigTable, TxTable, U16Table, U8Table, }, tx_circuit::{TxCircuit, TxCircuitConfig, TxCircuitConfigArgs}, util::{circuit_stats, log2_ceil, Challenges, SubCircuit, SubCircuitConfig}, @@ -97,6 +98,11 @@ use bus_mapping::{ mock::BlockData, }; use eth_types::{geth_types::GethData, Field}; +use gadgets::bus::{ + bus_builder::{BusAssigner, BusBuilder}, + bus_chip::BusConfig, + bus_codec::{BusCodecExpr, BusCodecVal}, +}; use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner, Value}, halo2curves::bn256::Fr, @@ -108,6 +114,8 @@ use snark_verifier_sdk::CircuitExt; /// Configuration of the Super Circuit #[derive(Clone)] pub struct SuperCircuitConfig { + bus: BusConfig, + evm_lookups: EVMBusLookups, block_table: BlockTable, mpt_table: MptTable, rlp_table: RlpTable, @@ -170,9 +178,16 @@ impl SubCircuitConfig for SuperCircuitConfig { }; let challenges_expr = challenges.exprs(meta); + let mut bus_builder = BusBuilder::new(BusCodecExpr::new(challenges_expr.lookup_input())); + + let dual_byte_table = DualByteTable::construct(meta); + let fixed_table = FixedTable::construct(meta); + fixed_table.annotate_columns(meta); let tx_table = TxTable::construct(meta); + tx_table.annotate_columns(meta); log_circuit_info(meta, "tx table"); let rw_table = RwTable::construct(meta); + rw_table.annotate_columns(meta); log_circuit_info(meta, "rw table"); let mpt_table = MptTable::construct(meta); @@ -204,12 +219,24 @@ impl SubCircuitConfig for SuperCircuitConfig { log_circuit_info(meta, "ecc table"); let pow_of_rand_table = PowOfRandTable::construct(meta, &challenges_expr); log_circuit_info(meta, "power of randomness table"); + // TODO: move pow_of_rand_table to EVMCircuit. let u8_table = U8Table::construct(meta); log_circuit_info(meta, "u8 table"); let u16_table = U16Table::construct(meta); log_circuit_info(meta, "u16 table"); + bytecode_table.annotate_columns(meta); + block_table.annotate_columns(meta); + copy_table.annotate_columns(meta); + keccak_table.annotate_columns(meta); + sha256_table.annotate_columns(meta); + exp_table.annotate_columns(meta); + sig_table.annotate_columns(meta); + modexp_table.annotate_columns(meta); + ecc_table.annotate_columns(meta); + pow_of_rand_table.annotate_columns(meta); + assert!(get_num_rows_per_round() == 12); let keccak_circuit = KeccakCircuitConfig::new( meta, @@ -341,19 +368,12 @@ impl SubCircuitConfig for SuperCircuitConfig { let evm_circuit = EvmCircuitConfig::new( meta, + &mut bus_builder, EvmCircuitConfigArgs { challenges: challenges_expr.clone(), - tx_table: tx_table.clone(), - rw_table, - bytecode_table, - block_table: block_table.clone(), - copy_table, - keccak_table: keccak_table.clone(), - sha256_table, - exp_table, - sig_table, - modexp_table, - ecc_table, + // Tables assigned by the EVM circuit. + dual_byte_table: dual_byte_table.clone(), + fixed_table: fixed_table.clone(), pow_of_rand_table, }, ); @@ -365,7 +385,7 @@ impl SubCircuitConfig for SuperCircuitConfig { let sig_circuit = SigCircuitConfig::new( meta, SigCircuitConfigArgs { - keccak_table, + keccak_table: keccak_table.clone(), sig_table, challenges: challenges_expr.clone(), }, @@ -381,12 +401,35 @@ impl SubCircuitConfig for SuperCircuitConfig { ); log_circuit_info(meta, "ecc circuit"); + let evm_lookups = EVMBusLookups::configure( + meta, + &mut bus_builder, + &dual_byte_table, + &fixed_table, + &rw_table, + &tx_table, + &bytecode_table, + &block_table, + ©_table, + &keccak_table, + &sha256_table, + &exp_table, + &sig_table, + &modexp_table, + &ecc_table, + &pow_of_rand_table, + ); + + let bus = BusConfig::new(meta, &bus_builder.build()); + #[cfg(feature = "onephase")] if meta.max_phase() != 0 { log::warn!("max_phase: {}", meta.max_phase()); } SuperCircuitConfig { + bus, + evm_lookups, block_table, mpt_table, tx_table, @@ -435,6 +478,8 @@ pub struct SuperCircuit< const MAX_INNER_BLOCKS: usize, const MOCK_RANDOMNESS: u64, > { + /// Number of rows required by the SuperCircuit + num_rows: usize, /// EVM Circuit pub evm_circuit: EvmCircuit, /// State Circuit @@ -580,6 +625,7 @@ impl< } fn new_from_block(block: &Block) -> Self { + let num_rows = Self::get_num_rows_required(block); let evm_circuit = EvmCircuit::new_from_block(block); let state_circuit = StateCircuit::new_from_block(block); let tx_circuit = TxCircuit::new_from_block(block); @@ -597,6 +643,7 @@ impl< #[cfg(feature = "zktrie")] let mpt_circuit = MptCircuit::new_from_block(block); SuperCircuit:: { + num_rows, evm_circuit, state_circuit, tx_circuit, @@ -648,9 +695,24 @@ impl< challenges: &crate::util::Challenges>, layouter: &mut impl Layouter, ) -> Result<(), Error> { + let mut bus_assigner = + BusAssigner::new(BusCodecVal::new(challenges.lookup_input()), self.num_rows); + + log::debug!("assigning state_circuit"); + self.state_circuit + .synthesize_sub(&config.state_circuit, challenges, layouter)?; + + log::debug!("assigning tx_circuit"); + self.tx_circuit + .synthesize_sub(&config.tx_circuit, challenges, layouter)?; + log::debug!("assigning evm_circuit"); - self.evm_circuit - .synthesize_sub(&config.evm_circuit, challenges, layouter)?; + self.evm_circuit.synthesize_sub2( + &config.evm_circuit, + challenges, + layouter, + &mut bus_assigner, + )?; if !challenges.lookup_input().is_none() { let is_mock_prover = format!("{:?}", challenges.lookup_input()) == *"Value { inner: Some(0x207a52ba34e1ed068be1e33b0bc39c8ede030835f549fe5c0dbe91dce97d17d2) }"; @@ -673,9 +735,6 @@ impl< log::debug!("assigning bytecode_circuit"); self.bytecode_circuit .synthesize_sub(&config.bytecode_circuit, challenges, layouter)?; - log::debug!("assigning tx_circuit"); - self.tx_circuit - .synthesize_sub(&config.tx_circuit, challenges, layouter)?; log::debug!("assigning sig_circuit"); self.sig_circuit .synthesize_sub(&config.sig_circuit, challenges, layouter)?; @@ -685,9 +744,6 @@ impl< log::debug!("assigning modexp_circuit"); self.modexp_circuit .synthesize_sub(&config.modexp_circuit, challenges, layouter)?; - log::debug!("assigning state_circuit"); - self.state_circuit - .synthesize_sub(&config.state_circuit, challenges, layouter)?; log::debug!("assigning copy_circuit"); self.copy_circuit .synthesize_sub(&config.copy_circuit, challenges, layouter)?; @@ -718,6 +774,14 @@ impl< .synthesize_sub(&config.mpt_circuit, challenges, layouter)?; } + config.evm_lookups.assign(layouter, &mut bus_assigner)?; + + if !bus_assigner.op_counter().is_complete() { + log::warn!("Incomplete bus assignment."); + log::debug!("Missing bus ops: {:?}", bus_assigner.op_counter()); + } + config.bus.finish_assigner(layouter, bus_assigner)?; + log::debug!("super circuit synthesize_sub done"); Ok(()) } diff --git a/zkevm-circuits/src/table.rs b/zkevm-circuits/src/table.rs index 42c1a3fb30..0791bc8c75 100644 --- a/zkevm-circuits/src/table.rs +++ b/zkevm-circuits/src/table.rs @@ -25,7 +25,7 @@ use core::iter::once; use eth_types::{sign_types::SignData, Field, ToLittleEndian, ToScalar, ToWord, Word, U256}; use gadgets::{ binary_number::{BinaryNumberChip, BinaryNumberConfig}, - util::{and, not, split_u256, split_u256_limb64, Expr}, + util::{and, assign_global, not, split_u256, split_u256_limb64, Expr}, }; use halo2_proofs::{ circuit::{AssignedCell, Layouter, Region, Value}, @@ -117,6 +117,69 @@ impl> + Copy, const W: usize> LookupTable for [ } } +/// The DualByteTable used to check the range of two bytes at once. +#[derive(Clone, Debug)] +pub struct DualByteTable { + /// Enabled. + // TODO: this could be removed. + pub q_enable: Column, + /// Two byte columns containing the 256*256 combinations of two bytes. + pub bytes: [Column; 2], +} + +impl DualByteTable { + /// Construct a new DualByteTable + pub fn construct(meta: &mut ConstraintSystem) -> Self { + Self { + q_enable: meta.fixed_column(), + bytes: [(); 2].map(|_| meta.fixed_column()), + } + } +} + +/// The table of fixed functions (range checks, XOR, etc). +#[derive(Clone, Debug)] +pub struct FixedTable { + /// Enabled. + // TODO: this could be removed. + pub q_enable: Column, + /// The tag that selects the function. + pub tag: Column, + /// The arguments of the function (or 0). + pub values: [Column; 3], +} + +impl FixedTable { + /// Construct a new FixedTable + pub fn construct(meta: &mut ConstraintSystem) -> Self { + Self { + q_enable: meta.fixed_column(), + tag: meta.fixed_column(), + values: [(); 3].map(|_| meta.fixed_column()), + } + } +} + +impl LookupTable for FixedTable { + fn columns(&self) -> Vec> { + vec![ + self.tag.into(), + self.values[0].into(), + self.values[1].into(), + self.values[2].into(), + ] + } + + fn annotations(&self) -> Vec { + vec![ + String::from("tag"), + String::from("values[0]"), + String::from("values[1]"), + String::from("values[2]"), + ] + } +} + /// Tag used to identify each field in the transaction in a row of the /// transaction table. #[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter)] @@ -254,15 +317,14 @@ impl TxTable { ); } - fn assign_row( - region: &mut Region<'_, F>, - offset: usize, - q_enable: Column, - advice_columns: &[Column], - tag: &Column, - row: &[Value; 4], - msg: &str, - ) -> Result, Error> { + let assign_row = |region: &mut Region<'_, F>, + offset: usize, + q_enable: Column, + advice_columns: &[Column], + tag: &Column, + row: &[Value; 4], + msg: &str| + -> Result, Error> { let mut value_cell = None; for (index, column) in advice_columns.iter().enumerate() { let cell = region.assign_advice( @@ -289,7 +351,7 @@ impl TxTable { || row[1], )?; Ok(value_cell.unwrap()) - } + }; layouter.assign_region( || "tx table", @@ -655,6 +717,7 @@ impl RwTable { ] { region.assign_advice(|| "assign rw row on rw table", column, offset, || value)?; } + Ok(()) } @@ -667,7 +730,8 @@ impl RwTable { n_rows: usize, challenges: Value, ) -> Result<(), Error> { - layouter.assign_region( + assign_global( + layouter, || "rw table", |mut region| self.load_with_region(&mut region, rws, n_rows, challenges), ) @@ -681,6 +745,7 @@ impl RwTable { challenges: Value, ) -> Result<(), Error> { let (rows, _) = RwMap::table_assignments_prepad(rws, n_rows); + for (offset, row) in rows.iter().enumerate() { self.assign(region, offset, &row.table_assignment(challenges))?; } @@ -1239,6 +1304,8 @@ impl From for usize { /// Table with Block header fields #[derive(Clone, Debug)] pub struct BlockTable { + /// Enabled. It is equivalent to tag != Null. + pub q_enable: Column, /// Tag pub tag: Column, /// Index @@ -1251,6 +1318,7 @@ impl BlockTable { /// Construct a new BlockTable pub fn construct(meta: &mut ConstraintSystem) -> Self { Self { + q_enable: meta.fixed_column(), tag: meta.fixed_column(), index: meta.advice_column(), value: meta.advice_column_in(SecondPhase), @@ -1288,6 +1356,12 @@ impl BlockTable { .count(); cum_num_txs += num_txs; for row in block_ctx.table_assignments(num_txs, cum_num_txs, 0, challenges) { + region.assign_fixed( + || format!("block table enabled {offset}"), + self.q_enable, + offset, + || Value::known(F::one()), + )?; region.assign_fixed( || format!("block table row {offset}"), self.tag, @@ -2493,6 +2567,8 @@ impl LookupTable for SigTable { /// - output1_rlc <- success {0, 1} #[derive(Clone, Copy, Debug)] pub struct EccTable { + /// Enabled. It is equivalent to op_type != 0. + pub q_enable: Column, /// Since the current design of the ECC circuit reserves fixed number of rows for EcAdd, EcMul /// and EcPairing ops respectively, we already know the `op_type` for each row. pub op_type: Column, @@ -2562,6 +2638,7 @@ impl EccTable { /// Construct the ECC table. pub(crate) fn construct(meta: &mut ConstraintSystem) -> Self { Self { + q_enable: meta.fixed_column(), op_type: meta.fixed_column(), is_valid: meta.advice_column(), arg1_rlc: meta.advice_column_in(SecondPhase), @@ -2664,6 +2741,12 @@ impl EccTable { || "ecc table dev load", |mut region| { for (i, row) in assignments.iter().enumerate() { + region.assign_fixed( + || format!("ecc table row = {i}, enabled"), + self.q_enable, + i, + || Value::known(F::one()), + )?; region.assign_fixed( || format!("ecc table row = {i}, op_type"), self.op_type, diff --git a/zkevm-circuits/src/tx_circuit.rs b/zkevm-circuits/src/tx_circuit.rs index bd886e5c5c..db3ee75eda 100644 --- a/zkevm-circuits/src/tx_circuit.rs +++ b/zkevm-circuits/src/tx_circuit.rs @@ -2277,9 +2277,10 @@ impl TxCircuitConfig { )?; // fixed columns + let tag_f = F::from(usize::from(tag) as u64); for (col_anno, col, col_val) in [ ("q_enable", self.tx_table.q_enable, F::one()), - ("tag", self.tx_table.tag, F::from(usize::from(tag) as u64)), + ("tag", self.tx_table.tag, tag_f), ] { region.assign_fixed(|| col_anno, col, offset, || Value::known(col_val))?; } @@ -2548,6 +2549,12 @@ impl TxCircuit { .enumerate() { let row = offset * 3 + j; + region.assign_fixed( + || "block table enabled", + config.block_table.q_enable, + row, + || Value::known(F::one()), + )?; region.assign_fixed( || "block_table.tag", config.block_table.tag, diff --git a/zkevm-circuits/src/util.rs b/zkevm-circuits/src/util.rs index a0e10fac08..95004bdb77 100644 --- a/zkevm-circuits/src/util.rs +++ b/zkevm-circuits/src/util.rs @@ -16,7 +16,7 @@ use halo2_proofs::plonk::SecondPhase; use crate::{evm_circuit::util::rlc, table::TxLogFieldTag, witness}; use eth_types::{Field, ToAddress, Word}; pub use ethers_core::types::{Address, U256}; -pub use gadgets::util::Expr; +pub use gadgets::util::{assign_global, Expr}; /// A wrapper of is_zero in gadgets which gives is_zero at any rotation pub mod is_zero; @@ -207,7 +207,7 @@ pub(crate) fn build_tx_log_expression( /// table(s) if any). pub trait SubCircuit { /// Configuration of the SubCircuit. - type Config: SubCircuitConfig; + type Config; /// Returns number of unusable rows of the SubCircuit, which should be /// `meta.blinding_factors() + 1`.