diff --git a/Cargo.toml b/Cargo.toml index ac534b4264..1c79548ab0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,5 @@ members = [ "halo2_middleware", "halo2_backend", "halo2_common", -] \ No newline at end of file +] +resolver = "2" diff --git a/halo2_backend/Cargo.toml b/halo2_backend/Cargo.toml index 807436a131..1d8ecc8e38 100644 --- a/halo2_backend/Cargo.toml +++ b/halo2_backend/Cargo.toml @@ -57,7 +57,6 @@ bits = ["halo2curves/bits"] gadget-traces = ["backtrace"] sanity-checks = [] batch = ["rand_core/getrandom"] -circuit-params = [] cost-estimator = ["serde", "serde_derive"] derive_serde = ["halo2curves/derive_serde"] diff --git a/halo2_backend/src/helpers.rs b/halo2_backend/src/helpers.rs index 25f1072832..a2dde04b17 100644 --- a/halo2_backend/src/helpers.rs +++ b/halo2_backend/src/helpers.rs @@ -3,7 +3,7 @@ pub(crate) use halo2_common::helpers::{SerdeFormat, SerdePrimeField}; use halo2_middleware::ff::PrimeField; use std::io; -pub(crate) use halo2_common::helpers::{pack, unpack, CurveRead, SerdeCurveAffine}; +pub(crate) use halo2_common::helpers::{CurveRead, SerdeCurveAffine}; /// Reads a vector of polynomials from buffer pub(crate) fn read_polynomial_vec( diff --git a/halo2_backend/src/lib.rs b/halo2_backend/src/lib.rs index 7dbf971d03..05a0a50ca1 100644 --- a/halo2_backend/src/lib.rs +++ b/halo2_backend/src/lib.rs @@ -5,6 +5,5 @@ pub mod poly; pub mod transcript; // Internal re-exports -pub use halo2_common::circuit; pub use halo2_common::multicore; pub use halo2_common::SerdeFormat; diff --git a/halo2_backend/src/plonk.rs b/halo2_backend/src/plonk.rs index de7530d5cb..c8aba93e6e 100644 --- a/halo2_backend/src/plonk.rs +++ b/halo2_backend/src/plonk.rs @@ -3,22 +3,22 @@ use group::ff::{Field, FromUniformBytes, PrimeField}; use crate::arithmetic::CurveAffine; use crate::helpers::{ - self, polynomial_slice_byte_length, read_polynomial_vec, write_polynomial_slice, - SerdeCurveAffine, SerdePrimeField, + polynomial_slice_byte_length, read_polynomial_vec, write_polynomial_slice, SerdeCurveAffine, + SerdePrimeField, }; +use crate::plonk::circuit::{ConstraintSystemBack, PinnedConstraintSystem}; use crate::poly::{ Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, PinnedEvaluationDomain, Polynomial, }; use crate::transcript::{ChallengeScalar, EncodedChallenge, Transcript}; pub(crate) use evaluation::Evaluator; -use halo2_common::plonk::{Circuit, ConstraintSystem, PinnedConstraintSystem}; use halo2_common::SerdeFormat; use std::io; -pub(crate) use halo2_common::plonk::Error; - +mod circuit; +mod error; mod evaluation; pub mod keygen; mod lookup; @@ -28,6 +28,8 @@ mod shuffle; mod vanishing; pub mod verifier; +pub use error::*; + /// This is a verifying key which allows for the verification of proofs for a /// particular circuit. #[derive(Clone, Debug)] @@ -39,20 +41,18 @@ pub struct VerifyingKey { /// Permutation verifying key permutation: permutation::VerifyingKey, /// Constraint system - cs: ConstraintSystem, + cs: ConstraintSystemBack, /// Cached maximum degree of `cs` (which doesn't change after construction). cs_degree: usize, /// The representative of this `VerifyingKey` in transcripts. transcript_repr: C::Scalar, - /// Selectors - selectors: Vec>, - // TODO: Use setter/getter https://github.com/privacy-scaling-explorations/halo2/issues/259 - /// Whether selector compression is turned on or not. - pub compress_selectors: bool, + /// Legacy field that indicates wether the circuit was compiled with compressed selectors or + /// not using the legacy API. + pub compress_selectors: Option, } // Current version of the VK -const VERSION: u8 = 0x03; +const VERSION: u8 = 0x04; impl VerifyingKey where @@ -74,23 +74,12 @@ where assert!(*k <= C::Scalar::S); // k value fits in 1 byte writer.write_all(&[*k as u8])?; - writer.write_all(&[self.compress_selectors as u8])?; writer.write_all(&(self.fixed_commitments.len() as u32).to_le_bytes())?; for commitment in &self.fixed_commitments { commitment.write(writer, format)?; } self.permutation.write(writer, format)?; - if !self.compress_selectors { - assert!(self.selectors.is_empty()); - } - // write self.selectors - for selector in &self.selectors { - // since `selector` is filled with `bool`, we pack them 8 at a time into bytes and then write - for bits in selector.chunks(8) { - writer.write_all(&[helpers::pack(bits)])?; - } - } Ok(()) } @@ -104,10 +93,10 @@ where /// Checks that field elements are less than modulus, and then checks that the point is on the curve. /// - `RawBytesUnchecked`: Reads an uncompressed curve element with coordinates in Montgomery form; /// does not perform any checks - pub fn read>( + pub fn read( reader: &mut R, format: SerdeFormat, - #[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params, + cs: ConstraintSystemBack, ) -> io::Result { let mut version_byte = [0u8; 1]; reader.read_exact(&mut version_byte)?; @@ -131,20 +120,7 @@ where ), )); } - let mut compress_selectors = [0u8; 1]; - reader.read_exact(&mut compress_selectors)?; - if compress_selectors[0] != 0 && compress_selectors[0] != 1 { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "unexpected compress_selectors not boolean", - )); - } - let compress_selectors = compress_selectors[0] == 1; - let (domain, cs, _) = keygen::create_domain::( - k as u32, - #[cfg(feature = "circuit-params")] - params, - ); + let domain = keygen::create_domain::(&cs, k as u32); let mut num_fixed_columns = [0u8; 4]; reader.read_exact(&mut num_fixed_columns)?; let num_fixed_columns = u32::from_le_bytes(num_fixed_columns); @@ -155,36 +131,7 @@ where let permutation = permutation::VerifyingKey::read(reader, &cs.permutation, format)?; - let (cs, selectors) = if compress_selectors { - // read selectors - let selectors: Vec> = vec![vec![false; 1 << k]; cs.num_selectors] - .into_iter() - .map(|mut selector| { - let mut selector_bytes = vec![0u8; (selector.len() + 7) / 8]; - reader.read_exact(&mut selector_bytes)?; - for (bits, byte) in selector.chunks_mut(8).zip(selector_bytes) { - helpers::unpack(byte, bits); - } - Ok(selector) - }) - .collect::>()?; - let (cs, _) = cs.compress_selectors(selectors.clone()); - (cs, selectors) - } else { - // we still need to replace selectors with fixed Expressions in `cs` - let fake_selectors = vec![vec![]; cs.num_selectors]; - let (cs, _) = cs.directly_convert_selectors_to_fixed(fake_selectors); - (cs, vec![]) - }; - - Ok(Self::from_parts( - domain, - fixed_commitments, - permutation, - cs, - selectors, - compress_selectors, - )) + Ok(Self::from_parts(domain, fixed_commitments, permutation, cs)) } /// Writes a verifying key to a vector of bytes using [`Self::write`]. @@ -195,17 +142,12 @@ where } /// Reads a verification key from a slice of bytes using [`Self::read`]. - pub fn from_bytes>( + pub fn from_bytes( mut bytes: &[u8], format: SerdeFormat, - #[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params, + cs: ConstraintSystemBack, ) -> io::Result { - Self::read::<_, ConcreteCircuit>( - &mut bytes, - format, - #[cfg(feature = "circuit-params")] - params, - ) + Self::read(&mut bytes, format, cs) } } @@ -216,21 +158,13 @@ impl VerifyingKey { { 10 + (self.fixed_commitments.len() * C::byte_length(format)) + self.permutation.bytes_length(format) - + self.selectors.len() - * (self - .selectors - .get(0) - .map(|selector| (selector.len() + 7) / 8) - .unwrap_or(0)) } fn from_parts( domain: EvaluationDomain, fixed_commitments: Vec, permutation: permutation::VerifyingKey, - cs: ConstraintSystem, - selectors: Vec>, - compress_selectors: bool, + cs: ConstraintSystemBack, ) -> Self where C::ScalarExt: FromUniformBytes<64>, @@ -246,8 +180,7 @@ impl VerifyingKey { cs_degree, // Temporary, this is not pinned. transcript_repr: C::Scalar::ZERO, - selectors, - compress_selectors, + compress_selectors: None, }; let mut hasher = Blake2bParams::new() @@ -300,7 +233,7 @@ impl VerifyingKey { } /// Returns `ConstraintSystem` - pub fn cs(&self) -> &ConstraintSystem { + pub fn cs(&self) -> &ConstraintSystemBack { &self.cs } @@ -400,17 +333,12 @@ where /// Checks that field elements are less than modulus, and then checks that the point is on the curve. /// - `RawBytesUnchecked`: Reads an uncompressed curve element with coordinates in Montgomery form; /// does not perform any checks - pub fn read>( + pub fn read( reader: &mut R, format: SerdeFormat, - #[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params, + cs: ConstraintSystemBack, ) -> io::Result { - let vk = VerifyingKey::::read::( - reader, - format, - #[cfg(feature = "circuit-params")] - params, - )?; + let vk = VerifyingKey::::read::(reader, format, cs)?; let l0 = Polynomial::read(reader, format)?; let l_last = Polynomial::read(reader, format)?; let l_active_row = Polynomial::read(reader, format)?; @@ -440,17 +368,12 @@ where } /// Reads a proving key from a slice of bytes using [`Self::read`]. - pub fn from_bytes>( + pub fn from_bytes( mut bytes: &[u8], format: SerdeFormat, - #[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params, + cs: ConstraintSystemBack, ) -> io::Result { - Self::read::<_, ConcreteCircuit>( - &mut bytes, - format, - #[cfg(feature = "circuit-params")] - params, - ) + Self::read(&mut bytes, format, cs) } } diff --git a/halo2_backend/src/plonk/circuit.rs b/halo2_backend/src/plonk/circuit.rs new file mode 100644 index 0000000000..30de9d3ff5 --- /dev/null +++ b/halo2_backend/src/plonk/circuit.rs @@ -0,0 +1,389 @@ +use group::ff::Field; +use halo2_middleware::circuit::{Any, ChallengeMid, ColumnMid, Gate}; +use halo2_middleware::expression::{Expression, Variable}; +use halo2_middleware::poly::Rotation; +use halo2_middleware::{lookup, permutation::ArgumentMid, shuffle}; + +// TODO: Reuse ColumnMid inside this. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct QueryBack { + /// Query index + pub(crate) index: usize, + /// Column index + pub(crate) column_index: usize, + /// The type of the column. + pub(crate) column_type: Any, + /// Rotation of this query + pub(crate) rotation: Rotation, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum VarBack { + /// This is a generic column query + Query(QueryBack), + /// This is a challenge + Challenge(ChallengeMid), +} + +impl Variable for VarBack { + fn degree(&self) -> usize { + match self { + VarBack::Query(_) => 1, + VarBack::Challenge(_) => 0, + } + } + + fn complexity(&self) -> usize { + match self { + VarBack::Query(_) => 1, + VarBack::Challenge(_) => 0, + } + } + + fn write_identifier(&self, _writer: &mut W) -> std::io::Result<()> { + unimplemented!("unused method") + } +} + +pub(crate) type ExpressionBack = Expression; +pub(crate) type GateBack = Gate; +pub(crate) type LookupArgumentBack = lookup::Argument; +pub(crate) type ShuffleArgumentBack = shuffle::Argument; +pub(crate) type PermutationArgumentBack = ArgumentMid; + +/// This is a description of the circuit environment, such as the gate, column and permutation +/// arrangements. This type is internal to the backend and will appear in the verifying key. +#[derive(Debug, Clone)] +pub struct ConstraintSystemBack { + pub(crate) num_fixed_columns: usize, + pub(crate) num_advice_columns: usize, + pub(crate) num_instance_columns: usize, + pub(crate) num_challenges: usize, + + /// Contains the index of each advice column that is left unblinded. + pub(crate) unblinded_advice_columns: Vec, + + /// Contains the phase for each advice column. Should have same length as num_advice_columns. + pub(crate) advice_column_phase: Vec, + /// Contains the phase for each challenge. Should have same length as num_challenges. + pub(crate) challenge_phase: Vec, + + pub(crate) gates: Vec>, + pub(crate) advice_queries: Vec<(ColumnMid, Rotation)>, + // Contains an integer for each advice column + // identifying how many distinct queries it has + // so far; should be same length as num_advice_columns. + pub(crate) num_advice_queries: Vec, + pub(crate) instance_queries: Vec<(ColumnMid, Rotation)>, + pub(crate) fixed_queries: Vec<(ColumnMid, Rotation)>, + + // Permutation argument for performing equality constraints + pub(crate) permutation: PermutationArgumentBack, + + // Vector of lookup arguments, where each corresponds to a sequence of + // input expressions and a sequence of table expressions involved in the lookup. + pub(crate) lookups: Vec>, + + // Vector of shuffle arguments, where each corresponds to a sequence of + // input expressions and a sequence of shuffle expressions involved in the shuffle. + pub(crate) shuffles: Vec>, + + // The minimum degree required by the circuit, which can be set to a + // larger amount than actually needed. This can be used, for example, to + // force the permutation argument to involve more columns in the same set. + pub(crate) minimum_degree: Option, +} + +impl ConstraintSystemBack { + /// Compute the degree of the constraint system (the maximum degree of all + /// constraints). + pub fn degree(&self) -> usize { + // The permutation argument will serve alongside the gates, so must be + // accounted for. + let mut degree = permutation_argument_required_degree(); + + // The lookup argument also serves alongside the gates and must be accounted + // for. + degree = std::cmp::max( + degree, + self.lookups + .iter() + .map(|l| lookup_argument_required_degree(l)) + .max() + .unwrap_or(1), + ); + + // The lookup argument also serves alongside the gates and must be accounted + // for. + degree = std::cmp::max( + degree, + self.shuffles + .iter() + .map(|l| shuffle_argument_required_degree(l)) + .max() + .unwrap_or(1), + ); + + // Account for each gate to ensure our quotient polynomial is the + // correct degree and that our extended domain is the right size. + degree = std::cmp::max( + degree, + self.gates + .iter() + .map(|gate| gate.poly.degree()) + .max() + .unwrap_or(0), + ); + + std::cmp::max(degree, self.minimum_degree.unwrap_or(1)) + } + + /// Compute the number of blinding factors necessary to perfectly blind + /// each of the prover's witness polynomials. + pub fn blinding_factors(&self) -> usize { + // All of the prover's advice columns are evaluated at no more than + let factors = *self.num_advice_queries.iter().max().unwrap_or(&1); + // distinct points during gate checks. + + // - The permutation argument witness polynomials are evaluated at most 3 times. + // - Each lookup argument has independent witness polynomials, and they are + // evaluated at most 2 times. + let factors = std::cmp::max(3, factors); + + // Each polynomial is evaluated at most an additional time during + // multiopen (at x_3 to produce q_evals): + let factors = factors + 1; + + // h(x) is derived by the other evaluations so it does not reveal + // anything; in fact it does not even appear in the proof. + + // h(x_3) is also not revealed; the verifier only learns a single + // evaluation of a polynomial in x_1 which has h(x_3) and another random + // polynomial evaluated at x_3 as coefficients -- this random polynomial + // is "random_poly" in the vanishing argument. + + // Add an additional blinding factor as a slight defense against + // off-by-one errors. + factors + 1 + } + + /// Returns the minimum necessary rows that need to exist in order to + /// account for e.g. blinding factors. + pub fn minimum_rows(&self) -> usize { + self.blinding_factors() // m blinding factors + + 1 // for l_{-(m + 1)} (l_last) + + 1 // for l_0 (just for extra breathing room for the permutation + // argument, to essentially force a separation in the + // permutation polynomial between the roles of l_last, l_0 + // and the interstitial values.) + + 1 // for at least one row + } + + pub fn get_any_query_index(&self, column: ColumnMid, at: Rotation) -> usize { + let queries = match column.column_type { + Any::Advice(_) => &self.advice_queries, + Any::Fixed => &self.fixed_queries, + Any::Instance => &self.instance_queries, + }; + for (index, instance_query) in queries.iter().enumerate() { + if instance_query == &(column, at) { + return index; + } + } + panic!("get_any_query_index called for non-existent query"); + } + + /// Returns the list of phases + pub fn phases(&self) -> impl Iterator { + let max_phase = self + .advice_column_phase + .iter() + .max() + .copied() + .unwrap_or_default(); + 0..=max_phase + } + + /// Obtain a pinned version of this constraint system; a structure with the + /// minimal parameters needed to determine the rest of the constraint + /// system. + pub fn pinned(&self) -> PinnedConstraintSystem<'_, F> { + PinnedConstraintSystem { + num_fixed_columns: &self.num_fixed_columns, + num_advice_columns: &self.num_advice_columns, + num_instance_columns: &self.num_instance_columns, + num_challenges: &self.num_challenges, + advice_column_phase: &self.advice_column_phase, + challenge_phase: &self.challenge_phase, + gates: PinnedGates(&self.gates), + fixed_queries: &self.fixed_queries, + advice_queries: &self.advice_queries, + instance_queries: &self.instance_queries, + permutation: &self.permutation, + lookups: &self.lookups, + shuffles: &self.shuffles, + minimum_degree: &self.minimum_degree, + } + } +} + +struct PinnedGates<'a, F: Field>(&'a Vec>); + +impl<'a, F: Field> std::fmt::Debug for PinnedGates<'a, F> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + f.debug_list() + .entries(self.0.iter().map(|gate| &gate.poly)) + .finish() + } +} + +/// Represents the minimal parameters that determine a `ConstraintSystem`. +pub struct PinnedConstraintSystem<'a, F: Field> { + num_fixed_columns: &'a usize, + num_advice_columns: &'a usize, + num_instance_columns: &'a usize, + num_challenges: &'a usize, + advice_column_phase: &'a Vec, + challenge_phase: &'a Vec, + gates: PinnedGates<'a, F>, + advice_queries: &'a Vec<(ColumnMid, Rotation)>, + instance_queries: &'a Vec<(ColumnMid, Rotation)>, + fixed_queries: &'a Vec<(ColumnMid, Rotation)>, + permutation: &'a PermutationArgumentBack, + lookups: &'a Vec>, + shuffles: &'a Vec>, + minimum_degree: &'a Option, +} + +impl<'a, F: Field> std::fmt::Debug for PinnedConstraintSystem<'a, F> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut debug_struct = f.debug_struct("PinnedConstraintSystem"); + debug_struct + .field("num_fixed_columns", self.num_fixed_columns) + .field("num_advice_columns", self.num_advice_columns) + .field("num_instance_columns", self.num_instance_columns); + // Only show multi-phase related fields if it's used. + if *self.num_challenges > 0 { + debug_struct + .field("num_challenges", self.num_challenges) + .field("advice_column_phase", self.advice_column_phase) + .field("challenge_phase", self.challenge_phase); + } + debug_struct + .field("gates", &self.gates) + .field("advice_queries", self.advice_queries) + .field("instance_queries", self.instance_queries) + .field("fixed_queries", self.fixed_queries) + .field("permutation", self.permutation) + .field("lookups", self.lookups); + if !self.shuffles.is_empty() { + debug_struct.field("shuffles", self.shuffles); + } + debug_struct.field("minimum_degree", self.minimum_degree); + debug_struct.finish() + } +} + +// Cost functions: arguments required degree + +/// Returns the minimum circuit degree required by the permutation argument. +/// The argument may use larger degree gates depending on the actual +/// circuit's degree and how many columns are involved in the permutation. +fn permutation_argument_required_degree() -> usize { + // degree 2: + // l_0(X) * (1 - z(X)) = 0 + // + // We will fit as many polynomials p_i(X) as possible + // into the required degree of the circuit, so the + // following will not affect the required degree of + // this middleware. + // + // (1 - (l_last(X) + l_blind(X))) * ( + // z(\omega X) \prod (p(X) + \beta s_i(X) + \gamma) + // - z(X) \prod (p(X) + \delta^i \beta X + \gamma) + // ) + // + // On the first sets of columns, except the first + // set, we will do + // + // l_0(X) * (z(X) - z'(\omega^(last) X)) = 0 + // + // where z'(X) is the permutation for the previous set + // of columns. + // + // On the final set of columns, we will do + // + // degree 3: + // l_last(X) * (z'(X)^2 - z'(X)) = 0 + // + // which will allow the last value to be zero to + // ensure the argument is perfectly complete. + + // There are constraints of degree 3 regardless of the + // number of columns involved. + 3 +} + +fn lookup_argument_required_degree(arg: &lookup::Argument) -> usize { + assert_eq!(arg.input_expressions.len(), arg.table_expressions.len()); + + // The first value in the permutation poly should be one. + // degree 2: + // l_0(X) * (1 - z(X)) = 0 + // + // The "last" value in the permutation poly should be a boolean, for + // completeness and soundness. + // degree 3: + // l_last(X) * (z(X)^2 - z(X)) = 0 + // + // Enable the permutation argument for only the rows involved. + // degree (2 + input_degree + table_degree) or 4, whichever is larger: + // (1 - (l_last(X) + l_blind(X))) * ( + // z(\omega X) (a'(X) + \beta) (s'(X) + \gamma) + // - z(X) (\theta^{m-1} a_0(X) + ... + a_{m-1}(X) + \beta) (\theta^{m-1} s_0(X) + ... + s_{m-1}(X) + \gamma) + // ) = 0 + // + // The first two values of a' and s' should be the same. + // degree 2: + // l_0(X) * (a'(X) - s'(X)) = 0 + // + // Either the two values are the same, or the previous + // value of a' is the same as the current value. + // degree 3: + // (1 - (l_last(X) + l_blind(X))) * (a′(X) − s′(X))⋅(a′(X) − a′(\omega^{-1} X)) = 0 + let mut input_degree = 1; + for expr in arg.input_expressions.iter() { + input_degree = std::cmp::max(input_degree, expr.degree()); + } + let mut table_degree = 1; + for expr in arg.table_expressions.iter() { + table_degree = std::cmp::max(table_degree, expr.degree()); + } + + // In practice because input_degree and table_degree are initialized to + // one, the latter half of this max() invocation is at least 4 always, + // rendering this call pointless except to be explicit in case we change + // the initialization of input_degree/table_degree in the future. + std::cmp::max( + // (1 - (l_last + l_blind)) z(\omega X) (a'(X) + \beta) (s'(X) + \gamma) + 4, + // (1 - (l_last + l_blind)) z(X) (\theta^{m-1} a_0(X) + ... + a_{m-1}(X) + \beta) (\theta^{m-1} s_0(X) + ... + s_{m-1}(X) + \gamma) + 2 + input_degree + table_degree, + ) +} + +fn shuffle_argument_required_degree(arg: &shuffle::Argument) -> usize { + assert_eq!(arg.input_expressions.len(), arg.shuffle_expressions.len()); + + let mut input_degree = 1; + for expr in arg.input_expressions.iter() { + input_degree = std::cmp::max(input_degree, expr.degree()); + } + let mut shuffle_degree = 1; + for expr in arg.shuffle_expressions.iter() { + shuffle_degree = std::cmp::max(shuffle_degree, expr.degree()); + } + + // (1 - (l_last + l_blind)) (z(\omega X) (s(X) + \gamma) - z(X) (a(X) + \gamma)) + std::cmp::max(2 + shuffle_degree, 2 + input_degree) +} diff --git a/halo2_backend/src/plonk/error.rs b/halo2_backend/src/plonk/error.rs new file mode 100644 index 0000000000..716e466d1b --- /dev/null +++ b/halo2_backend/src/plonk/error.rs @@ -0,0 +1,76 @@ +use std::error; +use std::fmt; +use std::io; + +use halo2_middleware::circuit::ColumnMid; + +/// This is an error that could occur during proving. +#[derive(Debug)] +pub enum Error { + /// The provided instances do not match the circuit parameters. + InvalidInstances, + /// The constraint system is not satisfied. + ConstraintSystemFailure, + /// Out of bounds index passed to a backend + BoundsFailure, + /// Opening error + Opening, + /// Transcript error + Transcript(io::Error), + /// `k` is too small for the given circuit. + NotEnoughRowsAvailable { + /// The current value of `k` being used. + current_k: u32, + }, + /// Instance provided exceeds number of available rows + InstanceTooLarge, + /// The instance sets up a copy constraint involving a column that has not been + /// included in the permutation. + ColumnNotInPermutation(ColumnMid), + /// Generic error not covered by previous cases + Other(String), +} + +impl From for Error { + fn from(error: io::Error) -> Self { + // The only place we can get io::Error from is the transcript. + Error::Transcript(error) + } +} + +impl Error { + /// Constructs an `Error::NotEnoughRowsAvailable`. + pub fn not_enough_rows_available(current_k: u32) -> Self { + Error::NotEnoughRowsAvailable { current_k } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::InvalidInstances => write!(f, "Provided instances do not match the circuit"), + Error::ConstraintSystemFailure => write!(f, "The constraint system is not satisfied"), + Error::BoundsFailure => write!(f, "An out-of-bounds index was passed to the backend"), + Error::Opening => write!(f, "Multi-opening proof was invalid"), + Error::Transcript(e) => write!(f, "Transcript error: {e}"), + Error::NotEnoughRowsAvailable { current_k } => write!( + f, + "k = {current_k} is too small for the given circuit. Try using a larger value of k", + ), + Error::InstanceTooLarge => write!(f, "Instance vectors are larger than the circuit"), + Error::ColumnNotInPermutation(column) => { + write!(f, "Column {column:?} must be included in the permutation",) + } + Error::Other(error) => write!(f, "Other: {error}"), + } + } +} + +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + Error::Transcript(e) => Some(e), + _ => None, + } + } +} diff --git a/halo2_backend/src/plonk/evaluation.rs b/halo2_backend/src/plonk/evaluation.rs index 697f29c43d..d3c911f906 100644 --- a/halo2_backend/src/plonk/evaluation.rs +++ b/halo2_backend/src/plonk/evaluation.rs @@ -3,14 +3,16 @@ //! - Evaluates an Expression using Lagrange basis use crate::multicore; -use crate::plonk::{lookup, permutation, ProvingKey}; +use crate::plonk::{ + circuit::{ConstraintSystemBack, ExpressionBack, VarBack}, + lookup, permutation, ProvingKey, +}; use crate::poly::{Basis, LagrangeBasis}; use crate::{ arithmetic::{parallelize, CurveAffine}, poly::{Coeff, ExtendedLagrangeCoeff, Polynomial}, }; use group::ff::{Field, PrimeField, WithSmallOrderMulGroup}; -use halo2_common::plonk::{ConstraintSystem, Expression}; use halo2_middleware::circuit::Any; use halo2_middleware::poly::Rotation; @@ -225,18 +227,13 @@ struct CalculationInfo { } impl Evaluator { - /// Creates a new evaluation structure from a [`ConstraintSystem`] - pub fn new(cs: &ConstraintSystem) -> Self { + /// Creates a new evaluation structure from a [`ConstraintSystemBack`] + pub fn new(cs: &ConstraintSystemBack) -> Self { let mut ev = Evaluator::default(); - // Custom gates let mut parts = Vec::new(); for gate in cs.gates.iter() { - parts.extend( - gate.polynomials() - .iter() - .map(|poly| ev.custom_gates.add_expression(poly)), - ); + parts.push(ev.custom_gates.add_expression(&gate.poly)); } ev.custom_gates.add_calculation(Calculation::Horner( ValueSource::PreviousValue(), @@ -248,7 +245,7 @@ impl Evaluator { for lookup in cs.lookups.iter() { let mut graph = GraphEvaluator::default(); - let mut evaluate_lc = |expressions: &Vec>| { + let mut evaluate_lc = |expressions: &Vec>| { let parts = expressions .iter() .map(|expr| graph.add_expression(expr)) @@ -280,7 +277,8 @@ impl Evaluator { // Shuffles for shuffle in cs.shuffles.iter() { - let evaluate_lc = |expressions: &Vec>, graph: &mut GraphEvaluator| { + let evaluate_lc = |expressions: &Vec>, + graph: &mut GraphEvaluator| { let parts = expressions .iter() .map(|expr| graph.add_expression(expr)) @@ -457,10 +455,10 @@ impl Evaluator { let mut left = set.permutation_product_coset[r_next]; for (values, permutation) in columns .iter() - .map(|&column| match column.column_type() { - Any::Advice(_) => &advice[column.index()], - Any::Fixed => &fixed[column.index()], - Any::Instance => &instance[column.index()], + .map(|&column| match column.column_type { + Any::Advice(_) => &advice[column.index], + Any::Fixed => &fixed[column.index], + Any::Instance => &instance[column.index], }) .zip(cosets.iter()) { @@ -468,10 +466,10 @@ impl Evaluator { } let mut right = set.permutation_product_coset[idx]; - for values in columns.iter().map(|&column| match column.column_type() { - Any::Advice(_) => &advice[column.index()], - Any::Fixed => &fixed[column.index()], - Any::Instance => &instance[column.index()], + for values in columns.iter().map(|&column| match column.column_type { + Any::Advice(_) => &advice[column.index], + Any::Fixed => &fixed[column.index], + Any::Instance => &instance[column.index], }) { right *= values[idx] + current_delta + gamma; current_delta *= &C::Scalar::DELTA; @@ -690,36 +688,29 @@ impl GraphEvaluator { } /// Generates an optimized evaluation for the expression - fn add_expression(&mut self, expr: &Expression) -> ValueSource { + fn add_expression(&mut self, expr: &ExpressionBack) -> ValueSource { match expr { - Expression::Constant(scalar) => self.add_constant(scalar), - Expression::Selector(_selector) => unreachable!(), - Expression::Fixed(query) => { - let rot_idx = self.add_rotation(&query.rotation); - self.add_calculation(Calculation::Store(ValueSource::Fixed( - query.column_index, - rot_idx, - ))) - } - Expression::Advice(query) => { - let rot_idx = self.add_rotation(&query.rotation); - self.add_calculation(Calculation::Store(ValueSource::Advice( - query.column_index, - rot_idx, - ))) - } - Expression::Instance(query) => { + ExpressionBack::Constant(scalar) => self.add_constant(scalar), + ExpressionBack::Var(VarBack::Query(query)) => { let rot_idx = self.add_rotation(&query.rotation); - self.add_calculation(Calculation::Store(ValueSource::Instance( - query.column_index, - rot_idx, - ))) + match query.column_type { + Any::Fixed => self.add_calculation(Calculation::Store(ValueSource::Fixed( + query.column_index, + rot_idx, + ))), + Any::Advice(_) => self.add_calculation(Calculation::Store( + ValueSource::Advice(query.column_index, rot_idx), + )), + Any::Instance => self.add_calculation(Calculation::Store( + ValueSource::Instance(query.column_index, rot_idx), + )), + } } - Expression::Challenge(challenge) => self.add_calculation(Calculation::Store( - ValueSource::Challenge(challenge.index()), - )), - Expression::Negated(a) => match **a { - Expression::Constant(scalar) => self.add_constant(&-scalar), + ExpressionBack::Var(VarBack::Challenge(challenge)) => self.add_calculation( + Calculation::Store(ValueSource::Challenge(challenge.index())), + ), + ExpressionBack::Negated(a) => match **a { + ExpressionBack::Constant(scalar) => self.add_constant(&-scalar), _ => { let result_a = self.add_expression(a); match result_a { @@ -728,10 +719,10 @@ impl GraphEvaluator { } } }, - Expression::Sum(a, b) => { + ExpressionBack::Sum(a, b) => { // Undo subtraction stored as a + (-b) in expressions match &**b { - Expression::Negated(b_int) => { + ExpressionBack::Negated(b_int) => { let result_a = self.add_expression(a); let result_b = self.add_expression(b_int); if result_a == ValueSource::Constant(0) { @@ -757,7 +748,7 @@ impl GraphEvaluator { } } } - Expression::Product(a, b) => { + ExpressionBack::Product(a, b) => { let result_a = self.add_expression(a); let result_b = self.add_expression(b); if result_a == ValueSource::Constant(0) || result_b == ValueSource::Constant(0) { @@ -778,7 +769,7 @@ impl GraphEvaluator { self.add_calculation(Calculation::Mul(result_b, result_a)) } } - Expression::Scaled(a, f) => { + ExpressionBack::Scaled(a, f) => { if *f == C::ScalarExt::ZERO { ValueSource::Constant(0) } else if *f == C::ScalarExt::ONE { @@ -853,9 +844,9 @@ impl GraphEvaluator { } } -/// Simple evaluation of an [`Expression`] over the provided lagrange polynomials +/// Simple evaluation of an [`ExpressionBack`] over the provided lagrange polynomials pub fn evaluate( - expression: &Expression, + expression: &ExpressionBack, size: usize, rot_scale: i32, fixed: &[Polynomial], @@ -870,20 +861,17 @@ pub fn evaluate( let idx = start + i; *value = expression.evaluate( &|scalar| scalar, - &|_| panic!("virtual selectors are removed during optimization"), - &|query| { - fixed[query.column_index] - [get_rotation_idx(idx, query.rotation.0, rot_scale, isize)] - }, - &|query| { - advice[query.column_index] - [get_rotation_idx(idx, query.rotation.0, rot_scale, isize)] - }, - &|query| { - instance[query.column_index] - [get_rotation_idx(idx, query.rotation.0, rot_scale, isize)] + &|var| match var { + VarBack::Challenge(challenge) => challenges[challenge.index()], + VarBack::Query(query) => { + let rot_idx = get_rotation_idx(idx, query.rotation.0, rot_scale, isize); + match query.column_type { + Any::Fixed => fixed[query.column_index][rot_idx], + Any::Advice(_) => advice[query.column_index][rot_idx], + Any::Instance => instance[query.column_index][rot_idx], + } + } }, - &|challenge| challenges[challenge.index()], &|a| -a, &|a, b| a + b, &|a, b| a * b, @@ -896,17 +884,15 @@ pub fn evaluate( #[cfg(test)] mod test { + use crate::plonk::circuit::{ExpressionBack, QueryBack, VarBack}; use crate::poly::LagrangeCoeff; - use halo2_common::plonk::sealed::Phase; - use halo2_common::plonk::{AdviceQuery, Challenge, FixedQuery}; - use halo2_common::plonk::{Expression, InstanceQuery}; - use halo2_middleware::circuit::ChallengeMid; + use halo2_middleware::circuit::{Advice, Any, ChallengeMid}; use halo2_middleware::poly::Rotation; use halo2curves::pasta::pallas::{Affine, Scalar}; use super::*; - fn check(calc: Option, expr: Option>, expected: i64) { + fn check(calc: Option, expr: Option>, expected: i64) { let lagranges = |v: &[&[u64]]| -> Vec> { v.iter() .map(|vv| { @@ -956,7 +942,7 @@ mod test { expected, result ); } - fn check_expr(expr: Expression, expected: i64) { + fn check_expr(expr: ExpressionBack, expected: i64) { check(None, Some(expr), expected); } fn check_calc(calc: Calculation, expected: i64) { @@ -965,41 +951,44 @@ mod test { #[test] fn graphevaluator_values() { + use VarBack::*; // Check values for (col, rot, expected) in [(0, 0, 2), (0, 1, 3), (1, 0, 1002), (1, 1, 1003)] { check_expr( - Expression::Fixed(FixedQuery { - index: None, + ExpressionBack::Var(Query(QueryBack { + index: 0, column_index: col, + column_type: Any::Fixed, rotation: Rotation(rot), - }), + })), expected, ); } for (col, rot, expected) in [(0, 0, 4), (0, 1, 5), (1, 0, 1004), (1, 1, 1005)] { check_expr( - Expression::Advice(AdviceQuery { - index: None, + ExpressionBack::Var(Query(QueryBack { + index: 0, column_index: col, + column_type: Any::Advice(Advice { phase: 0 }), rotation: Rotation(rot), - phase: Phase(0), - }), + })), expected, ); } for (col, rot, expected) in [(0, 0, 6), (0, 1, 7), (1, 0, 1006), (1, 1, 1007)] { check_expr( - Expression::Instance(InstanceQuery { - index: None, + ExpressionBack::Var(Query(QueryBack { + index: 0, column_index: col, + column_type: Any::Instance, rotation: Rotation(rot), - }), + })), expected, ); } for (ch, expected) in [(0, 8), (1, 9)] { check_expr( - Expression::Challenge(Challenge::from(ChallengeMid { + ExpressionBack::Var(Challenge(ChallengeMid { index: ch, phase: 0, })), @@ -1016,28 +1005,31 @@ mod test { #[test] fn graphevaluator_expr_operations() { + use VarBack::*; // Check expression operations let two = || { - Box::new(Expression::::Fixed(FixedQuery { - index: None, + Box::new(ExpressionBack::::Var(Query(QueryBack { + index: 0, column_index: 0, + column_type: Any::Fixed, rotation: Rotation(0), - })) + }))) }; let three = || { - Box::new(Expression::::Fixed(FixedQuery { - index: None, + Box::new(ExpressionBack::::Var(Query(QueryBack { + index: 0, column_index: 0, + column_type: Any::Fixed, rotation: Rotation(1), - })) + }))) }; - check_expr(Expression::Sum(two(), three()), 5); - check_expr(Expression::Product(two(), three()), 6); - check_expr(Expression::Scaled(two(), Scalar::from(5)), 10); + check_expr(ExpressionBack::Sum(two(), three()), 5); + check_expr(ExpressionBack::Product(two(), three()), 6); + check_expr(ExpressionBack::Scaled(two(), Scalar::from(5)), 10); check_expr( - Expression::Sum(Expression::Negated(two()).into(), three()), + ExpressionBack::Sum(ExpressionBack::Negated(two()).into(), three()), 1, ); } diff --git a/halo2_backend/src/plonk/keygen.rs b/halo2_backend/src/plonk/keygen.rs index 8d5d6409d3..8bace825d0 100644 --- a/halo2_backend/src/plonk/keygen.rs +++ b/halo2_backend/src/plonk/keygen.rs @@ -10,39 +10,33 @@ use halo2_middleware::ff::{Field, FromUniformBytes}; use super::{evaluation::Evaluator, permutation, Polynomial, ProvingKey, VerifyingKey}; use crate::{ arithmetic::{parallelize, CurveAffine}, + plonk::circuit::{ + ConstraintSystemBack, ExpressionBack, GateBack, LookupArgumentBack, QueryBack, + ShuffleArgumentBack, VarBack, + }, + plonk::Error, poly::{ commitment::{Blind, Params}, EvaluationDomain, }, }; -use halo2_common::plonk::circuit::{Circuit, ConstraintSystem}; -use halo2_common::plonk::Error; -use halo2_middleware::circuit::CompiledCircuitV2; +use halo2_common::plonk::Queries; +use halo2_middleware::circuit::{ + Any, ColumnMid, CompiledCircuitV2, ConstraintSystemMid, ExpressionMid, VarMid, +}; +use halo2_middleware::{lookup, poly::Rotation, shuffle}; +use std::collections::HashMap; /// Creates a domain, constraint system, and configuration for a circuit. -pub(crate) fn create_domain( +pub(crate) fn create_domain( + cs: &ConstraintSystemBack, k: u32, - #[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params, -) -> ( - EvaluationDomain, - ConstraintSystem, - ConcreteCircuit::Config, -) +) -> EvaluationDomain where C: CurveAffine, - ConcreteCircuit: Circuit, { - let mut cs = ConstraintSystem::default(); - #[cfg(feature = "circuit-params")] - let config = ConcreteCircuit::configure_with_params(&mut cs, params); - #[cfg(not(feature = "circuit-params"))] - let config = ConcreteCircuit::configure(&mut cs); - let degree = cs.degree(); - - let domain = EvaluationDomain::new(degree as u32, k); - - (domain, cs, config) + EvaluationDomain::new(degree as u32, k) } /// Generate a `VerifyingKey` from an instance of `CompiledCircuit`. @@ -55,8 +49,8 @@ where P: Params<'params, C>, C::Scalar: FromUniformBytes<64>, { - let cs_backend = &circuit.cs; - let cs: ConstraintSystem = cs_backend.clone().into(); + let cs_mid = &circuit.cs; + let cs: ConstraintSystemBack = cs_mid.clone().into(); let domain = EvaluationDomain::new(cs.degree() as u32, params.k()); if (params.n() as usize) < cs.minimum_rows() { @@ -65,7 +59,7 @@ where let permutation_vk = permutation::keygen::Assembly::new_from_assembly_mid( params.n() as usize, - &cs_backend.permutation, + &cs_mid.permutation, &circuit.preprocessing.permutation, )? .build_vk(params, &domain, &cs.permutation); @@ -89,10 +83,6 @@ where fixed_commitments, permutation_vk, cs, - // selectors - Vec::new(), - // compress_selectors - false, )) } @@ -180,7 +170,7 @@ where &cs.permutation, &circuit.preprocessing.permutation, )? - .build_pk(params, &vk.domain, &cs.permutation.clone().into()); + .build_pk(params, &vk.domain, &cs.permutation.clone()); Ok(ProvingKey { vk, @@ -194,3 +184,204 @@ where ev, }) } + +struct QueriesMap { + map: HashMap<(ColumnMid, Rotation), usize>, + advice: Vec<(ColumnMid, Rotation)>, + instance: Vec<(ColumnMid, Rotation)>, + fixed: Vec<(ColumnMid, Rotation)>, +} + +impl QueriesMap { + fn add(&mut self, col: ColumnMid, rot: Rotation) -> usize { + *self + .map + .entry((col, rot)) + .or_insert_with(|| match col.column_type { + Any::Advice(_) => { + self.advice.push((col, rot)); + self.advice.len() - 1 + } + Any::Instance => { + self.instance.push((col, rot)); + self.instance.len() - 1 + } + Any::Fixed => { + self.fixed.push((col, rot)); + self.fixed.len() - 1 + } + }) + } +} + +impl QueriesMap { + fn as_expression(&mut self, expr: &ExpressionMid) -> ExpressionBack { + match expr { + ExpressionMid::Constant(c) => ExpressionBack::Constant(*c), + ExpressionMid::Var(VarMid::Query(query)) => { + let column = ColumnMid::new(query.column_index, query.column_type); + let index = self.add(column, query.rotation); + ExpressionBack::Var(VarBack::Query(QueryBack { + index, + column_index: query.column_index, + column_type: query.column_type, + rotation: query.rotation, + })) + } + ExpressionMid::Var(VarMid::Challenge(c)) => ExpressionBack::Var(VarBack::Challenge(*c)), + ExpressionMid::Negated(e) => ExpressionBack::Negated(Box::new(self.as_expression(e))), + ExpressionMid::Sum(lhs, rhs) => ExpressionBack::Sum( + Box::new(self.as_expression(lhs)), + Box::new(self.as_expression(rhs)), + ), + ExpressionMid::Product(lhs, rhs) => ExpressionBack::Product( + Box::new(self.as_expression(lhs)), + Box::new(self.as_expression(rhs)), + ), + ExpressionMid::Scaled(e, c) => { + ExpressionBack::Scaled(Box::new(self.as_expression(e)), *c) + } + } + } +} + +/// Collect queries used in gates while mapping those gates to equivalent ones with indexed +/// query references in the expressions. +fn cs_mid_collect_queries_gates( + cs_mid: &ConstraintSystemMid, + queries: &mut QueriesMap, +) -> Vec> { + cs_mid + .gates + .iter() + .map(|gate| GateBack { + name: gate.name.clone(), + poly: queries.as_expression(&gate.poly), + }) + .collect() +} + +/// Collect queries used in lookups while mapping those lookups to equivalent ones with indexed +/// query references in the expressions. +fn cs_mid_collect_queries_lookups( + cs_mid: &ConstraintSystemMid, + queries: &mut QueriesMap, +) -> Vec> { + cs_mid + .lookups + .iter() + .map(|lookup| lookup::Argument { + name: lookup.name.clone(), + input_expressions: lookup + .input_expressions + .iter() + .map(|e| queries.as_expression(e)) + .collect(), + table_expressions: lookup + .table_expressions + .iter() + .map(|e| queries.as_expression(e)) + .collect(), + }) + .collect() +} + +/// Collect queries used in shuffles while mapping those lookups to equivalent ones with indexed +/// query references in the expressions. +fn cs_mid_collect_queries_shuffles( + cs_mid: &ConstraintSystemMid, + queries: &mut QueriesMap, +) -> Vec> { + cs_mid + .shuffles + .iter() + .map(|shuffle| shuffle::Argument { + name: shuffle.name.clone(), + input_expressions: shuffle + .input_expressions + .iter() + .map(|e| queries.as_expression(e)) + .collect(), + shuffle_expressions: shuffle + .shuffle_expressions + .iter() + .map(|e| queries.as_expression(e)) + .collect(), + }) + .collect() +} + +/// Collect all queries used in the expressions of gates, lookups and shuffles. Map the +/// expressions of gates, lookups and shuffles into equivalent ones with indexed query +/// references. +#[allow(clippy::type_complexity)] +fn collect_queries( + cs_mid: &ConstraintSystemMid, +) -> ( + Queries, + Vec>, + Vec>, + Vec>, +) { + let mut queries = QueriesMap { + map: HashMap::new(), + advice: Vec::new(), + instance: Vec::new(), + fixed: Vec::new(), + }; + + let gates = cs_mid_collect_queries_gates(cs_mid, &mut queries); + let lookups = cs_mid_collect_queries_lookups(cs_mid, &mut queries); + let shuffles = cs_mid_collect_queries_shuffles(cs_mid, &mut queries); + + // Each column used in a copy constraint involves a query at rotation current. + for column in &cs_mid.permutation.columns { + match column.column_type { + Any::Instance => { + queries.add(ColumnMid::new(column.index, Any::Instance), Rotation::cur()) + } + Any::Fixed => queries.add(ColumnMid::new(column.index, Any::Fixed), Rotation::cur()), + Any::Advice(advice) => queries.add( + ColumnMid::new(column.index, Any::Advice(advice)), + Rotation::cur(), + ), + }; + } + + let mut num_advice_queries = vec![0; cs_mid.num_advice_columns]; + for (column, _) in queries.advice.iter() { + num_advice_queries[column.index] += 1; + } + + let queries = Queries { + advice: queries.advice, + instance: queries.instance, + fixed: queries.fixed, + num_advice_queries, + }; + (queries, gates, lookups, shuffles) +} + +impl From> for ConstraintSystemBack { + fn from(cs_mid: ConstraintSystemMid) -> Self { + let (queries, gates, lookups, shuffles) = collect_queries(&cs_mid); + Self { + num_fixed_columns: cs_mid.num_fixed_columns, + num_advice_columns: cs_mid.num_advice_columns, + num_instance_columns: cs_mid.num_instance_columns, + num_challenges: cs_mid.num_challenges, + unblinded_advice_columns: cs_mid.unblinded_advice_columns, + advice_column_phase: cs_mid.advice_column_phase, + challenge_phase: cs_mid.challenge_phase, + gates, + advice_queries: queries.advice, + num_advice_queries: queries.num_advice_queries, + instance_queries: queries.instance, + fixed_queries: queries.fixed, + permutation: cs_mid.permutation, + lookups, + shuffles, + minimum_degree: cs_mid.minimum_degree, + } + } +} diff --git a/halo2_backend/src/plonk/lookup.rs b/halo2_backend/src/plonk/lookup.rs index 67abd8a614..795f3b744b 100644 --- a/halo2_backend/src/plonk/lookup.rs +++ b/halo2_backend/src/plonk/lookup.rs @@ -1,4 +1,4 @@ pub(crate) mod prover; pub(crate) mod verifier; -pub use halo2_common::plonk::lookup::Argument; +use crate::plonk::circuit::LookupArgumentBack as Argument; diff --git a/halo2_backend/src/plonk/lookup/prover.rs b/halo2_backend/src/plonk/lookup/prover.rs index 00967ccea1..5bd2827b39 100644 --- a/halo2_backend/src/plonk/lookup/prover.rs +++ b/halo2_backend/src/plonk/lookup/prover.rs @@ -3,7 +3,8 @@ use super::Argument; use crate::plonk::evaluation::evaluate; use crate::{ arithmetic::{eval_polynomial, parallelize, CurveAffine}, - plonk::{ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX}, + plonk::circuit::ExpressionBack, + plonk::{ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, Error}, poly::{ commitment::{Blind, Params}, Coeff, EvaluationDomain, LagrangeCoeff, Polynomial, ProverQuery, @@ -14,7 +15,6 @@ use group::{ ff::{BatchInvert, Field}, Curve, }; -use halo2_common::plonk::{Error, Expression}; use halo2_middleware::ff::WithSmallOrderMulGroup; use halo2_middleware::poly::Rotation; use rand_core::RngCore; @@ -87,7 +87,7 @@ where C::Curve: Mul + MulAssign, { // Closure to get values of expressions and compress them - let compress_expressions = |expressions: &[Expression]| { + let compress_expressions = |expressions: &[ExpressionBack]| { let compressed_expression = expressions .iter() .map(|expression| { diff --git a/halo2_backend/src/plonk/lookup/verifier.rs b/halo2_backend/src/plonk/lookup/verifier.rs index 1688afdd19..2d8759011a 100644 --- a/halo2_backend/src/plonk/lookup/verifier.rs +++ b/halo2_backend/src/plonk/lookup/verifier.rs @@ -3,11 +3,12 @@ use std::iter; use super::Argument; use crate::{ arithmetic::CurveAffine, - plonk::{ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, VerifyingKey}, + plonk::circuit::{ExpressionBack, QueryBack, VarBack}, + plonk::{ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, Error, VerifyingKey}, poly::{commitment::MSM, VerifierQuery}, transcript::{EncodedChallenge, TranscriptRead}, }; -use halo2_common::plonk::{Error, Expression}; +use halo2_middleware::circuit::Any; use halo2_middleware::ff::Field; use halo2_middleware::poly::Rotation; @@ -110,17 +111,22 @@ impl Evaluated { * (self.permuted_input_eval + *beta) * (self.permuted_table_eval + *gamma); - let compress_expressions = |expressions: &[Expression]| { + let compress_expressions = |expressions: &[ExpressionBack]| { expressions .iter() .map(|expression| { expression.evaluate( &|scalar| scalar, - &|_| panic!("virtual selectors are removed during optimization"), - &|query| fixed_evals[query.index.unwrap()], - &|query| advice_evals[query.index.unwrap()], - &|query| instance_evals[query.index.unwrap()], - &|challenge| challenges[challenge.index()], + &|var| match var { + VarBack::Challenge(challenge) => challenges[challenge.index], + VarBack::Query(QueryBack { + index, column_type, .. + }) => match column_type { + Any::Fixed => fixed_evals[index], + Any::Advice(_) => advice_evals[index], + Any::Instance => instance_evals[index], + }, + }, &|a| -a, &|a, b| a + b, &|a, b| a * b, diff --git a/halo2_backend/src/plonk/permutation.rs b/halo2_backend/src/plonk/permutation.rs index 1b7cff2a70..03b34103cd 100644 --- a/halo2_backend/src/plonk/permutation.rs +++ b/halo2_backend/src/plonk/permutation.rs @@ -7,7 +7,8 @@ use crate::{ SerdeFormat, }; use halo2_common::helpers::{SerdeCurveAffine, SerdePrimeField}; -pub use halo2_common::plonk::permutation::Argument; +// TODO: Remove the renaming +pub use halo2_middleware::permutation::ArgumentMid as Argument; use std::io; diff --git a/halo2_backend/src/plonk/permutation/keygen.rs b/halo2_backend/src/plonk/permutation/keygen.rs index a4a6ee6cdc..7fc4c6696b 100644 --- a/halo2_backend/src/plonk/permutation/keygen.rs +++ b/halo2_backend/src/plonk/permutation/keygen.rs @@ -4,14 +4,14 @@ use halo2_middleware::ff::{Field, PrimeField}; use super::{Argument, ProvingKey, VerifyingKey}; use crate::{ arithmetic::{parallelize, CurveAffine}, + plonk::Error, poly::{ commitment::{Blind, Params}, EvaluationDomain, }, }; -use halo2_common::plonk::Error; use halo2_middleware::circuit::ColumnMid; -use halo2_middleware::permutation::{ArgumentV2, AssemblyMid}; +use halo2_middleware::permutation::{ArgumentMid, AssemblyMid}; // NOTE: Temporarily disabled thread-safe-region feature. Regions are a frontend concept, so the // thread-safe support for them should be only in the frontend package. @@ -44,10 +44,10 @@ pub struct Assembly { impl Assembly { pub(crate) fn new_from_assembly_mid( n: usize, - p: &ArgumentV2, + p: &ArgumentMid, a: &AssemblyMid, ) -> Result { - let mut assembly = Self::new(n, &p.clone().into()); + let mut assembly = Self::new(n, &p.clone()); for copy in &a.copies { assembly.copy(copy.0.column, copy.0.row, copy.1.column, copy.1.row)?; } @@ -67,7 +67,7 @@ impl Assembly { // in a 1-cycle; therefore mapping and aux are identical, because every cell is // its own distinguished element. Assembly { - columns: p.columns.clone().into_iter().map(|c| c.into()).collect(), + columns: p.columns.clone(), mapping: columns.clone(), aux: columns, sizes: vec![vec![1usize; n]; p.columns.len()], @@ -85,12 +85,12 @@ impl Assembly { .columns .iter() .position(|c| c == &left_column) - .ok_or(Error::ColumnNotInPermutation(left_column.into()))?; + .ok_or(Error::ColumnNotInPermutation(left_column))?; let right_column = self .columns .iter() .position(|c| c == &right_column) - .ok_or(Error::ColumnNotInPermutation(right_column.into()))?; + .ok_or(Error::ColumnNotInPermutation(right_column))?; // Check bounds if left_row >= self.mapping[left_column].len() diff --git a/halo2_backend/src/plonk/permutation/prover.rs b/halo2_backend/src/plonk/permutation/prover.rs index fda5a21156..01565efbbc 100644 --- a/halo2_backend/src/plonk/permutation/prover.rs +++ b/halo2_backend/src/plonk/permutation/prover.rs @@ -9,14 +9,13 @@ use std::iter::{self, ExactSizeIterator}; use super::Argument; use crate::{ arithmetic::{eval_polynomial, parallelize, CurveAffine}, - plonk::{self, permutation::ProvingKey, ChallengeBeta, ChallengeGamma, ChallengeX}, + plonk::{self, permutation::ProvingKey, ChallengeBeta, ChallengeGamma, ChallengeX, Error}, poly::{ commitment::{Blind, Params}, Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, ProverQuery, }, transcript::{EncodedChallenge, TranscriptWrite}, }; -use halo2_common::plonk::Error; use halo2_middleware::circuit::Any; use halo2_middleware::poly::Rotation; @@ -102,7 +101,7 @@ pub(in crate::plonk) fn permutation_commit< // Iterate over each column of the permutation for (&column, permuted_column_values) in columns.iter().zip(permutations.iter()) { - let values = match column.column_type() { + let values = match column.column_type { Any::Advice(_) => advice, Any::Fixed => fixed, Any::Instance => instance, @@ -110,7 +109,7 @@ pub(in crate::plonk) fn permutation_commit< parallelize(&mut modified_values, |modified_values, start| { for ((modified_values, value), permuted_value) in modified_values .iter_mut() - .zip(values[column.index()][start..].iter()) + .zip(values[column.index][start..].iter()) .zip(permuted_column_values[start..].iter()) { *modified_values *= *beta * permuted_value + *gamma + value; @@ -125,7 +124,7 @@ pub(in crate::plonk) fn permutation_commit< // of the entire fraction by computing the numerators for &column in columns.iter() { let omega = domain.get_omega(); - let values = match column.column_type() { + let values = match column.column_type { Any::Advice(_) => advice, Any::Fixed => fixed, Any::Instance => instance, @@ -134,7 +133,7 @@ pub(in crate::plonk) fn permutation_commit< let mut deltaomega = deltaomega * omega.pow_vartime([start as u64, 0, 0, 0]); for (modified_values, value) in modified_values .iter_mut() - .zip(values[column.index()][start..].iter()) + .zip(values[column.index][start..].iter()) { // Multiply by p_j(\omega^i) + \delta^j \omega^i \beta *modified_values *= deltaomega * *beta + *gamma + value; diff --git a/halo2_backend/src/plonk/permutation/verifier.rs b/halo2_backend/src/plonk/permutation/verifier.rs index 5ad874ec68..d7ad3b87ae 100644 --- a/halo2_backend/src/plonk/permutation/verifier.rs +++ b/halo2_backend/src/plonk/permutation/verifier.rs @@ -4,11 +4,10 @@ use std::iter; use super::{Argument, VerifyingKey}; use crate::{ arithmetic::CurveAffine, - plonk::{self, ChallengeBeta, ChallengeGamma, ChallengeX}, + plonk::{self, ChallengeBeta, ChallengeGamma, ChallengeX, Error}, poly::{commitment::MSM, VerifierQuery}, transcript::{EncodedChallenge, TranscriptRead}, }; -use halo2_common::plonk::Error; use halo2_middleware::circuit::Any; use halo2_middleware::poly::Rotation; @@ -160,7 +159,7 @@ impl Evaluated { let mut left = set.permutation_product_next_eval; for (eval, permutation_eval) in columns .iter() - .map(|&column| match column.column_type() { + .map(|&column| match column.column_type { Any::Advice(_) => { advice_evals[vk.cs.get_any_query_index(column, Rotation::cur())] } @@ -181,7 +180,7 @@ impl Evaluated { let mut current_delta = (*beta * *x) * (::DELTA .pow_vartime([(chunk_index * chunk_len) as u64])); - for eval in columns.iter().map(|&column| match column.column_type() { + for eval in columns.iter().map(|&column| match column.column_type { Any::Advice(_) => { advice_evals[vk.cs.get_any_query_index(column, Rotation::cur())] } diff --git a/halo2_backend/src/plonk/prover.rs b/halo2_backend/src/plonk/prover.rs index bb9355da04..6ba517a4b4 100644 --- a/halo2_backend/src/plonk/prover.rs +++ b/halo2_backend/src/plonk/prover.rs @@ -1,27 +1,24 @@ //! Generate a proof +use group::prime::PrimeCurveAffine; use group::Curve; -use halo2_middleware::ff::{Field, FromUniformBytes, WithSmallOrderMulGroup}; use rand_core::RngCore; use std::collections::{BTreeSet, HashSet}; use std::{collections::HashMap, iter}; use crate::arithmetic::{eval_polynomial, CurveAffine}; -use crate::plonk::lookup::prover::lookup_commit_permuted; -use crate::plonk::permutation::prover::permutation_commit; -use crate::plonk::shuffle::prover::shuffle_commit_product; use crate::plonk::{ - lookup, permutation, shuffle, vanishing, ChallengeBeta, ChallengeGamma, ChallengeTheta, - ChallengeX, ChallengeY, ProvingKey, + lookup, lookup::prover::lookup_commit_permuted, permutation, + permutation::prover::permutation_commit, shuffle, shuffle::prover::shuffle_commit_product, + vanishing, ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, ChallengeY, Error, + ProvingKey, }; use crate::poly::{ commitment::{Blind, CommitmentScheme, Params, Prover}, Basis, Coeff, LagrangeCoeff, Polynomial, ProverQuery, }; - use crate::transcript::{EncodedChallenge, TranscriptWrite}; -use group::prime::PrimeCurveAffine; -use halo2_common::plonk::{circuit::sealed, Error}; +use halo2_middleware::ff::{Field, FromUniformBytes, WithSmallOrderMulGroup}; /// Collection of instance data used during proving for a single circuit proof. #[derive(Debug)] @@ -120,7 +117,7 @@ pub struct ProverV2< // Plonk proving key pk: &'a ProvingKey, // Phases - phases: Vec, + phases: Vec, // Polynomials (Lagrange and Coeff) for all circuits instances instances: Vec>, // Advice polynomials with its blindings @@ -294,10 +291,9 @@ impl< return Err(Error::Other("All phases already committed".to_string())); } }; - if phase != current_phase.0 { + if phase != *current_phase { return Err(Error::Other(format!( - "Committing invalid phase. Expected {}, got {}", - current_phase.0, phase + "Committing invalid phase. Expected {current_phase}, got {phase}", ))); } @@ -345,8 +341,7 @@ impl< match advice_column { None => { return Err(Error::Other(format!( - "expected advice column with index {} at phase {}", - column_index, current_phase.0 + "expected advice column with index {column_index} at phase {current_phase}", ))) } Some(advice_column) => { @@ -361,8 +356,7 @@ impl< } } else if advice_column.is_some() { return Err(Error::Other(format!( - "expected no advice column with index {} at phase {}", - column_index, current_phase.0 + "expected no advice column with index {column_index} at phase {current_phase}", ))); }; } @@ -691,7 +685,7 @@ impl< .iter() .map(|&(column, at)| { eval_polynomial( - &instance.instance_polys[column.index()], + &instance.instance_polys[column.index], domain.rotate_omega(*x, at), ) }) @@ -713,7 +707,7 @@ impl< .iter() .map(|&(column, at)| { eval_polynomial( - &advice.advice_polys[column.index()], + &advice.advice_polys[column.index], domain.rotate_omega(*x, at), ) }) @@ -730,7 +724,7 @@ impl< .fixed_queries .iter() .map(|&(column, at)| { - eval_polynomial(&pk.fixed_polys[column.index()], domain.rotate_omega(*x, at)) + eval_polynomial(&pk.fixed_polys[column.index], domain.rotate_omega(*x, at)) }) .collect(); @@ -803,7 +797,7 @@ impl< .then_some(cs.instance_queries.iter().map(move |&(column, at)| { ProverQuery { point: domain.rotate_omega(*x, at), - poly: &instance.instance_polys[column.index()], + poly: &instance.instance_polys[column.index], blind: Blind::default(), } })) @@ -816,8 +810,8 @@ impl< .iter() .map(move |&(column, at)| ProverQuery { point: domain.rotate_omega(*x, at), - poly: &advice.advice_polys[column.index()], - blind: advice.advice_blinds[column.index()], + poly: &advice.advice_polys[column.index], + blind: advice.advice_blinds[column.index], }), ) // Permutations @@ -830,7 +824,7 @@ impl< // Queries to fixed columns .chain(cs.fixed_queries.iter().map(|&(column, at)| ProverQuery { point: domain.rotate_omega(*x, at), - poly: &pk.fixed_polys[column.index()], + poly: &pk.fixed_polys[column.index], blind: Blind::default(), })) // Copy constraints @@ -849,7 +843,7 @@ impl< } /// Returns the phases of the circuit - pub fn phases(&'a self) -> &'a [sealed::Phase] { + pub fn phases(&self) -> &[u8] { self.phases.as_slice() } } diff --git a/halo2_backend/src/plonk/shuffle.rs b/halo2_backend/src/plonk/shuffle.rs index 87bb58b701..16e593775f 100644 --- a/halo2_backend/src/plonk/shuffle.rs +++ b/halo2_backend/src/plonk/shuffle.rs @@ -1,4 +1,4 @@ pub(crate) mod prover; pub(crate) mod verifier; -pub use halo2_common::plonk::shuffle::Argument; +use crate::plonk::circuit::ShuffleArgumentBack as Argument; diff --git a/halo2_backend/src/plonk/shuffle/prover.rs b/halo2_backend/src/plonk/shuffle/prover.rs index 4e32367153..393e4e73b1 100644 --- a/halo2_backend/src/plonk/shuffle/prover.rs +++ b/halo2_backend/src/plonk/shuffle/prover.rs @@ -3,16 +3,15 @@ use super::Argument; use crate::plonk::evaluation::evaluate; use crate::{ arithmetic::{eval_polynomial, parallelize, CurveAffine}, - plonk::{ChallengeGamma, ChallengeTheta, ChallengeX}, + plonk::circuit::ExpressionBack, + plonk::{ChallengeGamma, ChallengeTheta, ChallengeX, Error}, poly::{ commitment::{Blind, Params}, Coeff, EvaluationDomain, LagrangeCoeff, Polynomial, ProverQuery, }, transcript::{EncodedChallenge, TranscriptWrite}, }; -use group::{ff::BatchInvert, Curve}; -use halo2_common::plonk::{Error, Expression}; -use halo2_middleware::ff::WithSmallOrderMulGroup; +use group::{ff::BatchInvert, ff::WithSmallOrderMulGroup, Curve}; use halo2_middleware::poly::Rotation; use rand_core::RngCore; use std::{ @@ -57,7 +56,7 @@ where C::Curve: Mul + MulAssign, { // Closure to get values of expressions and compress them - let compress_expressions = |expressions: &[Expression]| { + let compress_expressions = |expressions: &[ExpressionBack]| { let compressed_expression = expressions .iter() .map(|expression| { diff --git a/halo2_backend/src/plonk/shuffle/verifier.rs b/halo2_backend/src/plonk/shuffle/verifier.rs index 20b8ec0532..44dd32f386 100644 --- a/halo2_backend/src/plonk/shuffle/verifier.rs +++ b/halo2_backend/src/plonk/shuffle/verifier.rs @@ -3,11 +3,12 @@ use std::iter; use super::Argument; use crate::{ arithmetic::CurveAffine, - plonk::{ChallengeGamma, ChallengeTheta, ChallengeX, VerifyingKey}, + plonk::circuit::{ExpressionBack, QueryBack, VarBack}, + plonk::{ChallengeGamma, ChallengeTheta, ChallengeX, Error, VerifyingKey}, poly::{commitment::MSM, VerifierQuery}, transcript::{EncodedChallenge, TranscriptRead}, }; -use halo2_common::plonk::{Error, Expression}; +use halo2_middleware::circuit::Any; use halo2_middleware::ff::Field; use halo2_middleware::poly::Rotation; @@ -69,17 +70,22 @@ impl Evaluated { let product_expression = || { // z(\omega X) (s(X) + \gamma) - z(X) (a(X) + \gamma) - let compress_expressions = |expressions: &[Expression]| { + let compress_expressions = |expressions: &[ExpressionBack]| { expressions .iter() .map(|expression| { expression.evaluate( &|scalar| scalar, - &|_| panic!("virtual selectors are removed during optimization"), - &|query| fixed_evals[query.index.unwrap()], - &|query| advice_evals[query.index.unwrap()], - &|query| instance_evals[query.index.unwrap()], - &|challenge| challenges[challenge.index()], + &|var| match var { + VarBack::Challenge(challenge) => challenges[challenge.index], + VarBack::Query(QueryBack { + index, column_type, .. + }) => match column_type { + Any::Fixed => fixed_evals[index], + Any::Advice(_) => advice_evals[index], + Any::Instance => instance_evals[index], + }, + }, &|a| -a, &|a, b| a + b, &|a, b| a * b, diff --git a/halo2_backend/src/plonk/vanishing/prover.rs b/halo2_backend/src/plonk/vanishing/prover.rs index 0300f22c4e..e047e020a8 100644 --- a/halo2_backend/src/plonk/vanishing/prover.rs +++ b/halo2_backend/src/plonk/vanishing/prover.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, iter}; +use crate::plonk::Error; use group::Curve; -use halo2_common::plonk::Error; use halo2_middleware::ff::Field; use rand_chacha::ChaCha20Rng; use rand_core::{RngCore, SeedableRng}; diff --git a/halo2_backend/src/plonk/vanishing/verifier.rs b/halo2_backend/src/plonk/vanishing/verifier.rs index 2e7394f7ca..c925c8cec9 100644 --- a/halo2_backend/src/plonk/vanishing/verifier.rs +++ b/halo2_backend/src/plonk/vanishing/verifier.rs @@ -1,11 +1,10 @@ use std::iter; -use halo2_common::plonk::Error; use halo2_middleware::ff::Field; use crate::{ arithmetic::CurveAffine, - plonk::{ChallengeX, ChallengeY, VerifyingKey}, + plonk::{ChallengeX, ChallengeY, Error, VerifyingKey}, poly::{ commitment::{Params, MSM}, VerifierQuery, diff --git a/halo2_backend/src/plonk/verifier.rs b/halo2_backend/src/plonk/verifier.rs index d8e76cd727..490d517598 100644 --- a/halo2_backend/src/plonk/verifier.rs +++ b/halo2_backend/src/plonk/verifier.rs @@ -1,20 +1,21 @@ //! Verify a plonk proof use group::Curve; +use halo2_middleware::circuit::Any; use halo2_middleware::ff::{Field, FromUniformBytes, WithSmallOrderMulGroup}; use std::iter; use super::{vanishing, VerifyingKey}; use crate::arithmetic::compute_inner_product; -use crate::plonk::lookup::verifier::lookup_read_permuted_commitments; -use crate::plonk::permutation::verifier::permutation_read_product_commitments; -use crate::plonk::shuffle::verifier::shuffle_read_product_commitment; -use crate::plonk::{ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, ChallengeY, Error}; -use crate::poly::commitment::{CommitmentScheme, Verifier}; -use crate::poly::VerificationStrategy; +use crate::plonk::{ + circuit::VarBack, lookup::verifier::lookup_read_permuted_commitments, + permutation::verifier::permutation_read_product_commitments, + shuffle::verifier::shuffle_read_product_commitment, ChallengeBeta, ChallengeGamma, + ChallengeTheta, ChallengeX, ChallengeY, Error, +}; use crate::poly::{ - commitment::{Blind, Params}, - VerifierQuery, + commitment::{Blind, CommitmentScheme, Params, Verifier}, + VerificationStrategy, VerifierQuery, }; use crate::transcript::{read_n_scalars, EncodedChallenge, TranscriptRead}; @@ -275,7 +276,7 @@ where .instance_queries .iter() .map(|(column, rotation)| { - let instances = instances[column.index()]; + let instances = instances[column.index]; let offset = (max_rotation - rotation.0) as usize; compute_inner_product(instances, &l_i_s[offset..offset + instances.len()]) }) @@ -356,23 +357,22 @@ where let fixed_evals = &fixed_evals; std::iter::empty() // Evaluate the circuit using the custom gates provided - .chain(vk.cs.gates.iter().flat_map(move |gate| { - gate.polynomials().iter().map(move |poly| { - poly.evaluate( - &|scalar| scalar, - &|_| { - panic!("virtual selectors are removed during optimization") + .chain(vk.cs.gates.iter().map(move |gate| { + gate.poly.evaluate( + &|scalar| scalar, + &|var| match var { + VarBack::Query(query) => match query.column_type { + Any::Fixed => fixed_evals[query.index], + Any::Advice(_) => advice_evals[query.index], + Any::Instance => instance_evals[query.index], }, - &|query| fixed_evals[query.index.unwrap()], - &|query| advice_evals[query.index.unwrap()], - &|query| instance_evals[query.index.unwrap()], - &|challenge| challenges[challenge.index()], - &|a| -a, - &|a, b| a + b, - &|a, b| a * b, - &|a, scalar| a * scalar, - ) - }) + VarBack::Challenge(challenge) => challenges[challenge.index], + }, + &|a| -a, + &|a, b| a + b, + &|a, b| a * b, + &|a, scalar| a * scalar, + ) })) .chain(permutation.expressions( vk, @@ -443,7 +443,7 @@ where .then_some(vk.cs.instance_queries.iter().enumerate().map( move |(query_index, &(column, at))| { VerifierQuery::new_commitment( - &instance_commitments[column.index()], + &instance_commitments[column.index], vk.domain.rotate_omega(*x, at), instance_evals[query_index], ) @@ -455,7 +455,7 @@ where .chain(vk.cs.advice_queries.iter().enumerate().map( move |(query_index, &(column, at))| { VerifierQuery::new_commitment( - &advice_commitments[column.index()], + &advice_commitments[column.index], vk.domain.rotate_omega(*x, at), advice_evals[query_index], ) @@ -473,7 +473,7 @@ where .enumerate() .map(|(query_index, &(column, at))| { VerifierQuery::new_commitment( - &vk.fixed_commitments[column.index()], + &vk.fixed_commitments[column.index], vk.domain.rotate_omega(*x, at), fixed_evals[query_index], ) diff --git a/halo2_backend/src/plonk/verifier/batch.rs b/halo2_backend/src/plonk/verifier/batch.rs index 883e3605c8..3740946ca7 100644 --- a/halo2_backend/src/plonk/verifier/batch.rs +++ b/halo2_backend/src/plonk/verifier/batch.rs @@ -1,5 +1,5 @@ +use crate::plonk::Error; use group::ff::Field; -use halo2_common::plonk::Error; use halo2_middleware::ff::FromUniformBytes; use halo2curves::CurveAffine; use rand_core::OsRng; diff --git a/halo2_common/Cargo.toml b/halo2_common/Cargo.toml index 4f60dbfa11..6185406210 100644 --- a/halo2_common/Cargo.toml +++ b/halo2_common/Cargo.toml @@ -25,13 +25,8 @@ all-features = true rustdoc-args = ["--cfg", "docsrs", "--html-in-header", "katex-header.html"] [dependencies] -backtrace = { version = "0.3", optional = true } group = "0.13" halo2curves = { version = "0.6.0", default-features = false } -rand_core = { version = "0.6", default-features = false } -blake2b_simd = "1" # MSRV 1.66.0 -sha3 = "0.9.1" -serde = { version = "1", optional = true, features = ["derive"] } serde_derive = { version = "1", optional = true} rayon = "1.8" halo2_middleware = { path = "../halo2_middleware" } @@ -41,18 +36,15 @@ halo2_legacy_pdqsort = { version = "0.1.0", optional = true } [dev-dependencies] proptest = "1" -rand_core = { version = "0.6", default-features = false, features = ["getrandom"] } serde_json = "1" -[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dev-dependencies] +[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] getrandom = { version = "0.2", features = ["js"] } [features] -default = ["batch", "bits"] +default = ["bits"] bits = ["halo2curves/bits"] -gadget-traces = ["backtrace"] thread-safe-region = [] -batch = ["rand_core/getrandom"] circuit-params = [] derive_serde = ["halo2curves/derive_serde"] diff --git a/halo2_common/src/circuit.rs b/halo2_common/src/circuit.rs deleted file mode 100644 index 0646f01416..0000000000 --- a/halo2_common/src/circuit.rs +++ /dev/null @@ -1,598 +0,0 @@ -//! Traits and structs for implementing circuit components. - -use std::{fmt, marker::PhantomData}; - -use halo2_middleware::ff::Field; - -use crate::plonk::Assigned; -use crate::plonk::{ - circuit::{Challenge, Column}, - Error, Selector, TableColumn, -}; -use halo2_middleware::circuit::{Advice, Any, Fixed, Instance}; - -mod value; -pub use value::Value; - -pub mod floor_planner; -pub use floor_planner::single_pass::SimpleFloorPlanner; - -pub mod layouter; -mod table_layouter; - -pub use table_layouter::{SimpleTableLayouter, TableLayouter}; - -/// A chip implements a set of instructions that can be used by gadgets. -/// -/// The chip stores state that is required at circuit synthesis time in -/// [`Chip::Config`], which can be fetched via [`Chip::config`]. -/// -/// The chip also loads any fixed configuration needed at synthesis time -/// using its own implementation of `load`, and stores it in [`Chip::Loaded`]. -/// This can be accessed via [`Chip::loaded`]. -pub trait Chip: Sized { - /// A type that holds the configuration for this chip, and any other state it may need - /// during circuit synthesis, that can be derived during [`Circuit::configure`]. - /// - /// [`Circuit::configure`]: crate::plonk::Circuit::configure - type Config: fmt::Debug + Clone; - - /// A type that holds any general chip state that needs to be loaded at the start of - /// [`Circuit::synthesize`]. This might simply be `()` for some chips. - /// - /// [`Circuit::synthesize`]: crate::plonk::Circuit::synthesize - type Loaded: fmt::Debug + Clone; - - /// The chip holds its own configuration. - fn config(&self) -> &Self::Config; - - /// Provides access to general chip state loaded at the beginning of circuit - /// synthesis. - /// - /// Panics if called before `Chip::load`. - fn loaded(&self) -> &Self::Loaded; -} - -/// Index of a region in a layouter -#[derive(Clone, Copy, Debug)] -pub struct RegionIndex(usize); - -impl From for RegionIndex { - fn from(idx: usize) -> RegionIndex { - RegionIndex(idx) - } -} - -impl std::ops::Deref for RegionIndex { - type Target = usize; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -/// Starting row of a region in a layouter -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct RegionStart(usize); - -impl From for RegionStart { - fn from(idx: usize) -> RegionStart { - RegionStart(idx) - } -} - -impl std::ops::Deref for RegionStart { - type Target = usize; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -/// A pointer to a cell within a circuit. -#[derive(Clone, Copy, Debug)] -pub struct Cell { - /// Identifies the region in which this cell resides. - pub region_index: RegionIndex, - /// The relative offset of this cell within its region. - pub row_offset: usize, - /// The column of this cell. - pub column: Column, -} - -/// An assigned cell. -#[derive(Clone, Debug)] -pub struct AssignedCell { - value: Value, - cell: Cell, - _marker: PhantomData, -} - -impl AssignedCell { - /// Returns the value of the [`AssignedCell`]. - pub fn value(&self) -> Value<&V> { - self.value.as_ref() - } - - /// Returns the cell. - pub fn cell(&self) -> Cell { - self.cell - } -} - -impl AssignedCell -where - for<'v> Assigned: From<&'v V>, -{ - /// Returns the field element value of the [`AssignedCell`]. - pub fn value_field(&self) -> Value> { - self.value.to_field() - } -} - -impl AssignedCell, F> { - /// Evaluates this assigned cell's value directly, performing an unbatched inversion - /// if necessary. - /// - /// If the denominator is zero, the returned cell's value is zero. - pub fn evaluate(self) -> AssignedCell { - AssignedCell { - value: self.value.evaluate(), - cell: self.cell, - _marker: Default::default(), - } - } -} - -impl AssignedCell -where - for<'v> Assigned: From<&'v V>, -{ - /// Copies the value to a given advice cell and constrains them to be equal. - /// - /// Returns an error if either this cell or the given cell are in columns - /// where equality has not been enabled. - pub fn copy_advice( - &self, - annotation: A, - region: &mut Region<'_, F>, - column: Column, - offset: usize, - ) -> Result - where - A: Fn() -> AR, - AR: Into, - { - let assigned_cell = - region.assign_advice(annotation, column, offset, || self.value.clone())?; - region.constrain_equal(assigned_cell.cell(), self.cell())?; - - Ok(assigned_cell) - } -} - -/// A region of the circuit in which a [`Chip`] can assign cells. -/// -/// Inside a region, the chip may freely use relative offsets; the [`Layouter`] will -/// treat these assignments as a single "region" within the circuit. -/// -/// The [`Layouter`] is allowed to optimise between regions as it sees fit. Chips must use -/// [`Region::constrain_equal`] to copy in variables assigned in other regions. -/// -/// TODO: It would be great if we could constrain the columns in these types to be -/// "logical" columns that are guaranteed to correspond to the chip (and have come from -/// `Chip::Config`). -#[derive(Debug)] -pub struct Region<'r, F: Field> { - region: &'r mut dyn layouter::RegionLayouter, -} - -impl<'r, F: Field> From<&'r mut dyn layouter::RegionLayouter> for Region<'r, F> { - fn from(region: &'r mut dyn layouter::RegionLayouter) -> Self { - Region { region } - } -} - -impl<'r, F: Field> Region<'r, F> { - /// Enables a selector at the given offset. - pub fn enable_selector( - &mut self, - annotation: A, - selector: &Selector, - offset: usize, - ) -> Result<(), Error> - where - A: Fn() -> AR, - AR: Into, - { - self.region - .enable_selector(&|| annotation().into(), selector, offset) - } - - /// Allows the circuit implementor to name/annotate a Column within a Region context. - /// - /// This is useful in order to improve the amount of information that `prover.verify()` - /// and `prover.assert_satisfied()` can provide. - pub fn name_column(&mut self, annotation: A, column: T) - where - A: Fn() -> AR, - AR: Into, - T: Into>, - { - self.region - .name_column(&|| annotation().into(), column.into()); - } - - /// Assign an advice column value (witness). - /// - /// Even though `to` has `FnMut` bounds, it is guaranteed to be called at most once. - pub fn assign_advice<'v, V, VR, A, AR>( - &'v mut self, - annotation: A, - column: Column, - offset: usize, - mut to: V, - ) -> Result, Error> - where - V: FnMut() -> Value + 'v, - for<'vr> Assigned: From<&'vr VR>, - A: Fn() -> AR, - AR: Into, - { - let mut value = Value::unknown(); - let cell = - self.region - .assign_advice(&|| annotation().into(), column, offset, &mut || { - let v = to(); - let value_f = v.to_field(); - value = v; - value_f - })?; - - Ok(AssignedCell { - value, - cell, - _marker: PhantomData, - }) - } - - /// Assigns a constant value to the column `advice` at `offset` within this region. - /// - /// The constant value will be assigned to a cell within one of the fixed columns - /// configured via `ConstraintSystem::enable_constant`. - /// - /// Returns the advice cell. - pub fn assign_advice_from_constant( - &mut self, - annotation: A, - column: Column, - offset: usize, - constant: VR, - ) -> Result, Error> - where - for<'vr> Assigned: From<&'vr VR>, - A: Fn() -> AR, - AR: Into, - { - let cell = self.region.assign_advice_from_constant( - &|| annotation().into(), - column, - offset, - (&constant).into(), - )?; - - Ok(AssignedCell { - value: Value::known(constant), - cell, - _marker: PhantomData, - }) - } - - /// Assign the value of the instance column's cell at absolute location - /// `row` to the column `advice` at `offset` within this region. - /// - /// Returns the advice cell, and its value if known. - pub fn assign_advice_from_instance( - &mut self, - annotation: A, - instance: Column, - row: usize, - advice: Column, - offset: usize, - ) -> Result, Error> - where - A: Fn() -> AR, - AR: Into, - { - let (cell, value) = self.region.assign_advice_from_instance( - &|| annotation().into(), - instance, - row, - advice, - offset, - )?; - - Ok(AssignedCell { - value, - cell, - _marker: PhantomData, - }) - } - - /// Returns the value of the instance column's cell at absolute location `row`. - /// - /// This method is only provided for convenience; it does not create any constraints. - /// Callers still need to use [`Self::assign_advice_from_instance`] to constrain the - /// instance values in their circuit. - pub fn instance_value( - &mut self, - instance: Column, - row: usize, - ) -> Result, Error> { - self.region.instance_value(instance, row) - } - - /// Assign a fixed value. - /// - /// Even though `to` has `FnMut` bounds, it is guaranteed to be called at most once. - pub fn assign_fixed<'v, V, VR, A, AR>( - &'v mut self, - annotation: A, - column: Column, - offset: usize, - mut to: V, - ) -> Result, Error> - where - V: FnMut() -> Value + 'v, - for<'vr> Assigned: From<&'vr VR>, - A: Fn() -> AR, - AR: Into, - { - let mut value = Value::unknown(); - let cell = - self.region - .assign_fixed(&|| annotation().into(), column, offset, &mut || { - let v = to(); - let value_f = v.to_field(); - value = v; - value_f - })?; - - Ok(AssignedCell { - value, - cell, - _marker: PhantomData, - }) - } - - /// Constrains a cell to have a constant value. - /// - /// Returns an error if the cell is in a column where equality has not been enabled. - pub fn constrain_constant(&mut self, cell: Cell, constant: VR) -> Result<(), Error> - where - VR: Into>, - { - self.region.constrain_constant(cell, constant.into()) - } - - /// Constrains two cells to have the same value. - /// - /// Returns an error if either of the cells are in columns where equality - /// has not been enabled. - pub fn constrain_equal(&mut self, left: Cell, right: Cell) -> Result<(), Error> { - self.region.constrain_equal(left, right) - } -} - -/// A lookup table in the circuit. -#[derive(Debug)] -pub struct Table<'r, F: Field> { - table: &'r mut dyn TableLayouter, -} - -impl<'r, F: Field> From<&'r mut dyn TableLayouter> for Table<'r, F> { - fn from(table: &'r mut dyn TableLayouter) -> Self { - Table { table } - } -} - -impl<'r, F: Field> Table<'r, F> { - /// Assigns a fixed value to a table cell. - /// - /// Returns an error if the table cell has already been assigned to. - /// - /// Even though `to` has `FnMut` bounds, it is guaranteed to be called at most once. - pub fn assign_cell<'v, V, VR, A, AR>( - &'v mut self, - annotation: A, - column: TableColumn, - offset: usize, - mut to: V, - ) -> Result<(), Error> - where - V: FnMut() -> Value + 'v, - VR: Into>, - A: Fn() -> AR, - AR: Into, - { - self.table - .assign_cell(&|| annotation().into(), column, offset, &mut || { - to().into_field() - }) - } -} - -/// A layout strategy within a circuit. The layouter is chip-agnostic and applies its -/// strategy to the context and config it is given. -/// -/// This abstracts over the circuit assignments, handling row indices etc. -/// -pub trait Layouter { - /// Represents the type of the "root" of this layouter, so that nested namespaces - /// can minimize indirection. - type Root: Layouter; - - /// Assign a region of gates to an absolute row number. - /// - /// Inside the closure, the chip may freely use relative offsets; the `Layouter` will - /// treat these assignments as a single "region" within the circuit. Outside this - /// closure, the `Layouter` is allowed to optimise as it sees fit. - /// - /// ```ignore - /// fn assign_region(&mut self, || "region name", |region| { - /// let config = chip.config(); - /// region.assign_advice(config.a, offset, || { Some(value)}); - /// }); - /// ``` - fn assign_region(&mut self, name: N, assignment: A) -> Result - where - A: FnMut(Region<'_, F>) -> Result, - N: Fn() -> NR, - NR: Into; - - /// Assign a table region to an absolute row number. - /// - /// ```ignore - /// fn assign_table(&mut self, || "table name", |table| { - /// let config = chip.config(); - /// table.assign_fixed(config.a, offset, || { Some(value)}); - /// }); - /// ``` - fn assign_table(&mut self, name: N, assignment: A) -> Result<(), Error> - where - A: FnMut(Table<'_, F>) -> Result<(), Error>, - N: Fn() -> NR, - NR: Into; - - /// Constrains a [`Cell`] to equal an instance column's row value at an - /// absolute position. - fn constrain_instance( - &mut self, - cell: Cell, - column: Column, - row: usize, - ) -> Result<(), Error>; - - /// Queries the value of the given challenge. - /// - /// Returns `Value::unknown()` if the current synthesis phase is before the challenge can be queried. - fn get_challenge(&self, challenge: Challenge) -> Value; - - /// Gets the "root" of this assignment, bypassing the namespacing. - /// - /// Not intended for downstream consumption; use [`Layouter::namespace`] instead. - fn get_root(&mut self) -> &mut Self::Root; - - /// Creates a new (sub)namespace and enters into it. - /// - /// Not intended for downstream consumption; use [`Layouter::namespace`] instead. - fn push_namespace(&mut self, name_fn: N) - where - NR: Into, - N: FnOnce() -> NR; - - /// Exits out of the existing namespace. - /// - /// Not intended for downstream consumption; use [`Layouter::namespace`] instead. - fn pop_namespace(&mut self, gadget_name: Option); - - /// Enters into a namespace. - fn namespace(&mut self, name_fn: N) -> NamespacedLayouter<'_, F, Self::Root> - where - NR: Into, - N: FnOnce() -> NR, - { - self.get_root().push_namespace(name_fn); - - NamespacedLayouter(self.get_root(), PhantomData) - } -} - -/// This is a "namespaced" layouter which borrows a `Layouter` (pushing a namespace -/// context) and, when dropped, pops out of the namespace context. -#[derive(Debug)] -pub struct NamespacedLayouter<'a, F: Field, L: Layouter + 'a>(&'a mut L, PhantomData); - -impl<'a, F: Field, L: Layouter + 'a> Layouter for NamespacedLayouter<'a, F, L> { - type Root = L::Root; - - fn assign_region(&mut self, name: N, assignment: A) -> Result - where - A: FnMut(Region<'_, F>) -> Result, - N: Fn() -> NR, - NR: Into, - { - self.0.assign_region(name, assignment) - } - - fn assign_table(&mut self, name: N, assignment: A) -> Result<(), Error> - where - A: FnMut(Table<'_, F>) -> Result<(), Error>, - N: Fn() -> NR, - NR: Into, - { - self.0.assign_table(name, assignment) - } - - fn constrain_instance( - &mut self, - cell: Cell, - column: Column, - row: usize, - ) -> Result<(), Error> { - self.0.constrain_instance(cell, column, row) - } - - fn get_challenge(&self, challenge: Challenge) -> Value { - self.0.get_challenge(challenge) - } - - fn get_root(&mut self) -> &mut Self::Root { - self.0.get_root() - } - - fn push_namespace(&mut self, _name_fn: N) - where - NR: Into, - N: FnOnce() -> NR, - { - panic!("Only the root's push_namespace should be called"); - } - - fn pop_namespace(&mut self, _gadget_name: Option) { - panic!("Only the root's pop_namespace should be called"); - } -} - -impl<'a, F: Field, L: Layouter + 'a> Drop for NamespacedLayouter<'a, F, L> { - fn drop(&mut self) { - let gadget_name = { - #[cfg(feature = "gadget-traces")] - { - let mut gadget_name = None; - let mut is_second_frame = false; - backtrace::trace(|frame| { - if is_second_frame { - // Resolve this instruction pointer to a symbol name. - backtrace::resolve_frame(frame, |symbol| { - gadget_name = symbol.name().map(|name| format!("{name:#}")); - }); - - // We are done! - false - } else { - // We want the next frame. - is_second_frame = true; - true - } - }); - gadget_name - } - - #[cfg(not(feature = "gadget-traces"))] - None - }; - - self.get_root().pop_namespace(gadget_name); - } -} diff --git a/halo2_common/src/circuit/floor_planner.rs b/halo2_common/src/circuit/floor_planner.rs deleted file mode 100644 index c3ba7d85b7..0000000000 --- a/halo2_common/src/circuit/floor_planner.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! Implementations of common circuit floor planners. - -pub mod single_pass; - -pub mod v1; -pub use v1::{V1Pass, V1}; diff --git a/halo2_common/src/circuit/floor_planner/single_pass.rs b/halo2_common/src/circuit/floor_planner/single_pass.rs deleted file mode 100644 index 66de896307..0000000000 --- a/halo2_common/src/circuit/floor_planner/single_pass.rs +++ /dev/null @@ -1,376 +0,0 @@ -use std::cmp; -use std::collections::HashMap; -use std::fmt; -use std::marker::PhantomData; - -use halo2_middleware::ff::Field; - -use crate::plonk::Assigned; -use crate::{ - circuit::{ - layouter::{RegionColumn, RegionLayouter, RegionShape, SyncDeps, TableLayouter}, - table_layouter::{compute_table_lengths, SimpleTableLayouter}, - Cell, Column, Layouter, Region, RegionIndex, RegionStart, Table, Value, - }, - plonk::{circuit::Challenge, Assignment, Circuit, Error, FloorPlanner, Selector, TableColumn}, -}; -use halo2_middleware::circuit::{Advice, Any, Fixed, Instance}; - -/// A simple [`FloorPlanner`] that performs minimal optimizations. -/// -/// This floor planner is suitable for debugging circuits. It aims to reflect the circuit -/// "business logic" in the circuit layout as closely as possible. It uses a single-pass -/// layouter that does not reorder regions for optimal packing. -#[derive(Debug)] -pub struct SimpleFloorPlanner; - -impl FloorPlanner for SimpleFloorPlanner { - fn synthesize + SyncDeps, C: Circuit>( - cs: &mut CS, - circuit: &C, - config: C::Config, - constants: Vec>, - ) -> Result<(), Error> { - let layouter = SingleChipLayouter::new(cs, constants)?; - circuit.synthesize(config, layouter) - } -} - -/// A [`Layouter`] for a single-chip circuit. -pub struct SingleChipLayouter<'a, F: Field, CS: Assignment + 'a> { - cs: &'a mut CS, - constants: Vec>, - /// Stores the starting row for each region. - regions: Vec, - /// Stores the first empty row for each column. - columns: HashMap, - /// Stores the table fixed columns. - table_columns: Vec, - _marker: PhantomData, -} - -impl<'a, F: Field, CS: Assignment + 'a> fmt::Debug for SingleChipLayouter<'a, F, CS> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("SingleChipLayouter") - .field("regions", &self.regions) - .field("columns", &self.columns) - .finish() - } -} - -impl<'a, F: Field, CS: Assignment> SingleChipLayouter<'a, F, CS> { - /// Creates a new single-chip layouter. - pub fn new(cs: &'a mut CS, constants: Vec>) -> Result { - let ret = SingleChipLayouter { - cs, - constants, - regions: vec![], - columns: HashMap::default(), - table_columns: vec![], - _marker: PhantomData, - }; - Ok(ret) - } -} - -impl<'a, F: Field, CS: Assignment + 'a + SyncDeps> Layouter - for SingleChipLayouter<'a, F, CS> -{ - type Root = Self; - - fn assign_region(&mut self, name: N, mut assignment: A) -> Result - where - A: FnMut(Region<'_, F>) -> Result, - N: Fn() -> NR, - NR: Into, - { - let region_index = self.regions.len(); - - // Get shape of the region. - let mut shape = RegionShape::new(region_index.into()); - { - let region: &mut dyn RegionLayouter = &mut shape; - assignment(region.into())?; - } - - // Lay out this region. We implement the simplest approach here: position the - // region starting at the earliest row for which none of the columns are in use. - let mut region_start = 0; - for column in &shape.columns { - region_start = cmp::max(region_start, self.columns.get(column).cloned().unwrap_or(0)); - } - self.regions.push(region_start.into()); - - // Update column usage information. - for column in shape.columns { - self.columns.insert(column, region_start + shape.row_count); - } - - // Assign region cells. - self.cs.enter_region(name); - let mut region = SingleChipLayouterRegion::new(self, region_index.into()); - let result = { - let region: &mut dyn RegionLayouter = &mut region; - assignment(region.into()) - }?; - let constants_to_assign = region.constants; - self.cs.exit_region(); - - // Assign constants. For the simple floor planner, we assign constants in order in - // the first `constants` column. - if self.constants.is_empty() { - if !constants_to_assign.is_empty() { - return Err(Error::NotEnoughColumnsForConstants); - } - } else { - let constants_column = self.constants[0]; - let next_constant_row = self - .columns - .entry(Column::::from(constants_column).into()) - .or_default(); - for (constant, advice) in constants_to_assign { - self.cs.assign_fixed( - || format!("Constant({:?})", constant.evaluate()), - constants_column, - *next_constant_row, - || Value::known(constant), - )?; - self.cs.copy( - constants_column.into(), - *next_constant_row, - advice.column, - *self.regions[*advice.region_index] + advice.row_offset, - )?; - *next_constant_row += 1; - } - } - - Ok(result) - } - - fn assign_table(&mut self, name: N, mut assignment: A) -> Result<(), Error> - where - A: FnMut(Table<'_, F>) -> Result<(), Error>, - N: Fn() -> NR, - NR: Into, - { - // Maintenance hazard: there is near-duplicate code in `v1::AssignmentPass::assign_table`. - // Assign table cells. - self.cs.enter_region(name); - let mut table = SimpleTableLayouter::new(self.cs, &self.table_columns); - { - let table: &mut dyn TableLayouter = &mut table; - assignment(table.into()) - }?; - let default_and_assigned = table.default_and_assigned; - self.cs.exit_region(); - - // Check that all table columns have the same length `first_unused`, - // and all cells up to that length are assigned. - let first_unused = compute_table_lengths(&default_and_assigned)?; - - // Record these columns so that we can prevent them from being used again. - for column in default_and_assigned.keys() { - self.table_columns.push(*column); - } - - for (col, (default_val, _)) in default_and_assigned { - // default_val must be Some because we must have assigned - // at least one cell in each column, and in that case we checked - // that all cells up to first_unused were assigned. - self.cs - .fill_from_row(col.inner(), first_unused, default_val.unwrap())?; - } - - Ok(()) - } - - fn constrain_instance( - &mut self, - cell: Cell, - instance: Column, - row: usize, - ) -> Result<(), Error> { - self.cs.copy( - cell.column, - *self.regions[*cell.region_index] + cell.row_offset, - instance.into(), - row, - ) - } - - fn get_challenge(&self, challenge: Challenge) -> Value { - self.cs.get_challenge(challenge) - } - - fn get_root(&mut self) -> &mut Self::Root { - self - } - - fn push_namespace(&mut self, name_fn: N) - where - NR: Into, - N: FnOnce() -> NR, - { - self.cs.push_namespace(name_fn) - } - - fn pop_namespace(&mut self, gadget_name: Option) { - self.cs.pop_namespace(gadget_name) - } -} - -struct SingleChipLayouterRegion<'r, 'a, F: Field, CS: Assignment + 'a> { - layouter: &'r mut SingleChipLayouter<'a, F, CS>, - region_index: RegionIndex, - /// Stores the constants to be assigned, and the cells to which they are copied. - constants: Vec<(Assigned, Cell)>, -} - -impl<'r, 'a, F: Field, CS: Assignment + 'a> fmt::Debug - for SingleChipLayouterRegion<'r, 'a, F, CS> -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("SingleChipLayouterRegion") - .field("layouter", &self.layouter) - .field("region_index", &self.region_index) - .finish() - } -} - -impl<'r, 'a, F: Field, CS: Assignment + 'a> SingleChipLayouterRegion<'r, 'a, F, CS> { - fn new(layouter: &'r mut SingleChipLayouter<'a, F, CS>, region_index: RegionIndex) -> Self { - SingleChipLayouterRegion { - layouter, - region_index, - constants: vec![], - } - } -} - -impl<'r, 'a, F: Field, CS: Assignment + 'a + SyncDeps> RegionLayouter - for SingleChipLayouterRegion<'r, 'a, F, CS> -{ - fn enable_selector<'v>( - &'v mut self, - annotation: &'v (dyn Fn() -> String + 'v), - selector: &Selector, - offset: usize, - ) -> Result<(), Error> { - self.layouter.cs.enable_selector( - annotation, - selector, - *self.layouter.regions[*self.region_index] + offset, - ) - } - - fn name_column<'v>( - &'v mut self, - annotation: &'v (dyn Fn() -> String + 'v), - column: Column, - ) { - self.layouter.cs.annotate_column(annotation, column); - } - - fn assign_advice<'v>( - &'v mut self, - annotation: &'v (dyn Fn() -> String + 'v), - column: Column, - offset: usize, - to: &'v mut (dyn FnMut() -> Value> + 'v), - ) -> Result { - self.layouter.cs.assign_advice( - annotation, - column, - *self.layouter.regions[*self.region_index] + offset, - to, - )?; - - Ok(Cell { - region_index: self.region_index, - row_offset: offset, - column: column.into(), - }) - } - - fn assign_advice_from_constant<'v>( - &'v mut self, - annotation: &'v (dyn Fn() -> String + 'v), - column: Column, - offset: usize, - constant: Assigned, - ) -> Result { - let advice = - self.assign_advice(annotation, column, offset, &mut || Value::known(constant))?; - self.constrain_constant(advice, constant)?; - - Ok(advice) - } - - fn assign_advice_from_instance<'v>( - &mut self, - annotation: &'v (dyn Fn() -> String + 'v), - instance: Column, - row: usize, - advice: Column, - offset: usize, - ) -> Result<(Cell, Value), Error> { - let value = self.layouter.cs.query_instance(instance, row)?; - - let cell = self.assign_advice(annotation, advice, offset, &mut || value.to_field())?; - - self.layouter.cs.copy( - cell.column, - *self.layouter.regions[*cell.region_index] + cell.row_offset, - instance.into(), - row, - )?; - - Ok((cell, value)) - } - - fn instance_value( - &mut self, - instance: Column, - row: usize, - ) -> Result, Error> { - self.layouter.cs.query_instance(instance, row) - } - - fn assign_fixed<'v>( - &'v mut self, - annotation: &'v (dyn Fn() -> String + 'v), - column: Column, - offset: usize, - to: &'v mut (dyn FnMut() -> Value> + 'v), - ) -> Result { - self.layouter.cs.assign_fixed( - annotation, - column, - *self.layouter.regions[*self.region_index] + offset, - to, - )?; - - Ok(Cell { - region_index: self.region_index, - row_offset: offset, - column: column.into(), - }) - } - - fn constrain_constant(&mut self, cell: Cell, constant: Assigned) -> Result<(), Error> { - self.constants.push((constant, cell)); - Ok(()) - } - - fn constrain_equal(&mut self, left: Cell, right: Cell) -> Result<(), Error> { - self.layouter.cs.copy( - left.column, - *self.layouter.regions[*left.region_index] + left.row_offset, - right.column, - *self.layouter.regions[*right.region_index] + right.row_offset, - )?; - - Ok(()) - } -} diff --git a/halo2_common/src/circuit/floor_planner/v1.rs b/halo2_common/src/circuit/floor_planner/v1.rs deleted file mode 100644 index e7709687a1..0000000000 --- a/halo2_common/src/circuit/floor_planner/v1.rs +++ /dev/null @@ -1,492 +0,0 @@ -use std::fmt; - -use halo2_middleware::ff::Field; - -use crate::plonk::Assigned; -use crate::{ - circuit::{ - layouter::{RegionColumn, RegionLayouter, RegionShape, SyncDeps, TableLayouter}, - table_layouter::{compute_table_lengths, SimpleTableLayouter}, - Cell, Column, Layouter, Region, RegionIndex, RegionStart, Table, Value, - }, - plonk::{circuit::Challenge, Assignment, Circuit, Error, FloorPlanner, Selector, TableColumn}, -}; -use halo2_middleware::circuit::{Advice, Any, Fixed, Instance}; - -pub mod strategy; - -/// The version 1 [`FloorPlanner`] provided by `halo2`. -/// -/// - No column optimizations are performed. Circuit configuration is left entirely to the -/// circuit designer. -/// - A dual-pass layouter is used to measures regions prior to assignment. -/// - Regions are measured as rectangles, bounded on the cells they assign. -/// - Regions are laid out using a greedy first-fit strategy, after sorting regions by -/// their "advice area" (number of advice columns * rows). -#[derive(Debug)] -pub struct V1; - -struct V1Plan<'a, F: Field, CS: Assignment + 'a> { - cs: &'a mut CS, - /// Stores the starting row for each region. - regions: Vec, - /// Stores the constants to be assigned, and the cells to which they are copied. - constants: Vec<(Assigned, Cell)>, - /// Stores the table fixed columns. - table_columns: Vec, -} - -impl<'a, F: Field, CS: Assignment + 'a> fmt::Debug for V1Plan<'a, F, CS> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("floor_planner::V1Plan").finish() - } -} - -impl<'a, F: Field, CS: Assignment + SyncDeps> V1Plan<'a, F, CS> { - /// Creates a new v1 layouter. - pub fn new(cs: &'a mut CS) -> Result { - let ret = V1Plan { - cs, - regions: vec![], - constants: vec![], - table_columns: vec![], - }; - Ok(ret) - } -} - -impl FloorPlanner for V1 { - fn synthesize + SyncDeps, C: Circuit>( - cs: &mut CS, - circuit: &C, - config: C::Config, - constants: Vec>, - ) -> Result<(), Error> { - let mut plan = V1Plan::new(cs)?; - - // First pass: measure the regions within the circuit. - let mut measure = MeasurementPass::new(); - { - let pass = &mut measure; - circuit - .without_witnesses() - .synthesize(config.clone(), V1Pass::<_, CS>::measure(pass))?; - } - - // Planning: - // - Position the regions. - let (regions, column_allocations) = strategy::slot_in_biggest_advice_first(measure.regions); - plan.regions = regions; - - // - Determine how many rows our planned circuit will require. - let first_unassigned_row = column_allocations - .values() - .map(|a| a.unbounded_interval_start()) - .max() - .unwrap_or(0); - - // - Position the constants within those rows. - let fixed_allocations: Vec<_> = constants - .into_iter() - .map(|c| { - ( - c, - column_allocations - .get(&Column::::from(c).into()) - .cloned() - .unwrap_or_default(), - ) - }) - .collect(); - let constant_positions = || { - fixed_allocations.iter().flat_map(|(c, a)| { - let c = *c; - a.free_intervals(0, Some(first_unassigned_row)) - .flat_map(move |e| e.range().unwrap().map(move |i| (c, i))) - }) - }; - - // Second pass: - // - Assign the regions. - let mut assign = AssignmentPass::new(&mut plan); - { - let pass = &mut assign; - circuit.synthesize(config, V1Pass::assign(pass))?; - } - - // - Assign the constants. - if constant_positions().count() < plan.constants.len() { - return Err(Error::NotEnoughColumnsForConstants); - } - for ((fixed_column, fixed_row), (value, advice)) in - constant_positions().zip(plan.constants.into_iter()) - { - plan.cs.assign_fixed( - || format!("Constant({:?})", value.evaluate()), - fixed_column, - fixed_row, - || Value::known(value), - )?; - plan.cs.copy( - fixed_column.into(), - fixed_row, - advice.column, - *plan.regions[*advice.region_index] + advice.row_offset, - )?; - } - - Ok(()) - } -} - -#[derive(Debug)] -enum Pass<'p, 'a, F: Field, CS: Assignment + 'a> { - Measurement(&'p mut MeasurementPass), - Assignment(&'p mut AssignmentPass<'p, 'a, F, CS>), -} - -/// A single pass of the [`V1`] layouter. -#[derive(Debug)] -pub struct V1Pass<'p, 'a, F: Field, CS: Assignment + 'a>(Pass<'p, 'a, F, CS>); - -impl<'p, 'a, F: Field, CS: Assignment + 'a> V1Pass<'p, 'a, F, CS> { - fn measure(pass: &'p mut MeasurementPass) -> Self { - V1Pass(Pass::Measurement(pass)) - } - - fn assign(pass: &'p mut AssignmentPass<'p, 'a, F, CS>) -> Self { - V1Pass(Pass::Assignment(pass)) - } -} - -impl<'p, 'a, F: Field, CS: Assignment + SyncDeps> Layouter for V1Pass<'p, 'a, F, CS> { - type Root = Self; - - fn assign_region(&mut self, name: N, assignment: A) -> Result - where - A: FnMut(Region<'_, F>) -> Result, - N: Fn() -> NR, - NR: Into, - { - match &mut self.0 { - Pass::Measurement(pass) => pass.assign_region(assignment), - Pass::Assignment(pass) => pass.assign_region(name, assignment), - } - } - - fn assign_table(&mut self, name: N, assignment: A) -> Result<(), Error> - where - A: FnMut(Table<'_, F>) -> Result<(), Error>, - N: Fn() -> NR, - NR: Into, - { - match &mut self.0 { - Pass::Measurement(_) => Ok(()), - Pass::Assignment(pass) => pass.assign_table(name, assignment), - } - } - - fn constrain_instance( - &mut self, - cell: Cell, - instance: Column, - row: usize, - ) -> Result<(), Error> { - match &mut self.0 { - Pass::Measurement(_) => Ok(()), - Pass::Assignment(pass) => pass.constrain_instance(cell, instance, row), - } - } - - fn get_challenge(&self, challenge: Challenge) -> Value { - match &self.0 { - Pass::Measurement(_) => Value::unknown(), - Pass::Assignment(pass) => pass.plan.cs.get_challenge(challenge), - } - } - - fn get_root(&mut self) -> &mut Self::Root { - self - } - - fn push_namespace(&mut self, name_fn: N) - where - NR: Into, - N: FnOnce() -> NR, - { - if let Pass::Assignment(pass) = &mut self.0 { - pass.plan.cs.push_namespace(name_fn); - } - } - - fn pop_namespace(&mut self, gadget_name: Option) { - if let Pass::Assignment(pass) = &mut self.0 { - pass.plan.cs.pop_namespace(gadget_name); - } - } -} - -/// Measures the circuit. -#[derive(Debug)] -pub struct MeasurementPass { - regions: Vec, -} - -impl MeasurementPass { - fn new() -> Self { - MeasurementPass { regions: vec![] } - } - - fn assign_region(&mut self, mut assignment: A) -> Result - where - A: FnMut(Region<'_, F>) -> Result, - { - let region_index = self.regions.len(); - - // Get shape of the region. - let mut shape = RegionShape::new(region_index.into()); - let result = { - let region: &mut dyn RegionLayouter = &mut shape; - assignment(region.into()) - }?; - self.regions.push(shape); - - Ok(result) - } -} - -/// Assigns the circuit. -#[derive(Debug)] -pub struct AssignmentPass<'p, 'a, F: Field, CS: Assignment + 'a> { - plan: &'p mut V1Plan<'a, F, CS>, - /// Counter tracking which region we need to assign next. - region_index: usize, -} - -impl<'p, 'a, F: Field, CS: Assignment + SyncDeps> AssignmentPass<'p, 'a, F, CS> { - fn new(plan: &'p mut V1Plan<'a, F, CS>) -> Self { - AssignmentPass { - plan, - region_index: 0, - } - } - - fn assign_region(&mut self, name: N, mut assignment: A) -> Result - where - A: FnMut(Region<'_, F>) -> Result, - N: Fn() -> NR, - NR: Into, - { - // Get the next region we are assigning. - let region_index = self.region_index; - self.region_index += 1; - - self.plan.cs.enter_region(name); - let mut region = V1Region::new(self.plan, region_index.into()); - let result = { - let region: &mut dyn RegionLayouter = &mut region; - assignment(region.into()) - }?; - self.plan.cs.exit_region(); - - Ok(result) - } - - fn assign_table(&mut self, name: N, mut assignment: A) -> Result - where - A: FnMut(Table<'_, F>) -> Result, - N: Fn() -> NR, - NR: Into, - { - // Maintenance hazard: there is near-duplicate code in `SingleChipLayouter::assign_table`. - - // Assign table cells. - self.plan.cs.enter_region(name); - let mut table = SimpleTableLayouter::new(self.plan.cs, &self.plan.table_columns); - let result = { - let table: &mut dyn TableLayouter = &mut table; - assignment(table.into()) - }?; - let default_and_assigned = table.default_and_assigned; - self.plan.cs.exit_region(); - - // Check that all table columns have the same length `first_unused`, - // and all cells up to that length are assigned. - let first_unused = compute_table_lengths(&default_and_assigned)?; - - // Record these columns so that we can prevent them from being used again. - for column in default_and_assigned.keys() { - self.plan.table_columns.push(*column); - } - - for (col, (default_val, _)) in default_and_assigned { - // default_val must be Some because we must have assigned - // at least one cell in each column, and in that case we checked - // that all cells up to first_unused were assigned. - self.plan - .cs - .fill_from_row(col.inner(), first_unused, default_val.unwrap())?; - } - - Ok(result) - } - - fn constrain_instance( - &mut self, - cell: Cell, - instance: Column, - row: usize, - ) -> Result<(), Error> { - self.plan.cs.copy( - cell.column, - *self.plan.regions[*cell.region_index] + cell.row_offset, - instance.into(), - row, - ) - } -} - -struct V1Region<'r, 'a, F: Field, CS: Assignment + 'a> { - plan: &'r mut V1Plan<'a, F, CS>, - region_index: RegionIndex, -} - -impl<'r, 'a, F: Field, CS: Assignment + 'a> fmt::Debug for V1Region<'r, 'a, F, CS> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("V1Region") - .field("plan", &self.plan) - .field("region_index", &self.region_index) - .finish() - } -} - -impl<'r, 'a, F: Field, CS: Assignment + 'a> V1Region<'r, 'a, F, CS> { - fn new(plan: &'r mut V1Plan<'a, F, CS>, region_index: RegionIndex) -> Self { - V1Region { plan, region_index } - } -} - -impl<'r, 'a, F: Field, CS: Assignment + SyncDeps> RegionLayouter for V1Region<'r, 'a, F, CS> { - fn enable_selector<'v>( - &'v mut self, - annotation: &'v (dyn Fn() -> String + 'v), - selector: &Selector, - offset: usize, - ) -> Result<(), Error> { - self.plan.cs.enable_selector( - annotation, - selector, - *self.plan.regions[*self.region_index] + offset, - ) - } - - fn assign_advice<'v>( - &'v mut self, - annotation: &'v (dyn Fn() -> String + 'v), - column: Column, - offset: usize, - to: &'v mut (dyn FnMut() -> Value> + 'v), - ) -> Result { - self.plan.cs.assign_advice( - annotation, - column, - *self.plan.regions[*self.region_index] + offset, - to, - )?; - - Ok(Cell { - region_index: self.region_index, - row_offset: offset, - column: column.into(), - }) - } - - fn assign_advice_from_constant<'v>( - &'v mut self, - annotation: &'v (dyn Fn() -> String + 'v), - column: Column, - offset: usize, - constant: Assigned, - ) -> Result { - let advice = - self.assign_advice(annotation, column, offset, &mut || Value::known(constant))?; - self.constrain_constant(advice, constant)?; - - Ok(advice) - } - - fn assign_advice_from_instance<'v>( - &mut self, - annotation: &'v (dyn Fn() -> String + 'v), - instance: Column, - row: usize, - advice: Column, - offset: usize, - ) -> Result<(Cell, Value), Error> { - let value = self.plan.cs.query_instance(instance, row)?; - - let cell = self.assign_advice(annotation, advice, offset, &mut || value.to_field())?; - - self.plan.cs.copy( - cell.column, - *self.plan.regions[*cell.region_index] + cell.row_offset, - instance.into(), - row, - )?; - - Ok((cell, value)) - } - - fn instance_value( - &mut self, - instance: Column, - row: usize, - ) -> Result, Error> { - self.plan.cs.query_instance(instance, row) - } - - fn assign_fixed<'v>( - &'v mut self, - annotation: &'v (dyn Fn() -> String + 'v), - column: Column, - offset: usize, - to: &'v mut (dyn FnMut() -> Value> + 'v), - ) -> Result { - self.plan.cs.assign_fixed( - annotation, - column, - *self.plan.regions[*self.region_index] + offset, - to, - )?; - - Ok(Cell { - region_index: self.region_index, - row_offset: offset, - column: column.into(), - }) - } - - fn constrain_constant(&mut self, cell: Cell, constant: Assigned) -> Result<(), Error> { - self.plan.constants.push((constant, cell)); - Ok(()) - } - - fn name_column<'v>( - &'v mut self, - annotation: &'v (dyn Fn() -> String + 'v), - column: Column, - ) { - self.plan.cs.annotate_column(annotation, column) - } - - fn constrain_equal(&mut self, left: Cell, right: Cell) -> Result<(), Error> { - self.plan.cs.copy( - left.column, - *self.plan.regions[*left.region_index] + left.row_offset, - right.column, - *self.plan.regions[*right.region_index] + right.row_offset, - )?; - - Ok(()) - } -} diff --git a/halo2_common/src/circuit/floor_planner/v1/strategy.rs b/halo2_common/src/circuit/floor_planner/v1/strategy.rs deleted file mode 100644 index ae5fe8acbe..0000000000 --- a/halo2_common/src/circuit/floor_planner/v1/strategy.rs +++ /dev/null @@ -1,243 +0,0 @@ -use std::{ - cmp, - collections::{BTreeSet, HashMap}, - ops::Range, -}; - -use super::{RegionColumn, RegionShape}; -use crate::circuit::RegionStart; -use halo2_middleware::circuit::Any; - -/// A region allocated within a column. -#[derive(Clone, Default, Debug, PartialEq, Eq)] -struct AllocatedRegion { - // The starting position of the region. - start: usize, - // The length of the region. - length: usize, -} - -impl Ord for AllocatedRegion { - fn cmp(&self, other: &Self) -> cmp::Ordering { - self.start.cmp(&other.start) - } -} - -impl PartialOrd for AllocatedRegion { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -/// An area of empty space within a column. -pub(crate) struct EmptySpace { - // The starting position (inclusive) of the empty space. - start: usize, - // The ending position (exclusive) of the empty space, or `None` if unbounded. - end: Option, -} - -impl EmptySpace { - pub(crate) fn range(&self) -> Option> { - self.end.map(|end| self.start..end) - } -} - -/// Allocated rows within a column. -/// -/// This is a set of [a_start, a_end) pairs representing disjoint allocated intervals. -#[derive(Clone, Default, Debug)] -pub struct Allocations(BTreeSet); - -impl Allocations { - /// Returns the row that forms the unbounded unallocated interval [row, None). - pub(crate) fn unbounded_interval_start(&self) -> usize { - self.0 - .iter() - .last() - .map(|r| r.start + r.length) - .unwrap_or(0) - } - - /// Return all the *unallocated* nonempty intervals intersecting [start, end). - /// - /// `end = None` represents an unbounded end. - pub(crate) fn free_intervals( - &self, - start: usize, - end: Option, - ) -> impl Iterator + '_ { - self.0 - .iter() - .map(Some) - .chain(Some(None)) - .scan(start, move |row, region| { - Some(if let Some(region) = region { - if end.map(|end| region.start >= end).unwrap_or(false) { - None - } else { - let ret = if *row < region.start { - Some(EmptySpace { - start: *row, - end: Some(region.start), - }) - } else { - None - }; - - *row = cmp::max(*row, region.start + region.length); - - ret - } - } else if end.map(|end| *row < end).unwrap_or(true) { - Some(EmptySpace { start: *row, end }) - } else { - None - }) - }) - .flatten() - } -} - -/// Allocated rows within a circuit. -pub type CircuitAllocations = HashMap; - -/// - `start` is the current start row of the region (not of this column). -/// - `slack` is the maximum number of rows the start could be moved down, taking into -/// account prior columns. -fn first_fit_region( - column_allocations: &mut CircuitAllocations, - region_columns: &[RegionColumn], - region_length: usize, - start: usize, - slack: Option, -) -> Option { - let (c, remaining_columns) = match region_columns.split_first() { - Some(cols) => cols, - None => return Some(start), - }; - let end = slack.map(|slack| start + region_length + slack); - - // Iterate over the unallocated non-empty intervals in c that intersect [start, end). - for space in column_allocations - .entry(*c) - .or_default() - .clone() - .free_intervals(start, end) - { - // Do we have enough room for this column of the region in this interval? - let s_slack = space - .end - .map(|end| (end as isize - space.start as isize) - region_length as isize); - if let Some((slack, s_slack)) = slack.zip(s_slack) { - assert!(s_slack <= slack as isize); - } - if s_slack.unwrap_or(0) >= 0 { - let row = first_fit_region( - column_allocations, - remaining_columns, - region_length, - space.start, - s_slack.map(|s| s as usize), - ); - if let Some(row) = row { - if let Some(end) = end { - assert!(row + region_length <= end); - } - column_allocations - .get_mut(c) - .unwrap() - .0 - .insert(AllocatedRegion { - start: row, - length: region_length, - }); - return Some(row); - } - } - } - - // No placement worked; the caller will need to try other possibilities. - None -} - -/// Positions the regions starting at the earliest row for which none of the columns are -/// in use, taking into account gaps between earlier regions. -pub fn slot_in( - region_shapes: Vec, -) -> (Vec<(RegionStart, RegionShape)>, CircuitAllocations) { - // Tracks the empty regions for each column. - let mut column_allocations: CircuitAllocations = Default::default(); - - let regions = region_shapes - .into_iter() - .map(|region| { - // Sort the region's columns to ensure determinism. - // - An unstable sort is fine, because region.columns() returns a set. - // - The sort order relies on Column's Ord implementation! - let mut region_columns: Vec<_> = region.columns().iter().cloned().collect(); - region_columns.sort_unstable(); - - let region_start = first_fit_region( - &mut column_allocations, - ®ion_columns, - region.row_count(), - 0, - None, - ) - .expect("We can always fit a region somewhere"); - - (region_start.into(), region) - }) - .collect(); - - // Return the column allocations for potential further processing. - (regions, column_allocations) -} - -/// Sorts the regions by advice area and then lays them out with the [`slot_in`] strategy. -pub fn slot_in_biggest_advice_first( - region_shapes: Vec, -) -> (Vec, CircuitAllocations) { - let mut sorted_regions: Vec<_> = region_shapes.into_iter().collect(); - let sort_key = |shape: &RegionShape| { - // Count the number of advice columns - let advice_cols = shape - .columns() - .iter() - .filter(|c| match c { - RegionColumn::Column(c) => matches!(c.column_type(), Any::Advice(_)), - _ => false, - }) - .count(); - // Sort by advice area (since this has the most contention). - advice_cols * shape.row_count() - }; - - // This used to incorrectly use `sort_unstable_by_key` with non-unique keys, which gave - // output that differed between 32-bit and 64-bit platforms, and potentially between Rust - // versions. - // We now use `sort_by_cached_key` with non-unique keys, and rely on `region_shapes` - // being sorted by region index (which we also rely on below to return `RegionStart`s - // in the correct order). - #[cfg(not(feature = "floor-planner-v1-legacy-pdqsort"))] - sorted_regions.sort_by_cached_key(sort_key); - - // To preserve compatibility, when the "floor-planner-v1-legacy-pdqsort" feature is enabled, - // we use a copy of the pdqsort implementation from the Rust 1.56.1 standard library, fixed - // to its behaviour on 64-bit platforms. - // https://github.com/rust-lang/rust/blob/1.56.1/library/core/src/slice/mod.rs#L2365-L2402 - #[cfg(feature = "floor-planner-v1-legacy-pdqsort")] - halo2_legacy_pdqsort::sort::quicksort(&mut sorted_regions, |a, b| sort_key(a).lt(&sort_key(b))); - - sorted_regions.reverse(); - - // Lay out the sorted regions. - let (mut regions, column_allocations) = slot_in(sorted_regions); - - // Un-sort the regions so they match the original indexing. - regions.sort_unstable_by_key(|(_, region)| region.region_index().0); - let regions = regions.into_iter().map(|(start, _)| start).collect(); - - (regions, column_allocations) -} diff --git a/halo2_common/src/circuit/table_layouter.rs b/halo2_common/src/circuit/table_layouter.rs deleted file mode 100644 index 8de7c0e221..0000000000 --- a/halo2_common/src/circuit/table_layouter.rs +++ /dev/null @@ -1,153 +0,0 @@ -//! Implementations of common table layouters. - -use std::{ - collections::HashMap, - fmt::{self, Debug}, -}; - -use halo2_middleware::ff::Field; - -use crate::plonk::Assigned; -use crate::plonk::{Assignment, Error, TableColumn, TableError}; - -use super::Value; - -/// Helper trait for implementing a custom [`Layouter`]. -/// -/// This trait is used for implementing table assignments. -/// -/// [`Layouter`]: super::Layouter -pub trait TableLayouter: std::fmt::Debug { - /// Assigns a fixed value to a table cell. - /// - /// Returns an error if the table cell has already been assigned to. - fn assign_cell<'v>( - &'v mut self, - annotation: &'v (dyn Fn() -> String + 'v), - column: TableColumn, - offset: usize, - to: &'v mut (dyn FnMut() -> Value> + 'v), - ) -> Result<(), Error>; -} - -/// The default value to fill a table column with. -/// -/// - The outer `Option` tracks whether the value in row 0 of the table column has been -/// assigned yet. This will always be `Some` once a valid table has been completely -/// assigned. -/// - The inner `Value` tracks whether the underlying `Assignment` is evaluating -/// witnesses or not. -type DefaultTableValue = Option>>; - -/// A table layouter that can be used to assign values to a table. -pub struct SimpleTableLayouter<'r, 'a, F: Field, CS: Assignment + 'a> { - cs: &'a mut CS, - used_columns: &'r [TableColumn], - /// maps from a fixed column to a pair (default value, vector saying which rows are assigned) - pub default_and_assigned: HashMap, Vec)>, -} - -impl<'r, 'a, F: Field, CS: Assignment + 'a> fmt::Debug for SimpleTableLayouter<'r, 'a, F, CS> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("SimpleTableLayouter") - .field("used_columns", &self.used_columns) - .field("default_and_assigned", &self.default_and_assigned) - .finish() - } -} - -impl<'r, 'a, F: Field, CS: Assignment + 'a> SimpleTableLayouter<'r, 'a, F, CS> { - /// Returns a new SimpleTableLayouter - pub fn new(cs: &'a mut CS, used_columns: &'r [TableColumn]) -> Self { - SimpleTableLayouter { - cs, - used_columns, - default_and_assigned: HashMap::default(), - } - } -} - -impl<'r, 'a, F: Field, CS: Assignment + 'a> TableLayouter - for SimpleTableLayouter<'r, 'a, F, CS> -{ - fn assign_cell<'v>( - &'v mut self, - annotation: &'v (dyn Fn() -> String + 'v), - column: TableColumn, - offset: usize, - to: &'v mut (dyn FnMut() -> Value> + 'v), - ) -> Result<(), Error> { - if self.used_columns.contains(&column) { - return Err(Error::TableError(TableError::UsedColumn(column))); - } - - let entry = self.default_and_assigned.entry(column).or_default(); - - let mut value = Value::unknown(); - self.cs.assign_fixed( - annotation, - column.inner(), - offset, // tables are always assigned starting at row 0 - || { - let res = to(); - value = res; - res - }, - )?; - - match (entry.0.is_none(), offset) { - // Use the value at offset 0 as the default value for this table column. - (true, 0) => entry.0 = Some(value), - // Since there is already an existing default value for this table column, - // the caller should not be attempting to assign another value at offset 0. - (false, 0) => { - return Err(Error::TableError(TableError::OverwriteDefault( - column, - format!("{:?}", entry.0.unwrap()), - format!("{value:?}"), - ))) - } - _ => (), - } - if entry.1.len() <= offset { - entry.1.resize(offset + 1, false); - } - entry.1[offset] = true; - - Ok(()) - } -} - -pub(crate) fn compute_table_lengths( - default_and_assigned: &HashMap, Vec)>, -) -> Result { - let column_lengths: Result, Error> = default_and_assigned - .iter() - .map(|(col, (default_value, assigned))| { - if default_value.is_none() || assigned.is_empty() { - return Err(Error::TableError(TableError::ColumnNotAssigned(*col))); - } - if assigned.iter().all(|b| *b) { - // All values in the column have been assigned - Ok((col, assigned.len())) - } else { - Err(Error::TableError(TableError::ColumnNotAssigned(*col))) - } - }) - .collect(); - let column_lengths = column_lengths?; - column_lengths - .into_iter() - .try_fold((None, 0), |acc, (col, col_len)| { - if acc.1 == 0 || acc.1 == col_len { - Ok((Some(*col), col_len)) - } else { - let mut cols = [(*col, col_len), (acc.0.unwrap(), acc.1)]; - cols.sort(); - Err(Error::TableError(TableError::UnevenColumnLengths( - cols[0], cols[1], - ))) - } - }) - .map(|col_len| col_len.1) -} diff --git a/halo2_common/src/lib.rs b/halo2_common/src/lib.rs index 26cef30838..d2d959586c 100644 --- a/halo2_common/src/lib.rs +++ b/halo2_common/src/lib.rs @@ -6,12 +6,11 @@ #![deny(rustdoc::broken_intra_doc_links)] #![deny(unsafe_code)] -pub mod circuit; -pub use halo2curves; +pub mod helpers; pub mod multicore; pub mod plonk; -pub mod helpers; +pub use halo2curves; pub use helpers::SerdeFormat; // TODO: Everything that is moved from this crate to frontend or backend should recover the diff --git a/halo2_common/src/plonk.rs b/halo2_common/src/plonk.rs index ecabbfea3a..38bbe64e79 100644 --- a/halo2_common/src/plonk.rs +++ b/halo2_common/src/plonk.rs @@ -5,394 +5,18 @@ //! [halo]: https://eprint.iacr.org/2019/1021 //! [plonk]: https://eprint.iacr.org/2019/953 -use halo2_middleware::circuit::{Advice, Fixed, Instance}; -use halo2_middleware::ff::Field; +use halo2_middleware::circuit::ColumnMid; use halo2_middleware::poly::Rotation; -use std::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; - -pub mod circuit; -pub mod error; -pub mod keygen; -pub mod lookup; -pub mod permutation; -pub mod shuffle; - -pub use circuit::*; -pub use error::*; -pub use keygen::*; - -/// A value assigned to a cell within a circuit. -/// -/// Stored as a fraction, so the backend can use batch inversion. -/// -/// A denominator of zero maps to an assigned value of zero. -#[derive(Clone, Copy, Debug)] -pub enum Assigned { - /// The field element zero. - Zero, - /// A value that does not require inversion to evaluate. - Trivial(F), - /// A value stored as a fraction to enable batch inversion. - Rational(F, F), -} - -impl From<&Assigned> for Assigned { - fn from(val: &Assigned) -> Self { - *val - } -} - -impl From<&F> for Assigned { - fn from(numerator: &F) -> Self { - Assigned::Trivial(*numerator) - } -} - -impl From for Assigned { - fn from(numerator: F) -> Self { - Assigned::Trivial(numerator) - } -} - -impl From<(F, F)> for Assigned { - fn from((numerator, denominator): (F, F)) -> Self { - Assigned::Rational(numerator, denominator) - } -} - -impl PartialEq for Assigned { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - // At least one side is directly zero. - (Self::Zero, Self::Zero) => true, - (Self::Zero, x) | (x, Self::Zero) => x.is_zero_vartime(), - - // One side is x/0 which maps to zero. - (Self::Rational(_, denominator), x) | (x, Self::Rational(_, denominator)) - if denominator.is_zero_vartime() => - { - x.is_zero_vartime() - } - - // Okay, we need to do some actual math... - (Self::Trivial(lhs), Self::Trivial(rhs)) => lhs == rhs, - (Self::Trivial(x), Self::Rational(numerator, denominator)) - | (Self::Rational(numerator, denominator), Self::Trivial(x)) => { - &(*x * denominator) == numerator - } - ( - Self::Rational(lhs_numerator, lhs_denominator), - Self::Rational(rhs_numerator, rhs_denominator), - ) => *lhs_numerator * rhs_denominator == *lhs_denominator * rhs_numerator, - } - } -} - -impl Eq for Assigned {} - -impl Neg for Assigned { - type Output = Assigned; - fn neg(self) -> Self::Output { - match self { - Self::Zero => Self::Zero, - Self::Trivial(numerator) => Self::Trivial(-numerator), - Self::Rational(numerator, denominator) => Self::Rational(-numerator, denominator), - } - } -} - -impl Neg for &Assigned { - type Output = Assigned; - fn neg(self) -> Self::Output { - -*self - } -} - -impl Add for Assigned { - type Output = Assigned; - fn add(self, rhs: Assigned) -> Assigned { - match (self, rhs) { - // One side is directly zero. - (Self::Zero, _) => rhs, - (_, Self::Zero) => self, - - // One side is x/0 which maps to zero. - (Self::Rational(_, denominator), other) | (other, Self::Rational(_, denominator)) - if denominator.is_zero_vartime() => - { - other - } - - // Okay, we need to do some actual math... - (Self::Trivial(lhs), Self::Trivial(rhs)) => Self::Trivial(lhs + rhs), - (Self::Rational(numerator, denominator), Self::Trivial(other)) - | (Self::Trivial(other), Self::Rational(numerator, denominator)) => { - Self::Rational(numerator + denominator * other, denominator) - } - ( - Self::Rational(lhs_numerator, lhs_denominator), - Self::Rational(rhs_numerator, rhs_denominator), - ) => Self::Rational( - lhs_numerator * rhs_denominator + lhs_denominator * rhs_numerator, - lhs_denominator * rhs_denominator, - ), - } - } -} - -impl Add for Assigned { - type Output = Assigned; - fn add(self, rhs: F) -> Assigned { - self + Self::Trivial(rhs) - } -} - -impl Add for &Assigned { - type Output = Assigned; - fn add(self, rhs: F) -> Assigned { - *self + rhs - } -} - -impl Add<&Assigned> for Assigned { - type Output = Assigned; - fn add(self, rhs: &Self) -> Assigned { - self + *rhs - } -} - -impl Add> for &Assigned { - type Output = Assigned; - fn add(self, rhs: Assigned) -> Assigned { - *self + rhs - } -} - -impl Add<&Assigned> for &Assigned { - type Output = Assigned; - fn add(self, rhs: &Assigned) -> Assigned { - *self + *rhs - } -} - -impl AddAssign for Assigned { - fn add_assign(&mut self, rhs: Self) { - *self = *self + rhs; - } -} - -impl AddAssign<&Assigned> for Assigned { - fn add_assign(&mut self, rhs: &Self) { - *self = *self + rhs; - } -} - -impl Sub for Assigned { - type Output = Assigned; - fn sub(self, rhs: Assigned) -> Assigned { - self + (-rhs) - } -} - -impl Sub for Assigned { - type Output = Assigned; - fn sub(self, rhs: F) -> Assigned { - self + (-rhs) - } -} - -impl Sub for &Assigned { - type Output = Assigned; - fn sub(self, rhs: F) -> Assigned { - *self - rhs - } -} - -impl Sub<&Assigned> for Assigned { - type Output = Assigned; - fn sub(self, rhs: &Self) -> Assigned { - self - *rhs - } -} - -impl Sub> for &Assigned { - type Output = Assigned; - fn sub(self, rhs: Assigned) -> Assigned { - *self - rhs - } -} - -impl Sub<&Assigned> for &Assigned { - type Output = Assigned; - fn sub(self, rhs: &Assigned) -> Assigned { - *self - *rhs - } -} - -impl SubAssign for Assigned { - fn sub_assign(&mut self, rhs: Self) { - *self = *self - rhs; - } -} - -impl SubAssign<&Assigned> for Assigned { - fn sub_assign(&mut self, rhs: &Self) { - *self = *self - rhs; - } -} - -impl Mul for Assigned { - type Output = Assigned; - fn mul(self, rhs: Assigned) -> Assigned { - match (self, rhs) { - (Self::Zero, _) | (_, Self::Zero) => Self::Zero, - (Self::Trivial(lhs), Self::Trivial(rhs)) => Self::Trivial(lhs * rhs), - (Self::Rational(numerator, denominator), Self::Trivial(other)) - | (Self::Trivial(other), Self::Rational(numerator, denominator)) => { - Self::Rational(numerator * other, denominator) - } - ( - Self::Rational(lhs_numerator, lhs_denominator), - Self::Rational(rhs_numerator, rhs_denominator), - ) => Self::Rational( - lhs_numerator * rhs_numerator, - lhs_denominator * rhs_denominator, - ), - } - } -} - -impl Mul for Assigned { - type Output = Assigned; - fn mul(self, rhs: F) -> Assigned { - self * Self::Trivial(rhs) - } -} - -impl Mul for &Assigned { - type Output = Assigned; - fn mul(self, rhs: F) -> Assigned { - *self * rhs - } -} - -impl Mul<&Assigned> for Assigned { - type Output = Assigned; - fn mul(self, rhs: &Assigned) -> Assigned { - self * *rhs - } -} - -impl MulAssign for Assigned { - fn mul_assign(&mut self, rhs: Self) { - *self = *self * rhs; - } -} - -impl MulAssign<&Assigned> for Assigned { - fn mul_assign(&mut self, rhs: &Self) { - *self = *self * rhs; - } -} - -impl Assigned { - /// Returns the numerator. - pub fn numerator(&self) -> F { - match self { - Self::Zero => F::ZERO, - Self::Trivial(x) => *x, - Self::Rational(numerator, _) => *numerator, - } - } - - /// Returns the denominator, if non-trivial. - pub fn denominator(&self) -> Option { - match self { - Self::Zero => None, - Self::Trivial(_) => None, - Self::Rational(_, denominator) => Some(*denominator), - } - } - - /// Returns true iff this element is zero. - pub fn is_zero_vartime(&self) -> bool { - match self { - Self::Zero => true, - Self::Trivial(x) => x.is_zero_vartime(), - // Assigned maps x/0 -> 0. - Self::Rational(numerator, denominator) => { - numerator.is_zero_vartime() || denominator.is_zero_vartime() - } - } - } - - /// Doubles this element. - #[must_use] - pub fn double(&self) -> Self { - match self { - Self::Zero => Self::Zero, - Self::Trivial(x) => Self::Trivial(x.double()), - Self::Rational(numerator, denominator) => { - Self::Rational(numerator.double(), *denominator) - } - } - } - - /// Squares this element. - #[must_use] - pub fn square(&self) -> Self { - match self { - Self::Zero => Self::Zero, - Self::Trivial(x) => Self::Trivial(x.square()), - Self::Rational(numerator, denominator) => { - Self::Rational(numerator.square(), denominator.square()) - } - } - } - - /// Cubes this element. - #[must_use] - pub fn cube(&self) -> Self { - self.square() * self - } - - /// Inverts this assigned value (taking the inverse of zero to be zero). - pub fn invert(&self) -> Self { - match self { - Self::Zero => Self::Zero, - Self::Trivial(x) => Self::Rational(F::ONE, *x), - Self::Rational(numerator, denominator) => Self::Rational(*denominator, *numerator), - } - } - - /// Evaluates this assigned value directly, performing an unbatched inversion if - /// necessary. - /// - /// If the denominator is zero, this returns zero. - pub fn evaluate(self) -> F { - match self { - Self::Zero => F::ZERO, - Self::Trivial(x) => x, - Self::Rational(numerator, denominator) => { - if denominator == F::ONE { - numerator - } else { - numerator * denominator.invert().unwrap_or(F::ZERO) - } - } - } - } -} /// List of queries (columns and rotations) used by a circuit #[derive(Debug, Clone)] pub struct Queries { /// List of unique advice queries - pub advice: Vec<(Column, Rotation)>, + pub advice: Vec<(ColumnMid, Rotation)>, /// List of unique instance queries - pub instance: Vec<(Column, Rotation)>, + pub instance: Vec<(ColumnMid, Rotation)>, /// List of unique fixed queries - pub fixed: Vec<(Column, Rotation)>, + pub fixed: Vec<(ColumnMid, Rotation)>, /// Contains an integer for each advice column /// identifying how many distinct queries it has /// so far; should be same length as cs.num_advice_columns. @@ -441,302 +65,3 @@ impl Queries { factors + 1 } } - -#[cfg(test)] -mod tests { - use halo2curves::pasta::Fp; - - use super::Assigned; - // We use (numerator, denominator) in the comments below to denote a rational. - #[test] - fn add_trivial_to_inv0_rational() { - // a = 2 - // b = (1,0) - let a = Assigned::Trivial(Fp::from(2)); - let b = Assigned::Rational(Fp::one(), Fp::zero()); - - // 2 + (1,0) = 2 + 0 = 2 - // This fails if addition is implemented using normal rules for rationals. - assert_eq!((a + b).evaluate(), a.evaluate()); - assert_eq!((b + a).evaluate(), a.evaluate()); - } - - #[test] - fn add_rational_to_inv0_rational() { - // a = (1,2) - // b = (1,0) - let a = Assigned::Rational(Fp::one(), Fp::from(2)); - let b = Assigned::Rational(Fp::one(), Fp::zero()); - - // (1,2) + (1,0) = (1,2) + 0 = (1,2) - // This fails if addition is implemented using normal rules for rationals. - assert_eq!((a + b).evaluate(), a.evaluate()); - assert_eq!((b + a).evaluate(), a.evaluate()); - } - - #[test] - fn sub_trivial_from_inv0_rational() { - // a = 2 - // b = (1,0) - let a = Assigned::Trivial(Fp::from(2)); - let b = Assigned::Rational(Fp::one(), Fp::zero()); - - // (1,0) - 2 = 0 - 2 = -2 - // This fails if subtraction is implemented using normal rules for rationals. - assert_eq!((b - a).evaluate(), (-a).evaluate()); - - // 2 - (1,0) = 2 - 0 = 2 - assert_eq!((a - b).evaluate(), a.evaluate()); - } - - #[test] - fn sub_rational_from_inv0_rational() { - // a = (1,2) - // b = (1,0) - let a = Assigned::Rational(Fp::one(), Fp::from(2)); - let b = Assigned::Rational(Fp::one(), Fp::zero()); - - // (1,0) - (1,2) = 0 - (1,2) = -(1,2) - // This fails if subtraction is implemented using normal rules for rationals. - assert_eq!((b - a).evaluate(), (-a).evaluate()); - - // (1,2) - (1,0) = (1,2) - 0 = (1,2) - assert_eq!((a - b).evaluate(), a.evaluate()); - } - - #[test] - fn mul_rational_by_inv0_rational() { - // a = (1,2) - // b = (1,0) - let a = Assigned::Rational(Fp::one(), Fp::from(2)); - let b = Assigned::Rational(Fp::one(), Fp::zero()); - - // (1,2) * (1,0) = (1,2) * 0 = 0 - assert_eq!((a * b).evaluate(), Fp::zero()); - - // (1,0) * (1,2) = 0 * (1,2) = 0 - assert_eq!((b * a).evaluate(), Fp::zero()); - } -} - -#[cfg(test)] -mod proptests { - use std::{ - cmp, - ops::{Add, Mul, Neg, Sub}, - }; - - use group::ff::Field; - use halo2curves::pasta::Fp; - use proptest::{collection::vec, prelude::*, sample::select}; - - use super::Assigned; - - trait UnaryOperand: Neg { - fn double(&self) -> Self; - fn square(&self) -> Self; - fn cube(&self) -> Self; - fn inv0(&self) -> Self; - } - - impl UnaryOperand for F { - fn double(&self) -> Self { - self.double() - } - - fn square(&self) -> Self { - self.square() - } - - fn cube(&self) -> Self { - self.cube() - } - - fn inv0(&self) -> Self { - self.invert().unwrap_or(F::ZERO) - } - } - - impl UnaryOperand for Assigned { - fn double(&self) -> Self { - self.double() - } - - fn square(&self) -> Self { - self.square() - } - - fn cube(&self) -> Self { - self.cube() - } - - fn inv0(&self) -> Self { - self.invert() - } - } - - #[derive(Clone, Debug)] - enum UnaryOperator { - Neg, - Double, - Square, - Cube, - Inv0, - } - - const UNARY_OPERATORS: &[UnaryOperator] = &[ - UnaryOperator::Neg, - UnaryOperator::Double, - UnaryOperator::Square, - UnaryOperator::Cube, - UnaryOperator::Inv0, - ]; - - impl UnaryOperator { - fn apply(&self, a: F) -> F { - match self { - Self::Neg => -a, - Self::Double => a.double(), - Self::Square => a.square(), - Self::Cube => a.cube(), - Self::Inv0 => a.inv0(), - } - } - } - - trait BinaryOperand: Sized + Add + Sub + Mul {} - impl BinaryOperand for F {} - impl BinaryOperand for Assigned {} - - #[derive(Clone, Debug)] - enum BinaryOperator { - Add, - Sub, - Mul, - } - - const BINARY_OPERATORS: &[BinaryOperator] = &[ - BinaryOperator::Add, - BinaryOperator::Sub, - BinaryOperator::Mul, - ]; - - impl BinaryOperator { - fn apply(&self, a: F, b: F) -> F { - match self { - Self::Add => a + b, - Self::Sub => a - b, - Self::Mul => a * b, - } - } - } - - #[derive(Clone, Debug)] - enum Operator { - Unary(UnaryOperator), - Binary(BinaryOperator), - } - - prop_compose! { - /// Use narrow that can be easily reduced. - fn arb_element()(val in any::()) -> Fp { - Fp::from(val) - } - } - - prop_compose! { - fn arb_trivial()(element in arb_element()) -> Assigned { - Assigned::Trivial(element) - } - } - - prop_compose! { - /// Generates half of the denominators as zero to represent a deferred inversion. - fn arb_rational()( - numerator in arb_element(), - denominator in prop_oneof![ - 1 => Just(Fp::zero()), - 2 => arb_element(), - ], - ) -> Assigned { - Assigned::Rational(numerator, denominator) - } - } - - prop_compose! { - fn arb_operators(num_unary: usize, num_binary: usize)( - unary in vec(select(UNARY_OPERATORS), num_unary), - binary in vec(select(BINARY_OPERATORS), num_binary), - ) -> Vec { - unary.into_iter() - .map(Operator::Unary) - .chain(binary.into_iter().map(Operator::Binary)) - .collect() - } - } - - prop_compose! { - fn arb_testcase()( - num_unary in 0usize..5, - num_binary in 0usize..5, - )( - values in vec( - prop_oneof![ - 1 => Just(Assigned::Zero), - 2 => arb_trivial(), - 2 => arb_rational(), - ], - // Ensure that: - // - we have at least one value to apply unary operators to. - // - we can apply every binary operator pairwise sequentially. - cmp::max(usize::from(num_unary > 0), num_binary + 1)), - operations in arb_operators(num_unary, num_binary).prop_shuffle(), - ) -> (Vec>, Vec) { - (values, operations) - } - } - - proptest! { - #[test] - fn operation_commutativity((values, operations) in arb_testcase()) { - // Evaluate the values at the start. - let elements: Vec<_> = values.iter().cloned().map(|v| v.evaluate()).collect(); - - // Apply the operations to both the deferred and evaluated values. - fn evaluate( - items: Vec, - operators: &[Operator], - ) -> F { - let mut ops = operators.iter(); - - // Process all binary operators. We are guaranteed to have exactly as many - // binary operators as we need calls to the reduction closure. - let mut res = items.into_iter().reduce(|mut a, b| loop { - match ops.next() { - Some(Operator::Unary(op)) => a = op.apply(a), - Some(Operator::Binary(op)) => break op.apply(a, b), - None => unreachable!(), - } - }).unwrap(); - - // Process any unary operators that weren't handled in the reduce() call - // above (either if we only had one item, or there were unary operators - // after the last binary operator). We are guaranteed to have no binary - // operators remaining at this point. - loop { - match ops.next() { - Some(Operator::Unary(op)) => res = op.apply(res), - Some(Operator::Binary(_)) => unreachable!(), - None => break res, - } - } - } - let deferred_result = evaluate(values, &operations); - let evaluated_result = evaluate(elements, &operations); - - // The two should be equal, i.e. deferred inversion should commute with the - // list of operations. - assert_eq!(deferred_result.evaluate(), evaluated_result); - } - } -} diff --git a/halo2_common/src/plonk/circuit.rs b/halo2_common/src/plonk/circuit.rs deleted file mode 100644 index a0d0304126..0000000000 --- a/halo2_common/src/plonk/circuit.rs +++ /dev/null @@ -1,2807 +0,0 @@ -use super::{lookup, permutation, shuffle, Error, Queries}; -use crate::circuit::layouter::SyncDeps; -use crate::circuit::{Layouter, Region, Value}; -use crate::plonk::Assigned; -use core::cmp::max; -use core::ops::{Add, Mul}; -use halo2_middleware::circuit::{ - Advice, AdviceQueryMid, Any, ChallengeMid, ColumnMid, ColumnType, ConstraintSystemV2Backend, - ExpressionMid, Fixed, FixedQueryMid, GateV2Backend, Instance, InstanceQueryMid, -}; -use halo2_middleware::ff::Field; -use halo2_middleware::metadata; -use halo2_middleware::poly::Rotation; -use sealed::SealedPhase; -use std::collections::HashMap; -use std::fmt::Debug; -use std::iter::{Product, Sum}; -use std::{ - convert::TryFrom, - ops::{Neg, Sub}, -}; - -mod compress_selectors; - -/// A column with an index and type -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] -pub struct Column { - pub index: usize, - pub column_type: C, -} - -impl From> for metadata::Column { - fn from(val: Column) -> Self { - metadata::Column { - index: val.index(), - column_type: *val.column_type(), - } - } -} - -// TODO: Remove all these methods, and directly access the fields? -impl Column { - pub fn new(index: usize, column_type: C) -> Self { - Column { index, column_type } - } - - /// Index of this column. - pub fn index(&self) -> usize { - self.index - } - - /// Type of this column. - pub fn column_type(&self) -> &C { - &self.column_type - } - - /// Return expression from column at a relative position - pub fn query_cell(&self, at: Rotation) -> Expression { - let expr_mid = self.column_type.query_cell::(self.index, at); - match expr_mid { - ExpressionMid::Advice(q) => Expression::Advice(AdviceQuery { - index: None, - column_index: q.column_index, - rotation: q.rotation, - phase: sealed::Phase(q.phase), - }), - ExpressionMid::Fixed(q) => Expression::Fixed(FixedQuery { - index: None, - column_index: q.column_index, - rotation: q.rotation, - }), - ExpressionMid::Instance(q) => Expression::Instance(InstanceQuery { - index: None, - column_index: q.column_index, - rotation: q.rotation, - }), - _ => unreachable!(), - } - } - - /// Return expression from column at the current row - pub fn cur(&self) -> Expression { - self.query_cell(Rotation::cur()) - } - - /// Return expression from column at the next row - pub fn next(&self) -> Expression { - self.query_cell(Rotation::next()) - } - - /// Return expression from column at the previous row - pub fn prev(&self) -> Expression { - self.query_cell(Rotation::prev()) - } - - /// Return expression from column at the specified rotation - pub fn rot(&self, rotation: i32) -> Expression { - self.query_cell(Rotation(rotation)) - } -} - -impl Ord for Column { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - // This ordering is consensus-critical! The layouters rely on deterministic column - // orderings. - match self.column_type.into().cmp(&other.column_type.into()) { - // Indices are assigned within column types. - std::cmp::Ordering::Equal => self.index.cmp(&other.index), - order => order, - } - } -} - -impl PartialOrd for Column { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl From for Column { - fn from(column: ColumnMid) -> Column { - Column { - index: column.index, - column_type: column.column_type, - } - } -} - -impl From> for ColumnMid { - fn from(val: Column) -> Self { - ColumnMid { - index: val.index(), - column_type: *val.column_type(), - } - } -} - -impl From> for Column { - fn from(advice: Column) -> Column { - Column { - index: advice.index(), - column_type: Any::Advice(advice.column_type), - } - } -} - -impl From> for Column { - fn from(advice: Column) -> Column { - Column { - index: advice.index(), - column_type: Any::Fixed, - } - } -} - -impl From> for Column { - fn from(advice: Column) -> Column { - Column { - index: advice.index(), - column_type: Any::Instance, - } - } -} - -impl TryFrom> for Column { - type Error = &'static str; - - fn try_from(any: Column) -> Result { - match any.column_type() { - Any::Advice(advice) => Ok(Column { - index: any.index(), - column_type: *advice, - }), - _ => Err("Cannot convert into Column"), - } - } -} - -impl TryFrom> for Column { - type Error = &'static str; - - fn try_from(any: Column) -> Result { - match any.column_type() { - Any::Fixed => Ok(Column { - index: any.index(), - column_type: Fixed, - }), - _ => Err("Cannot convert into Column"), - } - } -} - -impl TryFrom> for Column { - type Error = &'static str; - - fn try_from(any: Column) -> Result { - match any.column_type() { - Any::Instance => Ok(Column { - index: any.index(), - column_type: Instance, - }), - _ => Err("Cannot convert into Column"), - } - } -} - -// TODO: Move sealed phase to frontend, and always use u8 in middleware and backend -pub mod sealed { - /// Phase of advice column - #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] - pub struct Phase(pub u8); - - impl Phase { - pub fn prev(&self) -> Option { - self.0.checked_sub(1).map(Phase) - } - } - - impl SealedPhase for Phase { - fn to_sealed(self) -> Phase { - self - } - } - - /// Sealed trait to help keep `Phase` private. - pub trait SealedPhase { - fn to_sealed(self) -> Phase; - } -} - -/// Phase of advice column -pub trait Phase: SealedPhase {} - -impl Phase for P {} - -/// First phase -#[derive(Debug)] -pub struct FirstPhase; - -impl SealedPhase for super::FirstPhase { - fn to_sealed(self) -> sealed::Phase { - sealed::Phase(0) - } -} - -/// Second phase -#[derive(Debug)] -pub struct SecondPhase; - -impl SealedPhase for super::SecondPhase { - fn to_sealed(self) -> sealed::Phase { - sealed::Phase(1) - } -} - -/// Third phase -#[derive(Debug)] -pub struct ThirdPhase; - -impl SealedPhase for super::ThirdPhase { - fn to_sealed(self) -> sealed::Phase { - sealed::Phase(2) - } -} - -/// A selector, representing a fixed boolean value per row of the circuit. -/// -/// Selectors can be used to conditionally enable (portions of) gates: -/// ``` -/// use halo2_middleware::poly::Rotation; -/// # use halo2curves::pasta::Fp; -/// # use halo2_common::plonk::ConstraintSystem; -/// -/// # let mut meta = ConstraintSystem::::default(); -/// let a = meta.advice_column(); -/// let b = meta.advice_column(); -/// let s = meta.selector(); -/// -/// meta.create_gate("foo", |meta| { -/// let a = meta.query_advice(a, Rotation::prev()); -/// let b = meta.query_advice(b, Rotation::cur()); -/// let s = meta.query_selector(s); -/// -/// // On rows where the selector is enabled, a is constrained to equal b. -/// // On rows where the selector is disabled, a and b can take any value. -/// vec![s * (a - b)] -/// }); -/// ``` -/// -/// Selectors are disabled on all rows by default, and must be explicitly enabled on each -/// row when required: -/// ``` -/// use halo2_middleware::circuit::Advice; -/// use halo2_common::circuit::{Chip, Layouter, Value}; -/// use halo2_common::plonk::circuit::{Column, Selector}; -/// use halo2_common::plonk::Error; -/// use halo2_middleware::ff::Field; -/// # use halo2_middleware::circuit::Fixed; -/// -/// struct Config { -/// a: Column, -/// b: Column, -/// s: Selector, -/// } -/// -/// fn circuit_logic>(chip: C, mut layouter: impl Layouter) -> Result<(), Error> { -/// let config = chip.config(); -/// # let config: Config = todo!(); -/// layouter.assign_region(|| "bar", |mut region| { -/// region.assign_advice(|| "a", config.a, 0, || Value::known(F::ONE))?; -/// region.assign_advice(|| "a", config.b, 1, || Value::known(F::ONE))?; -/// config.s.enable(&mut region, 1) -/// })?; -/// Ok(()) -/// } -/// ``` -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct Selector(pub usize, bool); - -impl Selector { - /// Enable this selector at the given offset within the given region. - pub fn enable(&self, region: &mut Region, offset: usize) -> Result<(), Error> { - region.enable_selector(|| "", self, offset) - } - - /// Is this selector "simple"? Simple selectors can only be multiplied - /// by expressions that contain no other simple selectors. - pub fn is_simple(&self) -> bool { - self.1 - } - - /// Returns index of this selector - pub fn index(&self) -> usize { - self.0 - } - - /// Return expression from selector - pub fn expr(&self) -> Expression { - Expression::Selector(*self) - } -} - -/// Query of fixed column at a certain relative location -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct FixedQuery { - /// Query index - pub index: Option, - /// Column index - pub column_index: usize, - /// Rotation of this query - pub rotation: Rotation, -} - -impl FixedQuery { - /// Column index - pub fn column_index(&self) -> usize { - self.column_index - } - - /// Rotation of this query - pub fn rotation(&self) -> Rotation { - self.rotation - } -} - -/// Query of advice column at a certain relative location -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct AdviceQuery { - /// Query index - pub index: Option, - /// Column index - pub column_index: usize, - /// Rotation of this query - pub rotation: Rotation, - /// Phase of this advice column - pub phase: sealed::Phase, -} - -impl AdviceQuery { - /// Column index - pub fn column_index(&self) -> usize { - self.column_index - } - - /// Rotation of this query - pub fn rotation(&self) -> Rotation { - self.rotation - } - - /// Phase of this advice column - pub fn phase(&self) -> u8 { - self.phase.0 - } -} - -/// Query of instance column at a certain relative location -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct InstanceQuery { - /// Query index - pub index: Option, - /// Column index - pub column_index: usize, - /// Rotation of this query - pub rotation: Rotation, -} - -impl InstanceQuery { - /// Column index - pub fn column_index(&self) -> usize { - self.column_index - } - - /// Rotation of this query - pub fn rotation(&self) -> Rotation { - self.rotation - } -} - -/// A fixed column of a lookup table. -/// -/// A lookup table can be loaded into this column via [`Layouter::assign_table`]. Columns -/// can currently only contain a single table, but they may be used in multiple lookup -/// arguments via [`ConstraintSystem::lookup`]. -/// -/// Lookup table columns are always "encumbered" by the lookup arguments they are used in; -/// they cannot simultaneously be used as general fixed columns. -/// -/// [`Layouter::assign_table`]: crate::circuit::Layouter::assign_table -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub struct TableColumn { - /// The fixed column that this table column is stored in. - /// - /// # Security - /// - /// This inner column MUST NOT be exposed in the public API, or else chip developers - /// can load lookup tables into their circuits without default-value-filling the - /// columns, which can cause soundness bugs. - inner: Column, -} - -impl TableColumn { - /// Returns inner column - pub fn inner(&self) -> Column { - self.inner - } -} - -/// A challenge squeezed from transcript after advice columns at the phase have been committed. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] -pub struct Challenge { - pub index: usize, - pub(crate) phase: u8, -} - -impl Challenge { - /// Index of this challenge. - pub fn index(&self) -> usize { - self.index - } - - /// Phase of this challenge. - pub fn phase(&self) -> u8 { - self.phase - } - - /// Return Expression - pub fn expr(&self) -> Expression { - Expression::Challenge(*self) - } -} - -impl From for ChallengeMid { - fn from(val: Challenge) -> Self { - ChallengeMid { - index: val.index, - phase: val.phase, - } - } -} - -impl From for Challenge { - fn from(c: ChallengeMid) -> Self { - Self { - index: c.index, - phase: c.phase, - } - } -} - -/// This trait allows a [`Circuit`] to direct some backend to assign a witness -/// for a constraint system. -pub trait Assignment { - /// Creates a new region and enters into it. - /// - /// Panics if we are currently in a region (if `exit_region` was not called). - /// - /// Not intended for downstream consumption; use [`Layouter::assign_region`] instead. - /// - /// [`Layouter::assign_region`]: crate::circuit::Layouter#method.assign_region - fn enter_region(&mut self, name_fn: N) - where - NR: Into, - N: FnOnce() -> NR; - - /// Allows the developer to include an annotation for an specific column within a `Region`. - /// - /// This is usually useful for debugging circuit failures. - fn annotate_column(&mut self, annotation: A, column: Column) - where - A: FnOnce() -> AR, - AR: Into; - - /// Exits the current region. - /// - /// Panics if we are not currently in a region (if `enter_region` was not called). - /// - /// Not intended for downstream consumption; use [`Layouter::assign_region`] instead. - /// - /// [`Layouter::assign_region`]: crate::circuit::Layouter#method.assign_region - fn exit_region(&mut self); - - /// Enables a selector at the given row. - fn enable_selector( - &mut self, - annotation: A, - selector: &Selector, - row: usize, - ) -> Result<(), Error> - where - A: FnOnce() -> AR, - AR: Into; - - /// Queries the cell of an instance column at a particular absolute row. - /// - /// Returns the cell's value, if known. - fn query_instance(&self, column: Column, row: usize) -> Result, Error>; - - /// Assign an advice column value (witness) - fn assign_advice( - &mut self, - annotation: A, - column: Column, - row: usize, - to: V, - ) -> Result<(), Error> - where - V: FnOnce() -> Value, - VR: Into>, - A: FnOnce() -> AR, - AR: Into; - - /// Assign a fixed value - fn assign_fixed( - &mut self, - annotation: A, - column: Column, - row: usize, - to: V, - ) -> Result<(), Error> - where - V: FnOnce() -> Value, - VR: Into>, - A: FnOnce() -> AR, - AR: Into; - - /// Assign two cells to have the same value - fn copy( - &mut self, - left_column: Column, - left_row: usize, - right_column: Column, - right_row: usize, - ) -> Result<(), Error>; - - /// Fills a fixed `column` starting from the given `row` with value `to`. - fn fill_from_row( - &mut self, - column: Column, - row: usize, - to: Value>, - ) -> Result<(), Error>; - - /// Queries the value of the given challenge. - /// - /// Returns `Value::unknown()` if the current synthesis phase is before the challenge can be queried. - fn get_challenge(&self, challenge: Challenge) -> Value; - - /// Creates a new (sub)namespace and enters into it. - /// - /// Not intended for downstream consumption; use [`Layouter::namespace`] instead. - /// - /// [`Layouter::namespace`]: crate::circuit::Layouter#method.namespace - fn push_namespace(&mut self, name_fn: N) - where - NR: Into, - N: FnOnce() -> NR; - - /// Exits out of the existing namespace. - /// - /// Not intended for downstream consumption; use [`Layouter::namespace`] instead. - /// - /// [`Layouter::namespace`]: crate::circuit::Layouter#method.namespace - fn pop_namespace(&mut self, gadget_name: Option); -} - -/// A floor planning strategy for a circuit. -/// -/// The floor planner is chip-agnostic and applies its strategy to the circuit it is used -/// within. -pub trait FloorPlanner { - /// Given the provided `cs`, synthesize the given circuit. - /// - /// `constants` is the list of fixed columns that the layouter may use to assign - /// global constant values. These columns will all have been equality-enabled. - /// - /// Internally, a floor planner will perform the following operations: - /// - Instantiate a [`Layouter`] for this floor planner. - /// - Perform any necessary setup or measurement tasks, which may involve one or more - /// calls to `Circuit::default().synthesize(config, &mut layouter)`. - /// - Call `circuit.synthesize(config, &mut layouter)` exactly once. - fn synthesize + SyncDeps, C: Circuit>( - cs: &mut CS, - circuit: &C, - config: C::Config, - constants: Vec>, - ) -> Result<(), Error>; -} - -/// This is a trait that circuits provide implementations for so that the -/// backend prover can ask the circuit to synthesize using some given -/// [`ConstraintSystem`] implementation. -pub trait Circuit { - /// This is a configuration object that stores things like columns. - type Config: Clone; - /// The floor planner used for this circuit. This is an associated type of the - /// `Circuit` trait because its behaviour is circuit-critical. - type FloorPlanner: FloorPlanner; - /// Optional circuit configuration parameters. Requires the `circuit-params` feature. - #[cfg(feature = "circuit-params")] - type Params: Default; - - /// Returns a copy of this circuit with no witness values (i.e. all witnesses set to - /// `None`). For most circuits, this will be equal to `Self::default()`. - fn without_witnesses(&self) -> Self; - - /// Returns a reference to the parameters that should be used to configure the circuit. - /// Requires the `circuit-params` feature. - #[cfg(feature = "circuit-params")] - fn params(&self) -> Self::Params { - Self::Params::default() - } - - /// The circuit is given an opportunity to describe the exact gate - /// arrangement, column arrangement, etc. Takes a runtime parameter. The default - /// implementation calls `configure` ignoring the `_params` argument in order to easily support - /// circuits that don't use configuration parameters. - #[cfg(feature = "circuit-params")] - fn configure_with_params( - meta: &mut ConstraintSystem, - _params: Self::Params, - ) -> Self::Config { - Self::configure(meta) - } - - /// The circuit is given an opportunity to describe the exact gate - /// arrangement, column arrangement, etc. - fn configure(meta: &mut ConstraintSystem) -> Self::Config; - - /// Given the provided `cs`, synthesize the circuit. The concrete type of - /// the caller will be different depending on the context, and they may or - /// may not expect to have a witness present. - fn synthesize(&self, config: Self::Config, layouter: impl Layouter) -> Result<(), Error>; -} - -// TODO: Create two types from this, one with selector for the frontend (this way we can move the -// Layouter traits, Region and Selector to frontend). And one without selector for the backend. -/// Low-degree expression representing an identity that must hold over the committed columns. -#[derive(Clone, PartialEq, Eq)] -pub enum Expression { - /// This is a constant polynomial - Constant(F), - /// This is a virtual selector - Selector(Selector), - /// This is a fixed column queried at a certain relative location - Fixed(FixedQuery), - /// This is an advice (witness) column queried at a certain relative location - Advice(AdviceQuery), - /// This is an instance (external) column queried at a certain relative location - Instance(InstanceQuery), - /// This is a challenge - Challenge(Challenge), - /// This is a negated polynomial - Negated(Box>), - /// This is the sum of two polynomials - Sum(Box>, Box>), - /// This is the product of two polynomials - Product(Box>, Box>), - /// This is a scaled polynomial - Scaled(Box>, F), -} - -impl From> for ExpressionMid { - fn from(val: Expression) -> Self { - match val { - Expression::Constant(c) => ExpressionMid::Constant(c), - Expression::Selector(_) => unreachable!(), - Expression::Fixed(FixedQuery { - column_index, - rotation, - .. - }) => ExpressionMid::Fixed(FixedQueryMid { - column_index, - rotation, - }), - Expression::Advice(AdviceQuery { - column_index, - rotation, - phase, - .. - }) => ExpressionMid::Advice(AdviceQueryMid { - column_index, - rotation, - phase: phase.0, - }), - Expression::Instance(InstanceQuery { - column_index, - rotation, - .. - }) => ExpressionMid::Instance(InstanceQueryMid { - column_index, - rotation, - }), - Expression::Challenge(c) => ExpressionMid::Challenge(c.into()), - Expression::Negated(e) => ExpressionMid::Negated(Box::new((*e).into())), - Expression::Sum(lhs, rhs) => { - ExpressionMid::Sum(Box::new((*lhs).into()), Box::new((*rhs).into())) - } - Expression::Product(lhs, rhs) => { - ExpressionMid::Product(Box::new((*lhs).into()), Box::new((*rhs).into())) - } - Expression::Scaled(e, c) => ExpressionMid::Scaled(Box::new((*e).into()), c), - } - } -} - -impl Expression { - /// Make side effects - pub fn query_cells(&mut self, cells: &mut VirtualCells<'_, F>) { - match self { - Expression::Constant(_) => (), - Expression::Selector(selector) => { - if !cells.queried_selectors.contains(selector) { - cells.queried_selectors.push(*selector); - } - } - Expression::Fixed(query) => { - if query.index.is_none() { - let col = Column { - index: query.column_index, - column_type: Fixed, - }; - cells.queried_cells.push((col, query.rotation).into()); - query.index = Some(cells.meta.query_fixed_index(col, query.rotation)); - } - } - Expression::Advice(query) => { - if query.index.is_none() { - let col = Column { - index: query.column_index, - column_type: Advice { - phase: query.phase.0, - }, - }; - cells.queried_cells.push((col, query.rotation).into()); - query.index = Some(cells.meta.query_advice_index(col, query.rotation)); - } - } - Expression::Instance(query) => { - if query.index.is_none() { - let col = Column { - index: query.column_index, - column_type: Instance, - }; - cells.queried_cells.push((col, query.rotation).into()); - query.index = Some(cells.meta.query_instance_index(col, query.rotation)); - } - } - Expression::Challenge(_) => (), - Expression::Negated(a) => a.query_cells(cells), - Expression::Sum(a, b) => { - a.query_cells(cells); - b.query_cells(cells); - } - Expression::Product(a, b) => { - a.query_cells(cells); - b.query_cells(cells); - } - Expression::Scaled(a, _) => a.query_cells(cells), - }; - } - - /// Evaluate the polynomial using the provided closures to perform the - /// operations. - #[allow(clippy::too_many_arguments)] - pub fn evaluate( - &self, - constant: &impl Fn(F) -> T, - selector_column: &impl Fn(Selector) -> T, - fixed_column: &impl Fn(FixedQuery) -> T, - advice_column: &impl Fn(AdviceQuery) -> T, - instance_column: &impl Fn(InstanceQuery) -> T, - challenge: &impl Fn(Challenge) -> T, - negated: &impl Fn(T) -> T, - sum: &impl Fn(T, T) -> T, - product: &impl Fn(T, T) -> T, - scaled: &impl Fn(T, F) -> T, - ) -> T { - match self { - Expression::Constant(scalar) => constant(*scalar), - Expression::Selector(selector) => selector_column(*selector), - Expression::Fixed(query) => fixed_column(*query), - Expression::Advice(query) => advice_column(*query), - Expression::Instance(query) => instance_column(*query), - Expression::Challenge(value) => challenge(*value), - Expression::Negated(a) => { - let a = a.evaluate( - constant, - selector_column, - fixed_column, - advice_column, - instance_column, - challenge, - negated, - sum, - product, - scaled, - ); - negated(a) - } - Expression::Sum(a, b) => { - let a = a.evaluate( - constant, - selector_column, - fixed_column, - advice_column, - instance_column, - challenge, - negated, - sum, - product, - scaled, - ); - let b = b.evaluate( - constant, - selector_column, - fixed_column, - advice_column, - instance_column, - challenge, - negated, - sum, - product, - scaled, - ); - sum(a, b) - } - Expression::Product(a, b) => { - let a = a.evaluate( - constant, - selector_column, - fixed_column, - advice_column, - instance_column, - challenge, - negated, - sum, - product, - scaled, - ); - let b = b.evaluate( - constant, - selector_column, - fixed_column, - advice_column, - instance_column, - challenge, - negated, - sum, - product, - scaled, - ); - product(a, b) - } - Expression::Scaled(a, f) => { - let a = a.evaluate( - constant, - selector_column, - fixed_column, - advice_column, - instance_column, - challenge, - negated, - sum, - product, - scaled, - ); - scaled(a, *f) - } - } - } - - /// Evaluate the polynomial lazily using the provided closures to perform the - /// operations. - #[allow(clippy::too_many_arguments)] - pub fn evaluate_lazy( - &self, - constant: &impl Fn(F) -> T, - selector_column: &impl Fn(Selector) -> T, - fixed_column: &impl Fn(FixedQuery) -> T, - advice_column: &impl Fn(AdviceQuery) -> T, - instance_column: &impl Fn(InstanceQuery) -> T, - challenge: &impl Fn(Challenge) -> T, - negated: &impl Fn(T) -> T, - sum: &impl Fn(T, T) -> T, - product: &impl Fn(T, T) -> T, - scaled: &impl Fn(T, F) -> T, - zero: &T, - ) -> T { - match self { - Expression::Constant(scalar) => constant(*scalar), - Expression::Selector(selector) => selector_column(*selector), - Expression::Fixed(query) => fixed_column(*query), - Expression::Advice(query) => advice_column(*query), - Expression::Instance(query) => instance_column(*query), - Expression::Challenge(value) => challenge(*value), - Expression::Negated(a) => { - let a = a.evaluate_lazy( - constant, - selector_column, - fixed_column, - advice_column, - instance_column, - challenge, - negated, - sum, - product, - scaled, - zero, - ); - negated(a) - } - Expression::Sum(a, b) => { - let a = a.evaluate_lazy( - constant, - selector_column, - fixed_column, - advice_column, - instance_column, - challenge, - negated, - sum, - product, - scaled, - zero, - ); - let b = b.evaluate_lazy( - constant, - selector_column, - fixed_column, - advice_column, - instance_column, - challenge, - negated, - sum, - product, - scaled, - zero, - ); - sum(a, b) - } - Expression::Product(a, b) => { - let (a, b) = if a.complexity() <= b.complexity() { - (a, b) - } else { - (b, a) - }; - let a = a.evaluate_lazy( - constant, - selector_column, - fixed_column, - advice_column, - instance_column, - challenge, - negated, - sum, - product, - scaled, - zero, - ); - - if a == *zero { - a - } else { - let b = b.evaluate_lazy( - constant, - selector_column, - fixed_column, - advice_column, - instance_column, - challenge, - negated, - sum, - product, - scaled, - zero, - ); - product(a, b) - } - } - Expression::Scaled(a, f) => { - let a = a.evaluate_lazy( - constant, - selector_column, - fixed_column, - advice_column, - instance_column, - challenge, - negated, - sum, - product, - scaled, - zero, - ); - scaled(a, *f) - } - } - } - - fn write_identifier(&self, writer: &mut W) -> std::io::Result<()> { - match self { - Expression::Constant(scalar) => write!(writer, "{scalar:?}"), - Expression::Selector(selector) => write!(writer, "selector[{}]", selector.0), - Expression::Fixed(query) => { - write!( - writer, - "fixed[{}][{}]", - query.column_index, query.rotation.0 - ) - } - Expression::Advice(query) => { - write!( - writer, - "advice[{}][{}]", - query.column_index, query.rotation.0 - ) - } - Expression::Instance(query) => { - write!( - writer, - "instance[{}][{}]", - query.column_index, query.rotation.0 - ) - } - Expression::Challenge(challenge) => { - write!(writer, "challenge[{}]", challenge.index()) - } - Expression::Negated(a) => { - writer.write_all(b"(-")?; - a.write_identifier(writer)?; - writer.write_all(b")") - } - Expression::Sum(a, b) => { - writer.write_all(b"(")?; - a.write_identifier(writer)?; - writer.write_all(b"+")?; - b.write_identifier(writer)?; - writer.write_all(b")") - } - Expression::Product(a, b) => { - writer.write_all(b"(")?; - a.write_identifier(writer)?; - writer.write_all(b"*")?; - b.write_identifier(writer)?; - writer.write_all(b")") - } - Expression::Scaled(a, f) => { - a.write_identifier(writer)?; - write!(writer, "*{f:?}") - } - } - } - - /// Identifier for this expression. Expressions with identical identifiers - /// do the same calculation (but the expressions don't need to be exactly equal - /// in how they are composed e.g. `1 + 2` and `2 + 1` can have the same identifier). - pub fn identifier(&self) -> String { - let mut cursor = std::io::Cursor::new(Vec::new()); - self.write_identifier(&mut cursor).unwrap(); - String::from_utf8(cursor.into_inner()).unwrap() - } - - /// Compute the degree of this polynomial - pub fn degree(&self) -> usize { - match self { - Expression::Constant(_) => 0, - Expression::Selector(_) => 1, - Expression::Fixed(_) => 1, - Expression::Advice(_) => 1, - Expression::Instance(_) => 1, - Expression::Challenge(_) => 0, - Expression::Negated(poly) => poly.degree(), - Expression::Sum(a, b) => max(a.degree(), b.degree()), - Expression::Product(a, b) => a.degree() + b.degree(), - Expression::Scaled(poly, _) => poly.degree(), - } - } - - /// Approximate the computational complexity of this expression. - pub fn complexity(&self) -> usize { - match self { - Expression::Constant(_) => 0, - Expression::Selector(_) => 1, - Expression::Fixed(_) => 1, - Expression::Advice(_) => 1, - Expression::Instance(_) => 1, - Expression::Challenge(_) => 0, - Expression::Negated(poly) => poly.complexity() + 5, - Expression::Sum(a, b) => a.complexity() + b.complexity() + 15, - Expression::Product(a, b) => a.complexity() + b.complexity() + 30, - Expression::Scaled(poly, _) => poly.complexity() + 30, - } - } - - /// Square this expression. - pub fn square(self) -> Self { - self.clone() * self - } - - /// Returns whether or not this expression contains a simple `Selector`. - fn contains_simple_selector(&self) -> bool { - self.evaluate( - &|_| false, - &|selector| selector.is_simple(), - &|_| false, - &|_| false, - &|_| false, - &|_| false, - &|a| a, - &|a, b| a || b, - &|a, b| a || b, - &|a, _| a, - ) - } - - /// Extracts a simple selector from this gate, if present - fn extract_simple_selector(&self) -> Option { - let op = |a, b| match (a, b) { - (Some(a), None) | (None, Some(a)) => Some(a), - (Some(_), Some(_)) => panic!("two simple selectors cannot be in the same expression"), - _ => None, - }; - - self.evaluate( - &|_| None, - &|selector| { - if selector.is_simple() { - Some(selector) - } else { - None - } - }, - &|_| None, - &|_| None, - &|_| None, - &|_| None, - &|a| a, - &op, - &op, - &|a, _| a, - ) - } -} - -impl std::fmt::Debug for Expression { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Expression::Constant(scalar) => f.debug_tuple("Constant").field(scalar).finish(), - Expression::Selector(selector) => f.debug_tuple("Selector").field(selector).finish(), - // Skip enum variant and print query struct directly to maintain backwards compatibility. - Expression::Fixed(query) => { - let mut debug_struct = f.debug_struct("Fixed"); - match query.index { - None => debug_struct.field("query_index", &query.index), - Some(idx) => debug_struct.field("query_index", &idx), - }; - debug_struct - .field("column_index", &query.column_index) - .field("rotation", &query.rotation) - .finish() - } - Expression::Advice(query) => { - let mut debug_struct = f.debug_struct("Advice"); - match query.index { - None => debug_struct.field("query_index", &query.index), - Some(idx) => debug_struct.field("query_index", &idx), - }; - debug_struct - .field("column_index", &query.column_index) - .field("rotation", &query.rotation); - // Only show advice's phase if it's not in first phase. - if query.phase != FirstPhase.to_sealed() { - debug_struct.field("phase", &query.phase); - } - debug_struct.finish() - } - Expression::Instance(query) => { - let mut debug_struct = f.debug_struct("Instance"); - match query.index { - None => debug_struct.field("query_index", &query.index), - Some(idx) => debug_struct.field("query_index", &idx), - }; - debug_struct - .field("column_index", &query.column_index) - .field("rotation", &query.rotation) - .finish() - } - Expression::Challenge(challenge) => { - f.debug_tuple("Challenge").field(challenge).finish() - } - Expression::Negated(poly) => f.debug_tuple("Negated").field(poly).finish(), - Expression::Sum(a, b) => f.debug_tuple("Sum").field(a).field(b).finish(), - Expression::Product(a, b) => f.debug_tuple("Product").field(a).field(b).finish(), - Expression::Scaled(poly, scalar) => { - f.debug_tuple("Scaled").field(poly).field(scalar).finish() - } - } - } -} - -impl Neg for Expression { - type Output = Expression; - fn neg(self) -> Self::Output { - Expression::Negated(Box::new(self)) - } -} - -impl Add for Expression { - type Output = Expression; - fn add(self, rhs: Expression) -> Expression { - if self.contains_simple_selector() || rhs.contains_simple_selector() { - panic!("attempted to use a simple selector in an addition"); - } - Expression::Sum(Box::new(self), Box::new(rhs)) - } -} - -impl Sub for Expression { - type Output = Expression; - fn sub(self, rhs: Expression) -> Expression { - if self.contains_simple_selector() || rhs.contains_simple_selector() { - panic!("attempted to use a simple selector in a subtraction"); - } - Expression::Sum(Box::new(self), Box::new(-rhs)) - } -} - -impl Mul for Expression { - type Output = Expression; - fn mul(self, rhs: Expression) -> Expression { - if self.contains_simple_selector() && rhs.contains_simple_selector() { - panic!("attempted to multiply two expressions containing simple selectors"); - } - Expression::Product(Box::new(self), Box::new(rhs)) - } -} - -impl Mul for Expression { - type Output = Expression; - fn mul(self, rhs: F) -> Expression { - Expression::Scaled(Box::new(self), rhs) - } -} - -impl Sum for Expression { - fn sum>(iter: I) -> Self { - iter.reduce(|acc, x| acc + x) - .unwrap_or(Expression::Constant(F::ZERO)) - } -} - -impl Product for Expression { - fn product>(iter: I) -> Self { - iter.reduce(|acc, x| acc * x) - .unwrap_or(Expression::Constant(F::ONE)) - } -} - -/// Represents an index into a vector where each entry corresponds to a distinct -/// point that polynomials are queried at. -#[derive(Copy, Clone, Debug)] -pub(crate) struct PointIndex(pub usize); - -/// A "virtual cell" is a PLONK cell that has been queried at a particular relative offset -/// within a custom gate. -#[derive(Clone, Debug)] -pub struct VirtualCell { - pub column: Column, - pub rotation: Rotation, -} - -impl>> From<(Col, Rotation)> for VirtualCell { - fn from((column, rotation): (Col, Rotation)) -> Self { - VirtualCell { - column: column.into(), - rotation, - } - } -} - -/// An individual polynomial constraint. -/// -/// These are returned by the closures passed to `ConstraintSystem::create_gate`. -#[derive(Debug)] -pub struct Constraint { - name: String, - poly: Expression, -} - -impl From> for Constraint { - fn from(poly: Expression) -> Self { - Constraint { - name: "".to_string(), - poly, - } - } -} - -impl> From<(S, Expression)> for Constraint { - fn from((name, poly): (S, Expression)) -> Self { - Constraint { - name: name.as_ref().to_string(), - poly, - } - } -} - -impl From> for Vec> { - fn from(poly: Expression) -> Self { - vec![Constraint { - name: "".to_string(), - poly, - }] - } -} - -/// A set of polynomial constraints with a common selector. -/// -/// ``` -/// use halo2_common::{plonk::{Constraints, Expression}}; -/// use halo2_middleware::poly::Rotation; -/// use halo2curves::pasta::Fp; -/// # use halo2_common::plonk::ConstraintSystem; -/// -/// # let mut meta = ConstraintSystem::::default(); -/// let a = meta.advice_column(); -/// let b = meta.advice_column(); -/// let c = meta.advice_column(); -/// let s = meta.selector(); -/// -/// meta.create_gate("foo", |meta| { -/// let next = meta.query_advice(a, Rotation::next()); -/// let a = meta.query_advice(a, Rotation::cur()); -/// let b = meta.query_advice(b, Rotation::cur()); -/// let c = meta.query_advice(c, Rotation::cur()); -/// let s_ternary = meta.query_selector(s); -/// -/// let one_minus_a = Expression::Constant(Fp::one()) - a.clone(); -/// -/// Constraints::with_selector( -/// s_ternary, -/// std::array::IntoIter::new([ -/// ("a is boolean", a.clone() * one_minus_a.clone()), -/// ("next == a ? b : c", next - (a * b + one_minus_a * c)), -/// ]), -/// ) -/// }); -/// ``` -/// -/// Note that the use of `std::array::IntoIter::new` is only necessary if you need to -/// support Rust 1.51 or 1.52. If your minimum supported Rust version is 1.53 or greater, -/// you can pass an array directly. -#[derive(Debug)] -pub struct Constraints>, Iter: IntoIterator> { - selector: Expression, - constraints: Iter, -} - -impl>, Iter: IntoIterator> Constraints { - /// Constructs a set of constraints that are controlled by the given selector. - /// - /// Each constraint `c` in `iterator` will be converted into the constraint - /// `selector * c`. - pub fn with_selector(selector: Expression, constraints: Iter) -> Self { - Constraints { - selector, - constraints, - } - } -} - -fn apply_selector_to_constraint>>( - (selector, c): (Expression, C), -) -> Constraint { - let constraint: Constraint = c.into(); - Constraint { - name: constraint.name, - poly: selector * constraint.poly, - } -} - -type ApplySelectorToConstraint = fn((Expression, C)) -> Constraint; -type ConstraintsIterator = std::iter::Map< - std::iter::Zip>, I>, - ApplySelectorToConstraint, ->; - -impl>, Iter: IntoIterator> IntoIterator - for Constraints -{ - type Item = Constraint; - type IntoIter = ConstraintsIterator; - - fn into_iter(self) -> Self::IntoIter { - std::iter::repeat(self.selector) - .zip(self.constraints) - .map(apply_selector_to_constraint) - } -} - -/// Gate -#[derive(Clone, Debug)] -pub struct Gate { - name: String, - constraint_names: Vec, - polys: Vec>, - /// We track queried selectors separately from other cells, so that we can use them to - /// trigger debug checks on gates. - queried_selectors: Vec, - queried_cells: Vec, -} - -impl Gate { - /// Returns the gate name. - pub fn name(&self) -> &str { - self.name.as_str() - } - - /// Returns the name of the constraint at index `constraint_index`. - pub fn constraint_name(&self, constraint_index: usize) -> &str { - self.constraint_names[constraint_index].as_str() - } - - /// Returns constraints of this gate - pub fn polynomials(&self) -> &[Expression] { - &self.polys - } - - pub fn queried_selectors(&self) -> &[Selector] { - &self.queried_selectors - } - - pub fn queried_cells(&self) -> &[VirtualCell] { - &self.queried_cells - } -} - -struct QueriesMap { - advice_map: HashMap<(Column, Rotation), usize>, - instance_map: HashMap<(Column, Rotation), usize>, - fixed_map: HashMap<(Column, Rotation), usize>, - advice: Vec<(Column, Rotation)>, - instance: Vec<(Column, Rotation)>, - fixed: Vec<(Column, Rotation)>, -} - -impl QueriesMap { - fn add_advice(&mut self, col: Column, rot: Rotation) -> usize { - *self.advice_map.entry((col, rot)).or_insert_with(|| { - self.advice.push((col, rot)); - self.advice.len() - 1 - }) - } - fn add_instance(&mut self, col: Column, rot: Rotation) -> usize { - *self.instance_map.entry((col, rot)).or_insert_with(|| { - self.instance.push((col, rot)); - self.instance.len() - 1 - }) - } - fn add_fixed(&mut self, col: Column, rot: Rotation) -> usize { - *self.fixed_map.entry((col, rot)).or_insert_with(|| { - self.fixed.push((col, rot)); - self.fixed.len() - 1 - }) - } -} - -impl QueriesMap { - fn as_expression(&mut self, expr: &ExpressionMid) -> Expression { - match expr { - ExpressionMid::Constant(c) => Expression::Constant(*c), - ExpressionMid::Fixed(query) => { - let (col, rot) = (Column::new(query.column_index, Fixed), query.rotation); - let index = self.add_fixed(col, rot); - Expression::Fixed(FixedQuery { - index: Some(index), - column_index: query.column_index, - rotation: query.rotation, - }) - } - ExpressionMid::Advice(query) => { - let (col, rot) = ( - Column::new(query.column_index, Advice { phase: query.phase }), - query.rotation, - ); - let index = self.add_advice(col, rot); - Expression::Advice(AdviceQuery { - index: Some(index), - column_index: query.column_index, - rotation: query.rotation, - phase: sealed::Phase(query.phase), - }) - } - ExpressionMid::Instance(query) => { - let (col, rot) = (Column::new(query.column_index, Instance), query.rotation); - let index = self.add_instance(col, rot); - Expression::Instance(InstanceQuery { - index: Some(index), - column_index: query.column_index, - rotation: query.rotation, - }) - } - ExpressionMid::Challenge(c) => Expression::Challenge((*c).into()), - ExpressionMid::Negated(e) => Expression::Negated(Box::new(self.as_expression(e))), - ExpressionMid::Sum(lhs, rhs) => Expression::Sum( - Box::new(self.as_expression(lhs)), - Box::new(self.as_expression(rhs)), - ), - ExpressionMid::Product(lhs, rhs) => Expression::Product( - Box::new(self.as_expression(lhs)), - Box::new(self.as_expression(rhs)), - ), - ExpressionMid::Scaled(e, c) => Expression::Scaled(Box::new(self.as_expression(e)), *c), - } - } -} - -impl From> for ConstraintSystemV2Backend { - fn from(cs: ConstraintSystem) -> Self { - ConstraintSystemV2Backend { - num_fixed_columns: cs.num_fixed_columns, - num_advice_columns: cs.num_advice_columns, - num_instance_columns: cs.num_instance_columns, - num_challenges: cs.num_challenges, - unblinded_advice_columns: cs.unblinded_advice_columns, - advice_column_phase: cs.advice_column_phase.iter().map(|p| p.0).collect(), - challenge_phase: cs.challenge_phase.iter().map(|p| p.0).collect(), - gates: cs - .gates - .into_iter() - .flat_map(|mut g| { - let constraint_names = std::mem::take(&mut g.constraint_names); - let gate_name = g.name.clone(); - g.polys.into_iter().enumerate().map(move |(i, e)| { - let name = match constraint_names[i].as_str() { - "" => gate_name.clone(), - constraint_name => format!("{gate_name}:{constraint_name}"), - }; - GateV2Backend { - name, - poly: e.into(), - } - }) - }) - .collect(), - permutation: halo2_middleware::permutation::ArgumentV2 { - columns: cs - .permutation - .columns - .into_iter() - .map(|c| c.into()) - .collect(), - }, - lookups: cs - .lookups - .into_iter() - .map(|l| halo2_middleware::lookup::ArgumentV2 { - name: l.name, - input_expressions: l.input_expressions.into_iter().map(|e| e.into()).collect(), - table_expressions: l.table_expressions.into_iter().map(|e| e.into()).collect(), - }) - .collect(), - shuffles: cs - .shuffles - .into_iter() - .map(|s| halo2_middleware::shuffle::ArgumentV2 { - name: s.name, - input_expressions: s.input_expressions.into_iter().map(|e| e.into()).collect(), - shuffle_expressions: s - .shuffle_expressions - .into_iter() - .map(|e| e.into()) - .collect(), - }) - .collect(), - general_column_annotations: cs.general_column_annotations, - } - } -} - -/// Collect queries used in gates while mapping those gates to equivalent ones with indexed -/// query references in the expressions. -fn cs2_collect_queries_gates( - cs2: &ConstraintSystemV2Backend, - queries: &mut QueriesMap, -) -> Vec> { - cs2.gates - .iter() - .map(|gate| Gate { - name: gate.name.clone(), - constraint_names: Vec::new(), - polys: vec![queries.as_expression(gate.polynomial())], - queried_selectors: Vec::new(), // Unused? - queried_cells: Vec::new(), // Unused? - }) - .collect() -} - -/// Collect queries used in lookups while mapping those lookups to equivalent ones with indexed -/// query references in the expressions. -fn cs2_collect_queries_lookups( - cs2: &ConstraintSystemV2Backend, - queries: &mut QueriesMap, -) -> Vec> { - cs2.lookups - .iter() - .map(|lookup| lookup::Argument { - name: lookup.name.clone(), - input_expressions: lookup - .input_expressions - .iter() - .map(|e| queries.as_expression(e)) - .collect(), - table_expressions: lookup - .table_expressions - .iter() - .map(|e| queries.as_expression(e)) - .collect(), - }) - .collect() -} - -/// Collect queries used in shuffles while mapping those lookups to equivalent ones with indexed -/// query references in the expressions. -fn cs2_collect_queries_shuffles( - cs2: &ConstraintSystemV2Backend, - queries: &mut QueriesMap, -) -> Vec> { - cs2.shuffles - .iter() - .map(|shuffle| shuffle::Argument { - name: shuffle.name.clone(), - input_expressions: shuffle - .input_expressions - .iter() - .map(|e| queries.as_expression(e)) - .collect(), - shuffle_expressions: shuffle - .shuffle_expressions - .iter() - .map(|e| queries.as_expression(e)) - .collect(), - }) - .collect() -} - -/// Collect all queries used in the expressions of gates, lookups and shuffles. Map the -/// expressions of gates, lookups and shuffles into equivalent ones with indexed query -/// references. -#[allow(clippy::type_complexity)] -pub fn collect_queries( - cs2: &ConstraintSystemV2Backend, -) -> ( - Queries, - Vec>, - Vec>, - Vec>, -) { - let mut queries = QueriesMap { - advice_map: HashMap::new(), - instance_map: HashMap::new(), - fixed_map: HashMap::new(), - advice: Vec::new(), - instance: Vec::new(), - fixed: Vec::new(), - }; - - let gates = cs2_collect_queries_gates(cs2, &mut queries); - let lookups = cs2_collect_queries_lookups(cs2, &mut queries); - let shuffles = cs2_collect_queries_shuffles(cs2, &mut queries); - - // Each column used in a copy constraint involves a query at rotation current. - for column in &cs2.permutation.columns { - match column.column_type { - Any::Instance => { - queries.add_instance(Column::new(column.index, Instance), Rotation::cur()) - } - Any::Fixed => queries.add_fixed(Column::new(column.index, Fixed), Rotation::cur()), - Any::Advice(advice) => { - queries.add_advice(Column::new(column.index, advice), Rotation::cur()) - } - }; - } - - let mut num_advice_queries = vec![0; cs2.num_advice_columns]; - for (column, _) in queries.advice.iter() { - num_advice_queries[column.index()] += 1; - } - - let queries = Queries { - advice: queries.advice, - instance: queries.instance, - fixed: queries.fixed, - num_advice_queries, - }; - (queries, gates, lookups, shuffles) -} - -/// This is a description of the circuit environment, such as the gate, column and -/// permutation arrangements. -#[derive(Debug, Clone)] -pub struct ConstraintSystem { - pub num_fixed_columns: usize, - pub num_advice_columns: usize, - pub num_instance_columns: usize, - pub num_selectors: usize, - pub num_challenges: usize, - - /// Contains the index of each advice column that is left unblinded. - pub unblinded_advice_columns: Vec, - - /// Contains the phase for each advice column. Should have same length as num_advice_columns. - pub advice_column_phase: Vec, - /// Contains the phase for each challenge. Should have same length as num_challenges. - pub challenge_phase: Vec, - - /// This is a cached vector that maps virtual selectors to the concrete - /// fixed column that they were compressed into. This is just used by dev - /// tooling right now. - pub selector_map: Vec>, - - pub gates: Vec>, - pub advice_queries: Vec<(Column, Rotation)>, - // Contains an integer for each advice column - // identifying how many distinct queries it has - // so far; should be same length as num_advice_columns. - pub num_advice_queries: Vec, - pub instance_queries: Vec<(Column, Rotation)>, - pub fixed_queries: Vec<(Column, Rotation)>, - - // Permutation argument for performing equality constraints - pub permutation: permutation::Argument, - - // Vector of lookup arguments, where each corresponds to a sequence of - // input expressions and a sequence of table expressions involved in the lookup. - pub lookups: Vec>, - - // Vector of shuffle arguments, where each corresponds to a sequence of - // input expressions and a sequence of shuffle expressions involved in the shuffle. - pub shuffles: Vec>, - - // List of indexes of Fixed columns which are associated to a circuit-general Column tied to their annotation. - pub general_column_annotations: HashMap, - - // Vector of fixed columns, which can be used to store constant values - // that are copied into advice columns. - pub constants: Vec>, - - pub minimum_degree: Option, -} - -impl From> for ConstraintSystem { - fn from(cs2: ConstraintSystemV2Backend) -> Self { - let (queries, gates, lookups, shuffles) = collect_queries(&cs2); - ConstraintSystem { - num_fixed_columns: cs2.num_fixed_columns, - num_advice_columns: cs2.num_advice_columns, - num_instance_columns: cs2.num_instance_columns, - num_selectors: 0, - num_challenges: cs2.num_challenges, - unblinded_advice_columns: cs2.unblinded_advice_columns, - advice_column_phase: cs2 - .advice_column_phase - .into_iter() - .map(sealed::Phase) - .collect(), - challenge_phase: cs2.challenge_phase.into_iter().map(sealed::Phase).collect(), - selector_map: Vec::new(), - gates, - advice_queries: queries.advice, - num_advice_queries: queries.num_advice_queries, - instance_queries: queries.instance, - fixed_queries: queries.fixed, - permutation: cs2.permutation.into(), - lookups, - shuffles, - general_column_annotations: cs2.general_column_annotations, - constants: Vec::new(), - minimum_degree: None, - } - } -} - -/// Represents the minimal parameters that determine a `ConstraintSystem`. -#[allow(dead_code)] -pub struct PinnedConstraintSystem<'a, F: Field> { - num_fixed_columns: &'a usize, - num_advice_columns: &'a usize, - num_instance_columns: &'a usize, - num_selectors: &'a usize, - num_challenges: &'a usize, - advice_column_phase: &'a Vec, - challenge_phase: &'a Vec, - gates: PinnedGates<'a, F>, - advice_queries: &'a Vec<(Column, Rotation)>, - instance_queries: &'a Vec<(Column, Rotation)>, - fixed_queries: &'a Vec<(Column, Rotation)>, - permutation: &'a permutation::Argument, - lookups: &'a Vec>, - shuffles: &'a Vec>, - constants: &'a Vec>, - minimum_degree: &'a Option, -} - -impl<'a, F: Field> std::fmt::Debug for PinnedConstraintSystem<'a, F> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut debug_struct = f.debug_struct("PinnedConstraintSystem"); - debug_struct - .field("num_fixed_columns", self.num_fixed_columns) - .field("num_advice_columns", self.num_advice_columns) - .field("num_instance_columns", self.num_instance_columns) - .field("num_selectors", self.num_selectors); - // Only show multi-phase related fields if it's used. - if *self.num_challenges > 0 { - debug_struct - .field("num_challenges", self.num_challenges) - .field("advice_column_phase", self.advice_column_phase) - .field("challenge_phase", self.challenge_phase); - } - debug_struct - .field("gates", &self.gates) - .field("advice_queries", self.advice_queries) - .field("instance_queries", self.instance_queries) - .field("fixed_queries", self.fixed_queries) - .field("permutation", self.permutation) - .field("lookups", self.lookups); - if !self.shuffles.is_empty() { - debug_struct.field("shuffles", self.shuffles); - } - debug_struct - .field("constants", self.constants) - .field("minimum_degree", self.minimum_degree); - debug_struct.finish() - } -} - -struct PinnedGates<'a, F: Field>(&'a Vec>); - -impl<'a, F: Field> std::fmt::Debug for PinnedGates<'a, F> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - f.debug_list() - .entries(self.0.iter().flat_map(|gate| gate.polynomials().iter())) - .finish() - } -} - -impl Default for ConstraintSystem { - fn default() -> ConstraintSystem { - ConstraintSystem { - num_fixed_columns: 0, - num_advice_columns: 0, - num_instance_columns: 0, - num_selectors: 0, - num_challenges: 0, - unblinded_advice_columns: Vec::new(), - advice_column_phase: Vec::new(), - challenge_phase: Vec::new(), - selector_map: vec![], - gates: vec![], - fixed_queries: Vec::new(), - advice_queries: Vec::new(), - num_advice_queries: Vec::new(), - instance_queries: Vec::new(), - permutation: permutation::Argument::default(), - lookups: Vec::new(), - shuffles: Vec::new(), - general_column_annotations: HashMap::new(), - constants: vec![], - minimum_degree: None, - } - } -} - -impl ConstraintSystem { - /// Obtain a pinned version of this constraint system; a structure with the - /// minimal parameters needed to determine the rest of the constraint - /// system. - pub fn pinned(&self) -> PinnedConstraintSystem<'_, F> { - PinnedConstraintSystem { - num_fixed_columns: &self.num_fixed_columns, - num_advice_columns: &self.num_advice_columns, - num_instance_columns: &self.num_instance_columns, - num_selectors: &self.num_selectors, - num_challenges: &self.num_challenges, - advice_column_phase: &self.advice_column_phase, - challenge_phase: &self.challenge_phase, - gates: PinnedGates(&self.gates), - fixed_queries: &self.fixed_queries, - advice_queries: &self.advice_queries, - instance_queries: &self.instance_queries, - permutation: &self.permutation, - lookups: &self.lookups, - shuffles: &self.shuffles, - constants: &self.constants, - minimum_degree: &self.minimum_degree, - } - } - - /// Enables this fixed column to be used for global constant assignments. - /// - /// # Side-effects - /// - /// The column will be equality-enabled. - pub fn enable_constant(&mut self, column: Column) { - if !self.constants.contains(&column) { - self.constants.push(column); - self.enable_equality(column); - } - } - - /// Enable the ability to enforce equality over cells in this column - pub fn enable_equality>>(&mut self, column: C) { - let column = column.into(); - self.query_any_index(column, Rotation::cur()); - self.permutation.add_column(column); - } - - /// Add a lookup argument for some input expressions and table columns. - /// - /// `table_map` returns a map between input expressions and the table columns - /// they need to match. - pub fn lookup>( - &mut self, - name: S, - table_map: impl FnOnce(&mut VirtualCells<'_, F>) -> Vec<(Expression, TableColumn)>, - ) -> usize { - let mut cells = VirtualCells::new(self); - let table_map = table_map(&mut cells) - .into_iter() - .map(|(mut input, table)| { - if input.contains_simple_selector() { - panic!("expression containing simple selector supplied to lookup argument"); - } - let mut table = cells.query_fixed(table.inner(), Rotation::cur()); - input.query_cells(&mut cells); - table.query_cells(&mut cells); - (input, table) - }) - .collect(); - let index = self.lookups.len(); - - self.lookups - .push(lookup::Argument::new(name.as_ref(), table_map)); - - index - } - - /// Add a lookup argument for some input expressions and table expressions. - /// - /// `table_map` returns a map between input expressions and the table expressions - /// they need to match. - pub fn lookup_any>( - &mut self, - name: S, - table_map: impl FnOnce(&mut VirtualCells<'_, F>) -> Vec<(Expression, Expression)>, - ) -> usize { - let mut cells = VirtualCells::new(self); - let table_map = table_map(&mut cells) - .into_iter() - .map(|(mut input, mut table)| { - if input.contains_simple_selector() { - panic!("expression containing simple selector supplied to lookup argument"); - } - if table.contains_simple_selector() { - panic!("expression containing simple selector supplied to lookup argument"); - } - input.query_cells(&mut cells); - table.query_cells(&mut cells); - (input, table) - }) - .collect(); - let index = self.lookups.len(); - - self.lookups - .push(lookup::Argument::new(name.as_ref(), table_map)); - - index - } - - /// Add a shuffle argument for some input expressions and table expressions. - pub fn shuffle>( - &mut self, - name: S, - shuffle_map: impl FnOnce(&mut VirtualCells<'_, F>) -> Vec<(Expression, Expression)>, - ) -> usize { - let mut cells = VirtualCells::new(self); - let shuffle_map = shuffle_map(&mut cells) - .into_iter() - .map(|(mut input, mut table)| { - input.query_cells(&mut cells); - table.query_cells(&mut cells); - (input, table) - }) - .collect(); - let index = self.shuffles.len(); - - self.shuffles - .push(shuffle::Argument::new(name.as_ref(), shuffle_map)); - - index - } - - fn query_fixed_index(&mut self, column: Column, at: Rotation) -> usize { - // Return existing query, if it exists - for (index, fixed_query) in self.fixed_queries.iter().enumerate() { - if fixed_query == &(column, at) { - return index; - } - } - - // Make a new query - let index = self.fixed_queries.len(); - self.fixed_queries.push((column, at)); - - index - } - - pub(crate) fn query_advice_index(&mut self, column: Column, at: Rotation) -> usize { - // Return existing query, if it exists - for (index, advice_query) in self.advice_queries.iter().enumerate() { - if advice_query == &(column, at) { - return index; - } - } - - // Make a new query - let index = self.advice_queries.len(); - self.advice_queries.push((column, at)); - self.num_advice_queries[column.index] += 1; - - index - } - - fn query_instance_index(&mut self, column: Column, at: Rotation) -> usize { - // Return existing query, if it exists - for (index, instance_query) in self.instance_queries.iter().enumerate() { - if instance_query == &(column, at) { - return index; - } - } - - // Make a new query - let index = self.instance_queries.len(); - self.instance_queries.push((column, at)); - - index - } - - fn query_any_index(&mut self, column: Column, at: Rotation) -> usize { - match column.column_type() { - Any::Advice(_) => { - self.query_advice_index(Column::::try_from(column).unwrap(), at) - } - Any::Fixed => self.query_fixed_index(Column::::try_from(column).unwrap(), at), - Any::Instance => { - self.query_instance_index(Column::::try_from(column).unwrap(), at) - } - } - } - - pub(crate) fn get_advice_query_index(&self, column: Column, at: Rotation) -> usize { - for (index, advice_query) in self.advice_queries.iter().enumerate() { - if advice_query == &(column, at) { - return index; - } - } - - panic!("get_advice_query_index called for non-existent query"); - } - - pub(crate) fn get_fixed_query_index(&self, column: Column, at: Rotation) -> usize { - for (index, fixed_query) in self.fixed_queries.iter().enumerate() { - if fixed_query == &(column, at) { - return index; - } - } - - panic!("get_fixed_query_index called for non-existent query"); - } - - pub(crate) fn get_instance_query_index(&self, column: Column, at: Rotation) -> usize { - for (index, instance_query) in self.instance_queries.iter().enumerate() { - if instance_query == &(column, at) { - return index; - } - } - - panic!("get_instance_query_index called for non-existent query"); - } - - pub fn get_any_query_index(&self, column: Column, at: Rotation) -> usize { - match column.column_type() { - Any::Advice(_) => { - self.get_advice_query_index(Column::::try_from(column).unwrap(), at) - } - Any::Fixed => { - self.get_fixed_query_index(Column::::try_from(column).unwrap(), at) - } - Any::Instance => { - self.get_instance_query_index(Column::::try_from(column).unwrap(), at) - } - } - } - - /// Sets the minimum degree required by the circuit, which can be set to a - /// larger amount than actually needed. This can be used, for example, to - /// force the permutation argument to involve more columns in the same set. - pub fn set_minimum_degree(&mut self, degree: usize) { - self.minimum_degree = Some(degree); - } - - /// Creates a new gate. - /// - /// # Panics - /// - /// A gate is required to contain polynomial constraints. This method will panic if - /// `constraints` returns an empty iterator. - pub fn create_gate>, Iter: IntoIterator, S: AsRef>( - &mut self, - name: S, - constraints: impl FnOnce(&mut VirtualCells<'_, F>) -> Iter, - ) { - let mut cells = VirtualCells::new(self); - let constraints = constraints(&mut cells); - let (constraint_names, polys): (_, Vec<_>) = constraints - .into_iter() - .map(|c| c.into()) - .map(|mut c: Constraint| { - c.poly.query_cells(&mut cells); - (c.name, c.poly) - }) - .unzip(); - - let queried_selectors = cells.queried_selectors; - let queried_cells = cells.queried_cells; - - assert!( - !polys.is_empty(), - "Gates must contain at least one constraint." - ); - - self.gates.push(Gate { - name: name.as_ref().to_string(), - constraint_names, - polys, - queried_selectors, - queried_cells, - }); - } - - /// This will compress selectors together depending on their provided - /// assignments. This `ConstraintSystem` will then be modified to add new - /// fixed columns (representing the actual selectors) and will return the - /// polynomials for those columns. Finally, an internal map is updated to - /// find which fixed column corresponds with a given `Selector`. - /// - /// Do not call this twice. Yes, this should be a builder pattern instead. - pub fn compress_selectors(mut self, selectors: Vec>) -> (Self, Vec>) { - // The number of provided selector assignments must be the number we - // counted for this constraint system. - assert_eq!(selectors.len(), self.num_selectors); - - // Compute the maximal degree of every selector. We only consider the - // expressions in gates, as lookup arguments cannot support simple - // selectors. Selectors that are complex or do not appear in any gates - // will have degree zero. - let mut degrees = vec![0; selectors.len()]; - for expr in self.gates.iter().flat_map(|gate| gate.polys.iter()) { - if let Some(selector) = expr.extract_simple_selector() { - degrees[selector.0] = max(degrees[selector.0], expr.degree()); - } - } - - // We will not increase the degree of the constraint system, so we limit - // ourselves to the largest existing degree constraint. - let max_degree = self.degree(); - - let mut new_columns = vec![]; - let (polys, selector_assignment) = compress_selectors::process( - selectors - .into_iter() - .zip(degrees) - .enumerate() - .map( - |(i, (activations, max_degree))| compress_selectors::SelectorDescription { - selector: i, - activations, - max_degree, - }, - ) - .collect(), - max_degree, - || { - let column = self.fixed_column(); - new_columns.push(column); - Expression::Fixed(FixedQuery { - index: Some(self.query_fixed_index(column, Rotation::cur())), - column_index: column.index, - rotation: Rotation::cur(), - }) - }, - ); - - let mut selector_map = vec![None; selector_assignment.len()]; - let mut selector_replacements = vec![None; selector_assignment.len()]; - for assignment in selector_assignment { - selector_replacements[assignment.selector] = Some(assignment.expression); - selector_map[assignment.selector] = Some(new_columns[assignment.combination_index]); - } - - self.selector_map = selector_map - .into_iter() - .map(|a| a.unwrap()) - .collect::>(); - let selector_replacements = selector_replacements - .into_iter() - .map(|a| a.unwrap()) - .collect::>(); - self.replace_selectors_with_fixed(&selector_replacements); - - (self, polys) - } - - /// Does not combine selectors and directly replaces them everywhere with fixed columns. - pub fn directly_convert_selectors_to_fixed( - mut self, - selectors: Vec>, - ) -> (Self, Vec>) { - // The number of provided selector assignments must be the number we - // counted for this constraint system. - assert_eq!(selectors.len(), self.num_selectors); - - let (polys, selector_replacements): (Vec<_>, Vec<_>) = selectors - .into_iter() - .map(|selector| { - let poly = selector - .iter() - .map(|b| if *b { F::ONE } else { F::ZERO }) - .collect::>(); - let column = self.fixed_column(); - let rotation = Rotation::cur(); - let expr = Expression::Fixed(FixedQuery { - index: Some(self.query_fixed_index(column, rotation)), - column_index: column.index, - rotation, - }); - (poly, expr) - }) - .unzip(); - - self.replace_selectors_with_fixed(&selector_replacements); - self.num_selectors = 0; - - (self, polys) - } - - fn replace_selectors_with_fixed(&mut self, selector_replacements: &[Expression]) { - fn replace_selectors( - expr: &mut Expression, - selector_replacements: &[Expression], - must_be_nonsimple: bool, - ) { - *expr = expr.evaluate( - &|constant| Expression::Constant(constant), - &|selector| { - if must_be_nonsimple { - // Simple selectors are prohibited from appearing in - // expressions in the lookup argument by - // `ConstraintSystem`. - assert!(!selector.is_simple()); - } - - selector_replacements[selector.0].clone() - }, - &|query| Expression::Fixed(query), - &|query| Expression::Advice(query), - &|query| Expression::Instance(query), - &|challenge| Expression::Challenge(challenge), - &|a| -a, - &|a, b| a + b, - &|a, b| a * b, - &|a, f| a * f, - ); - } - - // Substitute selectors for the real fixed columns in all gates - for expr in self.gates.iter_mut().flat_map(|gate| gate.polys.iter_mut()) { - replace_selectors(expr, selector_replacements, false); - } - - // Substitute non-simple selectors for the real fixed columns in all - // lookup expressions - for expr in self.lookups.iter_mut().flat_map(|lookup| { - lookup - .input_expressions - .iter_mut() - .chain(lookup.table_expressions.iter_mut()) - }) { - replace_selectors(expr, selector_replacements, true); - } - - for expr in self.shuffles.iter_mut().flat_map(|shuffle| { - shuffle - .input_expressions - .iter_mut() - .chain(shuffle.shuffle_expressions.iter_mut()) - }) { - replace_selectors(expr, selector_replacements, true); - } - } - - /// Allocate a new (simple) selector. Simple selectors cannot be added to - /// expressions nor multiplied by other expressions containing simple - /// selectors. Also, simple selectors may not appear in lookup argument - /// inputs. - pub fn selector(&mut self) -> Selector { - let index = self.num_selectors; - self.num_selectors += 1; - Selector(index, true) - } - - /// Allocate a new complex selector that can appear anywhere - /// within expressions. - pub fn complex_selector(&mut self) -> Selector { - let index = self.num_selectors; - self.num_selectors += 1; - Selector(index, false) - } - - /// Allocates a new fixed column that can be used in a lookup table. - pub fn lookup_table_column(&mut self) -> TableColumn { - TableColumn { - inner: self.fixed_column(), - } - } - - /// Annotate a Lookup column. - pub fn annotate_lookup_column(&mut self, column: TableColumn, annotation: A) - where - A: Fn() -> AR, - AR: Into, - { - // We don't care if the table has already an annotation. If it's the case we keep the new one. - self.general_column_annotations.insert( - metadata::Column::from((Any::Fixed, column.inner().index)), - annotation().into(), - ); - } - - /// Annotate an Instance column. - pub fn annotate_lookup_any_column(&mut self, column: T, annotation: A) - where - A: Fn() -> AR, - AR: Into, - T: Into>, - { - let col_any = column.into(); - // We don't care if the table has already an annotation. If it's the case we keep the new one. - self.general_column_annotations.insert( - metadata::Column::from((col_any.column_type, col_any.index)), - annotation().into(), - ); - } - - /// Allocate a new fixed column - pub fn fixed_column(&mut self) -> Column { - let tmp = Column { - index: self.num_fixed_columns, - column_type: Fixed, - }; - self.num_fixed_columns += 1; - tmp - } - - /// Allocate a new unblinded advice column at `FirstPhase` - pub fn unblinded_advice_column(&mut self) -> Column { - self.unblinded_advice_column_in(FirstPhase) - } - - /// Allocate a new advice column at `FirstPhase` - pub fn advice_column(&mut self) -> Column { - self.advice_column_in(FirstPhase) - } - - /// Allocate a new unblinded advice column in given phase. This allows for the generation of deterministic commitments to advice columns - /// which can be used to split large circuits into smaller ones, whose proofs can then be "joined" together by their common witness commitments. - pub fn unblinded_advice_column_in(&mut self, phase: P) -> Column { - let phase = phase.to_sealed(); - if let Some(previous_phase) = phase.prev() { - self.assert_phase_exists( - previous_phase, - format!("Column in later phase {phase:?}").as_str(), - ); - } - - let tmp = Column { - index: self.num_advice_columns, - column_type: Advice { phase: phase.0 }, - }; - self.unblinded_advice_columns.push(tmp.index); - self.num_advice_columns += 1; - self.num_advice_queries.push(0); - self.advice_column_phase.push(phase); - tmp - } - - /// Allocate a new advice column in given phase - /// - /// # Panics - /// - /// It panics if previous phase before the given one doesn't have advice column allocated. - pub fn advice_column_in(&mut self, phase: P) -> Column { - let phase = phase.to_sealed(); - if let Some(previous_phase) = phase.prev() { - self.assert_phase_exists( - previous_phase, - format!("Column in later phase {phase:?}").as_str(), - ); - } - - let tmp = Column { - index: self.num_advice_columns, - column_type: Advice { phase: phase.0 }, - }; - self.num_advice_columns += 1; - self.num_advice_queries.push(0); - self.advice_column_phase.push(phase); - tmp - } - - /// Allocate a new instance column - pub fn instance_column(&mut self) -> Column { - let tmp = Column { - index: self.num_instance_columns, - column_type: Instance, - }; - self.num_instance_columns += 1; - tmp - } - - /// Requests a challenge that is usable after the given phase. - /// - /// # Panics - /// - /// It panics if the given phase doesn't have advice column allocated. - pub fn challenge_usable_after(&mut self, phase: P) -> Challenge { - let phase = phase.to_sealed(); - self.assert_phase_exists( - phase, - format!("Challenge usable after phase {phase:?}").as_str(), - ); - - let tmp = Challenge { - index: self.num_challenges, - phase: phase.0, - }; - self.num_challenges += 1; - self.challenge_phase.push(phase); - tmp - } - - /// Helper funciotn to assert phase exists, to make sure phase-aware resources - /// are allocated in order, and to avoid any phase to be skipped accidentally - /// to cause unexpected issue in the future. - fn assert_phase_exists(&self, phase: sealed::Phase, resource: &str) { - self.advice_column_phase - .iter() - .find(|advice_column_phase| **advice_column_phase == phase) - .unwrap_or_else(|| { - panic!( - "No Column is used in phase {phase:?} while allocating a new {resource:?}" - ) - }); - } - - /// Returns the list of phases - pub fn phases(&self) -> impl Iterator { - let max_phase = self - .advice_column_phase - .iter() - .max() - .map(|phase| phase.0) - .unwrap_or_default(); - (0..=max_phase).map(sealed::Phase) - } - - /// Compute the degree of the constraint system (the maximum degree of all - /// constraints). - pub fn degree(&self) -> usize { - // The permutation argument will serve alongside the gates, so must be - // accounted for. - let mut degree = self.permutation.required_degree(); - - // The lookup argument also serves alongside the gates and must be accounted - // for. - degree = std::cmp::max( - degree, - self.lookups - .iter() - .map(|l| l.required_degree()) - .max() - .unwrap_or(1), - ); - - // The lookup argument also serves alongside the gates and must be accounted - // for. - degree = std::cmp::max( - degree, - self.shuffles - .iter() - .map(|l| l.required_degree()) - .max() - .unwrap_or(1), - ); - - // Account for each gate to ensure our quotient polynomial is the - // correct degree and that our extended domain is the right size. - degree = std::cmp::max( - degree, - self.gates - .iter() - .flat_map(|gate| gate.polynomials().iter().map(|poly| poly.degree())) - .max() - .unwrap_or(0), - ); - - std::cmp::max(degree, self.minimum_degree.unwrap_or(1)) - } - - /// Compute the number of blinding factors necessary to perfectly blind - /// each of the prover's witness polynomials. - pub fn blinding_factors(&self) -> usize { - // All of the prover's advice columns are evaluated at no more than - let factors = *self.num_advice_queries.iter().max().unwrap_or(&1); - // distinct points during gate checks. - - // - The permutation argument witness polynomials are evaluated at most 3 times. - // - Each lookup argument has independent witness polynomials, and they are - // evaluated at most 2 times. - let factors = std::cmp::max(3, factors); - - // Each polynomial is evaluated at most an additional time during - // multiopen (at x_3 to produce q_evals): - let factors = factors + 1; - - // h(x) is derived by the other evaluations so it does not reveal - // anything; in fact it does not even appear in the proof. - - // h(x_3) is also not revealed; the verifier only learns a single - // evaluation of a polynomial in x_1 which has h(x_3) and another random - // polynomial evaluated at x_3 as coefficients -- this random polynomial - // is "random_poly" in the vanishing argument. - - // Add an additional blinding factor as a slight defense against - // off-by-one errors. - factors + 1 - } - - /// Returns the minimum necessary rows that need to exist in order to - /// account for e.g. blinding factors. - pub fn minimum_rows(&self) -> usize { - self.blinding_factors() // m blinding factors - + 1 // for l_{-(m + 1)} (l_last) - + 1 // for l_0 (just for extra breathing room for the permutation - // argument, to essentially force a separation in the - // permutation polynomial between the roles of l_last, l_0 - // and the interstitial values.) - + 1 // for at least one row - } - - /// Returns number of fixed columns - pub fn num_fixed_columns(&self) -> usize { - self.num_fixed_columns - } - - /// Returns number of advice columns - pub fn num_advice_columns(&self) -> usize { - self.num_advice_columns - } - - /// Returns number of instance columns - pub fn num_instance_columns(&self) -> usize { - self.num_instance_columns - } - - /// Returns number of selectors - pub fn num_selectors(&self) -> usize { - self.num_selectors - } - - /// Returns number of challenges - pub fn num_challenges(&self) -> usize { - self.num_challenges - } - - /// Returns phase of advice columns - pub fn advice_column_phase(&self) -> Vec { - self.advice_column_phase - .iter() - .map(|phase| phase.0) - .collect() - } - - /// Returns phase of challenges - pub fn challenge_phase(&self) -> Vec { - self.challenge_phase.iter().map(|phase| phase.0).collect() - } - - /// Returns gates - pub fn gates(&self) -> &Vec> { - &self.gates - } - - /// Returns general column annotations - pub fn general_column_annotations(&self) -> &HashMap { - &self.general_column_annotations - } - - /// Returns advice queries - pub fn advice_queries(&self) -> &Vec<(Column, Rotation)> { - &self.advice_queries - } - - /// Returns instance queries - pub fn instance_queries(&self) -> &Vec<(Column, Rotation)> { - &self.instance_queries - } - - /// Returns fixed queries - pub fn fixed_queries(&self) -> &Vec<(Column, Rotation)> { - &self.fixed_queries - } - - /// Returns permutation argument - pub fn permutation(&self) -> &permutation::Argument { - &self.permutation - } - - /// Returns lookup arguments - pub fn lookups(&self) -> &Vec> { - &self.lookups - } - - /// Returns shuffle arguments - pub fn shuffles(&self) -> &Vec> { - &self.shuffles - } - - /// Returns constants - pub fn constants(&self) -> &Vec> { - &self.constants - } -} - -/// Exposes the "virtual cells" that can be queried while creating a custom gate or lookup -/// table. -#[derive(Debug)] -pub struct VirtualCells<'a, F: Field> { - meta: &'a mut ConstraintSystem, - queried_selectors: Vec, - queried_cells: Vec, -} - -impl<'a, F: Field> VirtualCells<'a, F> { - fn new(meta: &'a mut ConstraintSystem) -> Self { - VirtualCells { - meta, - queried_selectors: vec![], - queried_cells: vec![], - } - } - - /// Query a selector at the current position. - pub fn query_selector(&mut self, selector: Selector) -> Expression { - self.queried_selectors.push(selector); - Expression::Selector(selector) - } - - /// Query a fixed column at a relative position - pub fn query_fixed(&mut self, column: Column, at: Rotation) -> Expression { - self.queried_cells.push((column, at).into()); - Expression::Fixed(FixedQuery { - index: Some(self.meta.query_fixed_index(column, at)), - column_index: column.index, - rotation: at, - }) - } - - /// Query an advice column at a relative position - pub fn query_advice(&mut self, column: Column, at: Rotation) -> Expression { - self.queried_cells.push((column, at).into()); - Expression::Advice(AdviceQuery { - index: Some(self.meta.query_advice_index(column, at)), - column_index: column.index, - rotation: at, - phase: sealed::Phase(column.column_type().phase), - }) - } - - /// Query an instance column at a relative position - pub fn query_instance(&mut self, column: Column, at: Rotation) -> Expression { - self.queried_cells.push((column, at).into()); - Expression::Instance(InstanceQuery { - index: Some(self.meta.query_instance_index(column, at)), - column_index: column.index, - rotation: at, - }) - } - - /// Query an Any column at a relative position - pub fn query_any>>(&mut self, column: C, at: Rotation) -> Expression { - let column = column.into(); - match column.column_type() { - Any::Advice(_) => self.query_advice(Column::::try_from(column).unwrap(), at), - Any::Fixed => self.query_fixed(Column::::try_from(column).unwrap(), at), - Any::Instance => self.query_instance(Column::::try_from(column).unwrap(), at), - } - } - - /// Query a challenge - pub fn query_challenge(&mut self, challenge: Challenge) -> Expression { - Expression::Challenge(challenge) - } -} - -#[cfg(test)] -mod tests { - use super::Expression; - use halo2curves::bn256::Fr; - - #[test] - fn iter_sum() { - let exprs: Vec> = vec![ - Expression::Constant(1.into()), - Expression::Constant(2.into()), - Expression::Constant(3.into()), - ]; - let happened: Expression = exprs.into_iter().sum(); - let expected: Expression = Expression::Sum( - Box::new(Expression::Sum( - Box::new(Expression::Constant(1.into())), - Box::new(Expression::Constant(2.into())), - )), - Box::new(Expression::Constant(3.into())), - ); - - assert_eq!(happened, expected); - } - - #[test] - fn iter_product() { - let exprs: Vec> = vec![ - Expression::Constant(1.into()), - Expression::Constant(2.into()), - Expression::Constant(3.into()), - ]; - let happened: Expression = exprs.into_iter().product(); - let expected: Expression = Expression::Product( - Box::new(Expression::Product( - Box::new(Expression::Constant(1.into())), - Box::new(Expression::Constant(2.into())), - )), - Box::new(Expression::Constant(3.into())), - ); - - assert_eq!(happened, expected); - } -} diff --git a/halo2_frontend/src/circuit.rs b/halo2_frontend/src/circuit.rs index 03c5d56697..fdea279e92 100644 --- a/halo2_frontend/src/circuit.rs +++ b/halo2_frontend/src/circuit.rs @@ -1,11 +1,11 @@ //! Traits and structs for implementing circuit components. -use halo2_common::plonk::{ - circuit::{Challenge, Column}, +use crate::plonk; +use crate::plonk::{ permutation, sealed::{self, SealedPhase}, - Assigned, Assignment, Circuit, ConstraintSystem, Error, FirstPhase, FloorPlanner, SecondPhase, - Selector, ThirdPhase, + Assignment, Circuit, ConstraintSystem, FirstPhase, FloorPlanner, SecondPhase, SelectorsToFixed, + ThirdPhase, }; use halo2_middleware::circuit::{Advice, Any, CompiledCircuitV2, Fixed, Instance, PreprocessingV2}; use halo2_middleware::ff::{BatchInvert, Field}; @@ -17,9 +17,49 @@ use std::ops::RangeTo; pub mod floor_planner; mod table_layouter; -// Re-exports from common -pub use halo2_common::circuit::floor_planner::single_pass::SimpleFloorPlanner; -pub use halo2_common::circuit::{layouter, Layouter, Value}; +use std::{fmt, marker::PhantomData}; + +use crate::plonk::Assigned; +use crate::plonk::{Challenge, Column, Error, Selector, TableColumn}; + +mod value; +pub use value::Value; + +pub use floor_planner::single_pass::SimpleFloorPlanner; + +pub mod layouter; + +pub use table_layouter::{SimpleTableLayouter, TableLayouter}; + +/// Compile a circuit, only generating the `Config` and the `ConstraintSystem` related information, +/// skipping all preprocessing data. +/// The `ConcreteCircuit::Config`, `ConstraintSystem` and `SelectorsToFixed` are outputs for the +/// frontend itself, which will be used for witness generation and fixed column assignment. +/// The `ConstraintSystem` can be converted to `ConstraintSystemMid` to be used to interface +/// with the backend. +pub fn compile_circuit_cs>( + compress_selectors: bool, + #[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params, +) -> ( + ConcreteCircuit::Config, + ConstraintSystem, + SelectorsToFixed, +) { + let mut cs = ConstraintSystem::default(); + #[cfg(feature = "circuit-params")] + let config = ConcreteCircuit::configure_with_params(&mut cs, params); + #[cfg(not(feature = "circuit-params"))] + let config = ConcreteCircuit::configure(&mut cs); + let cs = cs; + + let (cs, selectors_to_fixed) = if compress_selectors { + cs.selectors_to_fixed_compressed() + } else { + cs.selectors_to_fixed_direct() + }; + + (config, cs, selectors_to_fixed) +} /// Compile a circuit. Runs configure and synthesize on the circuit in order to materialize the /// circuit into its columns and the column configuration; as well as doing the fixed column and @@ -40,18 +80,18 @@ pub fn compile_circuit>( Error, > { let n = 2usize.pow(k); - let mut cs = ConstraintSystem::default(); - #[cfg(feature = "circuit-params")] - let config = ConcreteCircuit::configure_with_params(&mut cs, circuit.params()); - #[cfg(not(feature = "circuit-params"))] - let config = ConcreteCircuit::configure(&mut cs); - let cs = cs; + + // After this, the ConstraintSystem should not have any selectors: `verify` does not need them, and `keygen_pk` regenerates `cs` from scratch anyways. + let (config, cs, selectors_to_fixed) = compile_circuit_cs::<_, ConcreteCircuit>( + compress_selectors, + #[cfg(feature = "circuit-params")] + circuit.params(), + ); if n < cs.minimum_rows() { return Err(Error::not_enough_rows_available(k)); } - - let mut assembly = halo2_common::plonk::keygen::Assembly { + let mut assembly = plonk::keygen::Assembly { k, fixed: vec![vec![F::ZERO.into(); n]; cs.num_fixed_columns], permutation: permutation::Assembly::new(n, &cs.permutation), @@ -69,13 +109,7 @@ pub fn compile_circuit>( )?; let mut fixed = batch_invert_assigned(assembly.fixed); - let (cs, selector_polys) = if compress_selectors { - cs.compress_selectors(assembly.selectors.clone()) - } else { - // After this, the ConstraintSystem should not have any selectors: `verify` does not need them, and `keygen_pk` regenerates `cs` from scratch anyways. - let selectors = std::mem::take(&mut assembly.selectors); - cs.directly_convert_selectors_to_fixed(selectors) - }; + let selector_polys = selectors_to_fixed.convert(assembly.selectors); fixed.extend(selector_polys); let preprocessing = PreprocessingV2 { @@ -386,3 +420,578 @@ fn poly_invert( .map(|(a, inv_den)| a.numerator() * inv_den) .collect() } + +/// A chip implements a set of instructions that can be used by gadgets. +/// +/// The chip stores state that is required at circuit synthesis time in +/// [`Chip::Config`], which can be fetched via [`Chip::config`]. +/// +/// The chip also loads any fixed configuration needed at synthesis time +/// using its own implementation of `load`, and stores it in [`Chip::Loaded`]. +/// This can be accessed via [`Chip::loaded`]. +pub trait Chip: Sized { + /// A type that holds the configuration for this chip, and any other state it may need + /// during circuit synthesis, that can be derived during [`Circuit::configure`]. + /// + /// [`Circuit::configure`]: crate::plonk::Circuit::configure + type Config: fmt::Debug + Clone; + + /// A type that holds any general chip state that needs to be loaded at the start of + /// [`Circuit::synthesize`]. This might simply be `()` for some chips. + /// + /// [`Circuit::synthesize`]: crate::plonk::Circuit::synthesize + type Loaded: fmt::Debug + Clone; + + /// The chip holds its own configuration. + fn config(&self) -> &Self::Config; + + /// Provides access to general chip state loaded at the beginning of circuit + /// synthesis. + /// + /// Panics if called before `Chip::load`. + fn loaded(&self) -> &Self::Loaded; +} + +/// Index of a region in a layouter +#[derive(Clone, Copy, Debug)] +pub struct RegionIndex(usize); + +impl From for RegionIndex { + fn from(idx: usize) -> RegionIndex { + RegionIndex(idx) + } +} + +impl std::ops::Deref for RegionIndex { + type Target = usize; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// Starting row of a region in a layouter +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct RegionStart(usize); + +impl From for RegionStart { + fn from(idx: usize) -> RegionStart { + RegionStart(idx) + } +} + +impl std::ops::Deref for RegionStart { + type Target = usize; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// A pointer to a cell within a circuit. +#[derive(Clone, Copy, Debug)] +pub struct Cell { + /// Identifies the region in which this cell resides. + pub region_index: RegionIndex, + /// The relative offset of this cell within its region. + pub row_offset: usize, + /// The column of this cell. + pub column: Column, +} + +/// An assigned cell. +#[derive(Clone, Debug)] +pub struct AssignedCell { + value: Value, + cell: Cell, + _marker: PhantomData, +} + +impl AssignedCell { + /// Returns the value of the [`AssignedCell`]. + pub fn value(&self) -> Value<&V> { + self.value.as_ref() + } + + /// Returns the cell. + pub fn cell(&self) -> Cell { + self.cell + } +} + +impl AssignedCell +where + for<'v> Assigned: From<&'v V>, +{ + /// Returns the field element value of the [`AssignedCell`]. + pub fn value_field(&self) -> Value> { + self.value.to_field() + } +} + +impl AssignedCell, F> { + /// Evaluates this assigned cell's value directly, performing an unbatched inversion + /// if necessary. + /// + /// If the denominator is zero, the returned cell's value is zero. + pub fn evaluate(self) -> AssignedCell { + AssignedCell { + value: self.value.evaluate(), + cell: self.cell, + _marker: Default::default(), + } + } +} + +impl AssignedCell +where + for<'v> Assigned: From<&'v V>, +{ + /// Copies the value to a given advice cell and constrains them to be equal. + /// + /// Returns an error if either this cell or the given cell are in columns + /// where equality has not been enabled. + pub fn copy_advice( + &self, + annotation: A, + region: &mut Region<'_, F>, + column: Column, + offset: usize, + ) -> Result + where + A: Fn() -> AR, + AR: Into, + { + let assigned_cell = + region.assign_advice(annotation, column, offset, || self.value.clone())?; + region.constrain_equal(assigned_cell.cell(), self.cell())?; + + Ok(assigned_cell) + } +} + +/// A region of the circuit in which a [`Chip`] can assign cells. +/// +/// Inside a region, the chip may freely use relative offsets; the [`Layouter`] will +/// treat these assignments as a single "region" within the circuit. +/// +/// The [`Layouter`] is allowed to optimise between regions as it sees fit. Chips must use +/// [`Region::constrain_equal`] to copy in variables assigned in other regions. +/// +/// TODO: It would be great if we could constrain the columns in these types to be +/// "logical" columns that are guaranteed to correspond to the chip (and have come from +/// `Chip::Config`). +#[derive(Debug)] +pub struct Region<'r, F: Field> { + region: &'r mut dyn layouter::RegionLayouter, +} + +impl<'r, F: Field> From<&'r mut dyn layouter::RegionLayouter> for Region<'r, F> { + fn from(region: &'r mut dyn layouter::RegionLayouter) -> Self { + Region { region } + } +} + +impl<'r, F: Field> Region<'r, F> { + /// Enables a selector at the given offset. + pub fn enable_selector( + &mut self, + annotation: A, + selector: &Selector, + offset: usize, + ) -> Result<(), Error> + where + A: Fn() -> AR, + AR: Into, + { + self.region + .enable_selector(&|| annotation().into(), selector, offset) + } + + /// Allows the circuit implementor to name/annotate a Column within a Region context. + /// + /// This is useful in order to improve the amount of information that `prover.verify()` + /// and `prover.assert_satisfied()` can provide. + pub fn name_column(&mut self, annotation: A, column: T) + where + A: Fn() -> AR, + AR: Into, + T: Into>, + { + self.region + .name_column(&|| annotation().into(), column.into()); + } + + /// Assign an advice column value (witness). + /// + /// Even though `to` has `FnMut` bounds, it is guaranteed to be called at most once. + pub fn assign_advice<'v, V, VR, A, AR>( + &'v mut self, + annotation: A, + column: Column, + offset: usize, + mut to: V, + ) -> Result, Error> + where + V: FnMut() -> Value + 'v, + for<'vr> Assigned: From<&'vr VR>, + A: Fn() -> AR, + AR: Into, + { + let mut value = Value::unknown(); + let cell = + self.region + .assign_advice(&|| annotation().into(), column, offset, &mut || { + let v = to(); + let value_f = v.to_field(); + value = v; + value_f + })?; + + Ok(AssignedCell { + value, + cell, + _marker: PhantomData, + }) + } + + /// Assigns a constant value to the column `advice` at `offset` within this region. + /// + /// The constant value will be assigned to a cell within one of the fixed columns + /// configured via `ConstraintSystem::enable_constant`. + /// + /// Returns the advice cell. + pub fn assign_advice_from_constant( + &mut self, + annotation: A, + column: Column, + offset: usize, + constant: VR, + ) -> Result, Error> + where + for<'vr> Assigned: From<&'vr VR>, + A: Fn() -> AR, + AR: Into, + { + let cell = self.region.assign_advice_from_constant( + &|| annotation().into(), + column, + offset, + (&constant).into(), + )?; + + Ok(AssignedCell { + value: Value::known(constant), + cell, + _marker: PhantomData, + }) + } + + /// Assign the value of the instance column's cell at absolute location + /// `row` to the column `advice` at `offset` within this region. + /// + /// Returns the advice cell, and its value if known. + pub fn assign_advice_from_instance( + &mut self, + annotation: A, + instance: Column, + row: usize, + advice: Column, + offset: usize, + ) -> Result, Error> + where + A: Fn() -> AR, + AR: Into, + { + let (cell, value) = self.region.assign_advice_from_instance( + &|| annotation().into(), + instance, + row, + advice, + offset, + )?; + + Ok(AssignedCell { + value, + cell, + _marker: PhantomData, + }) + } + + /// Returns the value of the instance column's cell at absolute location `row`. + /// + /// This method is only provided for convenience; it does not create any constraints. + /// Callers still need to use [`Self::assign_advice_from_instance`] to constrain the + /// instance values in their circuit. + pub fn instance_value( + &mut self, + instance: Column, + row: usize, + ) -> Result, Error> { + self.region.instance_value(instance, row) + } + + /// Assign a fixed value. + /// + /// Even though `to` has `FnMut` bounds, it is guaranteed to be called at most once. + pub fn assign_fixed<'v, V, VR, A, AR>( + &'v mut self, + annotation: A, + column: Column, + offset: usize, + mut to: V, + ) -> Result, Error> + where + V: FnMut() -> Value + 'v, + for<'vr> Assigned: From<&'vr VR>, + A: Fn() -> AR, + AR: Into, + { + let mut value = Value::unknown(); + let cell = + self.region + .assign_fixed(&|| annotation().into(), column, offset, &mut || { + let v = to(); + let value_f = v.to_field(); + value = v; + value_f + })?; + + Ok(AssignedCell { + value, + cell, + _marker: PhantomData, + }) + } + + /// Constrains a cell to have a constant value. + /// + /// Returns an error if the cell is in a column where equality has not been enabled. + pub fn constrain_constant(&mut self, cell: Cell, constant: VR) -> Result<(), Error> + where + VR: Into>, + { + self.region.constrain_constant(cell, constant.into()) + } + + /// Constrains two cells to have the same value. + /// + /// Returns an error if either of the cells are in columns where equality + /// has not been enabled. + pub fn constrain_equal(&mut self, left: Cell, right: Cell) -> Result<(), Error> { + self.region.constrain_equal(left, right) + } +} + +/// A lookup table in the circuit. +#[derive(Debug)] +pub struct Table<'r, F: Field> { + table: &'r mut dyn TableLayouter, +} + +impl<'r, F: Field> From<&'r mut dyn TableLayouter> for Table<'r, F> { + fn from(table: &'r mut dyn TableLayouter) -> Self { + Table { table } + } +} + +impl<'r, F: Field> Table<'r, F> { + /// Assigns a fixed value to a table cell. + /// + /// Returns an error if the table cell has already been assigned to. + /// + /// Even though `to` has `FnMut` bounds, it is guaranteed to be called at most once. + pub fn assign_cell<'v, V, VR, A, AR>( + &'v mut self, + annotation: A, + column: TableColumn, + offset: usize, + mut to: V, + ) -> Result<(), Error> + where + V: FnMut() -> Value + 'v, + VR: Into>, + A: Fn() -> AR, + AR: Into, + { + self.table + .assign_cell(&|| annotation().into(), column, offset, &mut || { + to().into_field() + }) + } +} + +/// A layout strategy within a circuit. The layouter is chip-agnostic and applies its +/// strategy to the context and config it is given. +/// +/// This abstracts over the circuit assignments, handling row indices etc. +/// +pub trait Layouter { + /// Represents the type of the "root" of this layouter, so that nested namespaces + /// can minimize indirection. + type Root: Layouter; + + /// Assign a region of gates to an absolute row number. + /// + /// Inside the closure, the chip may freely use relative offsets; the `Layouter` will + /// treat these assignments as a single "region" within the circuit. Outside this + /// closure, the `Layouter` is allowed to optimise as it sees fit. + /// + /// ```ignore + /// fn assign_region(&mut self, || "region name", |region| { + /// let config = chip.config(); + /// region.assign_advice(config.a, offset, || { Some(value)}); + /// }); + /// ``` + fn assign_region(&mut self, name: N, assignment: A) -> Result + where + A: FnMut(Region<'_, F>) -> Result, + N: Fn() -> NR, + NR: Into; + + /// Assign a table region to an absolute row number. + /// + /// ```ignore + /// fn assign_table(&mut self, || "table name", |table| { + /// let config = chip.config(); + /// table.assign_fixed(config.a, offset, || { Some(value)}); + /// }); + /// ``` + fn assign_table(&mut self, name: N, assignment: A) -> Result<(), Error> + where + A: FnMut(Table<'_, F>) -> Result<(), Error>, + N: Fn() -> NR, + NR: Into; + + /// Constrains a [`Cell`] to equal an instance column's row value at an + /// absolute position. + fn constrain_instance( + &mut self, + cell: Cell, + column: Column, + row: usize, + ) -> Result<(), Error>; + + /// Queries the value of the given challenge. + /// + /// Returns `Value::unknown()` if the current synthesis phase is before the challenge can be queried. + fn get_challenge(&self, challenge: Challenge) -> Value; + + /// Gets the "root" of this assignment, bypassing the namespacing. + /// + /// Not intended for downstream consumption; use [`Layouter::namespace`] instead. + fn get_root(&mut self) -> &mut Self::Root; + + /// Creates a new (sub)namespace and enters into it. + /// + /// Not intended for downstream consumption; use [`Layouter::namespace`] instead. + fn push_namespace(&mut self, name_fn: N) + where + NR: Into, + N: FnOnce() -> NR; + + /// Exits out of the existing namespace. + /// + /// Not intended for downstream consumption; use [`Layouter::namespace`] instead. + fn pop_namespace(&mut self, gadget_name: Option); + + /// Enters into a namespace. + fn namespace(&mut self, name_fn: N) -> NamespacedLayouter<'_, F, Self::Root> + where + NR: Into, + N: FnOnce() -> NR, + { + self.get_root().push_namespace(name_fn); + + NamespacedLayouter(self.get_root(), PhantomData) + } +} + +/// This is a "namespaced" layouter which borrows a `Layouter` (pushing a namespace +/// context) and, when dropped, pops out of the namespace context. +#[derive(Debug)] +pub struct NamespacedLayouter<'a, F: Field, L: Layouter + 'a>(&'a mut L, PhantomData); + +impl<'a, F: Field, L: Layouter + 'a> Layouter for NamespacedLayouter<'a, F, L> { + type Root = L::Root; + + fn assign_region(&mut self, name: N, assignment: A) -> Result + where + A: FnMut(Region<'_, F>) -> Result, + N: Fn() -> NR, + NR: Into, + { + self.0.assign_region(name, assignment) + } + + fn assign_table(&mut self, name: N, assignment: A) -> Result<(), Error> + where + A: FnMut(Table<'_, F>) -> Result<(), Error>, + N: Fn() -> NR, + NR: Into, + { + self.0.assign_table(name, assignment) + } + + fn constrain_instance( + &mut self, + cell: Cell, + column: Column, + row: usize, + ) -> Result<(), Error> { + self.0.constrain_instance(cell, column, row) + } + + fn get_challenge(&self, challenge: Challenge) -> Value { + self.0.get_challenge(challenge) + } + + fn get_root(&mut self) -> &mut Self::Root { + self.0.get_root() + } + + fn push_namespace(&mut self, _name_fn: N) + where + NR: Into, + N: FnOnce() -> NR, + { + panic!("Only the root's push_namespace should be called"); + } + + fn pop_namespace(&mut self, _gadget_name: Option) { + panic!("Only the root's pop_namespace should be called"); + } +} + +impl<'a, F: Field, L: Layouter + 'a> Drop for NamespacedLayouter<'a, F, L> { + fn drop(&mut self) { + let gadget_name = { + #[cfg(feature = "gadget-traces")] + { + let mut gadget_name = None; + let mut is_second_frame = false; + backtrace::trace(|frame| { + if is_second_frame { + // Resolve this instruction pointer to a symbol name. + backtrace::resolve_frame(frame, |symbol| { + gadget_name = symbol.name().map(|name| format!("{name:#}")); + }); + + // We are done! + false + } else { + // We want the next frame. + is_second_frame = true; + true + } + }); + gadget_name + } + + #[cfg(not(feature = "gadget-traces"))] + None + }; + + self.get_root().pop_namespace(gadget_name); + } +} diff --git a/halo2_frontend/src/circuit/floor_planner.rs b/halo2_frontend/src/circuit/floor_planner.rs index 9ad3df1a15..e07adad9a4 100644 --- a/halo2_frontend/src/circuit/floor_planner.rs +++ b/halo2_frontend/src/circuit/floor_planner.rs @@ -1,4 +1,5 @@ +//! Implementations of common circuit floor planners. pub mod single_pass; pub mod v1; -pub use halo2_common::circuit::floor_planner::*; +pub use v1::{V1Pass, V1}; diff --git a/halo2_frontend/src/circuit/floor_planner/single_pass.rs b/halo2_frontend/src/circuit/floor_planner/single_pass.rs index 39d0a6a001..5845c392b6 100644 --- a/halo2_frontend/src/circuit/floor_planner/single_pass.rs +++ b/halo2_frontend/src/circuit/floor_planner/single_pass.rs @@ -1,11 +1,387 @@ -pub use halo2_common::circuit::floor_planner::single_pass::*; +use std::cmp; +use std::collections::HashMap; +use std::fmt; +use std::marker::PhantomData; + +use halo2_middleware::ff::Field; + +use crate::plonk::Assigned; +use crate::{ + circuit::{ + layouter::{RegionColumn, RegionLayouter, RegionShape, SyncDeps, TableLayouter}, + table_layouter::{compute_table_lengths, SimpleTableLayouter}, + Cell, Column, Layouter, Region, RegionIndex, RegionStart, Table, Value, + }, + plonk::{Assignment, Challenge, Circuit, Error, FloorPlanner, Selector, TableColumn}, +}; +use halo2_middleware::circuit::{Advice, Any, Fixed, Instance}; + +/// A simple [`FloorPlanner`] that performs minimal optimizations. +/// +/// This floor planner is suitable for debugging circuits. It aims to reflect the circuit +/// "business logic" in the circuit layout as closely as possible. It uses a single-pass +/// layouter that does not reorder regions for optimal packing. +#[derive(Debug)] +pub struct SimpleFloorPlanner; + +impl FloorPlanner for SimpleFloorPlanner { + fn synthesize + SyncDeps, C: Circuit>( + cs: &mut CS, + circuit: &C, + config: C::Config, + constants: Vec>, + ) -> Result<(), Error> { + let layouter = SingleChipLayouter::new(cs, constants)?; + circuit.synthesize(config, layouter) + } +} + +/// A [`Layouter`] for a single-chip circuit. +pub struct SingleChipLayouter<'a, F: Field, CS: Assignment + 'a> { + cs: &'a mut CS, + constants: Vec>, + /// Stores the starting row for each region. + regions: Vec, + /// Stores the first empty row for each column. + columns: HashMap, + /// Stores the table fixed columns. + table_columns: Vec, + _marker: PhantomData, +} + +impl<'a, F: Field, CS: Assignment + 'a> fmt::Debug for SingleChipLayouter<'a, F, CS> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SingleChipLayouter") + .field("regions", &self.regions) + .field("columns", &self.columns) + .finish() + } +} + +impl<'a, F: Field, CS: Assignment> SingleChipLayouter<'a, F, CS> { + /// Creates a new single-chip layouter. + pub fn new(cs: &'a mut CS, constants: Vec>) -> Result { + let ret = SingleChipLayouter { + cs, + constants, + regions: vec![], + columns: HashMap::default(), + table_columns: vec![], + _marker: PhantomData, + }; + Ok(ret) + } +} + +impl<'a, F: Field, CS: Assignment + 'a + SyncDeps> Layouter + for SingleChipLayouter<'a, F, CS> +{ + type Root = Self; + + fn assign_region(&mut self, name: N, mut assignment: A) -> Result + where + A: FnMut(Region<'_, F>) -> Result, + N: Fn() -> NR, + NR: Into, + { + let region_index = self.regions.len(); + + // Get shape of the region. + let mut shape = RegionShape::new(region_index.into()); + { + let region: &mut dyn RegionLayouter = &mut shape; + assignment(region.into())?; + } + + // Lay out this region. We implement the simplest approach here: position the + // region starting at the earliest row for which none of the columns are in use. + let mut region_start = 0; + for column in &shape.columns { + region_start = cmp::max(region_start, self.columns.get(column).cloned().unwrap_or(0)); + } + self.regions.push(region_start.into()); + + // Update column usage information. + for column in shape.columns { + self.columns.insert(column, region_start + shape.row_count); + } + + // Assign region cells. + self.cs.enter_region(name); + let mut region = SingleChipLayouterRegion::new(self, region_index.into()); + let result = { + let region: &mut dyn RegionLayouter = &mut region; + assignment(region.into()) + }?; + let constants_to_assign = region.constants; + self.cs.exit_region(); + + // Assign constants. For the simple floor planner, we assign constants in order in + // the first `constants` column. + if self.constants.is_empty() { + if !constants_to_assign.is_empty() { + return Err(Error::NotEnoughColumnsForConstants); + } + } else { + let constants_column = self.constants[0]; + let next_constant_row = self + .columns + .entry(Column::::from(constants_column).into()) + .or_default(); + for (constant, advice) in constants_to_assign { + self.cs.assign_fixed( + || format!("Constant({:?})", constant.evaluate()), + constants_column, + *next_constant_row, + || Value::known(constant), + )?; + self.cs.copy( + constants_column.into(), + *next_constant_row, + advice.column, + *self.regions[*advice.region_index] + advice.row_offset, + )?; + *next_constant_row += 1; + } + } + + Ok(result) + } + + fn assign_table(&mut self, name: N, mut assignment: A) -> Result<(), Error> + where + A: FnMut(Table<'_, F>) -> Result<(), Error>, + N: Fn() -> NR, + NR: Into, + { + // Maintenance hazard: there is near-duplicate code in `v1::AssignmentPass::assign_table`. + // Assign table cells. + self.cs.enter_region(name); + let mut table = SimpleTableLayouter::new(self.cs, &self.table_columns); + { + let table: &mut dyn TableLayouter = &mut table; + assignment(table.into()) + }?; + let default_and_assigned = table.default_and_assigned; + self.cs.exit_region(); + + // Check that all table columns have the same length `first_unused`, + // and all cells up to that length are assigned. + let first_unused = compute_table_lengths(&default_and_assigned)?; + + // Record these columns so that we can prevent them from being used again. + for column in default_and_assigned.keys() { + self.table_columns.push(*column); + } + + for (col, (default_val, _)) in default_and_assigned { + // default_val must be Some because we must have assigned + // at least one cell in each column, and in that case we checked + // that all cells up to first_unused were assigned. + self.cs + .fill_from_row(col.inner(), first_unused, default_val.unwrap())?; + } + + Ok(()) + } + + fn constrain_instance( + &mut self, + cell: Cell, + instance: Column, + row: usize, + ) -> Result<(), Error> { + self.cs.copy( + cell.column, + *self.regions[*cell.region_index] + cell.row_offset, + instance.into(), + row, + ) + } + + fn get_challenge(&self, challenge: Challenge) -> Value { + self.cs.get_challenge(challenge) + } + + fn get_root(&mut self) -> &mut Self::Root { + self + } + + fn push_namespace(&mut self, name_fn: N) + where + NR: Into, + N: FnOnce() -> NR, + { + self.cs.push_namespace(name_fn) + } + + fn pop_namespace(&mut self, gadget_name: Option) { + self.cs.pop_namespace(gadget_name) + } +} + +struct SingleChipLayouterRegion<'r, 'a, F: Field, CS: Assignment + 'a> { + layouter: &'r mut SingleChipLayouter<'a, F, CS>, + region_index: RegionIndex, + /// Stores the constants to be assigned, and the cells to which they are copied. + constants: Vec<(Assigned, Cell)>, +} + +impl<'r, 'a, F: Field, CS: Assignment + 'a> fmt::Debug + for SingleChipLayouterRegion<'r, 'a, F, CS> +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SingleChipLayouterRegion") + .field("layouter", &self.layouter) + .field("region_index", &self.region_index) + .finish() + } +} + +impl<'r, 'a, F: Field, CS: Assignment + 'a> SingleChipLayouterRegion<'r, 'a, F, CS> { + fn new(layouter: &'r mut SingleChipLayouter<'a, F, CS>, region_index: RegionIndex) -> Self { + SingleChipLayouterRegion { + layouter, + region_index, + constants: vec![], + } + } +} + +impl<'r, 'a, F: Field, CS: Assignment + 'a + SyncDeps> RegionLayouter + for SingleChipLayouterRegion<'r, 'a, F, CS> +{ + fn enable_selector<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + selector: &Selector, + offset: usize, + ) -> Result<(), Error> { + self.layouter.cs.enable_selector( + annotation, + selector, + *self.layouter.regions[*self.region_index] + offset, + ) + } + + fn name_column<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + column: Column, + ) { + self.layouter.cs.annotate_column(annotation, column); + } + + fn assign_advice<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + column: Column, + offset: usize, + to: &'v mut (dyn FnMut() -> Value> + 'v), + ) -> Result { + self.layouter.cs.assign_advice( + annotation, + column, + *self.layouter.regions[*self.region_index] + offset, + to, + )?; + + Ok(Cell { + region_index: self.region_index, + row_offset: offset, + column: column.into(), + }) + } + + fn assign_advice_from_constant<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + column: Column, + offset: usize, + constant: Assigned, + ) -> Result { + let advice = + self.assign_advice(annotation, column, offset, &mut || Value::known(constant))?; + self.constrain_constant(advice, constant)?; + + Ok(advice) + } + + fn assign_advice_from_instance<'v>( + &mut self, + annotation: &'v (dyn Fn() -> String + 'v), + instance: Column, + row: usize, + advice: Column, + offset: usize, + ) -> Result<(Cell, Value), Error> { + let value = self.layouter.cs.query_instance(instance, row)?; + + let cell = self.assign_advice(annotation, advice, offset, &mut || value.to_field())?; + + self.layouter.cs.copy( + cell.column, + *self.layouter.regions[*cell.region_index] + cell.row_offset, + instance.into(), + row, + )?; + + Ok((cell, value)) + } + + fn instance_value( + &mut self, + instance: Column, + row: usize, + ) -> Result, Error> { + self.layouter.cs.query_instance(instance, row) + } + + fn assign_fixed<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + column: Column, + offset: usize, + to: &'v mut (dyn FnMut() -> Value> + 'v), + ) -> Result { + self.layouter.cs.assign_fixed( + annotation, + column, + *self.layouter.regions[*self.region_index] + offset, + to, + )?; + + Ok(Cell { + region_index: self.region_index, + row_offset: offset, + column: column.into(), + }) + } + + fn constrain_constant(&mut self, cell: Cell, constant: Assigned) -> Result<(), Error> { + self.constants.push((constant, cell)); + Ok(()) + } + + fn constrain_equal(&mut self, left: Cell, right: Cell) -> Result<(), Error> { + self.layouter.cs.copy( + left.column, + *self.layouter.regions[*left.region_index] + left.row_offset, + right.column, + *self.layouter.regions[*right.region_index] + right.row_offset, + )?; + + Ok(()) + } +} + #[cfg(test)] mod tests { use halo2curves::pasta::vesta; use super::SimpleFloorPlanner; use crate::dev::MockProver; - use halo2_common::plonk::{circuit::Column, Circuit, ConstraintSystem, Error}; + use crate::plonk::{Circuit, Column, ConstraintSystem, Error}; use halo2_middleware::circuit::Advice; #[test] diff --git a/halo2_frontend/src/circuit/floor_planner/v1.rs b/halo2_frontend/src/circuit/floor_planner/v1.rs index 591cab81f8..9cd9403524 100644 --- a/halo2_frontend/src/circuit/floor_planner/v1.rs +++ b/halo2_frontend/src/circuit/floor_planner/v1.rs @@ -1,13 +1,502 @@ -mod strategy; +use std::fmt; -pub use halo2_common::circuit::floor_planner::V1; +use halo2_middleware::ff::Field; + +use crate::plonk::Assigned; +use crate::{ + circuit::{ + layouter::{RegionColumn, RegionLayouter, RegionShape, SyncDeps, TableLayouter}, + table_layouter::{compute_table_lengths, SimpleTableLayouter}, + Cell, Column, Layouter, Region, RegionIndex, RegionStart, Table, Value, + }, + plonk::{Assignment, Challenge, Circuit, Error, FloorPlanner, Selector, TableColumn}, +}; +use halo2_middleware::circuit::{Advice, Any, Fixed, Instance}; + +pub mod strategy; + +/// The version 1 [`FloorPlanner`] provided by `halo2`. +/// +/// - No column optimizations are performed. Circuit configuration is left entirely to the +/// circuit designer. +/// - A dual-pass layouter is used to measures regions prior to assignment. +/// - Regions are measured as rectangles, bounded on the cells they assign. +/// - Regions are laid out using a greedy first-fit strategy, after sorting regions by +/// their "advice area" (number of advice columns * rows). +#[derive(Debug)] +pub struct V1; + +struct V1Plan<'a, F: Field, CS: Assignment + 'a> { + cs: &'a mut CS, + /// Stores the starting row for each region. + regions: Vec, + /// Stores the constants to be assigned, and the cells to which they are copied. + constants: Vec<(Assigned, Cell)>, + /// Stores the table fixed columns. + table_columns: Vec, +} + +impl<'a, F: Field, CS: Assignment + 'a> fmt::Debug for V1Plan<'a, F, CS> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("floor_planner::V1Plan").finish() + } +} + +impl<'a, F: Field, CS: Assignment + SyncDeps> V1Plan<'a, F, CS> { + /// Creates a new v1 layouter. + pub fn new(cs: &'a mut CS) -> Result { + let ret = V1Plan { + cs, + regions: vec![], + constants: vec![], + table_columns: vec![], + }; + Ok(ret) + } +} + +impl FloorPlanner for V1 { + fn synthesize + SyncDeps, C: Circuit>( + cs: &mut CS, + circuit: &C, + config: C::Config, + constants: Vec>, + ) -> Result<(), Error> { + let mut plan = V1Plan::new(cs)?; + + // First pass: measure the regions within the circuit. + let mut measure = MeasurementPass::new(); + { + let pass = &mut measure; + circuit + .without_witnesses() + .synthesize(config.clone(), V1Pass::<_, CS>::measure(pass))?; + } + + // Planning: + // - Position the regions. + let (regions, column_allocations) = strategy::slot_in_biggest_advice_first(measure.regions); + plan.regions = regions; + + // - Determine how many rows our planned circuit will require. + let first_unassigned_row = column_allocations + .values() + .map(|a| a.unbounded_interval_start()) + .max() + .unwrap_or(0); + + // - Position the constants within those rows. + let fixed_allocations: Vec<_> = constants + .into_iter() + .map(|c| { + ( + c, + column_allocations + .get(&Column::::from(c).into()) + .cloned() + .unwrap_or_default(), + ) + }) + .collect(); + let constant_positions = || { + fixed_allocations.iter().flat_map(|(c, a)| { + let c = *c; + a.free_intervals(0, Some(first_unassigned_row)) + .flat_map(move |e| e.range().unwrap().map(move |i| (c, i))) + }) + }; + + // Second pass: + // - Assign the regions. + let mut assign = AssignmentPass::new(&mut plan); + { + let pass = &mut assign; + circuit.synthesize(config, V1Pass::assign(pass))?; + } + + // - Assign the constants. + if constant_positions().count() < plan.constants.len() { + return Err(Error::NotEnoughColumnsForConstants); + } + for ((fixed_column, fixed_row), (value, advice)) in + constant_positions().zip(plan.constants.into_iter()) + { + plan.cs.assign_fixed( + || format!("Constant({:?})", value.evaluate()), + fixed_column, + fixed_row, + || Value::known(value), + )?; + plan.cs.copy( + fixed_column.into(), + fixed_row, + advice.column, + *plan.regions[*advice.region_index] + advice.row_offset, + )?; + } + + Ok(()) + } +} + +#[derive(Debug)] +enum Pass<'p, 'a, F: Field, CS: Assignment + 'a> { + Measurement(&'p mut MeasurementPass), + Assignment(&'p mut AssignmentPass<'p, 'a, F, CS>), +} + +/// A single pass of the [`V1`] layouter. +#[derive(Debug)] +pub struct V1Pass<'p, 'a, F: Field, CS: Assignment + 'a>(Pass<'p, 'a, F, CS>); + +impl<'p, 'a, F: Field, CS: Assignment + 'a> V1Pass<'p, 'a, F, CS> { + fn measure(pass: &'p mut MeasurementPass) -> Self { + V1Pass(Pass::Measurement(pass)) + } + + fn assign(pass: &'p mut AssignmentPass<'p, 'a, F, CS>) -> Self { + V1Pass(Pass::Assignment(pass)) + } +} + +impl<'p, 'a, F: Field, CS: Assignment + SyncDeps> Layouter for V1Pass<'p, 'a, F, CS> { + type Root = Self; + + fn assign_region(&mut self, name: N, assignment: A) -> Result + where + A: FnMut(Region<'_, F>) -> Result, + N: Fn() -> NR, + NR: Into, + { + match &mut self.0 { + Pass::Measurement(pass) => pass.assign_region(assignment), + Pass::Assignment(pass) => pass.assign_region(name, assignment), + } + } + + fn assign_table(&mut self, name: N, assignment: A) -> Result<(), Error> + where + A: FnMut(Table<'_, F>) -> Result<(), Error>, + N: Fn() -> NR, + NR: Into, + { + match &mut self.0 { + Pass::Measurement(_) => Ok(()), + Pass::Assignment(pass) => pass.assign_table(name, assignment), + } + } + + fn constrain_instance( + &mut self, + cell: Cell, + instance: Column, + row: usize, + ) -> Result<(), Error> { + match &mut self.0 { + Pass::Measurement(_) => Ok(()), + Pass::Assignment(pass) => pass.constrain_instance(cell, instance, row), + } + } + + fn get_challenge(&self, challenge: Challenge) -> Value { + match &self.0 { + Pass::Measurement(_) => Value::unknown(), + Pass::Assignment(pass) => pass.plan.cs.get_challenge(challenge), + } + } + + fn get_root(&mut self) -> &mut Self::Root { + self + } + + fn push_namespace(&mut self, name_fn: N) + where + NR: Into, + N: FnOnce() -> NR, + { + if let Pass::Assignment(pass) = &mut self.0 { + pass.plan.cs.push_namespace(name_fn); + } + } + + fn pop_namespace(&mut self, gadget_name: Option) { + if let Pass::Assignment(pass) = &mut self.0 { + pass.plan.cs.pop_namespace(gadget_name); + } + } +} + +/// Measures the circuit. +#[derive(Debug)] +pub struct MeasurementPass { + regions: Vec, +} + +impl MeasurementPass { + fn new() -> Self { + MeasurementPass { regions: vec![] } + } + + fn assign_region(&mut self, mut assignment: A) -> Result + where + A: FnMut(Region<'_, F>) -> Result, + { + let region_index = self.regions.len(); + + // Get shape of the region. + let mut shape = RegionShape::new(region_index.into()); + let result = { + let region: &mut dyn RegionLayouter = &mut shape; + assignment(region.into()) + }?; + self.regions.push(shape); + + Ok(result) + } +} + +/// Assigns the circuit. +#[derive(Debug)] +pub struct AssignmentPass<'p, 'a, F: Field, CS: Assignment + 'a> { + plan: &'p mut V1Plan<'a, F, CS>, + /// Counter tracking which region we need to assign next. + region_index: usize, +} + +impl<'p, 'a, F: Field, CS: Assignment + SyncDeps> AssignmentPass<'p, 'a, F, CS> { + fn new(plan: &'p mut V1Plan<'a, F, CS>) -> Self { + AssignmentPass { + plan, + region_index: 0, + } + } + + fn assign_region(&mut self, name: N, mut assignment: A) -> Result + where + A: FnMut(Region<'_, F>) -> Result, + N: Fn() -> NR, + NR: Into, + { + // Get the next region we are assigning. + let region_index = self.region_index; + self.region_index += 1; + + self.plan.cs.enter_region(name); + let mut region = V1Region::new(self.plan, region_index.into()); + let result = { + let region: &mut dyn RegionLayouter = &mut region; + assignment(region.into()) + }?; + self.plan.cs.exit_region(); + + Ok(result) + } + + fn assign_table(&mut self, name: N, mut assignment: A) -> Result + where + A: FnMut(Table<'_, F>) -> Result, + N: Fn() -> NR, + NR: Into, + { + // Maintenance hazard: there is near-duplicate code in `SingleChipLayouter::assign_table`. + + // Assign table cells. + self.plan.cs.enter_region(name); + let mut table = SimpleTableLayouter::new(self.plan.cs, &self.plan.table_columns); + let result = { + let table: &mut dyn TableLayouter = &mut table; + assignment(table.into()) + }?; + let default_and_assigned = table.default_and_assigned; + self.plan.cs.exit_region(); + + // Check that all table columns have the same length `first_unused`, + // and all cells up to that length are assigned. + let first_unused = compute_table_lengths(&default_and_assigned)?; + + // Record these columns so that we can prevent them from being used again. + for column in default_and_assigned.keys() { + self.plan.table_columns.push(*column); + } + + for (col, (default_val, _)) in default_and_assigned { + // default_val must be Some because we must have assigned + // at least one cell in each column, and in that case we checked + // that all cells up to first_unused were assigned. + self.plan + .cs + .fill_from_row(col.inner(), first_unused, default_val.unwrap())?; + } + + Ok(result) + } + + fn constrain_instance( + &mut self, + cell: Cell, + instance: Column, + row: usize, + ) -> Result<(), Error> { + self.plan.cs.copy( + cell.column, + *self.plan.regions[*cell.region_index] + cell.row_offset, + instance.into(), + row, + ) + } +} + +struct V1Region<'r, 'a, F: Field, CS: Assignment + 'a> { + plan: &'r mut V1Plan<'a, F, CS>, + region_index: RegionIndex, +} + +impl<'r, 'a, F: Field, CS: Assignment + 'a> fmt::Debug for V1Region<'r, 'a, F, CS> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("V1Region") + .field("plan", &self.plan) + .field("region_index", &self.region_index) + .finish() + } +} + +impl<'r, 'a, F: Field, CS: Assignment + 'a> V1Region<'r, 'a, F, CS> { + fn new(plan: &'r mut V1Plan<'a, F, CS>, region_index: RegionIndex) -> Self { + V1Region { plan, region_index } + } +} + +impl<'r, 'a, F: Field, CS: Assignment + SyncDeps> RegionLayouter for V1Region<'r, 'a, F, CS> { + fn enable_selector<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + selector: &Selector, + offset: usize, + ) -> Result<(), Error> { + self.plan.cs.enable_selector( + annotation, + selector, + *self.plan.regions[*self.region_index] + offset, + ) + } + + fn assign_advice<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + column: Column, + offset: usize, + to: &'v mut (dyn FnMut() -> Value> + 'v), + ) -> Result { + self.plan.cs.assign_advice( + annotation, + column, + *self.plan.regions[*self.region_index] + offset, + to, + )?; + + Ok(Cell { + region_index: self.region_index, + row_offset: offset, + column: column.into(), + }) + } + + fn assign_advice_from_constant<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + column: Column, + offset: usize, + constant: Assigned, + ) -> Result { + let advice = + self.assign_advice(annotation, column, offset, &mut || Value::known(constant))?; + self.constrain_constant(advice, constant)?; + + Ok(advice) + } + + fn assign_advice_from_instance<'v>( + &mut self, + annotation: &'v (dyn Fn() -> String + 'v), + instance: Column, + row: usize, + advice: Column, + offset: usize, + ) -> Result<(Cell, Value), Error> { + let value = self.plan.cs.query_instance(instance, row)?; + + let cell = self.assign_advice(annotation, advice, offset, &mut || value.to_field())?; + + self.plan.cs.copy( + cell.column, + *self.plan.regions[*cell.region_index] + cell.row_offset, + instance.into(), + row, + )?; + + Ok((cell, value)) + } + + fn instance_value( + &mut self, + instance: Column, + row: usize, + ) -> Result, Error> { + self.plan.cs.query_instance(instance, row) + } + + fn assign_fixed<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + column: Column, + offset: usize, + to: &'v mut (dyn FnMut() -> Value> + 'v), + ) -> Result { + self.plan.cs.assign_fixed( + annotation, + column, + *self.plan.regions[*self.region_index] + offset, + to, + )?; + + Ok(Cell { + region_index: self.region_index, + row_offset: offset, + column: column.into(), + }) + } + + fn constrain_constant(&mut self, cell: Cell, constant: Assigned) -> Result<(), Error> { + self.plan.constants.push((constant, cell)); + Ok(()) + } + + fn name_column<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + column: Column, + ) { + self.plan.cs.annotate_column(annotation, column) + } + + fn constrain_equal(&mut self, left: Cell, right: Cell) -> Result<(), Error> { + self.plan.cs.copy( + left.column, + *self.plan.regions[*left.region_index] + left.row_offset, + right.column, + *self.plan.regions[*right.region_index] + right.row_offset, + )?; + + Ok(()) + } +} #[cfg(test)] mod tests { use halo2curves::pasta::vesta; use crate::dev::MockProver; - use halo2_common::plonk::{circuit::Column, Circuit, ConstraintSystem, Error}; + use crate::plonk::{Circuit, Column, ConstraintSystem, Error}; use halo2_middleware::circuit::Advice; #[test] diff --git a/halo2_frontend/src/circuit/floor_planner/v1/strategy.rs b/halo2_frontend/src/circuit/floor_planner/v1/strategy.rs index 29d8f431d5..2a4ce92a22 100644 --- a/halo2_frontend/src/circuit/floor_planner/v1/strategy.rs +++ b/halo2_frontend/src/circuit/floor_planner/v1/strategy.rs @@ -1,8 +1,252 @@ +use std::{ + cmp, + collections::{BTreeSet, HashMap}, + ops::Range, +}; + +use super::{RegionColumn, RegionShape}; +use crate::circuit::RegionStart; +use halo2_middleware::circuit::Any; + +/// A region allocated within a column. +#[derive(Clone, Default, Debug, PartialEq, Eq)] +struct AllocatedRegion { + // The starting position of the region. + start: usize, + // The length of the region. + length: usize, +} + +impl Ord for AllocatedRegion { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.start.cmp(&other.start) + } +} + +impl PartialOrd for AllocatedRegion { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +/// An area of empty space within a column. +pub(crate) struct EmptySpace { + // The starting position (inclusive) of the empty space. + start: usize, + // The ending position (exclusive) of the empty space, or `None` if unbounded. + end: Option, +} + +impl EmptySpace { + pub(crate) fn range(&self) -> Option> { + self.end.map(|end| self.start..end) + } +} + +/// Allocated rows within a column. +/// +/// This is a set of [a_start, a_end) pairs representing disjoint allocated intervals. +#[derive(Clone, Default, Debug)] +pub struct Allocations(BTreeSet); + +impl Allocations { + /// Returns the row that forms the unbounded unallocated interval [row, None). + pub(crate) fn unbounded_interval_start(&self) -> usize { + self.0 + .iter() + .last() + .map(|r| r.start + r.length) + .unwrap_or(0) + } + + /// Return all the *unallocated* nonempty intervals intersecting [start, end). + /// + /// `end = None` represents an unbounded end. + pub(crate) fn free_intervals( + &self, + start: usize, + end: Option, + ) -> impl Iterator + '_ { + self.0 + .iter() + .map(Some) + .chain(Some(None)) + .scan(start, move |row, region| { + Some(if let Some(region) = region { + if end.map(|end| region.start >= end).unwrap_or(false) { + None + } else { + let ret = if *row < region.start { + Some(EmptySpace { + start: *row, + end: Some(region.start), + }) + } else { + None + }; + + *row = cmp::max(*row, region.start + region.length); + + ret + } + } else if end.map(|end| *row < end).unwrap_or(true) { + Some(EmptySpace { start: *row, end }) + } else { + None + }) + }) + .flatten() + } +} + +/// Allocated rows within a circuit. +pub type CircuitAllocations = HashMap; + +/// - `start` is the current start row of the region (not of this column). +/// - `slack` is the maximum number of rows the start could be moved down, taking into +/// account prior columns. +fn first_fit_region( + column_allocations: &mut CircuitAllocations, + region_columns: &[RegionColumn], + region_length: usize, + start: usize, + slack: Option, +) -> Option { + let (c, remaining_columns) = match region_columns.split_first() { + Some(cols) => cols, + None => return Some(start), + }; + let end = slack.map(|slack| start + region_length + slack); + + // Iterate over the unallocated non-empty intervals in c that intersect [start, end). + for space in column_allocations + .entry(*c) + .or_default() + .clone() + .free_intervals(start, end) + { + // Do we have enough room for this column of the region in this interval? + let s_slack = space + .end + .map(|end| (end as isize - space.start as isize) - region_length as isize); + if let Some((slack, s_slack)) = slack.zip(s_slack) { + assert!(s_slack <= slack as isize); + } + if s_slack.unwrap_or(0) >= 0 { + let row = first_fit_region( + column_allocations, + remaining_columns, + region_length, + space.start, + s_slack.map(|s| s as usize), + ); + if let Some(row) = row { + if let Some(end) = end { + assert!(row + region_length <= end); + } + column_allocations + .get_mut(c) + .unwrap() + .0 + .insert(AllocatedRegion { + start: row, + length: region_length, + }); + return Some(row); + } + } + } + + // No placement worked; the caller will need to try other possibilities. + None +} + +/// Positions the regions starting at the earliest row for which none of the columns are +/// in use, taking into account gaps between earlier regions. +pub fn slot_in( + region_shapes: Vec, +) -> (Vec<(RegionStart, RegionShape)>, CircuitAllocations) { + // Tracks the empty regions for each column. + let mut column_allocations: CircuitAllocations = Default::default(); + + let regions = region_shapes + .into_iter() + .map(|region| { + // Sort the region's columns to ensure determinism. + // - An unstable sort is fine, because region.columns() returns a set. + // - The sort order relies on Column's Ord implementation! + let mut region_columns: Vec<_> = region.columns().iter().cloned().collect(); + region_columns.sort_unstable(); + + let region_start = first_fit_region( + &mut column_allocations, + ®ion_columns, + region.row_count(), + 0, + None, + ) + .expect("We can always fit a region somewhere"); + + (region_start.into(), region) + }) + .collect(); + + // Return the column allocations for potential further processing. + (regions, column_allocations) +} + +/// Sorts the regions by advice area and then lays them out with the [`slot_in`] strategy. +pub fn slot_in_biggest_advice_first( + region_shapes: Vec, +) -> (Vec, CircuitAllocations) { + let mut sorted_regions: Vec<_> = region_shapes.into_iter().collect(); + let sort_key = |shape: &RegionShape| { + // Count the number of advice columns + let advice_cols = shape + .columns() + .iter() + .filter(|c| match c { + RegionColumn::Column(c) => matches!(c.column_type(), Any::Advice(_)), + _ => false, + }) + .count(); + // Sort by advice area (since this has the most contention). + advice_cols * shape.row_count() + }; + + // This used to incorrectly use `sort_unstable_by_key` with non-unique keys, which gave + // output that differed between 32-bit and 64-bit platforms, and potentially between Rust + // versions. + // We now use `sort_by_cached_key` with non-unique keys, and rely on `region_shapes` + // being sorted by region index (which we also rely on below to return `RegionStart`s + // in the correct order). + #[cfg(not(feature = "floor-planner-v1-legacy-pdqsort"))] + sorted_regions.sort_by_cached_key(sort_key); + + // To preserve compatibility, when the "floor-planner-v1-legacy-pdqsort" feature is enabled, + // we use a copy of the pdqsort implementation from the Rust 1.56.1 standard library, fixed + // to its behaviour on 64-bit platforms. + // https://github.com/rust-lang/rust/blob/1.56.1/library/core/src/slice/mod.rs#L2365-L2402 + #[cfg(feature = "floor-planner-v1-legacy-pdqsort")] + halo2_legacy_pdqsort::sort::quicksort(&mut sorted_regions, |a, b| sort_key(a).lt(&sort_key(b))); + + sorted_regions.reverse(); + + // Lay out the sorted regions. + let (mut regions, column_allocations) = slot_in(sorted_regions); + + // Un-sort the regions so they match the original indexing. + regions.sort_unstable_by_key(|(_, region)| region.region_index().0); + let regions = regions.into_iter().map(|(start, _)| start).collect(); + + (regions, column_allocations) +} + #[test] fn test_slot_in() { + use crate::circuit::floor_planner::v1::strategy::slot_in; use crate::circuit::layouter::RegionShape; - use halo2_common::circuit::floor_planner::v1::strategy::slot_in; - use halo2_common::plonk::circuit::Column; + use crate::plonk::Column; use halo2_middleware::circuit::Any; let regions = vec![ diff --git a/halo2_common/src/circuit/layouter.rs b/halo2_frontend/src/circuit/layouter.rs similarity index 99% rename from halo2_common/src/circuit/layouter.rs rename to halo2_frontend/src/circuit/layouter.rs index 85d94bf74e..1ceda1ae30 100644 --- a/halo2_common/src/circuit/layouter.rs +++ b/halo2_frontend/src/circuit/layouter.rs @@ -9,7 +9,7 @@ use halo2_middleware::ff::Field; pub use super::table_layouter::TableLayouter; use super::{Cell, RegionIndex, Value}; use crate::plonk::Assigned; -use crate::plonk::{circuit::Column, Error, Selector}; +use crate::plonk::{Column, Error, Selector}; use halo2_middleware::circuit::{Advice, Any, Fixed, Instance}; /// Intermediate trait requirements for [`RegionLayouter`] when thread-safe regions are enabled. diff --git a/halo2_frontend/src/circuit/table_layouter.rs b/halo2_frontend/src/circuit/table_layouter.rs index f1c29c5214..5f6b9560b6 100644 --- a/halo2_frontend/src/circuit/table_layouter.rs +++ b/halo2_frontend/src/circuit/table_layouter.rs @@ -1,13 +1,164 @@ +//! Implementations of common table layouters. + +use std::{ + collections::HashMap, + fmt::{self, Debug}, +}; + +use super::Value; +use crate::plonk::{Assigned, Assignment, Error, TableColumn, TableError}; +use halo2_middleware::ff::Field; + +/// Helper trait for implementing a custom [`Layouter`]. +/// +/// This trait is used for implementing table assignments. +/// +/// [`Layouter`]: super::Layouter +pub trait TableLayouter: std::fmt::Debug { + /// Assigns a fixed value to a table cell. + /// + /// Returns an error if the table cell has already been assigned to. + fn assign_cell<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + column: TableColumn, + offset: usize, + to: &'v mut (dyn FnMut() -> Value> + 'v), + ) -> Result<(), Error>; +} + +/// The default value to fill a table column with. +/// +/// - The outer `Option` tracks whether the value in row 0 of the table column has been +/// assigned yet. This will always be `Some` once a valid table has been completely +/// assigned. +/// - The inner `Value` tracks whether the underlying `Assignment` is evaluating +/// witnesses or not. +type DefaultTableValue = Option>>; + +/// A table layouter that can be used to assign values to a table. +pub struct SimpleTableLayouter<'r, 'a, F: Field, CS: Assignment + 'a> { + cs: &'a mut CS, + used_columns: &'r [TableColumn], + /// maps from a fixed column to a pair (default value, vector saying which rows are assigned) + pub default_and_assigned: HashMap, Vec)>, +} + +impl<'r, 'a, F: Field, CS: Assignment + 'a> fmt::Debug for SimpleTableLayouter<'r, 'a, F, CS> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SimpleTableLayouter") + .field("used_columns", &self.used_columns) + .field("default_and_assigned", &self.default_and_assigned) + .finish() + } +} + +impl<'r, 'a, F: Field, CS: Assignment + 'a> SimpleTableLayouter<'r, 'a, F, CS> { + /// Returns a new SimpleTableLayouter + pub fn new(cs: &'a mut CS, used_columns: &'r [TableColumn]) -> Self { + SimpleTableLayouter { + cs, + used_columns, + default_and_assigned: HashMap::default(), + } + } +} + +impl<'r, 'a, F: Field, CS: Assignment + 'a> TableLayouter + for SimpleTableLayouter<'r, 'a, F, CS> +{ + fn assign_cell<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + column: TableColumn, + offset: usize, + to: &'v mut (dyn FnMut() -> Value> + 'v), + ) -> Result<(), Error> { + if self.used_columns.contains(&column) { + return Err(Error::TableError(TableError::UsedColumn(column))); + } + + let entry = self.default_and_assigned.entry(column).or_default(); + + let mut value = Value::unknown(); + self.cs.assign_fixed( + annotation, + column.inner(), + offset, // tables are always assigned starting at row 0 + || { + let res = to(); + value = res; + res + }, + )?; + + match (entry.0.is_none(), offset) { + // Use the value at offset 0 as the default value for this table column. + (true, 0) => entry.0 = Some(value), + // Since there is already an existing default value for this table column, + // the caller should not be attempting to assign another value at offset 0. + (false, 0) => { + return Err(Error::TableError(TableError::OverwriteDefault( + column, + format!("{:?}", entry.0.unwrap()), + format!("{value:?}"), + ))) + } + _ => (), + } + if entry.1.len() <= offset { + entry.1.resize(offset + 1, false); + } + entry.1[offset] = true; + + Ok(()) + } +} + +pub(crate) fn compute_table_lengths( + default_and_assigned: &HashMap, Vec)>, +) -> Result { + let column_lengths: Result, Error> = default_and_assigned + .iter() + .map(|(col, (default_value, assigned))| { + if default_value.is_none() || assigned.is_empty() { + return Err(Error::TableError(TableError::ColumnNotAssigned(*col))); + } + if assigned.iter().all(|b| *b) { + // All values in the column have been assigned + Ok((col, assigned.len())) + } else { + Err(Error::TableError(TableError::ColumnNotAssigned(*col))) + } + }) + .collect(); + let column_lengths = column_lengths?; + column_lengths + .into_iter() + .try_fold((None, 0), |acc, (col, col_len)| { + if acc.1 == 0 || acc.1 == col_len { + Ok((Some(*col), col_len)) + } else { + let mut cols = [(*col, col_len), (acc.0.unwrap(), acc.1)]; + cols.sort(); + Err(Error::TableError(TableError::UnevenColumnLengths( + cols[0], cols[1], + ))) + } + }) + .map(|col_len| col_len.1) +} + #[cfg(test)] mod tests { use halo2curves::pasta::Fp; + use crate::circuit::Value; + use crate::plonk::{Circuit, ConstraintSystem, Error, TableColumn}; use crate::{ circuit::{Layouter, SimpleFloorPlanner}, dev::MockProver, }; - use halo2_common::circuit::Value; - use halo2_common::plonk::{Circuit, ConstraintSystem, Error, TableColumn}; use halo2_middleware::poly::Rotation; #[test] diff --git a/halo2_common/src/circuit/value.rs b/halo2_frontend/src/circuit/value.rs similarity index 98% rename from halo2_common/src/circuit/value.rs rename to halo2_frontend/src/circuit/value.rs index c35e9dc4ad..e0426f6bfa 100644 --- a/halo2_common/src/circuit/value.rs +++ b/halo2_frontend/src/circuit/value.rs @@ -3,8 +3,7 @@ use std::ops::{Add, Mul, Neg, Sub}; use group::ff::Field; -use crate::plonk::Assigned; -use crate::plonk::Error; +use crate::plonk::{Assigned, Error}; /// A value that might exist within a circuit. /// @@ -35,7 +34,7 @@ impl Value { /// # Examples /// /// ``` - /// use halo2_common::circuit::Value; + /// use halo2_frontend::circuit::Value; /// /// let v = Value::known(37); /// ``` @@ -641,8 +640,8 @@ impl Value { /// If you have a `Value`, convert it to `Value>` first: /// ``` /// # use halo2curves::pasta::pallas::Base as F; - /// use halo2_common::circuit::Value; - /// use halo2_common::plonk::Assigned; + /// use halo2_frontend::circuit::Value; + /// use halo2_frontend::plonk::Assigned; /// /// let v = Value::known(F::from(2)); /// let v: Value> = v.into(); diff --git a/halo2_frontend/src/dev.rs b/halo2_frontend/src/dev.rs index 03c9fd885f..c83a3b9c91 100644 --- a/halo2_frontend/src/dev.rs +++ b/halo2_frontend/src/dev.rs @@ -6,24 +6,21 @@ use std::iter; use std::ops::{Add, Mul, Neg, Range}; use blake2b_simd::blake2b; -use halo2_middleware::ff::Field; -use halo2_middleware::ff::FromUniformBytes; -use halo2_common::{ +use crate::{ circuit, plonk::{ - circuit::{Challenge, Column}, permutation, sealed::{self, SealedPhase}, - Assigned, Assignment, Circuit, ConstraintSystem, Error, Expression, FirstPhase, - FloorPlanner, Phase, Selector, + Assigned, Assignment, Challenge, Circuit, Column, ConstraintSystem, Error, Expression, + FirstPhase, FloorPlanner, Phase, Selector, }, }; -use halo2_middleware::circuit::{Advice, Any, ColumnMid, Fixed, Instance}; - use halo2_common::multicore::{ IntoParallelIterator, IntoParallelRefIterator, ParallelIterator, ParallelSliceMut, }; +use halo2_middleware::circuit::{Advice, Any, ColumnMid, Fixed, Instance}; +use halo2_middleware::ff::{Field, FromUniformBytes}; pub mod metadata; use metadata::Column as ColumnMetadata; @@ -215,8 +212,6 @@ impl Mul for Value { /// use halo2_frontend::{ /// circuit::{Layouter, SimpleFloorPlanner, Value}, /// dev::{FailureLocation, MockProver, VerifyFailure}, -/// }; -/// use halo2_common::{ /// plonk::{circuit::Column, Circuit, ConstraintSystem, Error, Selector}, /// }; /// use halo2_middleware::circuit::{Advice, Any}; @@ -576,7 +571,7 @@ impl Assignment for MockProver { left_row: usize, right_column: Column, right_row: usize, - ) -> Result<(), halo2_common::plonk::Error> { + ) -> Result<(), crate::plonk::Error> { if !self.in_phase(FirstPhase) { return Ok(()); } @@ -744,7 +739,8 @@ impl + Ord> MockProver { )?; } - let (cs, selector_polys) = prover.cs.compress_selectors(prover.selectors.clone()); + let (cs, selectors_to_fixed) = prover.cs.selectors_to_fixed_compressed(); + let selector_polys = selectors_to_fixed.convert(prover.selectors.clone()); prover.cs = cs; prover.fixed.extend(selector_polys.into_iter().map(|poly| { let mut v = vec![CellValue::Unassigned; n]; @@ -1290,8 +1286,8 @@ mod tests { use super::{FailureLocation, MockProver, VerifyFailure}; use crate::circuit::{Layouter, SimpleFloorPlanner, Value}; - use halo2_common::plonk::{ - circuit::Column, Circuit, ConstraintSystem, Error, Expression, Selector, TableColumn, + use crate::plonk::{ + Circuit, Column, ConstraintSystem, Error, Expression, Selector, TableColumn, }; use halo2_middleware::circuit::{Advice, Any, Fixed, Instance}; use halo2_middleware::poly::Rotation; diff --git a/halo2_frontend/src/dev/cost.rs b/halo2_frontend/src/dev/cost.rs index 793871dbfe..992bdf7e21 100644 --- a/halo2_frontend/src/dev/cost.rs +++ b/halo2_frontend/src/dev/cost.rs @@ -12,11 +12,11 @@ use group::prime::PrimeGroup; use halo2_middleware::ff::{Field, PrimeField}; use halo2_middleware::poly::Rotation; -use halo2_common::{ +use crate::{ circuit::{layouter::RegionColumn, Value}, plonk::{ - circuit::{Challenge, Column}, - Assigned, Assignment, Circuit, ConstraintSystem, Error, FloorPlanner, Selector, + Assigned, Assignment, Challenge, Circuit, Column, ConstraintSystem, Error, FloorPlanner, + Selector, }, }; use halo2_middleware::circuit::{Advice, Any, Fixed, Instance}; @@ -237,7 +237,7 @@ impl Assignment for Layout { l_row: usize, r_col: Column, r_row: usize, - ) -> Result<(), halo2_common::plonk::Error> { + ) -> Result<(), crate::plonk::Error> { self.equality.push((l_col, l_row, r_col, r_row)); Ok(()) } @@ -284,7 +284,7 @@ impl> CircuitCost= cs.minimum_rows()); diff --git a/halo2_frontend/src/dev/cost_model.rs b/halo2_frontend/src/dev/cost_model.rs index ec0bd437c8..86ce03800a 100644 --- a/halo2_frontend/src/dev/cost_model.rs +++ b/halo2_frontend/src/dev/cost_model.rs @@ -4,7 +4,7 @@ use std::collections::HashSet; use std::{iter, num::ParseIntError, str::FromStr}; -use halo2_common::plonk::circuit::Circuit; +use crate::plonk::Circuit; use halo2_middleware::ff::{Field, FromUniformBytes}; use serde::Deserialize; use serde_derive::Serialize; diff --git a/halo2_frontend/src/dev/failure.rs b/halo2_frontend/src/dev/failure.rs index 7ca752c6b1..381792827b 100644 --- a/halo2_frontend/src/dev/failure.rs +++ b/halo2_frontend/src/dev/failure.rs @@ -12,7 +12,10 @@ use super::{ }; use crate::dev::metadata::Constraint; use crate::dev::{Instance, Value}; -use halo2_common::plonk::{circuit::Column, ConstraintSystem, Expression, Gate}; +use crate::plonk::{ + circuit::expression::{Column, Expression}, + ConstraintSystem, Gate, +}; use halo2_middleware::circuit::Any; mod emitter; diff --git a/halo2_frontend/src/dev/failure/emitter.rs b/halo2_frontend/src/dev/failure/emitter.rs index db228ba8eb..6768925f10 100644 --- a/halo2_frontend/src/dev/failure/emitter.rs +++ b/halo2_frontend/src/dev/failure/emitter.rs @@ -5,7 +5,7 @@ use group::ff::Field; use super::FailureLocation; use crate::dev::{metadata, util}; -use halo2_common::plonk::Expression; +use crate::plonk::circuit::expression::Expression; use halo2_middleware::circuit::{Advice, Any}; fn padded(p: char, width: usize, text: &str) -> String { diff --git a/halo2_frontend/src/dev/gates.rs b/halo2_frontend/src/dev/gates.rs index 732ec7eeb3..9c911b7ed1 100644 --- a/halo2_frontend/src/dev/gates.rs +++ b/halo2_frontend/src/dev/gates.rs @@ -6,7 +6,7 @@ use std::{ use halo2_middleware::ff::PrimeField; use crate::dev::util; -use halo2_common::plonk::{sealed::SealedPhase, Circuit, ConstraintSystem, FirstPhase}; +use crate::plonk::{sealed::SealedPhase, Circuit, ConstraintSystem, FirstPhase}; #[derive(Debug)] struct Constraint { @@ -31,8 +31,6 @@ struct Gate { /// use halo2_frontend::{ /// circuit::{Layouter, SimpleFloorPlanner}, /// dev::CircuitGates, -/// }; -/// use halo2_common::{ /// plonk::{Circuit, ConstraintSystem, Error}, /// }; /// use halo2curves::pasta::pallas; diff --git a/halo2_frontend/src/dev/graph.rs b/halo2_frontend/src/dev/graph.rs index 538c76dc04..6ab2e779c7 100644 --- a/halo2_frontend/src/dev/graph.rs +++ b/halo2_frontend/src/dev/graph.rs @@ -1,6 +1,6 @@ -use halo2_common::plonk::{ - circuit::{Circuit, Column}, - Assigned, Assignment, Challenge, ConstraintSystem, Error, FloorPlanner, Selector, +use crate::plonk::{ + Assigned, Assignment, Challenge, Circuit, Column, ConstraintSystem, Error, FloorPlanner, + Selector, }; use halo2_middleware::circuit::{Advice, Any, Fixed, Instance}; use halo2_middleware::ff::Field; @@ -153,7 +153,7 @@ impl Assignment for Graph { _: usize, _: Column, _: usize, - ) -> Result<(), halo2_common::plonk::Error> { + ) -> Result<(), crate::plonk::Error> { // Do nothing; we don't care about permutations in this context. Ok(()) } diff --git a/halo2_frontend/src/dev/graph/layout.rs b/halo2_frontend/src/dev/graph/layout.rs index 829479ad0d..887bd3bd87 100644 --- a/halo2_frontend/src/dev/graph/layout.rs +++ b/halo2_frontend/src/dev/graph/layout.rs @@ -6,8 +6,8 @@ use plotters::{ use std::collections::HashSet; use std::ops::Range; +use crate::plonk::{Circuit, Column, ConstraintSystem, FloorPlanner}; use crate::{circuit::layouter::RegionColumn, dev::cost::Layout}; -use halo2_common::plonk::{circuit::Column, Circuit, ConstraintSystem, FloorPlanner}; use halo2_middleware::circuit::Any; /// Graphical renderer for circuit layouts. @@ -104,7 +104,8 @@ impl CircuitLayout { cs.constants.clone(), ) .unwrap(); - let (cs, selector_polys) = cs.compress_selectors(layout.selectors); + let (cs, selectors_to_fixed) = cs.selectors_to_fixed_compressed(); + let selector_polys = selectors_to_fixed.convert::(layout.selectors); let non_selector_fixed_columns = cs.num_fixed_columns - selector_polys.len(); // Figure out what order to render the columns in. diff --git a/halo2_frontend/src/dev/metadata.rs b/halo2_frontend/src/dev/metadata.rs index d3cf96abb7..cafbfa76f5 100644 --- a/halo2_frontend/src/dev/metadata.rs +++ b/halo2_frontend/src/dev/metadata.rs @@ -1,7 +1,7 @@ //! Metadata about circuits. use super::metadata::Column as ColumnMetadata; -use halo2_common::plonk; +use crate::plonk; use halo2_middleware::circuit::Any; pub use halo2_middleware::metadata::Column; use std::{ diff --git a/halo2_frontend/src/dev/tfp.rs b/halo2_frontend/src/dev/tfp.rs index 6ad66d8fe3..2951cd068e 100644 --- a/halo2_frontend/src/dev/tfp.rs +++ b/halo2_frontend/src/dev/tfp.rs @@ -3,12 +3,12 @@ use std::{fmt, marker::PhantomData}; use halo2_middleware::ff::Field; use tracing::{debug, debug_span, span::EnteredSpan}; -use halo2_common::circuit::{ +use crate::circuit::{ layouter::{RegionLayouter, SyncDeps}, AssignedCell, Cell, Layouter, Region, Table, Value, }; -use halo2_common::plonk::{ - circuit::{Challenge, Column}, +use crate::plonk::{ + circuit::expression::{Challenge, Column}, Assigned, Assignment, Circuit, ConstraintSystem, Error, FloorPlanner, Selector, }; use halo2_middleware::circuit::{Advice, Any, Fixed, Instance}; @@ -32,8 +32,6 @@ use halo2_middleware::circuit::{Advice, Any, Fixed, Instance}; /// use halo2_frontend::{ /// circuit::{floor_planner, Layouter, Value}, /// dev::TracingFloorPlanner, -/// }; -/// use halo2_common::{ /// plonk::{Circuit, ConstraintSystem, Error}, /// }; /// diff --git a/halo2_frontend/src/dev/util.rs b/halo2_frontend/src/dev/util.rs index ac7017e833..8ed1b80be5 100644 --- a/halo2_frontend/src/dev/util.rs +++ b/halo2_frontend/src/dev/util.rs @@ -2,9 +2,7 @@ use group::ff::Field; use std::collections::BTreeMap; use super::{metadata, CellValue, InstanceValue, Value}; -use halo2_common::plonk::{ - circuit::Column, AdviceQuery, Expression, FixedQuery, Gate, InstanceQuery, VirtualCell, -}; +use crate::plonk::{AdviceQuery, Column, Expression, FixedQuery, Gate, InstanceQuery, VirtualCell}; use halo2_middleware::circuit::{Advice, Any, ColumnType}; use halo2_middleware::poly::Rotation; diff --git a/halo2_frontend/src/lib.rs b/halo2_frontend/src/lib.rs index 8fa079cfaa..01e8112210 100644 --- a/halo2_frontend/src/lib.rs +++ b/halo2_frontend/src/lib.rs @@ -1,2 +1,3 @@ pub mod circuit; pub mod dev; +pub mod plonk; diff --git a/halo2_frontend/src/plonk.rs b/halo2_frontend/src/plonk.rs new file mode 100644 index 0000000000..db37295807 --- /dev/null +++ b/halo2_frontend/src/plonk.rs @@ -0,0 +1,12 @@ +pub mod assigned; +pub mod circuit; +pub mod error; +pub mod keygen; +pub mod lookup; +pub mod permutation; +pub mod shuffle; + +pub use assigned::*; +pub use circuit::*; +pub use error::*; +pub use keygen::*; diff --git a/halo2_frontend/src/plonk/assigned.rs b/halo2_frontend/src/plonk/assigned.rs new file mode 100644 index 0000000000..44ec9887bd --- /dev/null +++ b/halo2_frontend/src/plonk/assigned.rs @@ -0,0 +1,664 @@ +use halo2_middleware::ff::Field; +use std::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; + +/// A value assigned to a cell within a circuit. +/// +/// Stored as a fraction, so the backend can use batch inversion. +/// +/// A denominator of zero maps to an assigned value of zero. +#[derive(Clone, Copy, Debug)] +pub enum Assigned { + /// The field element zero. + Zero, + /// A value that does not require inversion to evaluate. + Trivial(F), + /// A value stored as a fraction to enable batch inversion. + Rational(F, F), +} + +impl From<&Assigned> for Assigned { + fn from(val: &Assigned) -> Self { + *val + } +} + +impl From<&F> for Assigned { + fn from(numerator: &F) -> Self { + Assigned::Trivial(*numerator) + } +} + +impl From for Assigned { + fn from(numerator: F) -> Self { + Assigned::Trivial(numerator) + } +} + +impl From<(F, F)> for Assigned { + fn from((numerator, denominator): (F, F)) -> Self { + Assigned::Rational(numerator, denominator) + } +} + +impl PartialEq for Assigned { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + // At least one side is directly zero. + (Self::Zero, Self::Zero) => true, + (Self::Zero, x) | (x, Self::Zero) => x.is_zero_vartime(), + + // One side is x/0 which maps to zero. + (Self::Rational(_, denominator), x) | (x, Self::Rational(_, denominator)) + if denominator.is_zero_vartime() => + { + x.is_zero_vartime() + } + + // Okay, we need to do some actual math... + (Self::Trivial(lhs), Self::Trivial(rhs)) => lhs == rhs, + (Self::Trivial(x), Self::Rational(numerator, denominator)) + | (Self::Rational(numerator, denominator), Self::Trivial(x)) => { + &(*x * denominator) == numerator + } + ( + Self::Rational(lhs_numerator, lhs_denominator), + Self::Rational(rhs_numerator, rhs_denominator), + ) => *lhs_numerator * rhs_denominator == *lhs_denominator * rhs_numerator, + } + } +} + +impl Eq for Assigned {} + +impl Neg for Assigned { + type Output = Assigned; + fn neg(self) -> Self::Output { + match self { + Self::Zero => Self::Zero, + Self::Trivial(numerator) => Self::Trivial(-numerator), + Self::Rational(numerator, denominator) => Self::Rational(-numerator, denominator), + } + } +} + +impl Neg for &Assigned { + type Output = Assigned; + fn neg(self) -> Self::Output { + -*self + } +} + +impl Add for Assigned { + type Output = Assigned; + fn add(self, rhs: Assigned) -> Assigned { + match (self, rhs) { + // One side is directly zero. + (Self::Zero, _) => rhs, + (_, Self::Zero) => self, + + // One side is x/0 which maps to zero. + (Self::Rational(_, denominator), other) | (other, Self::Rational(_, denominator)) + if denominator.is_zero_vartime() => + { + other + } + + // Okay, we need to do some actual math... + (Self::Trivial(lhs), Self::Trivial(rhs)) => Self::Trivial(lhs + rhs), + (Self::Rational(numerator, denominator), Self::Trivial(other)) + | (Self::Trivial(other), Self::Rational(numerator, denominator)) => { + Self::Rational(numerator + denominator * other, denominator) + } + ( + Self::Rational(lhs_numerator, lhs_denominator), + Self::Rational(rhs_numerator, rhs_denominator), + ) => Self::Rational( + lhs_numerator * rhs_denominator + lhs_denominator * rhs_numerator, + lhs_denominator * rhs_denominator, + ), + } + } +} + +impl Add for Assigned { + type Output = Assigned; + fn add(self, rhs: F) -> Assigned { + self + Self::Trivial(rhs) + } +} + +impl Add for &Assigned { + type Output = Assigned; + fn add(self, rhs: F) -> Assigned { + *self + rhs + } +} + +impl Add<&Assigned> for Assigned { + type Output = Assigned; + fn add(self, rhs: &Self) -> Assigned { + self + *rhs + } +} + +impl Add> for &Assigned { + type Output = Assigned; + fn add(self, rhs: Assigned) -> Assigned { + *self + rhs + } +} + +impl Add<&Assigned> for &Assigned { + type Output = Assigned; + fn add(self, rhs: &Assigned) -> Assigned { + *self + *rhs + } +} + +impl AddAssign for Assigned { + fn add_assign(&mut self, rhs: Self) { + *self = *self + rhs; + } +} + +impl AddAssign<&Assigned> for Assigned { + fn add_assign(&mut self, rhs: &Self) { + *self = *self + rhs; + } +} + +impl Sub for Assigned { + type Output = Assigned; + fn sub(self, rhs: Assigned) -> Assigned { + self + (-rhs) + } +} + +impl Sub for Assigned { + type Output = Assigned; + fn sub(self, rhs: F) -> Assigned { + self + (-rhs) + } +} + +impl Sub for &Assigned { + type Output = Assigned; + fn sub(self, rhs: F) -> Assigned { + *self - rhs + } +} + +impl Sub<&Assigned> for Assigned { + type Output = Assigned; + fn sub(self, rhs: &Self) -> Assigned { + self - *rhs + } +} + +impl Sub> for &Assigned { + type Output = Assigned; + fn sub(self, rhs: Assigned) -> Assigned { + *self - rhs + } +} + +impl Sub<&Assigned> for &Assigned { + type Output = Assigned; + fn sub(self, rhs: &Assigned) -> Assigned { + *self - *rhs + } +} + +impl SubAssign for Assigned { + fn sub_assign(&mut self, rhs: Self) { + *self = *self - rhs; + } +} + +impl SubAssign<&Assigned> for Assigned { + fn sub_assign(&mut self, rhs: &Self) { + *self = *self - rhs; + } +} + +impl Mul for Assigned { + type Output = Assigned; + fn mul(self, rhs: Assigned) -> Assigned { + match (self, rhs) { + (Self::Zero, _) | (_, Self::Zero) => Self::Zero, + (Self::Trivial(lhs), Self::Trivial(rhs)) => Self::Trivial(lhs * rhs), + (Self::Rational(numerator, denominator), Self::Trivial(other)) + | (Self::Trivial(other), Self::Rational(numerator, denominator)) => { + Self::Rational(numerator * other, denominator) + } + ( + Self::Rational(lhs_numerator, lhs_denominator), + Self::Rational(rhs_numerator, rhs_denominator), + ) => Self::Rational( + lhs_numerator * rhs_numerator, + lhs_denominator * rhs_denominator, + ), + } + } +} + +impl Mul for Assigned { + type Output = Assigned; + fn mul(self, rhs: F) -> Assigned { + self * Self::Trivial(rhs) + } +} + +impl Mul for &Assigned { + type Output = Assigned; + fn mul(self, rhs: F) -> Assigned { + *self * rhs + } +} + +impl Mul<&Assigned> for Assigned { + type Output = Assigned; + fn mul(self, rhs: &Assigned) -> Assigned { + self * *rhs + } +} + +impl MulAssign for Assigned { + fn mul_assign(&mut self, rhs: Self) { + *self = *self * rhs; + } +} + +impl MulAssign<&Assigned> for Assigned { + fn mul_assign(&mut self, rhs: &Self) { + *self = *self * rhs; + } +} + +impl Assigned { + /// Returns the numerator. + pub fn numerator(&self) -> F { + match self { + Self::Zero => F::ZERO, + Self::Trivial(x) => *x, + Self::Rational(numerator, _) => *numerator, + } + } + + /// Returns the denominator, if non-trivial. + pub fn denominator(&self) -> Option { + match self { + Self::Zero => None, + Self::Trivial(_) => None, + Self::Rational(_, denominator) => Some(*denominator), + } + } + + /// Returns true iff this element is zero. + pub fn is_zero_vartime(&self) -> bool { + match self { + Self::Zero => true, + Self::Trivial(x) => x.is_zero_vartime(), + // Assigned maps x/0 -> 0. + Self::Rational(numerator, denominator) => { + numerator.is_zero_vartime() || denominator.is_zero_vartime() + } + } + } + + /// Doubles this element. + #[must_use] + pub fn double(&self) -> Self { + match self { + Self::Zero => Self::Zero, + Self::Trivial(x) => Self::Trivial(x.double()), + Self::Rational(numerator, denominator) => { + Self::Rational(numerator.double(), *denominator) + } + } + } + + /// Squares this element. + #[must_use] + pub fn square(&self) -> Self { + match self { + Self::Zero => Self::Zero, + Self::Trivial(x) => Self::Trivial(x.square()), + Self::Rational(numerator, denominator) => { + Self::Rational(numerator.square(), denominator.square()) + } + } + } + + /// Cubes this element. + #[must_use] + pub fn cube(&self) -> Self { + self.square() * self + } + + /// Inverts this assigned value (taking the inverse of zero to be zero). + pub fn invert(&self) -> Self { + match self { + Self::Zero => Self::Zero, + Self::Trivial(x) => Self::Rational(F::ONE, *x), + Self::Rational(numerator, denominator) => Self::Rational(*denominator, *numerator), + } + } + + /// Evaluates this assigned value directly, performing an unbatched inversion if + /// necessary. + /// + /// If the denominator is zero, this returns zero. + pub fn evaluate(self) -> F { + match self { + Self::Zero => F::ZERO, + Self::Trivial(x) => x, + Self::Rational(numerator, denominator) => { + if denominator == F::ONE { + numerator + } else { + numerator * denominator.invert().unwrap_or(F::ZERO) + } + } + } + } +} + +#[cfg(test)] +mod proptests { + use std::{ + cmp, + ops::{Add, Mul, Neg, Sub}, + }; + + use group::ff::Field; + use halo2curves::pasta::Fp; + use proptest::{collection::vec, prelude::*, sample::select}; + + use super::Assigned; + + trait UnaryOperand: Neg { + fn double(&self) -> Self; + fn square(&self) -> Self; + fn cube(&self) -> Self; + fn inv0(&self) -> Self; + } + + impl UnaryOperand for F { + fn double(&self) -> Self { + self.double() + } + + fn square(&self) -> Self { + self.square() + } + + fn cube(&self) -> Self { + self.cube() + } + + fn inv0(&self) -> Self { + self.invert().unwrap_or(F::ZERO) + } + } + + impl UnaryOperand for Assigned { + fn double(&self) -> Self { + self.double() + } + + fn square(&self) -> Self { + self.square() + } + + fn cube(&self) -> Self { + self.cube() + } + + fn inv0(&self) -> Self { + self.invert() + } + } + + #[derive(Clone, Debug)] + enum UnaryOperator { + Neg, + Double, + Square, + Cube, + Inv0, + } + + const UNARY_OPERATORS: &[UnaryOperator] = &[ + UnaryOperator::Neg, + UnaryOperator::Double, + UnaryOperator::Square, + UnaryOperator::Cube, + UnaryOperator::Inv0, + ]; + + impl UnaryOperator { + fn apply(&self, a: F) -> F { + match self { + Self::Neg => -a, + Self::Double => a.double(), + Self::Square => a.square(), + Self::Cube => a.cube(), + Self::Inv0 => a.inv0(), + } + } + } + + trait BinaryOperand: Sized + Add + Sub + Mul {} + impl BinaryOperand for F {} + impl BinaryOperand for Assigned {} + + #[derive(Clone, Debug)] + enum BinaryOperator { + Add, + Sub, + Mul, + } + + const BINARY_OPERATORS: &[BinaryOperator] = &[ + BinaryOperator::Add, + BinaryOperator::Sub, + BinaryOperator::Mul, + ]; + + impl BinaryOperator { + fn apply(&self, a: F, b: F) -> F { + match self { + Self::Add => a + b, + Self::Sub => a - b, + Self::Mul => a * b, + } + } + } + + #[derive(Clone, Debug)] + enum Operator { + Unary(UnaryOperator), + Binary(BinaryOperator), + } + + prop_compose! { + /// Use narrow that can be easily reduced. + fn arb_element()(val in any::()) -> Fp { + Fp::from(val) + } + } + + prop_compose! { + fn arb_trivial()(element in arb_element()) -> Assigned { + Assigned::Trivial(element) + } + } + + prop_compose! { + /// Generates half of the denominators as zero to represent a deferred inversion. + fn arb_rational()( + numerator in arb_element(), + denominator in prop_oneof![ + 1 => Just(Fp::zero()), + 2 => arb_element(), + ], + ) -> Assigned { + Assigned::Rational(numerator, denominator) + } + } + + prop_compose! { + fn arb_operators(num_unary: usize, num_binary: usize)( + unary in vec(select(UNARY_OPERATORS), num_unary), + binary in vec(select(BINARY_OPERATORS), num_binary), + ) -> Vec { + unary.into_iter() + .map(Operator::Unary) + .chain(binary.into_iter().map(Operator::Binary)) + .collect() + } + } + + prop_compose! { + fn arb_testcase()( + num_unary in 0usize..5, + num_binary in 0usize..5, + )( + values in vec( + prop_oneof![ + 1 => Just(Assigned::Zero), + 2 => arb_trivial(), + 2 => arb_rational(), + ], + // Ensure that: + // - we have at least one value to apply unary operators to. + // - we can apply every binary operator pairwise sequentially. + cmp::max(usize::from(num_unary > 0), num_binary + 1)), + operations in arb_operators(num_unary, num_binary).prop_shuffle(), + ) -> (Vec>, Vec) { + (values, operations) + } + } + + proptest! { + #[test] + fn operation_commutativity((values, operations) in arb_testcase()) { + // Evaluate the values at the start. + let elements: Vec<_> = values.iter().cloned().map(|v| v.evaluate()).collect(); + + // Apply the operations to both the deferred and evaluated values. + fn evaluate( + items: Vec, + operators: &[Operator], + ) -> F { + let mut ops = operators.iter(); + + // Process all binary operators. We are guaranteed to have exactly as many + // binary operators as we need calls to the reduction closure. + let mut res = items.into_iter().reduce(|mut a, b| loop { + match ops.next() { + Some(Operator::Unary(op)) => a = op.apply(a), + Some(Operator::Binary(op)) => break op.apply(a, b), + None => unreachable!(), + } + }).unwrap(); + + // Process any unary operators that weren't handled in the reduce() call + // above (either if we only had one item, or there were unary operators + // after the last binary operator). We are guaranteed to have no binary + // operators remaining at this point. + loop { + match ops.next() { + Some(Operator::Unary(op)) => res = op.apply(res), + Some(Operator::Binary(_)) => unreachable!(), + None => break res, + } + } + } + let deferred_result = evaluate(values, &operations); + let evaluated_result = evaluate(elements, &operations); + + // The two should be equal, i.e. deferred inversion should commute with the + // list of operations. + assert_eq!(deferred_result.evaluate(), evaluated_result); + } + } +} + +#[cfg(test)] +mod tests { + use halo2curves::pasta::Fp; + + use super::Assigned; + // We use (numerator, denominator) in the comments below to denote a rational. + #[test] + fn add_trivial_to_inv0_rational() { + // a = 2 + // b = (1,0) + let a = Assigned::Trivial(Fp::from(2)); + let b = Assigned::Rational(Fp::one(), Fp::zero()); + + // 2 + (1,0) = 2 + 0 = 2 + // This fails if addition is implemented using normal rules for rationals. + assert_eq!((a + b).evaluate(), a.evaluate()); + assert_eq!((b + a).evaluate(), a.evaluate()); + } + + #[test] + fn add_rational_to_inv0_rational() { + // a = (1,2) + // b = (1,0) + let a = Assigned::Rational(Fp::one(), Fp::from(2)); + let b = Assigned::Rational(Fp::one(), Fp::zero()); + + // (1,2) + (1,0) = (1,2) + 0 = (1,2) + // This fails if addition is implemented using normal rules for rationals. + assert_eq!((a + b).evaluate(), a.evaluate()); + assert_eq!((b + a).evaluate(), a.evaluate()); + } + + #[test] + fn sub_trivial_from_inv0_rational() { + // a = 2 + // b = (1,0) + let a = Assigned::Trivial(Fp::from(2)); + let b = Assigned::Rational(Fp::one(), Fp::zero()); + + // (1,0) - 2 = 0 - 2 = -2 + // This fails if subtraction is implemented using normal rules for rationals. + assert_eq!((b - a).evaluate(), (-a).evaluate()); + + // 2 - (1,0) = 2 - 0 = 2 + assert_eq!((a - b).evaluate(), a.evaluate()); + } + + #[test] + fn sub_rational_from_inv0_rational() { + // a = (1,2) + // b = (1,0) + let a = Assigned::Rational(Fp::one(), Fp::from(2)); + let b = Assigned::Rational(Fp::one(), Fp::zero()); + + // (1,0) - (1,2) = 0 - (1,2) = -(1,2) + // This fails if subtraction is implemented using normal rules for rationals. + assert_eq!((b - a).evaluate(), (-a).evaluate()); + + // (1,2) - (1,0) = (1,2) - 0 = (1,2) + assert_eq!((a - b).evaluate(), a.evaluate()); + } + + #[test] + fn mul_rational_by_inv0_rational() { + // a = (1,2) + // b = (1,0) + let a = Assigned::Rational(Fp::one(), Fp::from(2)); + let b = Assigned::Rational(Fp::one(), Fp::zero()); + + // (1,2) * (1,0) = (1,2) * 0 = 0 + assert_eq!((a * b).evaluate(), Fp::zero()); + + // (1,0) * (1,2) = 0 * (1,2) = 0 + assert_eq!((b * a).evaluate(), Fp::zero()); + } +} diff --git a/halo2_frontend/src/plonk/circuit.rs b/halo2_frontend/src/plonk/circuit.rs new file mode 100644 index 0000000000..259a1f511a --- /dev/null +++ b/halo2_frontend/src/plonk/circuit.rs @@ -0,0 +1,200 @@ +use crate::circuit::{layouter::SyncDeps, Layouter, Value}; +use crate::plonk::{Assigned, Error}; +use halo2_middleware::circuit::{Advice, Any, Fixed, Instance}; +use halo2_middleware::ff::Field; + +pub mod compress_selectors; +pub mod constraint_system; +pub mod expression; + +pub use constraint_system::*; +pub use expression::*; + +// TODO: Bring ColumnType, Advice, Fixed, Instance and Any here, where Advice has a sealed phase +// Keep a slim copy of those types in middleware. +// https://github.com/privacy-scaling-explorations/halo2/issues/295 + +/// This trait allows a [`Circuit`] to direct some backend to assign a witness +/// for a constraint system. +pub trait Assignment { + /// Creates a new region and enters into it. + /// + /// Panics if we are currently in a region (if `exit_region` was not called). + /// + /// Not intended for downstream consumption; use [`Layouter::assign_region`] instead. + /// + /// [`Layouter::assign_region`]: crate::circuit::Layouter#method.assign_region + fn enter_region(&mut self, name_fn: N) + where + NR: Into, + N: FnOnce() -> NR; + + /// Allows the developer to include an annotation for an specific column within a `Region`. + /// + /// This is usually useful for debugging circuit failures. + fn annotate_column(&mut self, annotation: A, column: Column) + where + A: FnOnce() -> AR, + AR: Into; + + /// Exits the current region. + /// + /// Panics if we are not currently in a region (if `enter_region` was not called). + /// + /// Not intended for downstream consumption; use [`Layouter::assign_region`] instead. + /// + /// [`Layouter::assign_region`]: crate::circuit::Layouter#method.assign_region + fn exit_region(&mut self); + + /// Enables a selector at the given row. + fn enable_selector( + &mut self, + annotation: A, + selector: &Selector, + row: usize, + ) -> Result<(), Error> + where + A: FnOnce() -> AR, + AR: Into; + + /// Queries the cell of an instance column at a particular absolute row. + /// + /// Returns the cell's value, if known. + fn query_instance(&self, column: Column, row: usize) -> Result, Error>; + + /// Assign an advice column value (witness) + fn assign_advice( + &mut self, + annotation: A, + column: Column, + row: usize, + to: V, + ) -> Result<(), Error> + where + V: FnOnce() -> Value, + VR: Into>, + A: FnOnce() -> AR, + AR: Into; + + /// Assign a fixed value + fn assign_fixed( + &mut self, + annotation: A, + column: Column, + row: usize, + to: V, + ) -> Result<(), Error> + where + V: FnOnce() -> Value, + VR: Into>, + A: FnOnce() -> AR, + AR: Into; + + /// Assign two cells to have the same value + fn copy( + &mut self, + left_column: Column, + left_row: usize, + right_column: Column, + right_row: usize, + ) -> Result<(), Error>; + + /// Fills a fixed `column` starting from the given `row` with value `to`. + fn fill_from_row( + &mut self, + column: Column, + row: usize, + to: Value>, + ) -> Result<(), Error>; + + /// Queries the value of the given challenge. + /// + /// Returns `Value::unknown()` if the current synthesis phase is before the challenge can be queried. + fn get_challenge(&self, challenge: Challenge) -> Value; + + /// Creates a new (sub)namespace and enters into it. + /// + /// Not intended for downstream consumption; use [`Layouter::namespace`] instead. + /// + /// [`Layouter::namespace`]: crate::circuit::Layouter#method.namespace + fn push_namespace(&mut self, name_fn: N) + where + NR: Into, + N: FnOnce() -> NR; + + /// Exits out of the existing namespace. + /// + /// Not intended for downstream consumption; use [`Layouter::namespace`] instead. + /// + /// [`Layouter::namespace`]: crate::circuit::Layouter#method.namespace + fn pop_namespace(&mut self, gadget_name: Option); +} + +/// A floor planning strategy for a circuit. +/// +/// The floor planner is chip-agnostic and applies its strategy to the circuit it is used +/// within. +pub trait FloorPlanner { + /// Given the provided `cs`, synthesize the given circuit. + /// + /// `constants` is the list of fixed columns that the layouter may use to assign + /// global constant values. These columns will all have been equality-enabled. + /// + /// Internally, a floor planner will perform the following operations: + /// - Instantiate a [`Layouter`] for this floor planner. + /// - Perform any necessary setup or measurement tasks, which may involve one or more + /// calls to `Circuit::default().synthesize(config, &mut layouter)`. + /// - Call `circuit.synthesize(config, &mut layouter)` exactly once. + fn synthesize + SyncDeps, C: Circuit>( + cs: &mut CS, + circuit: &C, + config: C::Config, + constants: Vec>, + ) -> Result<(), Error>; +} + +/// This is a trait that circuits provide implementations for so that the +/// backend prover can ask the circuit to synthesize using some given +/// [`ConstraintSystem`] implementation. +pub trait Circuit { + /// This is a configuration object that stores things like columns. + type Config: Clone; + /// The floor planner used for this circuit. This is an associated type of the + /// `Circuit` trait because its behaviour is circuit-critical. + type FloorPlanner: FloorPlanner; + /// Optional circuit configuration parameters. Requires the `circuit-params` feature. + #[cfg(feature = "circuit-params")] + type Params: Default; + + /// Returns a copy of this circuit with no witness values (i.e. all witnesses set to + /// `None`). For most circuits, this will be equal to `Self::default()`. + fn without_witnesses(&self) -> Self; + + /// Returns a reference to the parameters that should be used to configure the circuit. + /// Requires the `circuit-params` feature. + #[cfg(feature = "circuit-params")] + fn params(&self) -> Self::Params { + Self::Params::default() + } + + /// The circuit is given an opportunity to describe the exact gate + /// arrangement, column arrangement, etc. Takes a runtime parameter. The default + /// implementation calls `configure` ignoring the `_params` argument in order to easily support + /// circuits that don't use configuration parameters. + #[cfg(feature = "circuit-params")] + fn configure_with_params( + meta: &mut ConstraintSystem, + _params: Self::Params, + ) -> Self::Config { + Self::configure(meta) + } + + /// The circuit is given an opportunity to describe the exact gate + /// arrangement, column arrangement, etc. + fn configure(meta: &mut ConstraintSystem) -> Self::Config; + + /// Given the provided `cs`, synthesize the circuit. The concrete type of + /// the caller will be different depending on the context, and they may or + /// may not expect to have a witness present. + fn synthesize(&self, config: Self::Config, layouter: impl Layouter) -> Result<(), Error>; +} diff --git a/halo2_common/src/plonk/circuit/compress_selectors.rs b/halo2_frontend/src/plonk/circuit/compress_selectors.rs similarity index 100% rename from halo2_common/src/plonk/circuit/compress_selectors.rs rename to halo2_frontend/src/plonk/circuit/compress_selectors.rs diff --git a/halo2_frontend/src/plonk/circuit/constraint_system.rs b/halo2_frontend/src/plonk/circuit/constraint_system.rs new file mode 100644 index 0000000000..8b19b9a3a8 --- /dev/null +++ b/halo2_frontend/src/plonk/circuit/constraint_system.rs @@ -0,0 +1,1242 @@ +use super::compress_selectors; +use super::expression::sealed; +use crate::plonk::{ + lookup, permutation, shuffle, AdviceQuery, Challenge, Column, Expression, FirstPhase, + FixedQuery, InstanceQuery, Phase, Selector, TableColumn, +}; +use core::cmp::max; +use halo2_middleware::circuit::{Advice, Any, ConstraintSystemMid, Fixed, GateMid, Instance}; +use halo2_middleware::ff::Field; +use halo2_middleware::metadata; +use halo2_middleware::poly::Rotation; +use std::collections::HashMap; +use std::convert::TryFrom; +use std::fmt::Debug; + +/// Represents an index into a vector where each entry corresponds to a distinct +/// point that polynomials are queried at. +#[derive(Copy, Clone, Debug)] +pub(crate) struct PointIndex(pub usize); + +/// A "virtual cell" is a PLONK cell that has been queried at a particular relative offset +/// within a custom gate. +#[derive(Clone, Debug)] +pub struct VirtualCell { + pub column: Column, + pub rotation: Rotation, +} + +impl>> From<(Col, Rotation)> for VirtualCell { + fn from((column, rotation): (Col, Rotation)) -> Self { + VirtualCell { + column: column.into(), + rotation, + } + } +} + +/// An individual polynomial constraint. +/// +/// These are returned by the closures passed to `ConstraintSystem::create_gate`. +#[derive(Debug)] +pub struct Constraint { + name: String, + poly: Expression, +} + +impl From> for Constraint { + fn from(poly: Expression) -> Self { + Constraint { + name: "".to_string(), + poly, + } + } +} + +impl> From<(S, Expression)> for Constraint { + fn from((name, poly): (S, Expression)) -> Self { + Constraint { + name: name.as_ref().to_string(), + poly, + } + } +} + +impl From> for Vec> { + fn from(poly: Expression) -> Self { + vec![Constraint { + name: "".to_string(), + poly, + }] + } +} + +/// A set of polynomial constraints with a common selector. +/// +/// ``` +/// use halo2_middleware::poly::Rotation; +/// use halo2curves::pasta::Fp; +/// # use halo2_frontend::plonk::{Constraints, Expression, ConstraintSystem}; +/// +/// # let mut meta = ConstraintSystem::::default(); +/// let a = meta.advice_column(); +/// let b = meta.advice_column(); +/// let c = meta.advice_column(); +/// let s = meta.selector(); +/// +/// meta.create_gate("foo", |meta| { +/// let next = meta.query_advice(a, Rotation::next()); +/// let a = meta.query_advice(a, Rotation::cur()); +/// let b = meta.query_advice(b, Rotation::cur()); +/// let c = meta.query_advice(c, Rotation::cur()); +/// let s_ternary = meta.query_selector(s); +/// +/// let one_minus_a = Expression::Constant(Fp::one()) - a.clone(); +/// +/// Constraints::with_selector( +/// s_ternary, +/// std::array::IntoIter::new([ +/// ("a is boolean", a.clone() * one_minus_a.clone()), +/// ("next == a ? b : c", next - (a * b + one_minus_a * c)), +/// ]), +/// ) +/// }); +/// ``` +/// +/// Note that the use of `std::array::IntoIter::new` is only necessary if you need to +/// support Rust 1.51 or 1.52. If your minimum supported Rust version is 1.53 or greater, +/// you can pass an array directly. +#[derive(Debug)] +pub struct Constraints>, Iter: IntoIterator> { + selector: Expression, + constraints: Iter, +} + +impl>, Iter: IntoIterator> Constraints { + /// Constructs a set of constraints that are controlled by the given selector. + /// + /// Each constraint `c` in `iterator` will be converted into the constraint + /// `selector * c`. + pub fn with_selector(selector: Expression, constraints: Iter) -> Self { + Constraints { + selector, + constraints, + } + } +} + +fn apply_selector_to_constraint>>( + (selector, c): (Expression, C), +) -> Constraint { + let constraint: Constraint = c.into(); + Constraint { + name: constraint.name, + poly: selector * constraint.poly, + } +} + +type ApplySelectorToConstraint = fn((Expression, C)) -> Constraint; +type ConstraintsIterator = std::iter::Map< + std::iter::Zip>, I>, + ApplySelectorToConstraint, +>; + +impl>, Iter: IntoIterator> IntoIterator + for Constraints +{ + type Item = Constraint; + type IntoIter = ConstraintsIterator; + + fn into_iter(self) -> Self::IntoIter { + std::iter::repeat(self.selector) + .zip(self.constraints) + .map(apply_selector_to_constraint) + } +} + +/// Gate +#[derive(Clone, Debug)] +pub struct Gate { + pub(crate) name: String, + pub(crate) constraint_names: Vec, + pub(crate) polys: Vec>, + /// We track queried selectors separately from other cells, so that we can use them to + /// trigger debug checks on gates. + pub(crate) queried_selectors: Vec, + pub(crate) queried_cells: Vec, +} + +impl Gate { + /// Returns the gate name. + pub fn name(&self) -> &str { + self.name.as_str() + } + + /// Returns the name of the constraint at index `constraint_index`. + pub fn constraint_name(&self, constraint_index: usize) -> &str { + self.constraint_names[constraint_index].as_str() + } + + /// Returns constraints of this gate + pub fn polynomials(&self) -> &[Expression] { + &self.polys + } + + pub fn queried_selectors(&self) -> &[Selector] { + &self.queried_selectors + } + + pub fn queried_cells(&self) -> &[VirtualCell] { + &self.queried_cells + } +} + +impl From> for ConstraintSystemMid { + fn from(cs: ConstraintSystem) -> Self { + ConstraintSystemMid { + num_fixed_columns: cs.num_fixed_columns, + num_advice_columns: cs.num_advice_columns, + num_instance_columns: cs.num_instance_columns, + num_challenges: cs.num_challenges, + unblinded_advice_columns: cs.unblinded_advice_columns, + advice_column_phase: cs.advice_column_phase.iter().map(|p| p.0).collect(), + challenge_phase: cs.challenge_phase.iter().map(|p| p.0).collect(), + gates: cs + .gates + .into_iter() + .flat_map(|mut g| { + let constraint_names = std::mem::take(&mut g.constraint_names); + let gate_name = g.name.clone(); + g.polys.into_iter().enumerate().map(move |(i, e)| { + let name = match constraint_names[i].as_str() { + "" => gate_name.clone(), + constraint_name => format!("{gate_name}:{constraint_name}"), + }; + GateMid { + name, + poly: e.into(), + } + }) + }) + .collect(), + permutation: halo2_middleware::permutation::ArgumentMid { + columns: cs + .permutation + .columns + .into_iter() + .map(|c| c.into()) + .collect(), + }, + lookups: cs + .lookups + .into_iter() + .map(|l| halo2_middleware::lookup::ArgumentMid { + name: l.name, + input_expressions: l.input_expressions.into_iter().map(|e| e.into()).collect(), + table_expressions: l.table_expressions.into_iter().map(|e| e.into()).collect(), + }) + .collect(), + shuffles: cs + .shuffles + .into_iter() + .map(|s| halo2_middleware::shuffle::ArgumentMid { + name: s.name.clone(), + input_expressions: s.input_expressions.into_iter().map(|e| e.into()).collect(), + shuffle_expressions: s + .shuffle_expressions + .into_iter() + .map(|e| e.into()) + .collect(), + }) + .collect(), + general_column_annotations: cs.general_column_annotations, + minimum_degree: cs.minimum_degree, + } + } +} + +/// This is a description of the circuit environment, such as the gate, column and +/// permutation arrangements. +#[derive(Debug, Clone)] +pub struct ConstraintSystem { + pub(crate) num_fixed_columns: usize, + pub(crate) num_advice_columns: usize, + pub(crate) num_instance_columns: usize, + pub(crate) num_selectors: usize, + pub(crate) num_challenges: usize, + + /// Contains the index of each advice column that is left unblinded. + pub(crate) unblinded_advice_columns: Vec, + + /// Contains the phase for each advice column. Should have same length as num_advice_columns. + pub(crate) advice_column_phase: Vec, + /// Contains the phase for each challenge. Should have same length as num_challenges. + pub(crate) challenge_phase: Vec, + + /// This is a cached vector that maps virtual selectors to the concrete + /// fixed column that they were compressed into. This is just used by dev + /// tooling right now. + pub(crate) selector_map: Vec>, + /// Status boolean indicating wether the selectors have been already transformed to fixed columns + /// or not. + selectors_to_fixed: bool, + + pub(crate) gates: Vec>, + pub(crate) advice_queries: Vec<(Column, Rotation)>, + // Contains an integer for each advice column + // identifying how many distinct queries it has + // so far; should be same length as num_advice_columns. + pub(crate) num_advice_queries: Vec, + pub(crate) instance_queries: Vec<(Column, Rotation)>, + pub(crate) fixed_queries: Vec<(Column, Rotation)>, + + // Permutation argument for performing equality constraints + pub(crate) permutation: permutation::Argument, + + // Vector of lookup arguments, where each corresponds to a sequence of + // input expressions and a sequence of table expressions involved in the lookup. + pub(crate) lookups: Vec>, + + // Vector of shuffle arguments, where each corresponds to a sequence of + // input expressions and a sequence of shuffle expressions involved in the shuffle. + pub(crate) shuffles: Vec>, + + // List of indexes of Fixed columns which are associated to a circuit-general Column tied to their annotation. + pub(crate) general_column_annotations: HashMap, + + // Vector of fixed columns, which can be used to store constant values + // that are copied into advice columns. + pub(crate) constants: Vec>, + + pub(crate) minimum_degree: Option, +} + +/// Helper struct with the parameters required to convert selector assignments into fixed column +/// assignments. +pub struct SelectorsToFixed { + compress: bool, + num_selectors: usize, + max_degree: usize, + degrees: Vec, +} + +impl SelectorsToFixed { + /// Convert selector assignments into fixed column assignments based on the parameters in + /// `SelectorsToFixed`. + pub fn convert(&self, selectors: Vec>) -> Vec> { + // The number of provided selector assignments must be the number we + // counted for this constraint system. + assert_eq!(selectors.len(), self.num_selectors); + + if self.compress { + let (polys, _) = compress_selectors::process( + selectors + .into_iter() + .zip(self.degrees.iter()) + .enumerate() + .map( + |(i, (activations, max_degree))| compress_selectors::SelectorDescription { + selector: i, + activations, + max_degree: *max_degree, + }, + ) + .collect(), + self.max_degree, + || Expression::Constant(F::ZERO), + ); + polys + } else { + selectors + .into_iter() + .map(|selector| { + selector + .iter() + .map(|b| if *b { F::ONE } else { F::ZERO }) + .collect::>() + }) + .collect() + } + } +} + +impl Default for ConstraintSystem { + fn default() -> ConstraintSystem { + ConstraintSystem { + num_fixed_columns: 0, + num_advice_columns: 0, + num_instance_columns: 0, + num_selectors: 0, + num_challenges: 0, + unblinded_advice_columns: Vec::new(), + advice_column_phase: Vec::new(), + challenge_phase: Vec::new(), + selector_map: vec![], + selectors_to_fixed: false, + gates: vec![], + fixed_queries: Vec::new(), + advice_queries: Vec::new(), + num_advice_queries: Vec::new(), + instance_queries: Vec::new(), + permutation: permutation::Argument::default(), + lookups: Vec::new(), + shuffles: Vec::new(), + general_column_annotations: HashMap::new(), + constants: vec![], + minimum_degree: None, + } + } +} + +impl ConstraintSystem { + /// Enables this fixed column to be used for global constant assignments. + /// + /// # Side-effects + /// + /// The column will be equality-enabled. + pub fn enable_constant(&mut self, column: Column) { + if !self.constants.contains(&column) { + self.constants.push(column); + self.enable_equality(column); + } + } + + /// Enable the ability to enforce equality over cells in this column + pub fn enable_equality>>(&mut self, column: C) { + let column = column.into(); + self.query_any_index(column, Rotation::cur()); + self.permutation.add_column(column); + } + + /// Add a lookup argument for some input expressions and table columns. + /// + /// `table_map` returns a map between input expressions and the table columns + /// they need to match. + pub fn lookup>( + &mut self, + name: S, + table_map: impl FnOnce(&mut VirtualCells<'_, F>) -> Vec<(Expression, TableColumn)>, + ) -> usize { + let mut cells = VirtualCells::new(self); + let table_map = table_map(&mut cells) + .into_iter() + .map(|(mut input, table)| { + if input.contains_simple_selector() { + panic!("expression containing simple selector supplied to lookup argument"); + } + let mut table = cells.query_fixed(table.inner(), Rotation::cur()); + input.query_cells(&mut cells); + table.query_cells(&mut cells); + (input, table) + }) + .collect(); + let index = self.lookups.len(); + + self.lookups + .push(lookup::Argument::new(name.as_ref(), table_map)); + + index + } + + /// Add a lookup argument for some input expressions and table expressions. + /// + /// `table_map` returns a map between input expressions and the table expressions + /// they need to match. + pub fn lookup_any>( + &mut self, + name: S, + table_map: impl FnOnce(&mut VirtualCells<'_, F>) -> Vec<(Expression, Expression)>, + ) -> usize { + let mut cells = VirtualCells::new(self); + let table_map = table_map(&mut cells) + .into_iter() + .map(|(mut input, mut table)| { + if input.contains_simple_selector() { + panic!("expression containing simple selector supplied to lookup argument"); + } + if table.contains_simple_selector() { + panic!("expression containing simple selector supplied to lookup argument"); + } + input.query_cells(&mut cells); + table.query_cells(&mut cells); + (input, table) + }) + .collect(); + let index = self.lookups.len(); + + self.lookups + .push(lookup::Argument::new(name.as_ref(), table_map)); + + index + } + + /// Add a shuffle argument for some input expressions and table expressions. + pub fn shuffle>( + &mut self, + name: S, + shuffle_map: impl FnOnce(&mut VirtualCells<'_, F>) -> Vec<(Expression, Expression)>, + ) -> usize { + let mut cells = VirtualCells::new(self); + let shuffle_map = shuffle_map(&mut cells) + .into_iter() + .map(|(mut input, mut table)| { + input.query_cells(&mut cells); + table.query_cells(&mut cells); + (input, table) + }) + .collect(); + let index = self.shuffles.len(); + + self.shuffles + .push(shuffle::Argument::new(name.as_ref(), shuffle_map)); + + index + } + + pub(super) fn query_fixed_index(&mut self, column: Column, at: Rotation) -> usize { + // Return existing query, if it exists + for (index, fixed_query) in self.fixed_queries.iter().enumerate() { + if fixed_query == &(column, at) { + return index; + } + } + + // Make a new query + let index = self.fixed_queries.len(); + self.fixed_queries.push((column, at)); + + index + } + + pub(crate) fn query_advice_index(&mut self, column: Column, at: Rotation) -> usize { + // Return existing query, if it exists + for (index, advice_query) in self.advice_queries.iter().enumerate() { + if advice_query == &(column, at) { + return index; + } + } + + // Make a new query + let index = self.advice_queries.len(); + self.advice_queries.push((column, at)); + self.num_advice_queries[column.index] += 1; + + index + } + + pub(super) fn query_instance_index(&mut self, column: Column, at: Rotation) -> usize { + // Return existing query, if it exists + for (index, instance_query) in self.instance_queries.iter().enumerate() { + if instance_query == &(column, at) { + return index; + } + } + + // Make a new query + let index = self.instance_queries.len(); + self.instance_queries.push((column, at)); + + index + } + + fn query_any_index(&mut self, column: Column, at: Rotation) -> usize { + match column.column_type() { + Any::Advice(_) => { + self.query_advice_index(Column::::try_from(column).unwrap(), at) + } + Any::Fixed => self.query_fixed_index(Column::::try_from(column).unwrap(), at), + Any::Instance => { + self.query_instance_index(Column::::try_from(column).unwrap(), at) + } + } + } + + pub(crate) fn get_advice_query_index(&self, column: Column, at: Rotation) -> usize { + for (index, advice_query) in self.advice_queries.iter().enumerate() { + if advice_query == &(column, at) { + return index; + } + } + + panic!("get_advice_query_index called for non-existent query"); + } + + pub(crate) fn get_fixed_query_index(&self, column: Column, at: Rotation) -> usize { + for (index, fixed_query) in self.fixed_queries.iter().enumerate() { + if fixed_query == &(column, at) { + return index; + } + } + + panic!("get_fixed_query_index called for non-existent query"); + } + + pub(crate) fn get_instance_query_index(&self, column: Column, at: Rotation) -> usize { + for (index, instance_query) in self.instance_queries.iter().enumerate() { + if instance_query == &(column, at) { + return index; + } + } + + panic!("get_instance_query_index called for non-existent query"); + } + + pub fn get_any_query_index(&self, column: Column, at: Rotation) -> usize { + match column.column_type() { + Any::Advice(_) => { + self.get_advice_query_index(Column::::try_from(column).unwrap(), at) + } + Any::Fixed => { + self.get_fixed_query_index(Column::::try_from(column).unwrap(), at) + } + Any::Instance => { + self.get_instance_query_index(Column::::try_from(column).unwrap(), at) + } + } + } + + /// Sets the minimum degree required by the circuit, which can be set to a + /// larger amount than actually needed. This can be used, for example, to + /// force the permutation argument to involve more columns in the same set. + pub fn set_minimum_degree(&mut self, degree: usize) { + self.minimum_degree = Some(degree); + } + + /// Creates a new gate. + /// + /// # Panics + /// + /// A gate is required to contain polynomial constraints. This method will panic if + /// `constraints` returns an empty iterator. + pub fn create_gate>, Iter: IntoIterator, S: AsRef>( + &mut self, + name: S, + constraints: impl FnOnce(&mut VirtualCells<'_, F>) -> Iter, + ) { + let mut cells = VirtualCells::new(self); + let constraints = constraints(&mut cells); + let (constraint_names, polys): (_, Vec<_>) = constraints + .into_iter() + .map(|c| c.into()) + .map(|mut c: Constraint| { + c.poly.query_cells(&mut cells); + (c.name, c.poly) + }) + .unzip(); + + let queried_selectors = cells.queried_selectors; + let queried_cells = cells.queried_cells; + + assert!( + !polys.is_empty(), + "Gates must contain at least one constraint." + ); + + self.gates.push(Gate { + name: name.as_ref().to_string(), + constraint_names, + polys, + queried_selectors, + queried_cells, + }); + } + + /// Transform this `ConstraintSystem` into an equivalent one that replaces the selector columns + /// by fixed columns applying compression where possible. + /// + /// Panics if called twice. + pub fn selectors_to_fixed_compressed(mut self) -> (Self, SelectorsToFixed) { + if self.selectors_to_fixed { + panic!("the selectors have already been transformed to fixed columns"); + } + // Compute the maximal degree of every selector. We only consider the + // expressions in gates, as lookup arguments cannot support simple + // selectors. Selectors that are complex or do not appear in any gates + // will have degree zero. + let mut degrees = vec![0; self.num_selectors]; + for expr in self.gates.iter().flat_map(|gate| gate.polys.iter()) { + if let Some(selector) = expr.extract_simple_selector() { + degrees[selector.0] = max(degrees[selector.0], expr.degree()); + } + } + + // We will not increase the degree of the constraint system, so we limit + // ourselves to the largest existing degree constraint. + let max_degree = self.degree(); + let selectors_to_fixed = SelectorsToFixed { + compress: true, + num_selectors: self.num_selectors, + max_degree, + degrees: degrees.clone(), + }; + + let mut new_columns = vec![]; + let (_, selector_assignment) = compress_selectors::process( + (0..self.num_selectors) + .zip(degrees) + .map(|(i, max_degree)| compress_selectors::SelectorDescription { + selector: i, + activations: vec![], + max_degree, + }) + .collect(), + max_degree, + || { + let column = self.fixed_column(); + new_columns.push(column); + Expression::Fixed(FixedQuery { + index: Some(self.query_fixed_index(column, Rotation::cur())), + column_index: column.index, + rotation: Rotation::cur(), + }) + }, + ); + + let mut selector_map = vec![None; selector_assignment.len()]; + let mut selector_replacements = vec![None; selector_assignment.len()]; + for assignment in selector_assignment { + selector_replacements[assignment.selector] = Some(assignment.expression); + selector_map[assignment.selector] = Some(new_columns[assignment.combination_index]); + } + + self.selector_map = selector_map + .into_iter() + .map(|a| a.unwrap()) + .collect::>(); + let selector_replacements = selector_replacements + .into_iter() + .map(|a| a.unwrap()) + .collect::>(); + self.replace_selectors_with_fixed(&selector_replacements); + self.selectors_to_fixed = true; + + (self, selectors_to_fixed) + } + + /// This will compress selectors together depending on their provided + /// assignments. This `ConstraintSystem` will then be modified to add new + /// fixed columns (representing the actual selectors) and will return the + /// polynomials for those columns. Finally, an internal map is updated to + /// find which fixed column corresponds with a given `Selector`. + /// + /// Do not call this twice. Yes, this should be a builder pattern instead. + #[deprecated(note = "Use `selectors_to_fixed_compressed` instead")] + pub fn compress_selectors(self, selectors: Vec>) -> (Self, Vec>) { + let (cs, selectors_to_fixed) = self.selectors_to_fixed_compressed(); + let fixed_polys = selectors_to_fixed.convert(selectors); + + (cs, fixed_polys) + } + + /// Transform this `ConstraintSystem` into an equivalent one that replaces the selector columns + /// by fixed columns with a direct mapping. + /// + /// Panics if called twice. + pub fn selectors_to_fixed_direct(mut self) -> (Self, SelectorsToFixed) { + if self.selectors_to_fixed { + panic!("the selectors have already been transformed to fixed columns"); + } + let selectors_to_fixed = SelectorsToFixed { + compress: false, + num_selectors: self.num_selectors, + max_degree: 0, + degrees: vec![], + }; + + let selector_replacements: Vec<_> = (0..self.num_selectors) + .map(|_| { + let column = self.fixed_column(); + let rotation = Rotation::cur(); + Expression::Fixed(FixedQuery { + index: Some(self.query_fixed_index(column, rotation)), + column_index: column.index, + rotation, + }) + }) + .collect(); + + self.replace_selectors_with_fixed(&selector_replacements); + self.selectors_to_fixed = true; + (self, selectors_to_fixed) + } + + /// Does not combine selectors and directly replaces them everywhere with fixed columns. + #[deprecated(note = "Use `selectors_to_fixed_direct` instead")] + pub fn directly_convert_selectors_to_fixed( + self, + selectors: Vec>, + ) -> (Self, Vec>) { + let (cs, selectors_to_fixed) = self.selectors_to_fixed_direct(); + let fixed_polys = selectors_to_fixed.convert(selectors); + + (cs, fixed_polys) + } + + fn replace_selectors_with_fixed(&mut self, selector_replacements: &[Expression]) { + fn replace_selectors( + expr: &mut Expression, + selector_replacements: &[Expression], + must_be_nonsimple: bool, + ) { + *expr = expr.evaluate( + &|constant| Expression::Constant(constant), + &|selector| { + if must_be_nonsimple { + // Simple selectors are prohibited from appearing in + // expressions in the lookup argument by + // `ConstraintSystem`. + assert!(!selector.is_simple()); + } + + selector_replacements[selector.0].clone() + }, + &|query| Expression::Fixed(query), + &|query| Expression::Advice(query), + &|query| Expression::Instance(query), + &|challenge| Expression::Challenge(challenge), + &|a| -a, + &|a, b| a + b, + &|a, b| a * b, + &|a, f| a * f, + ); + } + + // Substitute selectors for the real fixed columns in all gates + for expr in self.gates.iter_mut().flat_map(|gate| gate.polys.iter_mut()) { + replace_selectors(expr, selector_replacements, false); + } + + // Substitute non-simple selectors for the real fixed columns in all + // lookup expressions + for expr in self.lookups.iter_mut().flat_map(|lookup| { + lookup + .input_expressions + .iter_mut() + .chain(lookup.table_expressions.iter_mut()) + }) { + replace_selectors(expr, selector_replacements, true); + } + + for expr in self.shuffles.iter_mut().flat_map(|shuffle| { + shuffle + .input_expressions + .iter_mut() + .chain(shuffle.shuffle_expressions.iter_mut()) + }) { + replace_selectors(expr, selector_replacements, true); + } + } + + /// Allocate a new (simple) selector. Simple selectors cannot be added to + /// expressions nor multiplied by other expressions containing simple + /// selectors. Also, simple selectors may not appear in lookup argument + /// inputs. + pub fn selector(&mut self) -> Selector { + let index = self.num_selectors; + self.num_selectors += 1; + Selector(index, true) + } + + /// Allocate a new complex selector that can appear anywhere + /// within expressions. + pub fn complex_selector(&mut self) -> Selector { + let index = self.num_selectors; + self.num_selectors += 1; + Selector(index, false) + } + + /// Allocates a new fixed column that can be used in a lookup table. + pub fn lookup_table_column(&mut self) -> TableColumn { + TableColumn { + inner: self.fixed_column(), + } + } + + /// Annotate a Lookup column. + pub fn annotate_lookup_column(&mut self, column: TableColumn, annotation: A) + where + A: Fn() -> AR, + AR: Into, + { + // We don't care if the table has already an annotation. If it's the case we keep the new one. + self.general_column_annotations.insert( + metadata::Column::from((Any::Fixed, column.inner().index)), + annotation().into(), + ); + } + + /// Annotate an Instance column. + pub fn annotate_lookup_any_column(&mut self, column: T, annotation: A) + where + A: Fn() -> AR, + AR: Into, + T: Into>, + { + let col_any = column.into(); + // We don't care if the table has already an annotation. If it's the case we keep the new one. + self.general_column_annotations.insert( + metadata::Column::from((col_any.column_type, col_any.index)), + annotation().into(), + ); + } + + /// Allocate a new fixed column + pub fn fixed_column(&mut self) -> Column { + let tmp = Column { + index: self.num_fixed_columns, + column_type: Fixed, + }; + self.num_fixed_columns += 1; + tmp + } + + /// Allocate a new unblinded advice column at `FirstPhase` + pub fn unblinded_advice_column(&mut self) -> Column { + self.unblinded_advice_column_in(FirstPhase) + } + + /// Allocate a new advice column at `FirstPhase` + pub fn advice_column(&mut self) -> Column { + self.advice_column_in(FirstPhase) + } + + /// Allocate a new unblinded advice column in given phase. This allows for the generation of deterministic commitments to advice columns + /// which can be used to split large circuits into smaller ones, whose proofs can then be "joined" together by their common witness commitments. + pub fn unblinded_advice_column_in(&mut self, phase: P) -> Column { + let phase = phase.to_sealed(); + if let Some(previous_phase) = phase.prev() { + self.assert_phase_exists( + previous_phase, + format!("Column in later phase {phase:?}").as_str(), + ); + } + + let tmp = Column { + index: self.num_advice_columns, + column_type: Advice { phase: phase.0 }, + }; + self.unblinded_advice_columns.push(tmp.index); + self.num_advice_columns += 1; + self.num_advice_queries.push(0); + self.advice_column_phase.push(phase); + tmp + } + + /// Allocate a new advice column in given phase + /// + /// # Panics + /// + /// It panics if previous phase before the given one doesn't have advice column allocated. + pub fn advice_column_in(&mut self, phase: P) -> Column { + let phase = phase.to_sealed(); + if let Some(previous_phase) = phase.prev() { + self.assert_phase_exists( + previous_phase, + format!("Column in later phase {phase:?}").as_str(), + ); + } + + let tmp = Column { + index: self.num_advice_columns, + column_type: Advice { phase: phase.0 }, + }; + self.num_advice_columns += 1; + self.num_advice_queries.push(0); + self.advice_column_phase.push(phase); + tmp + } + + /// Allocate a new instance column + pub fn instance_column(&mut self) -> Column { + let tmp = Column { + index: self.num_instance_columns, + column_type: Instance, + }; + self.num_instance_columns += 1; + tmp + } + + /// Requests a challenge that is usable after the given phase. + /// + /// # Panics + /// + /// It panics if the given phase doesn't have advice column allocated. + pub fn challenge_usable_after(&mut self, phase: P) -> Challenge { + let phase = phase.to_sealed(); + self.assert_phase_exists( + phase, + format!("Challenge usable after phase {phase:?}").as_str(), + ); + + let tmp = Challenge { + index: self.num_challenges, + phase: phase.0, + }; + self.num_challenges += 1; + self.challenge_phase.push(phase); + tmp + } + + /// Helper funciotn to assert phase exists, to make sure phase-aware resources + /// are allocated in order, and to avoid any phase to be skipped accidentally + /// to cause unexpected issue in the future. + fn assert_phase_exists(&self, phase: sealed::Phase, resource: &str) { + self.advice_column_phase + .iter() + .find(|advice_column_phase| **advice_column_phase == phase) + .unwrap_or_else(|| { + panic!( + "No Column is used in phase {phase:?} while allocating a new {resource:?}" + ) + }); + } + + /// Returns the list of phases + pub fn phases(&self) -> impl Iterator { + let max_phase = self + .advice_column_phase + .iter() + .max() + .map(|phase| phase.0) + .unwrap_or_default(); + (0..=max_phase).map(sealed::Phase) + } + + /// Compute the degree of the constraint system (the maximum degree of all + /// constraints). + pub fn degree(&self) -> usize { + // The permutation argument will serve alongside the gates, so must be + // accounted for. + let mut degree = self.permutation.required_degree(); + + // The lookup argument also serves alongside the gates and must be accounted + // for. + degree = std::cmp::max( + degree, + self.lookups + .iter() + .map(|l| l.required_degree()) + .max() + .unwrap_or(1), + ); + + // The lookup argument also serves alongside the gates and must be accounted + // for. + degree = std::cmp::max( + degree, + self.shuffles + .iter() + .map(|l| l.required_degree()) + .max() + .unwrap_or(1), + ); + + // Account for each gate to ensure our quotient polynomial is the + // correct degree and that our extended domain is the right size. + degree = std::cmp::max( + degree, + self.gates + .iter() + .flat_map(|gate| gate.polynomials().iter().map(|poly| poly.degree())) + .max() + .unwrap_or(0), + ); + + std::cmp::max(degree, self.minimum_degree.unwrap_or(1)) + } + + /// Compute the number of blinding factors necessary to perfectly blind + /// each of the prover's witness polynomials. + pub fn blinding_factors(&self) -> usize { + // All of the prover's advice columns are evaluated at no more than + let factors = *self.num_advice_queries.iter().max().unwrap_or(&1); + // distinct points during gate checks. + + // - The permutation argument witness polynomials are evaluated at most 3 times. + // - Each lookup argument has independent witness polynomials, and they are + // evaluated at most 2 times. + let factors = std::cmp::max(3, factors); + + // Each polynomial is evaluated at most an additional time during + // multiopen (at x_3 to produce q_evals): + let factors = factors + 1; + + // h(x) is derived by the other evaluations so it does not reveal + // anything; in fact it does not even appear in the proof. + + // h(x_3) is also not revealed; the verifier only learns a single + // evaluation of a polynomial in x_1 which has h(x_3) and another random + // polynomial evaluated at x_3 as coefficients -- this random polynomial + // is "random_poly" in the vanishing argument. + + // Add an additional blinding factor as a slight defense against + // off-by-one errors. + factors + 1 + } + + /// Returns the minimum necessary rows that need to exist in order to + /// account for e.g. blinding factors. + pub fn minimum_rows(&self) -> usize { + self.blinding_factors() // m blinding factors + + 1 // for l_{-(m + 1)} (l_last) + + 1 // for l_0 (just for extra breathing room for the permutation + // argument, to essentially force a separation in the + // permutation polynomial between the roles of l_last, l_0 + // and the interstitial values.) + + 1 // for at least one row + } + + /// Returns number of fixed columns + pub fn num_fixed_columns(&self) -> usize { + self.num_fixed_columns + } + + /// Returns number of advice columns + pub fn num_advice_columns(&self) -> usize { + self.num_advice_columns + } + + /// Returns number of instance columns + pub fn num_instance_columns(&self) -> usize { + self.num_instance_columns + } + + /// Returns number of selectors + pub fn num_selectors(&self) -> usize { + self.num_selectors + } + + /// Returns number of challenges + pub fn num_challenges(&self) -> usize { + self.num_challenges + } + + /// Returns phase of advice columns + pub fn advice_column_phase(&self) -> Vec { + self.advice_column_phase + .iter() + .map(|phase| phase.0) + .collect() + } + + /// Returns phase of challenges + pub fn challenge_phase(&self) -> Vec { + self.challenge_phase.iter().map(|phase| phase.0).collect() + } + + /// Returns gates + pub fn gates(&self) -> &Vec> { + &self.gates + } + + /// Returns general column annotations + pub fn general_column_annotations(&self) -> &HashMap { + &self.general_column_annotations + } + + /// Returns advice queries + pub fn advice_queries(&self) -> &Vec<(Column, Rotation)> { + &self.advice_queries + } + + /// Returns instance queries + pub fn instance_queries(&self) -> &Vec<(Column, Rotation)> { + &self.instance_queries + } + + /// Returns fixed queries + pub fn fixed_queries(&self) -> &Vec<(Column, Rotation)> { + &self.fixed_queries + } + + /// Returns permutation argument + pub fn permutation(&self) -> &permutation::Argument { + &self.permutation + } + + /// Returns lookup arguments + pub fn lookups(&self) -> &Vec> { + &self.lookups + } + + /// Returns shuffle arguments + pub fn shuffles(&self) -> &Vec> { + &self.shuffles + } + + /// Returns constants + pub fn constants(&self) -> &Vec> { + &self.constants + } +} + +/// Exposes the "virtual cells" that can be queried while creating a custom gate or lookup +/// table. +#[derive(Debug)] +pub struct VirtualCells<'a, F: Field> { + pub(super) meta: &'a mut ConstraintSystem, + pub(super) queried_selectors: Vec, + pub(super) queried_cells: Vec, +} + +impl<'a, F: Field> VirtualCells<'a, F> { + fn new(meta: &'a mut ConstraintSystem) -> Self { + VirtualCells { + meta, + queried_selectors: vec![], + queried_cells: vec![], + } + } + + /// Query a selector at the current position. + pub fn query_selector(&mut self, selector: Selector) -> Expression { + self.queried_selectors.push(selector); + Expression::Selector(selector) + } + + /// Query a fixed column at a relative position + pub fn query_fixed(&mut self, column: Column, at: Rotation) -> Expression { + self.queried_cells.push((column, at).into()); + Expression::Fixed(FixedQuery { + index: Some(self.meta.query_fixed_index(column, at)), + column_index: column.index, + rotation: at, + }) + } + + /// Query an advice column at a relative position + pub fn query_advice(&mut self, column: Column, at: Rotation) -> Expression { + self.queried_cells.push((column, at).into()); + Expression::Advice(AdviceQuery { + index: Some(self.meta.query_advice_index(column, at)), + column_index: column.index, + rotation: at, + phase: sealed::Phase(column.column_type().phase), + }) + } + + /// Query an instance column at a relative position + pub fn query_instance(&mut self, column: Column, at: Rotation) -> Expression { + self.queried_cells.push((column, at).into()); + Expression::Instance(InstanceQuery { + index: Some(self.meta.query_instance_index(column, at)), + column_index: column.index, + rotation: at, + }) + } + + /// Query an Any column at a relative position + pub fn query_any>>(&mut self, column: C, at: Rotation) -> Expression { + let column = column.into(); + match column.column_type() { + Any::Advice(_) => self.query_advice(Column::::try_from(column).unwrap(), at), + Any::Fixed => self.query_fixed(Column::::try_from(column).unwrap(), at), + Any::Instance => self.query_instance(Column::::try_from(column).unwrap(), at), + } + } + + /// Query a challenge + pub fn query_challenge(&mut self, challenge: Challenge) -> Expression { + Expression::Challenge(challenge) + } +} diff --git a/halo2_frontend/src/plonk/circuit/expression.rs b/halo2_frontend/src/plonk/circuit/expression.rs new file mode 100644 index 0000000000..b8f61f3a1c --- /dev/null +++ b/halo2_frontend/src/plonk/circuit/expression.rs @@ -0,0 +1,1151 @@ +use crate::circuit::Region; +use crate::plonk::circuit::VirtualCells; +use crate::plonk::Error; +use core::cmp::max; +use core::ops::{Add, Mul}; +use halo2_middleware::circuit::{ + Advice, Any, ChallengeMid, ColumnMid, ColumnType, ExpressionMid, Fixed, Instance, QueryMid, + VarMid, +}; +use halo2_middleware::ff::Field; +use halo2_middleware::metadata; +use halo2_middleware::poly::Rotation; +use sealed::SealedPhase; +use std::fmt::Debug; +use std::iter::{Product, Sum}; +use std::{ + convert::TryFrom, + ops::{Neg, Sub}, +}; + +/// A column with an index and type +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +pub struct Column { + pub index: usize, + pub column_type: C, +} + +impl From> for metadata::Column { + fn from(val: Column) -> Self { + metadata::Column { + index: val.index(), + column_type: *val.column_type(), + } + } +} + +impl Column { + pub fn new(index: usize, column_type: C) -> Self { + Column { index, column_type } + } + + /// Index of this column. + pub fn index(&self) -> usize { + self.index + } + + /// Type of this column. + pub fn column_type(&self) -> &C { + &self.column_type + } + + /// Return expression from column at a relative position + pub fn query_cell(&self, at: Rotation) -> Expression { + let expr_mid = self.column_type.query_cell::(self.index, at); + match expr_mid { + ExpressionMid::Var(VarMid::Query(q)) => match q.column_type { + Any::Advice(Advice { phase }) => Expression::Advice(AdviceQuery { + index: None, + column_index: q.column_index, + rotation: q.rotation, + phase: sealed::Phase(phase), + }), + Any::Fixed => Expression::Fixed(FixedQuery { + index: None, + column_index: q.column_index, + rotation: q.rotation, + }), + Any::Instance => Expression::Instance(InstanceQuery { + index: None, + column_index: q.column_index, + rotation: q.rotation, + }), + }, + _ => unreachable!(), + } + } + + /// Return expression from column at the current row + pub fn cur(&self) -> Expression { + self.query_cell(Rotation::cur()) + } + + /// Return expression from column at the next row + pub fn next(&self) -> Expression { + self.query_cell(Rotation::next()) + } + + /// Return expression from column at the previous row + pub fn prev(&self) -> Expression { + self.query_cell(Rotation::prev()) + } + + /// Return expression from column at the specified rotation + pub fn rot(&self, rotation: i32) -> Expression { + self.query_cell(Rotation(rotation)) + } +} + +impl Ord for Column { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + // This ordering is consensus-critical! The layouters rely on deterministic column + // orderings. + match self.column_type.into().cmp(&other.column_type.into()) { + // Indices are assigned within column types. + std::cmp::Ordering::Equal => self.index.cmp(&other.index), + order => order, + } + } +} + +impl PartialOrd for Column { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl From for Column { + fn from(column: ColumnMid) -> Column { + Column { + index: column.index, + column_type: column.column_type, + } + } +} + +impl From> for ColumnMid { + fn from(val: Column) -> Self { + ColumnMid { + index: val.index(), + column_type: *val.column_type(), + } + } +} + +impl From> for Column { + fn from(advice: Column) -> Column { + Column { + index: advice.index(), + column_type: Any::Advice(advice.column_type), + } + } +} + +impl From> for Column { + fn from(advice: Column) -> Column { + Column { + index: advice.index(), + column_type: Any::Fixed, + } + } +} + +impl From> for Column { + fn from(advice: Column) -> Column { + Column { + index: advice.index(), + column_type: Any::Instance, + } + } +} + +impl TryFrom> for Column { + type Error = &'static str; + + fn try_from(any: Column) -> Result { + match any.column_type() { + Any::Advice(advice) => Ok(Column { + index: any.index(), + column_type: *advice, + }), + _ => Err("Cannot convert into Column"), + } + } +} + +impl TryFrom> for Column { + type Error = &'static str; + + fn try_from(any: Column) -> Result { + match any.column_type() { + Any::Fixed => Ok(Column { + index: any.index(), + column_type: Fixed, + }), + _ => Err("Cannot convert into Column"), + } + } +} + +impl TryFrom> for Column { + type Error = &'static str; + + fn try_from(any: Column) -> Result { + match any.column_type() { + Any::Instance => Ok(Column { + index: any.index(), + column_type: Instance, + }), + _ => Err("Cannot convert into Column"), + } + } +} + +pub mod sealed { + /// Phase of advice column + #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] + pub struct Phase(pub u8); + + impl Phase { + pub fn prev(&self) -> Option { + self.0.checked_sub(1).map(Phase) + } + } + + impl SealedPhase for Phase { + fn to_sealed(self) -> Phase { + self + } + } + + /// Sealed trait to help keep `Phase` private. + pub trait SealedPhase { + fn to_sealed(self) -> Phase; + } +} + +/// Phase of advice column +pub trait Phase: SealedPhase {} + +impl Phase for P {} + +/// First phase +#[derive(Debug)] +pub struct FirstPhase; + +impl SealedPhase for FirstPhase { + fn to_sealed(self) -> sealed::Phase { + sealed::Phase(0) + } +} + +/// Second phase +#[derive(Debug)] +pub struct SecondPhase; + +impl SealedPhase for SecondPhase { + fn to_sealed(self) -> sealed::Phase { + sealed::Phase(1) + } +} + +/// Third phase +#[derive(Debug)] +pub struct ThirdPhase; + +impl SealedPhase for ThirdPhase { + fn to_sealed(self) -> sealed::Phase { + sealed::Phase(2) + } +} + +/// A selector, representing a fixed boolean value per row of the circuit. +/// +/// Selectors can be used to conditionally enable (portions of) gates: +/// ``` +/// use halo2_middleware::poly::Rotation; +/// # use halo2curves::pasta::Fp; +/// # use halo2_frontend::plonk::ConstraintSystem; +/// +/// # let mut meta = ConstraintSystem::::default(); +/// let a = meta.advice_column(); +/// let b = meta.advice_column(); +/// let s = meta.selector(); +/// +/// meta.create_gate("foo", |meta| { +/// let a = meta.query_advice(a, Rotation::prev()); +/// let b = meta.query_advice(b, Rotation::cur()); +/// let s = meta.query_selector(s); +/// +/// // On rows where the selector is enabled, a is constrained to equal b. +/// // On rows where the selector is disabled, a and b can take any value. +/// vec![s * (a - b)] +/// }); +/// ``` +/// +/// Selectors are disabled on all rows by default, and must be explicitly enabled on each +/// row when required: +/// ``` +/// use halo2_middleware::circuit::Advice; +/// use halo2_frontend::circuit::{Chip, Layouter, Value}; +/// use halo2_frontend::plonk::{Error, Column, Selector}; +/// use halo2_middleware::ff::Field; +/// # use halo2_middleware::circuit::Fixed; +/// +/// struct Config { +/// a: Column, +/// b: Column, +/// s: Selector, +/// } +/// +/// fn circuit_logic>(chip: C, mut layouter: impl Layouter) -> Result<(), Error> { +/// let config = chip.config(); +/// # let config: Config = todo!(); +/// layouter.assign_region(|| "bar", |mut region| { +/// region.assign_advice(|| "a", config.a, 0, || Value::known(F::ONE))?; +/// region.assign_advice(|| "a", config.b, 1, || Value::known(F::ONE))?; +/// config.s.enable(&mut region, 1) +/// })?; +/// Ok(()) +/// } +/// ``` +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct Selector(pub usize, pub(crate) bool); + +impl Selector { + /// Enable this selector at the given offset within the given region. + pub fn enable(&self, region: &mut Region, offset: usize) -> Result<(), Error> { + region.enable_selector(|| "", self, offset) + } + + /// Is this selector "simple"? Simple selectors can only be multiplied + /// by expressions that contain no other simple selectors. + pub fn is_simple(&self) -> bool { + self.1 + } + + /// Returns index of this selector + pub fn index(&self) -> usize { + self.0 + } + + /// Return expression from selector + pub fn expr(&self) -> Expression { + Expression::Selector(*self) + } +} + +/// Query of fixed column at a certain relative location +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct FixedQuery { + /// Query index + pub index: Option, + /// Column index + pub column_index: usize, + /// Rotation of this query + pub rotation: Rotation, +} + +impl FixedQuery { + /// Column index + pub fn column_index(&self) -> usize { + self.column_index + } + + /// Rotation of this query + pub fn rotation(&self) -> Rotation { + self.rotation + } +} + +/// Query of advice column at a certain relative location +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct AdviceQuery { + /// Query index + pub index: Option, + /// Column index + pub column_index: usize, + /// Rotation of this query + pub rotation: Rotation, + /// Phase of this advice column + pub phase: sealed::Phase, +} + +impl AdviceQuery { + /// Column index + pub fn column_index(&self) -> usize { + self.column_index + } + + /// Rotation of this query + pub fn rotation(&self) -> Rotation { + self.rotation + } + + /// Phase of this advice column + pub fn phase(&self) -> u8 { + self.phase.0 + } +} + +/// Query of instance column at a certain relative location +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct InstanceQuery { + /// Query index + pub index: Option, + /// Column index + pub column_index: usize, + /// Rotation of this query + pub rotation: Rotation, +} + +impl InstanceQuery { + /// Column index + pub fn column_index(&self) -> usize { + self.column_index + } + + /// Rotation of this query + pub fn rotation(&self) -> Rotation { + self.rotation + } +} + +/// A fixed column of a lookup table. +/// +/// A lookup table can be loaded into this column via [`Layouter::assign_table`]. Columns +/// can currently only contain a single table, but they may be used in multiple lookup +/// arguments via [`ConstraintSystem::lookup`]. +/// +/// Lookup table columns are always "encumbered" by the lookup arguments they are used in; +/// they cannot simultaneously be used as general fixed columns. +/// +/// [`Layouter::assign_table`]: crate::circuit::Layouter::assign_table +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct TableColumn { + /// The fixed column that this table column is stored in. + /// + /// # Security + /// + /// This inner column MUST NOT be exposed in the public API, or else chip developers + /// can load lookup tables into their circuits without default-value-filling the + /// columns, which can cause soundness bugs. + pub(super) inner: Column, +} + +impl TableColumn { + /// Returns inner column + pub fn inner(&self) -> Column { + self.inner + } +} + +/// A challenge squeezed from transcript after advice columns at the phase have been committed. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +pub struct Challenge { + pub index: usize, + pub(crate) phase: u8, +} + +impl Challenge { + /// Index of this challenge. + pub fn index(&self) -> usize { + self.index + } + + /// Phase of this challenge. + pub fn phase(&self) -> u8 { + self.phase + } + + /// Return Expression + pub fn expr(&self) -> Expression { + Expression::Challenge(*self) + } +} + +impl From for ChallengeMid { + fn from(val: Challenge) -> Self { + ChallengeMid { + index: val.index, + phase: val.phase, + } + } +} + +impl From for Challenge { + fn from(c: ChallengeMid) -> Self { + Self { + index: c.index, + phase: c.phase, + } + } +} + +/// Low-degree expression representing an identity that must hold over the committed columns. +#[derive(Clone, PartialEq, Eq)] +pub enum Expression { + /// This is a constant polynomial + Constant(F), + /// This is a virtual selector + Selector(Selector), + /// This is a fixed column queried at a certain relative location + Fixed(FixedQuery), + /// This is an advice (witness) column queried at a certain relative location + Advice(AdviceQuery), + /// This is an instance (external) column queried at a certain relative location + Instance(InstanceQuery), + /// This is a challenge + Challenge(Challenge), + /// This is a negated polynomial + Negated(Box>), + /// This is the sum of two polynomials + Sum(Box>, Box>), + /// This is the product of two polynomials + Product(Box>, Box>), + /// This is a scaled polynomial + Scaled(Box>, F), +} + +impl From> for ExpressionMid { + fn from(val: Expression) -> Self { + match val { + Expression::Constant(c) => ExpressionMid::Constant(c), + Expression::Selector(_) => unreachable!(), + Expression::Fixed(FixedQuery { + column_index, + rotation, + .. + }) => ExpressionMid::Var(VarMid::Query(QueryMid { + column_index, + column_type: Any::Fixed, + rotation, + })), + Expression::Advice(AdviceQuery { + column_index, + rotation, + phase, + .. + }) => ExpressionMid::Var(VarMid::Query(QueryMid { + column_index, + column_type: Any::Advice(Advice { phase: phase.0 }), + rotation, + })), + Expression::Instance(InstanceQuery { + column_index, + rotation, + .. + }) => ExpressionMid::Var(VarMid::Query(QueryMid { + column_index, + column_type: Any::Instance, + rotation, + })), + Expression::Challenge(c) => ExpressionMid::Var(VarMid::Challenge(c.into())), + Expression::Negated(e) => ExpressionMid::Negated(Box::new((*e).into())), + Expression::Sum(lhs, rhs) => { + ExpressionMid::Sum(Box::new((*lhs).into()), Box::new((*rhs).into())) + } + Expression::Product(lhs, rhs) => { + ExpressionMid::Product(Box::new((*lhs).into()), Box::new((*rhs).into())) + } + Expression::Scaled(e, c) => ExpressionMid::Scaled(Box::new((*e).into()), c), + } + } +} + +impl Expression { + /// Make side effects + pub fn query_cells(&mut self, cells: &mut VirtualCells<'_, F>) { + match self { + Expression::Constant(_) => (), + Expression::Selector(selector) => { + if !cells.queried_selectors.contains(selector) { + cells.queried_selectors.push(*selector); + } + } + Expression::Fixed(query) => { + if query.index.is_none() { + let col = Column { + index: query.column_index, + column_type: Fixed, + }; + cells.queried_cells.push((col, query.rotation).into()); + query.index = Some(cells.meta.query_fixed_index(col, query.rotation)); + } + } + Expression::Advice(query) => { + if query.index.is_none() { + let col = Column { + index: query.column_index, + column_type: Advice { + phase: query.phase.0, + }, + }; + cells.queried_cells.push((col, query.rotation).into()); + query.index = Some(cells.meta.query_advice_index(col, query.rotation)); + } + } + Expression::Instance(query) => { + if query.index.is_none() { + let col = Column { + index: query.column_index, + column_type: Instance, + }; + cells.queried_cells.push((col, query.rotation).into()); + query.index = Some(cells.meta.query_instance_index(col, query.rotation)); + } + } + Expression::Challenge(_) => (), + Expression::Negated(a) => a.query_cells(cells), + Expression::Sum(a, b) => { + a.query_cells(cells); + b.query_cells(cells); + } + Expression::Product(a, b) => { + a.query_cells(cells); + b.query_cells(cells); + } + Expression::Scaled(a, _) => a.query_cells(cells), + }; + } + + /// Evaluate the polynomial using the provided closures to perform the + /// operations. + #[allow(clippy::too_many_arguments)] + pub fn evaluate( + &self, + constant: &impl Fn(F) -> T, + selector_column: &impl Fn(Selector) -> T, + fixed_column: &impl Fn(FixedQuery) -> T, + advice_column: &impl Fn(AdviceQuery) -> T, + instance_column: &impl Fn(InstanceQuery) -> T, + challenge: &impl Fn(Challenge) -> T, + negated: &impl Fn(T) -> T, + sum: &impl Fn(T, T) -> T, + product: &impl Fn(T, T) -> T, + scaled: &impl Fn(T, F) -> T, + ) -> T { + match self { + Expression::Constant(scalar) => constant(*scalar), + Expression::Selector(selector) => selector_column(*selector), + Expression::Fixed(query) => fixed_column(*query), + Expression::Advice(query) => advice_column(*query), + Expression::Instance(query) => instance_column(*query), + Expression::Challenge(value) => challenge(*value), + Expression::Negated(a) => { + let a = a.evaluate( + constant, + selector_column, + fixed_column, + advice_column, + instance_column, + challenge, + negated, + sum, + product, + scaled, + ); + negated(a) + } + Expression::Sum(a, b) => { + let a = a.evaluate( + constant, + selector_column, + fixed_column, + advice_column, + instance_column, + challenge, + negated, + sum, + product, + scaled, + ); + let b = b.evaluate( + constant, + selector_column, + fixed_column, + advice_column, + instance_column, + challenge, + negated, + sum, + product, + scaled, + ); + sum(a, b) + } + Expression::Product(a, b) => { + let a = a.evaluate( + constant, + selector_column, + fixed_column, + advice_column, + instance_column, + challenge, + negated, + sum, + product, + scaled, + ); + let b = b.evaluate( + constant, + selector_column, + fixed_column, + advice_column, + instance_column, + challenge, + negated, + sum, + product, + scaled, + ); + product(a, b) + } + Expression::Scaled(a, f) => { + let a = a.evaluate( + constant, + selector_column, + fixed_column, + advice_column, + instance_column, + challenge, + negated, + sum, + product, + scaled, + ); + scaled(a, *f) + } + } + } + + /// Evaluate the polynomial lazily using the provided closures to perform the + /// operations. + #[allow(clippy::too_many_arguments)] + pub fn evaluate_lazy( + &self, + constant: &impl Fn(F) -> T, + selector_column: &impl Fn(Selector) -> T, + fixed_column: &impl Fn(FixedQuery) -> T, + advice_column: &impl Fn(AdviceQuery) -> T, + instance_column: &impl Fn(InstanceQuery) -> T, + challenge: &impl Fn(Challenge) -> T, + negated: &impl Fn(T) -> T, + sum: &impl Fn(T, T) -> T, + product: &impl Fn(T, T) -> T, + scaled: &impl Fn(T, F) -> T, + zero: &T, + ) -> T { + match self { + Expression::Constant(scalar) => constant(*scalar), + Expression::Selector(selector) => selector_column(*selector), + Expression::Fixed(query) => fixed_column(*query), + Expression::Advice(query) => advice_column(*query), + Expression::Instance(query) => instance_column(*query), + Expression::Challenge(value) => challenge(*value), + Expression::Negated(a) => { + let a = a.evaluate_lazy( + constant, + selector_column, + fixed_column, + advice_column, + instance_column, + challenge, + negated, + sum, + product, + scaled, + zero, + ); + negated(a) + } + Expression::Sum(a, b) => { + let a = a.evaluate_lazy( + constant, + selector_column, + fixed_column, + advice_column, + instance_column, + challenge, + negated, + sum, + product, + scaled, + zero, + ); + let b = b.evaluate_lazy( + constant, + selector_column, + fixed_column, + advice_column, + instance_column, + challenge, + negated, + sum, + product, + scaled, + zero, + ); + sum(a, b) + } + Expression::Product(a, b) => { + let (a, b) = if a.complexity() <= b.complexity() { + (a, b) + } else { + (b, a) + }; + let a = a.evaluate_lazy( + constant, + selector_column, + fixed_column, + advice_column, + instance_column, + challenge, + negated, + sum, + product, + scaled, + zero, + ); + + if a == *zero { + a + } else { + let b = b.evaluate_lazy( + constant, + selector_column, + fixed_column, + advice_column, + instance_column, + challenge, + negated, + sum, + product, + scaled, + zero, + ); + product(a, b) + } + } + Expression::Scaled(a, f) => { + let a = a.evaluate_lazy( + constant, + selector_column, + fixed_column, + advice_column, + instance_column, + challenge, + negated, + sum, + product, + scaled, + zero, + ); + scaled(a, *f) + } + } + } + + fn write_identifier(&self, writer: &mut W) -> std::io::Result<()> { + match self { + Expression::Constant(scalar) => write!(writer, "{scalar:?}"), + Expression::Selector(selector) => write!(writer, "selector[{}]", selector.0), + Expression::Fixed(query) => { + write!( + writer, + "fixed[{}][{}]", + query.column_index, query.rotation.0 + ) + } + Expression::Advice(query) => { + write!( + writer, + "advice[{}][{}]", + query.column_index, query.rotation.0 + ) + } + Expression::Instance(query) => { + write!( + writer, + "instance[{}][{}]", + query.column_index, query.rotation.0 + ) + } + Expression::Challenge(challenge) => { + write!(writer, "challenge[{}]", challenge.index()) + } + Expression::Negated(a) => { + writer.write_all(b"(-")?; + a.write_identifier(writer)?; + writer.write_all(b")") + } + Expression::Sum(a, b) => { + writer.write_all(b"(")?; + a.write_identifier(writer)?; + writer.write_all(b"+")?; + b.write_identifier(writer)?; + writer.write_all(b")") + } + Expression::Product(a, b) => { + writer.write_all(b"(")?; + a.write_identifier(writer)?; + writer.write_all(b"*")?; + b.write_identifier(writer)?; + writer.write_all(b")") + } + Expression::Scaled(a, f) => { + a.write_identifier(writer)?; + write!(writer, "*{f:?}") + } + } + } + + /// Identifier for this expression. Expressions with identical identifiers + /// do the same calculation (but the expressions don't need to be exactly equal + /// in how they are composed e.g. `1 + 2` and `2 + 1` can have the same identifier). + pub fn identifier(&self) -> String { + let mut cursor = std::io::Cursor::new(Vec::new()); + self.write_identifier(&mut cursor).unwrap(); + String::from_utf8(cursor.into_inner()).unwrap() + } + + /// Compute the degree of this polynomial + pub fn degree(&self) -> usize { + match self { + Expression::Constant(_) => 0, + Expression::Selector(_) => 1, + Expression::Fixed(_) => 1, + Expression::Advice(_) => 1, + Expression::Instance(_) => 1, + Expression::Challenge(_) => 0, + Expression::Negated(poly) => poly.degree(), + Expression::Sum(a, b) => max(a.degree(), b.degree()), + Expression::Product(a, b) => a.degree() + b.degree(), + Expression::Scaled(poly, _) => poly.degree(), + } + } + + /// Approximate the computational complexity of this expression. + pub fn complexity(&self) -> usize { + match self { + Expression::Constant(_) => 0, + Expression::Selector(_) => 1, + Expression::Fixed(_) => 1, + Expression::Advice(_) => 1, + Expression::Instance(_) => 1, + Expression::Challenge(_) => 0, + Expression::Negated(poly) => poly.complexity() + 5, + Expression::Sum(a, b) => a.complexity() + b.complexity() + 15, + Expression::Product(a, b) => a.complexity() + b.complexity() + 30, + Expression::Scaled(poly, _) => poly.complexity() + 30, + } + } + + /// Square this expression. + pub fn square(self) -> Self { + self.clone() * self + } + + /// Returns whether or not this expression contains a simple `Selector`. + pub(super) fn contains_simple_selector(&self) -> bool { + self.evaluate( + &|_| false, + &|selector| selector.is_simple(), + &|_| false, + &|_| false, + &|_| false, + &|_| false, + &|a| a, + &|a, b| a || b, + &|a, b| a || b, + &|a, _| a, + ) + } + + // TODO: Where is this used? + /// Extracts a simple selector from this gate, if present + pub(super) fn extract_simple_selector(&self) -> Option { + let op = |a, b| match (a, b) { + (Some(a), None) | (None, Some(a)) => Some(a), + (Some(_), Some(_)) => panic!("two simple selectors cannot be in the same expression"), + _ => None, + }; + + self.evaluate( + &|_| None, + &|selector| { + if selector.is_simple() { + Some(selector) + } else { + None + } + }, + &|_| None, + &|_| None, + &|_| None, + &|_| None, + &|a| a, + &op, + &op, + &|a, _| a, + ) + } +} + +impl std::fmt::Debug for Expression { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Expression::Constant(scalar) => f.debug_tuple("Constant").field(scalar).finish(), + Expression::Selector(selector) => f.debug_tuple("Selector").field(selector).finish(), + // Skip enum variant and print query struct directly to maintain backwards compatibility. + Expression::Fixed(query) => { + let mut debug_struct = f.debug_struct("Fixed"); + match query.index { + None => debug_struct.field("query_index", &query.index), + Some(idx) => debug_struct.field("query_index", &idx), + }; + debug_struct + .field("column_index", &query.column_index) + .field("rotation", &query.rotation) + .finish() + } + Expression::Advice(query) => { + let mut debug_struct = f.debug_struct("Advice"); + match query.index { + None => debug_struct.field("query_index", &query.index), + Some(idx) => debug_struct.field("query_index", &idx), + }; + debug_struct + .field("column_index", &query.column_index) + .field("rotation", &query.rotation); + // Only show advice's phase if it's not in first phase. + if query.phase != FirstPhase.to_sealed() { + debug_struct.field("phase", &query.phase); + } + debug_struct.finish() + } + Expression::Instance(query) => { + let mut debug_struct = f.debug_struct("Instance"); + match query.index { + None => debug_struct.field("query_index", &query.index), + Some(idx) => debug_struct.field("query_index", &idx), + }; + debug_struct + .field("column_index", &query.column_index) + .field("rotation", &query.rotation) + .finish() + } + Expression::Challenge(challenge) => { + f.debug_tuple("Challenge").field(challenge).finish() + } + Expression::Negated(poly) => f.debug_tuple("Negated").field(poly).finish(), + Expression::Sum(a, b) => f.debug_tuple("Sum").field(a).field(b).finish(), + Expression::Product(a, b) => f.debug_tuple("Product").field(a).field(b).finish(), + Expression::Scaled(poly, scalar) => { + f.debug_tuple("Scaled").field(poly).field(scalar).finish() + } + } + } +} + +impl Neg for Expression { + type Output = Expression; + fn neg(self) -> Self::Output { + Expression::Negated(Box::new(self)) + } +} + +impl Add for Expression { + type Output = Expression; + fn add(self, rhs: Expression) -> Expression { + if self.contains_simple_selector() || rhs.contains_simple_selector() { + panic!("attempted to use a simple selector in an addition"); + } + Expression::Sum(Box::new(self), Box::new(rhs)) + } +} + +impl Sub for Expression { + type Output = Expression; + fn sub(self, rhs: Expression) -> Expression { + if self.contains_simple_selector() || rhs.contains_simple_selector() { + panic!("attempted to use a simple selector in a subtraction"); + } + Expression::Sum(Box::new(self), Box::new(-rhs)) + } +} + +impl Mul for Expression { + type Output = Expression; + fn mul(self, rhs: Expression) -> Expression { + if self.contains_simple_selector() && rhs.contains_simple_selector() { + panic!("attempted to multiply two expressions containing simple selectors"); + } + Expression::Product(Box::new(self), Box::new(rhs)) + } +} + +impl Mul for Expression { + type Output = Expression; + fn mul(self, rhs: F) -> Expression { + Expression::Scaled(Box::new(self), rhs) + } +} + +impl Sum for Expression { + fn sum>(iter: I) -> Self { + iter.reduce(|acc, x| acc + x) + .unwrap_or(Expression::Constant(F::ZERO)) + } +} + +impl Product for Expression { + fn product>(iter: I) -> Self { + iter.reduce(|acc, x| acc * x) + .unwrap_or(Expression::Constant(F::ONE)) + } +} + +#[cfg(test)] +mod tests { + use super::Expression; + use halo2curves::bn256::Fr; + + #[test] + fn iter_sum() { + let exprs: Vec> = vec![ + Expression::Constant(1.into()), + Expression::Constant(2.into()), + Expression::Constant(3.into()), + ]; + let happened: Expression = exprs.into_iter().sum(); + let expected: Expression = Expression::Sum( + Box::new(Expression::Sum( + Box::new(Expression::Constant(1.into())), + Box::new(Expression::Constant(2.into())), + )), + Box::new(Expression::Constant(3.into())), + ); + + assert_eq!(happened, expected); + } + + #[test] + fn iter_product() { + let exprs: Vec> = vec![ + Expression::Constant(1.into()), + Expression::Constant(2.into()), + Expression::Constant(3.into()), + ]; + let happened: Expression = exprs.into_iter().product(); + let expected: Expression = Expression::Product( + Box::new(Expression::Product( + Box::new(Expression::Constant(1.into())), + Box::new(Expression::Constant(2.into())), + )), + Box::new(Expression::Constant(3.into())), + ); + + assert_eq!(happened, expected); + } +} diff --git a/halo2_common/src/plonk/error.rs b/halo2_frontend/src/plonk/error.rs similarity index 71% rename from halo2_common/src/plonk/error.rs rename to halo2_frontend/src/plonk/error.rs index 095b8dceb9..18133f2683 100644 --- a/halo2_common/src/plonk/error.rs +++ b/halo2_frontend/src/plonk/error.rs @@ -1,38 +1,22 @@ -use std::error; use std::fmt; -use std::io; use super::TableColumn; -use crate::plonk::circuit::Column; +use crate::plonk::Column; use halo2_middleware::circuit::Any; -// TODO: Split this Error into a frontend and backend version -// https://github.com/privacy-scaling-explorations/halo2/issues/266 - -/// This is an error that could occur during proving or circuit synthesis. -// TODO: these errors need to be cleaned up +/// This is an error that could occur during circuit synthesis. #[derive(Debug)] pub enum Error { /// This is an error that can occur during synthesis of the circuit, for /// example, when the witness is not present. Synthesis, - /// The provided instances do not match the circuit parameters. - InvalidInstances, - /// The constraint system is not satisfied. - ConstraintSystemFailure, /// Out of bounds index passed to a backend BoundsFailure, - /// Opening error - Opening, - /// Transcript error - Transcript(io::Error), /// `k` is too small for the given circuit. NotEnoughRowsAvailable { /// The current value of `k` being used. current_k: u32, }, - /// Instance provided exceeds number of available rows - InstanceTooLarge, /// Circuit synthesis requires global constants, but circuit configuration did not /// call [`ConstraintSystem::enable_constant`] on fixed columns with sufficient space. /// @@ -47,13 +31,6 @@ pub enum Error { Other(String), } -impl From for Error { - fn from(error: io::Error) -> Self { - // The only place we can get io::Error from is the transcript. - Error::Transcript(error) - } -} - impl Error { /// Constructs an `Error::NotEnoughRowsAvailable`. pub fn not_enough_rows_available(current_k: u32) -> Self { @@ -65,16 +42,11 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::Synthesis => write!(f, "General synthesis error"), - Error::InvalidInstances => write!(f, "Provided instances do not match the circuit"), - Error::ConstraintSystemFailure => write!(f, "The constraint system is not satisfied"), Error::BoundsFailure => write!(f, "An out-of-bounds index was passed to the backend"), - Error::Opening => write!(f, "Multi-opening proof was invalid"), - Error::Transcript(e) => write!(f, "Transcript error: {e}"), Error::NotEnoughRowsAvailable { current_k } => write!( f, "k = {current_k} is too small for the given circuit. Try using a larger value of k", ), - Error::InstanceTooLarge => write!(f, "Instance vectors are larger than the circuit"), Error::NotEnoughColumnsForConstants => { write!( f, @@ -91,15 +63,6 @@ impl fmt::Display for Error { } } -impl error::Error for Error { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - match self { - Error::Transcript(e) => Some(e), - _ => None, - } - } -} - /// This is an error that could occur during table synthesis. #[derive(Debug)] pub enum TableError { diff --git a/halo2_common/src/plonk/keygen.rs b/halo2_frontend/src/plonk/keygen.rs similarity index 97% rename from halo2_common/src/plonk/keygen.rs rename to halo2_frontend/src/plonk/keygen.rs index 4e53c01110..7d20e88e45 100644 --- a/halo2_common/src/plonk/keygen.rs +++ b/halo2_frontend/src/plonk/keygen.rs @@ -2,12 +2,8 @@ use std::ops::Range; use halo2_middleware::ff::Field; -use super::{ - circuit::{Assignment, Challenge, Column, Selector}, - permutation, Error, -}; use crate::circuit::Value; -use crate::plonk::Assigned; +use crate::plonk::{permutation, Assigned, Assignment, Challenge, Column, Error, Selector}; use halo2_middleware::circuit::{Advice, Any, Fixed, Instance}; /// Assembly to be used in circuit synthesis. diff --git a/halo2_common/src/plonk/lookup.rs b/halo2_frontend/src/plonk/lookup.rs similarity index 94% rename from halo2_common/src/plonk/lookup.rs rename to halo2_frontend/src/plonk/lookup.rs index 2993029b56..85098d7fb0 100644 --- a/halo2_common/src/plonk/lookup.rs +++ b/halo2_frontend/src/plonk/lookup.rs @@ -1,14 +1,13 @@ -use super::circuit::Expression; +use crate::plonk::Expression; use halo2_middleware::ff::Field; use std::fmt::{self, Debug}; /// Expressions involved in a lookup argument, with a name as metadata. -/// TODO: possible to move to "halo2_backend", if moved, pub(crate) fields. #[derive(Clone)] pub struct Argument { - pub name: String, - pub input_expressions: Vec>, - pub table_expressions: Vec>, + pub(crate) name: String, + pub(crate) input_expressions: Vec>, + pub(crate) table_expressions: Vec>, } impl Debug for Argument { diff --git a/halo2_common/src/plonk/permutation.rs b/halo2_frontend/src/plonk/permutation.rs similarity index 92% rename from halo2_common/src/plonk/permutation.rs rename to halo2_frontend/src/plonk/permutation.rs index ba307bd2f8..eb12ec7401 100644 --- a/halo2_common/src/plonk/permutation.rs +++ b/halo2_frontend/src/plonk/permutation.rs @@ -2,17 +2,17 @@ use crate::plonk::{Column, Error}; use halo2_middleware::circuit::{Any, Cell}; -use halo2_middleware::permutation::ArgumentV2; +use halo2_middleware::permutation::ArgumentMid; /// A permutation argument. #[derive(Default, Debug, Clone)] pub struct Argument { /// A sequence of columns involved in the argument. - pub columns: Vec>, + pub(crate) columns: Vec>, } -impl From for Argument { - fn from(arg: ArgumentV2) -> Self { +impl From for Argument { + fn from(arg: ArgumentMid) -> Self { Self { columns: arg.columns.into_iter().map(|c| c.into()).collect(), } @@ -72,9 +72,9 @@ impl Argument { #[derive(Clone, Debug)] pub struct Assembly { - pub n: usize, - pub columns: Vec>, - pub copies: Vec<(Cell, Cell)>, + pub(crate) n: usize, + pub(crate) columns: Vec>, + pub(crate) copies: Vec<(Cell, Cell)>, } impl Assembly { diff --git a/halo2_common/src/plonk/shuffle.rs b/halo2_frontend/src/plonk/shuffle.rs similarity index 92% rename from halo2_common/src/plonk/shuffle.rs rename to halo2_frontend/src/plonk/shuffle.rs index cdc773efef..7bf1052208 100644 --- a/halo2_common/src/plonk/shuffle.rs +++ b/halo2_frontend/src/plonk/shuffle.rs @@ -1,13 +1,13 @@ -use super::circuit::Expression; +use crate::plonk::Expression; use halo2_middleware::ff::Field; use std::fmt::{self, Debug}; /// Expressions involved in a shuffle argument, with a name as metadata. #[derive(Clone)] pub struct Argument { - pub name: String, - pub input_expressions: Vec>, - pub shuffle_expressions: Vec>, + pub(crate) name: String, + pub(crate) input_expressions: Vec>, + pub(crate) shuffle_expressions: Vec>, } impl Debug for Argument { diff --git a/halo2_middleware/src/circuit.rs b/halo2_middleware/src/circuit.rs index 2bc17222f1..4a28bd5558 100644 --- a/halo2_middleware/src/circuit.rs +++ b/halo2_middleware/src/circuit.rs @@ -1,38 +1,9 @@ +use crate::expression::{Expression, Variable}; use crate::poly::Rotation; use crate::{lookup, metadata, permutation, shuffle}; -use core::cmp::max; use ff::Field; use std::collections::HashMap; -/// Query of fixed column at a certain relative location -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct FixedQueryMid { - /// Column index - pub column_index: usize, - /// Rotation of this query - pub rotation: Rotation, -} - -/// Query of advice column at a certain relative location -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct AdviceQueryMid { - /// Column index - pub column_index: usize, - /// Rotation of this query - pub rotation: Rotation, - /// Phase of this advice column - pub phase: u8, -} - -/// Query of instance column at a certain relative location -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct InstanceQueryMid { - /// Column index - pub column_index: usize, - /// Rotation of this query - pub rotation: Rotation, -} - /// A challenge squeezed from transcript after advice columns at the phase have been committed. #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] pub struct ChallengeMid { @@ -52,70 +23,83 @@ impl ChallengeMid { } } -/// Low-degree expression representing an identity that must hold over the committed columns. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum ExpressionMid { - /// This is a constant polynomial - Constant(F), - /// This is a fixed column queried at a certain relative location - Fixed(FixedQueryMid), - /// This is an advice (witness) column queried at a certain relative location - Advice(AdviceQueryMid), - /// This is an instance (external) column queried at a certain relative location - Instance(InstanceQueryMid), +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct QueryMid { + /// Column index + pub column_index: usize, + /// The type of the column. + pub column_type: Any, + /// Rotation of this query + pub rotation: Rotation, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum VarMid { + /// This is a generic column query + Query(QueryMid), /// This is a challenge Challenge(ChallengeMid), - /// This is a negated polynomial - Negated(Box>), - /// This is the sum of two polynomials - Sum(Box>, Box>), - /// This is the product of two polynomials - Product(Box>, Box>), - /// This is a scaled polynomial - Scaled(Box>, F), } -impl ExpressionMid { - /// Compute the degree of this polynomial - pub fn degree(&self) -> usize { - use ExpressionMid::*; +impl Variable for VarMid { + fn degree(&self) -> usize { match self { - Constant(_) => 0, - Fixed(_) => 1, - Advice(_) => 1, - Instance(_) => 1, - Challenge(_) => 0, - Negated(poly) => poly.degree(), - Sum(a, b) => max(a.degree(), b.degree()), - Product(a, b) => a.degree() + b.degree(), - Scaled(poly, _) => poly.degree(), + VarMid::Query(_) => 1, + VarMid::Challenge(_) => 0, + } + } + + fn complexity(&self) -> usize { + match self { + VarMid::Query(_) => 1, + VarMid::Challenge(_) => 0, + } + } + + fn write_identifier(&self, writer: &mut W) -> std::io::Result<()> { + match self { + VarMid::Query(query) => { + match query.column_type { + Any::Fixed => write!(writer, "fixed")?, + Any::Advice(_) => write!(writer, "advice")?, + Any::Instance => write!(writer, "instance")?, + }; + write!(writer, "[{}][{}]", query.column_index, query.rotation.0) + } + VarMid::Challenge(challenge) => { + write!(writer, "challenge[{}]", challenge.index()) + } } } } +pub type ExpressionMid = Expression; + /// A Gate contains a single polynomial identity with a name as metadata. #[derive(Clone, Debug)] -pub struct GateV2Backend { +pub struct Gate { pub name: String, - pub poly: ExpressionMid, + pub poly: Expression, } -impl GateV2Backend { +impl Gate { /// Returns the gate name. pub fn name(&self) -> &str { self.name.as_str() } /// Returns the polynomial identity of this gate - pub fn polynomial(&self) -> &ExpressionMid { + pub fn polynomial(&self) -> &Expression { &self.poly } } +pub type GateMid = Gate; + /// This is a description of the circuit environment, such as the gate, column and /// permutation arrangements. #[derive(Debug, Clone)] -pub struct ConstraintSystemV2Backend { +pub struct ConstraintSystemMid { pub num_fixed_columns: usize, pub num_advice_columns: usize, pub num_instance_columns: usize, @@ -129,21 +113,26 @@ pub struct ConstraintSystemV2Backend { /// Contains the phase for each challenge. Should have same length as num_challenges. pub challenge_phase: Vec, - pub gates: Vec>, + pub gates: Vec>, // Permutation argument for performing equality constraints - pub permutation: permutation::ArgumentV2, + pub permutation: permutation::ArgumentMid, // Vector of lookup arguments, where each corresponds to a sequence of // input expressions and a sequence of table expressions involved in the lookup. - pub lookups: Vec>, + pub lookups: Vec>, // Vector of shuffle arguments, where each corresponds to a sequence of // input expressions and a sequence of shuffle expressions involved in the shuffle. - pub shuffles: Vec>, + pub shuffles: Vec>, // List of indexes of Fixed columns which are associated to a circuit-general Column tied to their annotation. pub general_column_annotations: HashMap, + + // The minimum degree required by the circuit, which can be set to a + // larger amount than actually needed. This can be used, for example, to + // force the permutation argument to involve more columns in the same set. + pub minimum_degree: Option, } /// Data that needs to be preprocessed from a circuit @@ -158,7 +147,7 @@ pub struct PreprocessingV2 { #[derive(Debug, Clone)] pub struct CompiledCircuitV2 { pub preprocessing: PreprocessingV2, - pub cs: ConstraintSystemV2Backend, + pub cs: ConstraintSystemMid, } // TODO: The query_cell method is only used in the frontend, which uses Expression. By having this @@ -185,6 +174,12 @@ pub struct ColumnMid { pub column_type: Any, } +impl ColumnMid { + pub fn new(index: usize, column_type: Any) -> Self { + ColumnMid { index, column_type } + } +} + /// A cell identifies a position in the plonkish matrix identified by a column and a row offset. #[derive(Clone, Debug)] pub struct Cell { @@ -295,45 +290,49 @@ impl PartialOrd for Any { impl ColumnType for Advice { fn query_cell(&self, index: usize, at: Rotation) -> ExpressionMid { - ExpressionMid::Advice(AdviceQueryMid { + ExpressionMid::Var(VarMid::Query(QueryMid { column_index: index, + column_type: Any::Advice(Advice { phase: self.phase }), rotation: at, - phase: self.phase, - }) + })) } } impl ColumnType for Fixed { fn query_cell(&self, index: usize, at: Rotation) -> ExpressionMid { - ExpressionMid::Fixed(FixedQueryMid { + ExpressionMid::Var(VarMid::Query(QueryMid { column_index: index, + column_type: Any::Fixed, rotation: at, - }) + })) } } impl ColumnType for Instance { fn query_cell(&self, index: usize, at: Rotation) -> ExpressionMid { - ExpressionMid::Instance(InstanceQueryMid { + ExpressionMid::Var(VarMid::Query(QueryMid { column_index: index, + column_type: Any::Instance, rotation: at, - }) + })) } } impl ColumnType for Any { fn query_cell(&self, index: usize, at: Rotation) -> ExpressionMid { match self { - Any::Advice(Advice { phase }) => ExpressionMid::Advice(AdviceQueryMid { + Any::Advice(Advice { phase }) => ExpressionMid::Var(VarMid::Query(QueryMid { column_index: index, + column_type: Any::Advice(Advice { phase: *phase }), rotation: at, - phase: *phase, - }), - Any::Fixed => ExpressionMid::Fixed(FixedQueryMid { + })), + Any::Fixed => ExpressionMid::Var(VarMid::Query(QueryMid { column_index: index, + column_type: Any::Fixed, rotation: at, - }), - Any::Instance => ExpressionMid::Instance(InstanceQueryMid { + })), + Any::Instance => ExpressionMid::Var(VarMid::Query(QueryMid { column_index: index, + column_type: Any::Instance, rotation: at, - }), + })), } } } diff --git a/halo2_middleware/src/expression.rs b/halo2_middleware/src/expression.rs new file mode 100644 index 0000000000..fd6a45f6de --- /dev/null +++ b/halo2_middleware/src/expression.rs @@ -0,0 +1,192 @@ +use core::cmp::max; +use core::ops::{Add, Mul, Neg, Sub}; +use ff::Field; +use std::iter::{Product, Sum}; + +pub trait Variable: Clone + Copy + std::fmt::Debug + Eq + PartialEq { + /// Degree that an expression would have if it was only this variable. + fn degree(&self) -> usize; + + /// Approximate the computational complexity an expression would have if it was only this + /// variable. + fn complexity(&self) -> usize { + 0 + } + + /// Write an identifier of the variable. If two variables have the same identifier, they must + /// be the same variable. + fn write_identifier(&self, writer: &mut W) -> std::io::Result<()>; +} + +/// Low-degree expression representing an identity that must hold over the committed columns. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Expression { + /// This is a constant polynomial + Constant(F), + /// This is a variable + Var(V), + /// This is a negated polynomial + Negated(Box>), + /// This is the sum of two polynomials + Sum(Box>, Box>), + /// This is the product of two polynomials + Product(Box>, Box>), + /// This is a scaled polynomial + Scaled(Box>, F), +} + +impl Expression { + /// Evaluate the polynomial using the provided closures to perform the + /// operations. + #[allow(clippy::too_many_arguments)] + pub fn evaluate( + &self, + constant: &impl Fn(F) -> T, + var: &impl Fn(V) -> T, + negated: &impl Fn(T) -> T, + sum: &impl Fn(T, T) -> T, + product: &impl Fn(T, T) -> T, + scaled: &impl Fn(T, F) -> T, + ) -> T { + match self { + Expression::Constant(scalar) => constant(*scalar), + Expression::Var(v) => var(*v), + Expression::Negated(a) => { + let a = a.evaluate(constant, var, negated, sum, product, scaled); + negated(a) + } + Expression::Sum(a, b) => { + let a = a.evaluate(constant, var, negated, sum, product, scaled); + let b = b.evaluate(constant, var, negated, sum, product, scaled); + sum(a, b) + } + Expression::Product(a, b) => { + let a = a.evaluate(constant, var, negated, sum, product, scaled); + let b = b.evaluate(constant, var, negated, sum, product, scaled); + product(a, b) + } + Expression::Scaled(a, f) => { + let a = a.evaluate(constant, var, negated, sum, product, scaled); + scaled(a, *f) + } + } + } + + fn write_identifier(&self, writer: &mut W) -> std::io::Result<()> { + match self { + Expression::Constant(scalar) => write!(writer, "{scalar:?}"), + Expression::Var(v) => v.write_identifier(writer), + Expression::Negated(a) => { + writer.write_all(b"(-")?; + a.write_identifier(writer)?; + writer.write_all(b")") + } + Expression::Sum(a, b) => { + writer.write_all(b"(")?; + a.write_identifier(writer)?; + writer.write_all(b"+")?; + b.write_identifier(writer)?; + writer.write_all(b")") + } + Expression::Product(a, b) => { + writer.write_all(b"(")?; + a.write_identifier(writer)?; + writer.write_all(b"*")?; + b.write_identifier(writer)?; + writer.write_all(b")") + } + Expression::Scaled(a, f) => { + a.write_identifier(writer)?; + write!(writer, "*{f:?}") + } + } + } + + /// Identifier for this expression. Expressions with identical identifiers + /// do the same calculation (but the expressions don't need to be exactly equal + /// in how they are composed e.g. `1 + 2` and `2 + 1` can have the same identifier). + pub fn identifier(&self) -> String { + let mut cursor = std::io::Cursor::new(Vec::new()); + self.write_identifier(&mut cursor).unwrap(); + String::from_utf8(cursor.into_inner()).unwrap() + } + + /// Compute the degree of this polynomial + pub fn degree(&self) -> usize { + use Expression::*; + match self { + Constant(_) => 0, + Var(v) => v.degree(), + Negated(poly) => poly.degree(), + Sum(a, b) => max(a.degree(), b.degree()), + Product(a, b) => a.degree() + b.degree(), + Scaled(poly, _) => poly.degree(), + } + } + + /// Approximate the computational complexity of this expression. + pub fn complexity(&self) -> usize { + match self { + Expression::Constant(_) => 0, + Expression::Var(v) => v.complexity(), + Expression::Negated(poly) => poly.complexity() + 5, + Expression::Sum(a, b) => a.complexity() + b.complexity() + 15, + Expression::Product(a, b) => a.complexity() + b.complexity() + 30, + Expression::Scaled(poly, _) => poly.complexity() + 30, + } + } + + /// Square this expression. + pub fn square(self) -> Self { + self.clone() * self + } +} + +impl Neg for Expression { + type Output = Expression; + fn neg(self) -> Self::Output { + Expression::Negated(Box::new(self)) + } +} + +impl Add for Expression { + type Output = Expression; + fn add(self, rhs: Expression) -> Expression { + Expression::Sum(Box::new(self), Box::new(rhs)) + } +} + +impl Sub for Expression { + type Output = Expression; + fn sub(self, rhs: Expression) -> Expression { + Expression::Sum(Box::new(self), Box::new(-rhs)) + } +} + +impl Mul for Expression { + type Output = Expression; + fn mul(self, rhs: Expression) -> Expression { + Expression::Product(Box::new(self), Box::new(rhs)) + } +} + +impl Mul for Expression { + type Output = Expression; + fn mul(self, rhs: F) -> Expression { + Expression::Scaled(Box::new(self), rhs) + } +} + +impl Sum for Expression { + fn sum>(iter: I) -> Self { + iter.reduce(|acc, x| acc + x) + .unwrap_or(Expression::Constant(F::ZERO)) + } +} + +impl Product for Expression { + fn product>(iter: I) -> Self { + iter.reduce(|acc, x| acc * x) + .unwrap_or(Expression::Constant(F::ONE)) + } +} diff --git a/halo2_middleware/src/lib.rs b/halo2_middleware/src/lib.rs index db9734d819..9dc5f89a86 100644 --- a/halo2_middleware/src/lib.rs +++ b/halo2_middleware/src/lib.rs @@ -1,4 +1,5 @@ pub mod circuit; +pub mod expression; pub mod lookup; pub mod metadata; pub mod permutation; diff --git a/halo2_middleware/src/lookup.rs b/halo2_middleware/src/lookup.rs index 4ef8dfea8f..d2084010bf 100644 --- a/halo2_middleware/src/lookup.rs +++ b/halo2_middleware/src/lookup.rs @@ -1,10 +1,13 @@ -use super::circuit::ExpressionMid; +use super::circuit::VarMid; +use super::expression::{Expression, Variable}; use ff::Field; /// Expressions involved in a lookup argument, with a name as metadata. #[derive(Clone, Debug)] -pub struct ArgumentV2 { +pub struct Argument { pub name: String, - pub input_expressions: Vec>, - pub table_expressions: Vec>, + pub input_expressions: Vec>, + pub table_expressions: Vec>, } + +pub type ArgumentMid = Argument; diff --git a/halo2_middleware/src/permutation.rs b/halo2_middleware/src/permutation.rs index d23520548c..02d71a7b03 100644 --- a/halo2_middleware/src/permutation.rs +++ b/halo2_middleware/src/permutation.rs @@ -7,7 +7,7 @@ pub struct AssemblyMid { /// A permutation argument. #[derive(Debug, Clone)] -pub struct ArgumentV2 { +pub struct ArgumentMid { /// A sequence of columns involved in the argument. pub columns: Vec, } diff --git a/halo2_middleware/src/shuffle.rs b/halo2_middleware/src/shuffle.rs index 293f7adc22..a56678b576 100644 --- a/halo2_middleware/src/shuffle.rs +++ b/halo2_middleware/src/shuffle.rs @@ -1,10 +1,13 @@ -use super::circuit::ExpressionMid; +use super::circuit::VarMid; +use super::expression::{Expression, Variable}; use ff::Field; /// Expressions involved in a shuffle argument, with a name as metadata. #[derive(Clone, Debug)] -pub struct ArgumentV2 { +pub struct Argument { pub name: String, - pub input_expressions: Vec>, - pub shuffle_expressions: Vec>, + pub input_expressions: Vec>, + pub shuffle_expressions: Vec>, } + +pub type ArgumentMid = Argument; diff --git a/halo2_proofs/Cargo.toml b/halo2_proofs/Cargo.toml index 4a1d8892aa..9f197f86cf 100644 --- a/halo2_proofs/Cargo.toml +++ b/halo2_proofs/Cargo.toml @@ -48,6 +48,7 @@ halo2_frontend = { path = "../halo2_frontend" } halo2curves = { version = "0.6.0", default-features = false } rand_core = { version = "0.6", default-features = false, features = ["getrandom"] } plotters = { version = "0.3.0", default-features = false, optional = true } +group = "0.13" [dev-dependencies] ff = "0.13" @@ -76,11 +77,11 @@ test-dev-graph = [ "plotters/ttf" ] bits = ["halo2curves/bits"] -gadget-traces = ["halo2_common/gadget-traces"] +gadget-traces = [] thread-safe-region = [] sanity-checks = [] batch = ["rand_core/getrandom"] -circuit-params = ["halo2_common/circuit-params", "halo2_frontend/circuit-params", "halo2_backend/circuit-params"] +circuit-params = ["halo2_common/circuit-params", "halo2_frontend/circuit-params"] heap-profiling = [] cost-estimator = ["halo2_frontend/cost-estimator"] derive_serde = ["halo2curves/derive_serde"] diff --git a/halo2_proofs/benches/dev_lookup.rs b/halo2_proofs/benches/dev_lookup.rs index 569ffd1019..05ff08da3d 100644 --- a/halo2_proofs/benches/dev_lookup.rs +++ b/halo2_proofs/benches/dev_lookup.rs @@ -56,7 +56,7 @@ fn criterion_benchmark(c: &mut Criterion) { &self, config: MyConfig, mut layouter: impl Layouter, - ) -> Result<(), Error> { + ) -> Result<(), ErrorFront> { layouter.assign_table( || "8-bit table", |mut table| { diff --git a/halo2_proofs/benches/plonk.rs b/halo2_proofs/benches/plonk.rs index 9c9bd2618a..7551fde229 100644 --- a/halo2_proofs/benches/plonk.rs +++ b/halo2_proofs/benches/plonk.rs @@ -47,17 +47,22 @@ fn criterion_benchmark(c: &mut Criterion) { &self, layouter: &mut impl Layouter, f: F, - ) -> Result<(Cell, Cell, Cell), Error> + ) -> Result<(Cell, Cell, Cell), ErrorFront> where F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; fn raw_add( &self, layouter: &mut impl Layouter, f: F, - ) -> Result<(Cell, Cell, Cell), Error> + ) -> Result<(Cell, Cell, Cell), ErrorFront> where F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; - fn copy(&self, layouter: &mut impl Layouter, a: Cell, b: Cell) -> Result<(), Error>; + fn copy( + &self, + layouter: &mut impl Layouter, + a: Cell, + b: Cell, + ) -> Result<(), ErrorFront>; } #[derive(Clone)] @@ -85,7 +90,7 @@ fn criterion_benchmark(c: &mut Criterion) { &self, layouter: &mut impl Layouter, mut f: F, - ) -> Result<(Cell, Cell, Cell), Error> + ) -> Result<(Cell, Cell, Cell), ErrorFront> where F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, { @@ -127,7 +132,7 @@ fn criterion_benchmark(c: &mut Criterion) { &self, layouter: &mut impl Layouter, mut f: F, - ) -> Result<(Cell, Cell, Cell), Error> + ) -> Result<(Cell, Cell, Cell), ErrorFront> where F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, { @@ -175,7 +180,7 @@ fn criterion_benchmark(c: &mut Criterion) { layouter: &mut impl Layouter, left: Cell, right: Cell, - ) -> Result<(), Error> { + ) -> Result<(), ErrorFront> { layouter.assign_region(|| "copy", |mut region| region.constrain_equal(left, right)) } } @@ -237,7 +242,7 @@ fn criterion_benchmark(c: &mut Criterion) { &self, config: PlonkConfig, mut layouter: impl Layouter, - ) -> Result<(), Error> { + ) -> Result<(), ErrorFront> { let cs = StandardPlonk::new(config); for _ in 0..((1 << (self.k - 1)) - 3) { diff --git a/halo2_proofs/examples/circuit-layout.rs b/halo2_proofs/examples/circuit-layout.rs index b65adf5599..3d18a74786 100644 --- a/halo2_proofs/examples/circuit-layout.rs +++ b/halo2_proofs/examples/circuit-layout.rs @@ -1,7 +1,7 @@ use ff::Field; use halo2_proofs::{ circuit::{Cell, Layouter, Region, SimpleFloorPlanner, Value}, - plonk::{Advice, Assigned, Circuit, Column, ConstraintSystem, Error, Fixed, TableColumn}, + plonk::{Advice, Assigned, Circuit, Column, ConstraintSystem, ErrorFront, Fixed, TableColumn}, poly::Rotation, }; use halo2curves::pasta::Fp; @@ -28,14 +28,22 @@ struct PlonkConfig { } trait StandardCs { - fn raw_multiply(&self, region: &mut Region, f: F) -> Result<(Cell, Cell, Cell), Error> + fn raw_multiply( + &self, + region: &mut Region, + f: F, + ) -> Result<(Cell, Cell, Cell), ErrorFront> where F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; - fn raw_add(&self, region: &mut Region, f: F) -> Result<(Cell, Cell, Cell), Error> + fn raw_add(&self, region: &mut Region, f: F) -> Result<(Cell, Cell, Cell), ErrorFront> where F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; - fn copy(&self, region: &mut Region, a: Cell, b: Cell) -> Result<(), Error>; - fn lookup_table(&self, layouter: &mut impl Layouter, values: &[FF]) -> Result<(), Error>; + fn copy(&self, region: &mut Region, a: Cell, b: Cell) -> Result<(), ErrorFront>; + fn lookup_table( + &self, + layouter: &mut impl Layouter, + values: &[FF], + ) -> Result<(), ErrorFront>; } struct MyCircuit { @@ -62,7 +70,7 @@ impl StandardCs for StandardPlonk { &self, region: &mut Region, mut f: F, - ) -> Result<(Cell, Cell, Cell), Error> + ) -> Result<(Cell, Cell, Cell), ErrorFront> where F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, { @@ -99,7 +107,11 @@ impl StandardCs for StandardPlonk { region.assign_fixed(|| "a * b", self.config.sm, 0, || Value::known(FF::ONE))?; Ok((lhs.cell(), rhs.cell(), out.cell())) } - fn raw_add(&self, region: &mut Region, mut f: F) -> Result<(Cell, Cell, Cell), Error> + fn raw_add( + &self, + region: &mut Region, + mut f: F, + ) -> Result<(Cell, Cell, Cell), ErrorFront> where F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, { @@ -136,10 +148,14 @@ impl StandardCs for StandardPlonk { region.assign_fixed(|| "a * b", self.config.sm, 0, || Value::known(FF::ZERO))?; Ok((lhs.cell(), rhs.cell(), out.cell())) } - fn copy(&self, region: &mut Region, left: Cell, right: Cell) -> Result<(), Error> { + fn copy(&self, region: &mut Region, left: Cell, right: Cell) -> Result<(), ErrorFront> { region.constrain_equal(left, right) } - fn lookup_table(&self, layouter: &mut impl Layouter, values: &[FF]) -> Result<(), Error> { + fn lookup_table( + &self, + layouter: &mut impl Layouter, + values: &[FF], + ) -> Result<(), ErrorFront> { layouter.assign_table( || "", |mut table| { @@ -240,7 +256,11 @@ impl Circuit for MyCircuit { } } - fn synthesize(&self, config: PlonkConfig, mut layouter: impl Layouter) -> Result<(), Error> { + fn synthesize( + &self, + config: PlonkConfig, + mut layouter: impl Layouter, + ) -> Result<(), ErrorFront> { let cs = StandardPlonk::new(config); for i in 0..10 { diff --git a/halo2_proofs/examples/proof-size.rs b/halo2_proofs/examples/proof-size.rs index 3d5b242fb0..f1504e6f96 100644 --- a/halo2_proofs/examples/proof-size.rs +++ b/halo2_proofs/examples/proof-size.rs @@ -1,7 +1,7 @@ use ff::Field; use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner, Value}, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error}, + plonk::{Advice, Circuit, Column, ConstraintSystem, ErrorFront}, }; use halo2curves::pasta::Fp; @@ -47,7 +47,11 @@ impl Circuit for TestCircuit { config } - fn synthesize(&self, config: MyConfig, mut layouter: impl Layouter) -> Result<(), Error> { + fn synthesize( + &self, + config: MyConfig, + mut layouter: impl Layouter, + ) -> Result<(), ErrorFront> { layouter.assign_table( || "8-bit table", |mut table| { diff --git a/halo2_proofs/examples/serialization.rs b/halo2_proofs/examples/serialization.rs index 39b6b1192f..4a83392e37 100644 --- a/halo2_proofs/examples/serialization.rs +++ b/halo2_proofs/examples/serialization.rs @@ -7,8 +7,8 @@ use ff::Field; use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner, Value}, plonk::{ - create_proof, keygen_pk, keygen_vk, verify_proof, Advice, Circuit, Column, - ConstraintSystem, Error, Fixed, Instance, ProvingKey, + create_proof, keygen_pk, keygen_vk_custom, pk_read, verify_proof, Advice, Circuit, Column, + ConstraintSystem, ErrorFront, Fixed, Instance, }, poly::{ kzg::{ @@ -101,7 +101,7 @@ impl Circuit for StandardPlonk { &self, config: Self::Config, mut layouter: impl Layouter, - ) -> Result<(), Error> { + ) -> Result<(), ErrorFront> { layouter.assign_region( || "", |mut region| { @@ -132,7 +132,8 @@ fn main() { let k = 4; let circuit = StandardPlonk(Fr::random(OsRng)); let params = ParamsKZG::::setup(k, OsRng); - let vk = keygen_vk(¶ms, &circuit).expect("vk should not fail"); + let compress_selectors = true; + let vk = keygen_vk_custom(¶ms, &circuit, compress_selectors).expect("vk should not fail"); let pk = keygen_pk(¶ms, vk, &circuit).expect("pk should not fail"); let f = File::create("serialization-test.pk").unwrap(); @@ -143,9 +144,10 @@ fn main() { let f = File::open("serialization-test.pk").unwrap(); let mut reader = BufReader::new(f); #[allow(clippy::unit_arg)] - let pk = ProvingKey::::read::<_, StandardPlonk>( + let pk = pk_read::( &mut reader, SerdeFormat::RawBytes, + compress_selectors, #[cfg(feature = "circuit-params")] circuit.params(), ) diff --git a/halo2_proofs/examples/shuffle.rs b/halo2_proofs/examples/shuffle.rs index 35a85cb9f0..f61d17fd09 100644 --- a/halo2_proofs/examples/shuffle.rs +++ b/halo2_proofs/examples/shuffle.rs @@ -145,7 +145,7 @@ impl Circuit for MyCircuit &self, config: Self::Config, mut layouter: impl Layouter, - ) -> Result<(), Error> { + ) -> Result<(), ErrorFront> { let theta = layouter.get_challenge(config.theta); let gamma = layouter.get_challenge(config.gamma); diff --git a/halo2_proofs/examples/shuffle_api.rs b/halo2_proofs/examples/shuffle_api.rs index 259e038d06..f3080cd393 100644 --- a/halo2_proofs/examples/shuffle_api.rs +++ b/halo2_proofs/examples/shuffle_api.rs @@ -6,7 +6,7 @@ use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner, Value}, plonk::{ create_proof, keygen_pk, keygen_vk, verify_proof, Advice, Circuit, Column, - ConstraintSystem, Error, Fixed, Selector, + ConstraintSystem, ErrorFront, Fixed, Selector, }, poly::Rotation, poly::{ @@ -111,7 +111,7 @@ impl Circuit for MyCircuit { &self, config: Self::Config, mut layouter: impl Layouter, - ) -> Result<(), Error> { + ) -> Result<(), ErrorFront> { let ch = ShuffleChip::::construct(config); layouter.assign_region( || "load inputs", diff --git a/halo2_proofs/examples/simple-example.rs b/halo2_proofs/examples/simple-example.rs index 242257a692..a938065d57 100644 --- a/halo2_proofs/examples/simple-example.rs +++ b/halo2_proofs/examples/simple-example.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use halo2_proofs::{ arithmetic::Field, circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed, Instance, Selector}, + plonk::{Advice, Circuit, Column, ConstraintSystem, ErrorFront, Fixed, Instance, Selector}, poly::Rotation, }; @@ -13,10 +13,18 @@ trait NumericInstructions: Chip { type Num; /// Loads a number into the circuit as a private input. - fn load_private(&self, layouter: impl Layouter, a: Value) -> Result; + fn load_private( + &self, + layouter: impl Layouter, + a: Value, + ) -> Result; /// Loads a number into the circuit as a fixed constant. - fn load_constant(&self, layouter: impl Layouter, constant: F) -> Result; + fn load_constant( + &self, + layouter: impl Layouter, + constant: F, + ) -> Result; /// Returns `c = a * b`. fn mul( @@ -24,7 +32,7 @@ trait NumericInstructions: Chip { layouter: impl Layouter, a: Self::Num, b: Self::Num, - ) -> Result; + ) -> Result; /// Exposes a number as a public input to the circuit. fn expose_public( @@ -32,7 +40,7 @@ trait NumericInstructions: Chip { layouter: impl Layouter, num: Self::Num, row: usize, - ) -> Result<(), Error>; + ) -> Result<(), ErrorFront>; } // ANCHOR_END: instructions @@ -152,7 +160,7 @@ impl NumericInstructions for FieldChip { &self, mut layouter: impl Layouter, value: Value, - ) -> Result { + ) -> Result { let config = self.config(); layouter.assign_region( @@ -169,7 +177,7 @@ impl NumericInstructions for FieldChip { &self, mut layouter: impl Layouter, constant: F, - ) -> Result { + ) -> Result { let config = self.config(); layouter.assign_region( @@ -187,7 +195,7 @@ impl NumericInstructions for FieldChip { mut layouter: impl Layouter, a: Self::Num, b: Self::Num, - ) -> Result { + ) -> Result { let config = self.config(); layouter.assign_region( @@ -223,7 +231,7 @@ impl NumericInstructions for FieldChip { mut layouter: impl Layouter, num: Self::Num, row: usize, - ) -> Result<(), Error> { + ) -> Result<(), ErrorFront> { let config = self.config(); layouter.constrain_instance(num.0.cell(), config.instance, row) @@ -272,7 +280,7 @@ impl Circuit for MyCircuit { &self, config: Self::Config, mut layouter: impl Layouter, - ) -> Result<(), Error> { + ) -> Result<(), ErrorFront> { let field_chip = FieldChip::::construct(config); // Load our private values into the circuit. diff --git a/halo2_proofs/examples/two-chip.rs b/halo2_proofs/examples/two-chip.rs index 336f9c4957..b0b614cb60 100644 --- a/halo2_proofs/examples/two-chip.rs +++ b/halo2_proofs/examples/two-chip.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use halo2_proofs::{ arithmetic::Field, circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Instance, Selector}, + plonk::{Advice, Circuit, Column, ConstraintSystem, ErrorFront, Instance, Selector}, poly::Rotation, }; @@ -21,7 +21,7 @@ trait FieldInstructions: AddInstructions + MulInstructions { &self, layouter: impl Layouter, a: Value, - ) -> Result<>::Num, Error>; + ) -> Result<>::Num, ErrorFront>; /// Returns `d = (a + b) * c`. fn add_and_mul( @@ -30,7 +30,7 @@ trait FieldInstructions: AddInstructions + MulInstructions { a: >::Num, b: >::Num, c: >::Num, - ) -> Result<>::Num, Error>; + ) -> Result<>::Num, ErrorFront>; /// Exposes a number as a public input to the circuit. fn expose_public( @@ -38,7 +38,7 @@ trait FieldInstructions: AddInstructions + MulInstructions { layouter: impl Layouter, num: >::Num, row: usize, - ) -> Result<(), Error>; + ) -> Result<(), ErrorFront>; } // ANCHOR_END: field-instructions @@ -53,7 +53,7 @@ trait AddInstructions: Chip { layouter: impl Layouter, a: Self::Num, b: Self::Num, - ) -> Result; + ) -> Result; } // ANCHOR_END: add-instructions @@ -68,7 +68,7 @@ trait MulInstructions: Chip { layouter: impl Layouter, a: Self::Num, b: Self::Num, - ) -> Result; + ) -> Result; } // ANCHOR_END: mul-instructions @@ -181,7 +181,7 @@ impl AddInstructions for FieldChip { layouter: impl Layouter, a: Self::Num, b: Self::Num, - ) -> Result { + ) -> Result { let config = self.config().add_config.clone(); let add_chip = AddChip::::construct(config, ()); @@ -197,7 +197,7 @@ impl AddInstructions for AddChip { mut layouter: impl Layouter, a: Self::Num, b: Self::Num, - ) -> Result { + ) -> Result { let config = self.config(); layouter.assign_region( @@ -303,7 +303,7 @@ impl MulInstructions for FieldChip { layouter: impl Layouter, a: Self::Num, b: Self::Num, - ) -> Result { + ) -> Result { let config = self.config().mul_config.clone(); let mul_chip = MulChip::::construct(config, ()); mul_chip.mul(layouter, a, b) @@ -318,7 +318,7 @@ impl MulInstructions for MulChip { mut layouter: impl Layouter, a: Self::Num, b: Self::Num, - ) -> Result { + ) -> Result { let config = self.config(); layouter.assign_region( @@ -403,7 +403,7 @@ impl FieldInstructions for FieldChip { &self, mut layouter: impl Layouter, value: Value, - ) -> Result<>::Num, Error> { + ) -> Result<>::Num, ErrorFront> { let config = self.config(); layouter.assign_region( @@ -423,7 +423,7 @@ impl FieldInstructions for FieldChip { a: >::Num, b: >::Num, c: >::Num, - ) -> Result<>::Num, Error> { + ) -> Result<>::Num, ErrorFront> { let ab = self.add(layouter.namespace(|| "a + b"), a, b)?; self.mul(layouter.namespace(|| "(a + b) * c"), ab, c) } @@ -433,7 +433,7 @@ impl FieldInstructions for FieldChip { mut layouter: impl Layouter, num: >::Num, row: usize, - ) -> Result<(), Error> { + ) -> Result<(), ErrorFront> { let config = self.config(); layouter.constrain_instance(num.0.cell(), config.instance, row) @@ -479,7 +479,7 @@ impl Circuit for MyCircuit { &self, config: Self::Config, mut layouter: impl Layouter, - ) -> Result<(), Error> { + ) -> Result<(), ErrorFront> { let field_chip = FieldChip::::construct(config, ()); // Load our private values into the circuit. diff --git a/halo2_proofs/examples/vector-mul.rs b/halo2_proofs/examples/vector-mul.rs index 01728fdf36..c5e4dc577c 100644 --- a/halo2_proofs/examples/vector-mul.rs +++ b/halo2_proofs/examples/vector-mul.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use halo2_proofs::{ arithmetic::Field, circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Instance, Selector}, + plonk::{Advice, Circuit, Column, ConstraintSystem, ErrorFront, Instance, Selector}, poly::Rotation, }; @@ -17,7 +17,7 @@ trait NumericInstructions: Chip { &self, layouter: impl Layouter, a: &[Value], - ) -> Result, Error>; + ) -> Result, ErrorFront>; /// Returns `c = a * b`. The caller is responsible for ensuring that `a.len() == b.len()`. fn mul( @@ -25,7 +25,7 @@ trait NumericInstructions: Chip { layouter: impl Layouter, a: &[Self::Num], b: &[Self::Num], - ) -> Result, Error>; + ) -> Result, ErrorFront>; /// Exposes a number as a public input to the circuit. fn expose_public( @@ -33,7 +33,7 @@ trait NumericInstructions: Chip { layouter: impl Layouter, num: &Self::Num, row: usize, - ) -> Result<(), Error>; + ) -> Result<(), ErrorFront>; } // ANCHOR_END: instructions @@ -150,7 +150,7 @@ impl NumericInstructions for FieldChip { &self, mut layouter: impl Layouter, values: &[Value], - ) -> Result, Error> { + ) -> Result, ErrorFront> { let config = self.config(); layouter.assign_region( @@ -174,7 +174,7 @@ impl NumericInstructions for FieldChip { mut layouter: impl Layouter, a: &[Self::Num], b: &[Self::Num], - ) -> Result, Error> { + ) -> Result, ErrorFront> { let config = self.config(); assert_eq!(a.len(), b.len()); @@ -208,7 +208,7 @@ impl NumericInstructions for FieldChip { mut layouter: impl Layouter, num: &Self::Num, row: usize, - ) -> Result<(), Error> { + ) -> Result<(), ErrorFront> { let config = self.config(); layouter.constrain_instance(num.0.cell(), config.instance, row) @@ -257,7 +257,7 @@ impl Circuit for MyCircuit { &self, config: Self::Config, mut layouter: impl Layouter, - ) -> Result<(), Error> { + ) -> Result<(), ErrorFront> { let field_chip = FieldChip::::construct(config); // Load our private values into the circuit. diff --git a/halo2_proofs/examples/vector-ops-unblinded.rs b/halo2_proofs/examples/vector-ops-unblinded.rs index 7e9ebd1d81..386d6b34a3 100644 --- a/halo2_proofs/examples/vector-ops-unblinded.rs +++ b/halo2_proofs/examples/vector-ops-unblinded.rs @@ -33,7 +33,7 @@ trait NumericInstructions: Chip { &self, layouter: impl Layouter, a: &[Value], - ) -> Result, Error>; + ) -> Result, ErrorFront>; /// Returns `c = a * b`. The caller is responsible for ensuring that `a.len() == b.len()`. fn mul( @@ -41,7 +41,7 @@ trait NumericInstructions: Chip { layouter: impl Layouter, a: &[Self::Num], b: &[Self::Num], - ) -> Result, Error>; + ) -> Result, ErrorFront>; /// Returns `c = a + b`. The caller is responsible for ensuring that `a.len() == b.len()`. fn add( @@ -49,7 +49,7 @@ trait NumericInstructions: Chip { layouter: impl Layouter, a: &[Self::Num], b: &[Self::Num], - ) -> Result, Error>; + ) -> Result, ErrorFront>; /// Exposes a number as a public input to the circuit. fn expose_public( @@ -57,7 +57,7 @@ trait NumericInstructions: Chip { layouter: impl Layouter, num: &Self::Num, row: usize, - ) -> Result<(), Error>; + ) -> Result<(), ErrorFront>; } // ANCHOR_END: instructions @@ -204,7 +204,7 @@ impl NumericInstructions for MultChip { &self, mut layouter: impl Layouter, values: &[Value], - ) -> Result, Error> { + ) -> Result, ErrorFront> { let config = self.config(); layouter.assign_region( @@ -228,7 +228,7 @@ impl NumericInstructions for MultChip { _: impl Layouter, _: &[Self::Num], _: &[Self::Num], - ) -> Result, Error> { + ) -> Result, ErrorFront> { panic!("Not implemented") } @@ -237,7 +237,7 @@ impl NumericInstructions for MultChip { mut layouter: impl Layouter, a: &[Self::Num], b: &[Self::Num], - ) -> Result, Error> { + ) -> Result, ErrorFront> { let config = self.config(); assert_eq!(a.len(), b.len()); @@ -271,7 +271,7 @@ impl NumericInstructions for MultChip { mut layouter: impl Layouter, num: &Self::Num, row: usize, - ) -> Result<(), Error> { + ) -> Result<(), ErrorFront> { let config = self.config(); layouter.constrain_instance(num.0.cell(), config.instance, row) @@ -286,7 +286,7 @@ impl NumericInstructions for AddChip { &self, mut layouter: impl Layouter, values: &[Value], - ) -> Result, Error> { + ) -> Result, ErrorFront> { let config = self.config(); layouter.assign_region( @@ -310,7 +310,7 @@ impl NumericInstructions for AddChip { _: impl Layouter, _: &[Self::Num], _: &[Self::Num], - ) -> Result, Error> { + ) -> Result, ErrorFront> { panic!("Not implemented") } @@ -319,7 +319,7 @@ impl NumericInstructions for AddChip { mut layouter: impl Layouter, a: &[Self::Num], b: &[Self::Num], - ) -> Result, Error> { + ) -> Result, ErrorFront> { let config = self.config(); assert_eq!(a.len(), b.len()); @@ -353,7 +353,7 @@ impl NumericInstructions for AddChip { mut layouter: impl Layouter, num: &Self::Num, row: usize, - ) -> Result<(), Error> { + ) -> Result<(), ErrorFront> { let config = self.config(); layouter.constrain_instance(num.0.cell(), config.instance, row) @@ -395,7 +395,7 @@ impl Circuit for MulCircuit { &self, config: Self::Config, mut layouter: impl Layouter, - ) -> Result<(), Error> { + ) -> Result<(), ErrorFront> { let field_chip = MultChip::::construct(config); // Load our unblinded values into the circuit. @@ -448,7 +448,7 @@ impl Circuit for AddCircuit { &self, config: Self::Config, mut layouter: impl Layouter, - ) -> Result<(), Error> { + ) -> Result<(), ErrorFront> { let field_chip = AddChip::::construct(config); // Load our unblinded values into the circuit. diff --git a/halo2_proofs/src/lib.rs b/halo2_proofs/src/lib.rs index 72e41d7ccc..b1c0ac5744 100644 --- a/halo2_proofs/src/lib.rs +++ b/halo2_proofs/src/lib.rs @@ -15,8 +15,8 @@ pub mod plonk; /// Traits and structs for implementing circuit components. pub mod circuit { - pub use halo2_common::circuit::floor_planner; - pub use halo2_common::circuit::{ + pub use halo2_frontend::circuit::floor_planner; + pub use halo2_frontend::circuit::{ AssignedCell, Cell, Chip, Layouter, Region, SimpleFloorPlanner, Value, }; } @@ -47,8 +47,8 @@ pub mod poly { /// transcripts. pub mod transcript { pub use halo2_backend::transcript::{ - Blake2bRead, Blake2bWrite, Challenge255, EncodedChallenge, TranscriptReadBuffer, - TranscriptWriterBuffer, + Blake2bRead, Blake2bWrite, Challenge255, EncodedChallenge, TranscriptRead, + TranscriptReadBuffer, TranscriptWrite, TranscriptWriterBuffer, }; } mod helpers { diff --git a/halo2_proofs/src/plonk.rs b/halo2_proofs/src/plonk.rs index 1cfb9ec4e4..f5f16b5cc7 100644 --- a/halo2_proofs/src/plonk.rs +++ b/halo2_proofs/src/plonk.rs @@ -5,21 +5,86 @@ //! [halo]: https://eprint.iacr.org/2019/1021 //! [plonk]: https://eprint.iacr.org/2019/953 +mod error; mod keygen; mod prover; mod verifier { pub use halo2_backend::plonk::verifier::verify_proof; } -pub use keygen::{keygen_pk, keygen_vk}; +pub use keygen::{keygen_pk, keygen_vk, keygen_vk_custom}; pub use prover::create_proof; pub use verifier::verify_proof; -pub use halo2_backend::plonk::{ProvingKey, VerifyingKey}; -pub use halo2_common::plonk::{ - circuit::{Challenge, Column}, - Assigned, Circuit, ConstraintSystem, Error, Expression, FirstPhase, SecondPhase, Selector, - TableColumn, ThirdPhase, +pub use error::Error; +pub use halo2_backend::plonk::{Error as ErrorBack, ProvingKey, VerifyingKey}; +pub use halo2_frontend::plonk::{ + Assigned, Challenge, Circuit, Column, ConstraintSystem, Error as ErrorFront, Expression, + FirstPhase, SecondPhase, Selector, TableColumn, ThirdPhase, }; -pub use halo2_middleware::circuit::{Advice, Fixed, Instance}; +pub use halo2_middleware::circuit::{Advice, ConstraintSystemMid, Fixed, Instance}; + +use group::ff::FromUniformBytes; +use halo2_common::helpers::{SerdeCurveAffine, SerdePrimeField}; +use halo2_common::SerdeFormat; +use halo2_frontend::circuit::compile_circuit_cs; +use std::io; + +/// Reads a verification key from a buffer. +/// +/// Reads a curve element from the buffer and parses it according to the `format`: +/// - `Processed`: Reads a compressed curve element and decompresses it. +/// Reads a field element in standard form, with endianness specified by the +/// `PrimeField` implementation, and checks that the element is less than the modulus. +/// - `RawBytes`: Reads an uncompressed curve element with coordinates in Montgomery form. +/// Checks that field elements are less than modulus, and then checks that the point is on the curve. +/// - `RawBytesUnchecked`: Reads an uncompressed curve element with coordinates in Montgomery form; +/// does not perform any checks +pub fn vk_read>( + reader: &mut R, + format: SerdeFormat, + compress_selectors: bool, + #[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params, +) -> io::Result> +where + C::Scalar: SerdePrimeField + FromUniformBytes<64>, +{ + let (_, cs, _) = compile_circuit_cs::<_, ConcreteCircuit>( + compress_selectors, + #[cfg(feature = "circuit-params")] + params, + ); + let cs_mid: ConstraintSystemMid<_> = cs.into(); + VerifyingKey::read(reader, format, cs_mid.into()) +} + +/// Reads a proving key from a buffer. +/// Does so by reading verification key first, and then deserializing the rest of the file into the +/// remaining proving key data. +/// +/// Reads a curve element from the buffer and parses it according to the `format`: +/// - `Processed`: Reads a compressed curve element and decompresses it. +/// Reads a field element in standard form, with endianness specified by the +/// `PrimeField` implementation, and checks that the element is less than the modulus. +/// - `RawBytes`: Reads an uncompressed curve element with coordinates in Montgomery form. +/// Checks that field elements are less than modulus, and then checks that the point is on the curve. +/// - `RawBytesUnchecked`: Reads an uncompressed curve element with coordinates in Montgomery form; +/// does not perform any checks +pub fn pk_read>( + reader: &mut R, + format: SerdeFormat, + compress_selectors: bool, + #[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params, +) -> io::Result> +where + C::Scalar: SerdePrimeField + FromUniformBytes<64>, +{ + let (_, cs, _) = compile_circuit_cs::<_, ConcreteCircuit>( + compress_selectors, + #[cfg(feature = "circuit-params")] + params, + ); + let cs_mid: ConstraintSystemMid<_> = cs.into(); + ProvingKey::read(reader, format, cs_mid.into()) +} diff --git a/halo2_proofs/src/plonk/error.rs b/halo2_proofs/src/plonk/error.rs new file mode 100644 index 0000000000..a7a78a1ab2 --- /dev/null +++ b/halo2_proofs/src/plonk/error.rs @@ -0,0 +1,32 @@ +use super::{ErrorBack, ErrorFront}; +use std::fmt; + +/// This is an error that could occur during proving or circuit synthesis. +#[derive(Debug)] +pub enum Error { + /// Frontend error case + Frontend(ErrorFront), + /// Backend error case + Backend(ErrorBack), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::Frontend(err) => write!(f, "Frontend: {err}"), + Error::Backend(err) => write!(f, "Backend: {err}"), + } + } +} + +impl From for Error { + fn from(err: ErrorFront) -> Self { + Error::Frontend(err) + } +} + +impl From for Error { + fn from(err: ErrorBack) -> Self { + Error::Backend(err) + } +} diff --git a/halo2_proofs/src/plonk/keygen.rs b/halo2_proofs/src/plonk/keygen.rs index 7f40a2cfcf..3fc407315a 100644 --- a/halo2_proofs/src/plonk/keygen.rs +++ b/halo2_proofs/src/plonk/keygen.rs @@ -1,10 +1,11 @@ +use crate::plonk::Error; use halo2_backend::plonk::{ keygen::{keygen_pk_v2, keygen_vk_v2}, ProvingKey, VerifyingKey, }; use halo2_backend::{arithmetic::CurveAffine, poly::commitment::Params}; -use halo2_common::plonk::{circuit::Circuit, Error}; use halo2_frontend::circuit::compile_circuit; +use halo2_frontend::plonk::Circuit; use halo2_middleware::ff::FromUniformBytes; /// Generate a `VerifyingKey` from an instance of `Circuit`. @@ -38,7 +39,7 @@ where { let (compiled_circuit, _, _) = compile_circuit(params.k(), circuit, compress_selectors)?; let mut vk = keygen_vk_v2(params, &compiled_circuit)?; - vk.compress_selectors = compress_selectors; + vk.compress_selectors = Some(compress_selectors); Ok(vk) } @@ -53,6 +54,10 @@ where P: Params<'params, C>, ConcreteCircuit: Circuit, { - let (compiled_circuit, _, _) = compile_circuit(params.k(), circuit, vk.compress_selectors)?; - keygen_pk_v2(params, vk, &compiled_circuit) + let (compiled_circuit, _, _) = compile_circuit( + params.k(), + circuit, + vk.compress_selectors.unwrap_or_default(), + )?; + Ok(keygen_pk_v2(params, vk, &compiled_circuit)?) } diff --git a/halo2_proofs/src/plonk/prover.rs b/halo2_proofs/src/plonk/prover.rs index e46f108eb9..cd4524c364 100644 --- a/halo2_proofs/src/plonk/prover.rs +++ b/halo2_proofs/src/plonk/prover.rs @@ -1,8 +1,9 @@ +use crate::plonk::{Error, ErrorBack}; use crate::poly::commitment::{CommitmentScheme, Params, Prover}; +use crate::transcript::{EncodedChallenge, TranscriptWrite}; use halo2_backend::plonk::{prover::ProverV2, ProvingKey}; -use halo2_backend::transcript::{EncodedChallenge, TranscriptWrite}; -use halo2_common::plonk::{circuit::Circuit, Error}; -use halo2_frontend::circuit::{compile_circuit, WitnessCalculator}; +use halo2_frontend::circuit::{compile_circuit_cs, WitnessCalculator}; +use halo2_frontend::plonk::Circuit; use halo2_middleware::ff::{FromUniformBytes, WithSmallOrderMulGroup}; use rand_core::RngCore; use std::collections::HashMap; @@ -31,10 +32,13 @@ where Scheme::Scalar: WithSmallOrderMulGroup<3> + FromUniformBytes<64>, { if circuits.len() != instances.len() { - return Err(Error::InvalidInstances); + return Err(Error::Backend(ErrorBack::InvalidInstances)); } - let (_, config, cs) = - compile_circuit(params.k(), &circuits[0], pk.get_vk().compress_selectors)?; + let (config, cs, _) = compile_circuit_cs::<_, ConcreteCircuit>( + pk.get_vk().compress_selectors.unwrap_or_default(), + #[cfg(feature = "circuit-params")] + circuits[0].params(), + ); let mut witness_calcs: Vec<_> = circuits .iter() .enumerate() @@ -46,18 +50,18 @@ where for phase in phases.iter() { let mut witnesses = Vec::with_capacity(circuits.len()); for witness_calc in witness_calcs.iter_mut() { - witnesses.push(witness_calc.calc(phase.0, &challenges)?); + witnesses.push(witness_calc.calc(*phase, &challenges)?); } - challenges = prover.commit_phase(phase.0, witnesses).unwrap(); + challenges = prover.commit_phase(*phase, witnesses).unwrap(); } - prover.create_proof() + Ok(prover.create_proof()?) } #[test] fn test_create_proof() { use crate::{ circuit::SimpleFloorPlanner, - plonk::{keygen_pk, keygen_vk, ConstraintSystem}, + plonk::{keygen_pk, keygen_vk, ConstraintSystem, ErrorFront}, poly::kzg::{ commitment::{KZGCommitmentScheme, ParamsKZG}, multiopen::ProverSHPLONK, @@ -87,7 +91,7 @@ fn test_create_proof() { &self, _config: Self::Config, _layouter: impl crate::circuit::Layouter, - ) -> Result<(), Error> { + ) -> Result<(), ErrorFront> { Ok(()) } } @@ -106,7 +110,10 @@ fn test_create_proof() { OsRng, &mut transcript, ); - assert!(matches!(proof.unwrap_err(), Error::InvalidInstances)); + assert!(matches!( + proof.unwrap_err(), + Error::Backend(ErrorBack::InvalidInstances) + )); // Create proof with correct number of instances create_proof::, ProverSHPLONK<_>, _, _, _, _>( diff --git a/halo2_proofs/tests/frontend_backend_split.rs b/halo2_proofs/tests/frontend_backend_split.rs index f67e2201ca..e67f0acade 100644 --- a/halo2_proofs/tests/frontend_backend_split.rs +++ b/halo2_proofs/tests/frontend_backend_split.rs @@ -15,17 +15,18 @@ use halo2_backend::{ Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, }, }; -use halo2_common::{ - circuit::{AssignedCell, Layouter, Region, SimpleFloorPlanner, Value}, +use halo2_frontend::{ + circuit::{ + compile_circuit, AssignedCell, Layouter, Region, SimpleFloorPlanner, Value, + WitnessCalculator, + }, + dev::MockProver, plonk::{ circuit::{Challenge, Column}, - Circuit, ConstraintSystem, Error, Expression, FirstPhase, SecondPhase, Selector, + Circuit, ConstraintSystem, Error as ErrorFront, Expression, FirstPhase, SecondPhase, + Selector, }, }; -use halo2_frontend::{ - circuit::{compile_circuit, WitnessCalculator}, - dev::MockProver, -}; use halo2_middleware::{ circuit::{Advice, Fixed, Instance}, ff::Field, @@ -74,7 +75,7 @@ impl MyCircuitConfig { offset: &mut usize, a_assigned: Option>, abcd: [u64; 4], - ) -> Result<(AssignedCell, [AssignedCell; 4]), Error> { + ) -> Result<(AssignedCell, [AssignedCell; 4]), ErrorFront> { let [a, b, c, d] = abcd; self.s_gate.enable(region, *offset)?; let a_assigned = if let Some(a_assigned) = a_assigned { @@ -139,7 +140,7 @@ impl, const WIDTH_FACTOR: usize> MyCircuit (0..WIDTH_FACTOR).map(|_| instance.clone()).collect() } - fn configure_single(meta: &mut ConstraintSystem) -> MyCircuitConfig { + fn configure_single(meta: &mut ConstraintSystem, id: usize) -> MyCircuitConfig { let s_gate = meta.selector(); let a = meta.advice_column(); let b = meta.advice_column(); @@ -166,7 +167,7 @@ impl, const WIDTH_FACTOR: usize> MyCircuit let one = Expression::Constant(F::ONE); - meta.create_gate("gate_a", |meta| { + meta.create_gate(format!("gate_a.{id}"), |meta| { let s_gate = meta.query_selector(s_gate); let b = meta.query_advice(b, Rotation::cur()); let a1 = meta.query_advice(a, Rotation::next()); @@ -177,7 +178,7 @@ impl, const WIDTH_FACTOR: usize> MyCircuit vec![s_gate * (a0 + b * c * d - a1)] }); - meta.lookup_any("lookup", |meta| { + meta.lookup_any(format!("lookup.{id}"), |meta| { let s_lookup = meta.query_fixed(s_lookup, Rotation::cur()); let s_ltable = meta.query_fixed(s_ltable, Rotation::cur()); let a = meta.query_advice(a, Rotation::cur()); @@ -189,7 +190,7 @@ impl, const WIDTH_FACTOR: usize> MyCircuit lhs.into_iter().zip(rhs).collect() }); - meta.shuffle("shuffle", |meta| { + meta.shuffle(format!("shuffle.{id}"), |meta| { let s_shuffle = meta.query_fixed(s_shuffle, Rotation::cur()); let s_stable = meta.query_fixed(s_stable, Rotation::cur()); let a = meta.query_advice(a, Rotation::cur()); @@ -199,7 +200,7 @@ impl, const WIDTH_FACTOR: usize> MyCircuit lhs.into_iter().zip(rhs).collect() }); - meta.create_gate("gate_rlc", |meta| { + meta.create_gate(format!("gate_rlc.{id}"), |meta| { let s_rlc = meta.query_selector(s_rlc); let a = meta.query_advice(a, Rotation::cur()); let b = meta.query_advice(b, Rotation::cur()); @@ -236,11 +237,25 @@ impl, const WIDTH_FACTOR: usize> MyCircuit &self, config: &MyCircuitConfig, layouter: &mut impl Layouter, - ) -> Result<(usize, Vec>), Error> { + id: usize, + unit_id: usize, + ) -> Result<(usize, Vec>), ErrorFront> { let challenge = layouter.get_challenge(config.challenge); let (rows, instance_copy) = layouter.assign_region( - || "unit", + || format!("unit.{id}-{unit_id}"), |mut region| { + // Column annotations + region.name_column(|| format!("a.{id}"), config.a); + region.name_column(|| format!("b.{id}"), config.b); + region.name_column(|| format!("c.{id}"), config.c); + region.name_column(|| format!("d.{id}"), config.d); + region.name_column(|| format!("e.{id}"), config.e); + region.name_column(|| format!("instance.{id}"), config.instance); + region.name_column(|| format!("s_lookup.{id}"), config.s_lookup); + region.name_column(|| format!("s_ltable.{id}"), config.s_ltable); + region.name_column(|| format!("s_shuffle.{id}"), config.s_shuffle); + region.name_column(|| format!("s_stable.{id}"), config.s_stable); + let mut offset = 0; let mut instance_copy = Vec::new(); // First "a" value comes from instance @@ -416,7 +431,7 @@ impl, const WIDTH_FACTOR: usize> Circuit for MyCircuit) -> Vec { assert!(WIDTH_FACTOR > 0); (0..WIDTH_FACTOR) - .map(|_| Self::configure_single(meta)) + .map(|id| Self::configure_single(meta, id)) .collect() } @@ -424,7 +439,7 @@ impl, const WIDTH_FACTOR: usize> Circuit for MyCircuit, mut layouter: impl Layouter, - ) -> Result<(), Error> { + ) -> Result<(), ErrorFront> { // - 2 queries from first gate // - 3 for permutation argument // - 1 for multipoen @@ -432,11 +447,13 @@ impl, const WIDTH_FACTOR: usize> Circuit for MyCircuit, const WIDTH_FACTOR: usize> Circuit for MyCircuit max_rows { break; } + unit_id += 1; } assert!(total_rows <= max_rows); } @@ -486,10 +504,8 @@ fn test_mycircuit_mock() { use std::time::Instant; -// const K: u32 = 8; -// const WIDTH_FACTOR: usize = 1; -const K: u32 = 16; -const WIDTH_FACTOR: usize = 4; +const K: u32 = 6; +const WIDTH_FACTOR: usize = 1; #[test] fn test_mycircuit_full_legacy() { diff --git a/halo2_proofs/tests/plonk_api.rs b/halo2_proofs/tests/plonk_api.rs index c3e21112aa..01643e3352 100644 --- a/halo2_proofs/tests/plonk_api.rs +++ b/halo2_proofs/tests/plonk_api.rs @@ -8,8 +8,8 @@ use halo2_proofs::circuit::{Cell, Layouter, SimpleFloorPlanner, Value}; use halo2_proofs::dev::MockProver; use halo2_proofs::plonk::{ create_proof as create_plonk_proof, keygen_pk, keygen_vk, verify_proof as verify_plonk_proof, - Advice, Assigned, Circuit, Column, ConstraintSystem, Error, Fixed, ProvingKey, TableColumn, - VerifyingKey, + Advice, Assigned, Circuit, Column, ConstraintSystem, Error, ErrorFront, Fixed, ProvingKey, + TableColumn, VerifyingKey, }; use halo2_proofs::poly::commitment::{CommitmentScheme, ParamsProver, Prover, Verifier}; use halo2_proofs::poly::Rotation; @@ -51,25 +51,34 @@ fn plonk_api() { &self, layouter: &mut impl Layouter, f: F, - ) -> Result<(Cell, Cell, Cell), Error> + ) -> Result<(Cell, Cell, Cell), ErrorFront> where F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; fn raw_add( &self, layouter: &mut impl Layouter, f: F, - ) -> Result<(Cell, Cell, Cell), Error> + ) -> Result<(Cell, Cell, Cell), ErrorFront> where F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; - fn copy(&self, layouter: &mut impl Layouter, a: Cell, b: Cell) -> Result<(), Error>; - fn public_input(&self, layouter: &mut impl Layouter, f: F) -> Result + fn copy( + &self, + layouter: &mut impl Layouter, + a: Cell, + b: Cell, + ) -> Result<(), ErrorFront>; + fn public_input( + &self, + layouter: &mut impl Layouter, + f: F, + ) -> Result where F: FnMut() -> Value; fn lookup_table( &self, layouter: &mut impl Layouter, values: &[FF], - ) -> Result<(), Error>; + ) -> Result<(), ErrorFront>; } #[derive(Clone)] @@ -97,7 +106,7 @@ fn plonk_api() { &self, layouter: &mut impl Layouter, mut f: F, - ) -> Result<(Cell, Cell, Cell), Error> + ) -> Result<(Cell, Cell, Cell), ErrorFront> where F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, { @@ -151,7 +160,7 @@ fn plonk_api() { &self, layouter: &mut impl Layouter, mut f: F, - ) -> Result<(Cell, Cell, Cell), Error> + ) -> Result<(Cell, Cell, Cell), ErrorFront> where F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, { @@ -211,7 +220,7 @@ fn plonk_api() { layouter: &mut impl Layouter, left: Cell, right: Cell, - ) -> Result<(), Error> { + ) -> Result<(), ErrorFront> { layouter.assign_region( || "copy", |mut region| { @@ -220,7 +229,11 @@ fn plonk_api() { }, ) } - fn public_input(&self, layouter: &mut impl Layouter, mut f: F) -> Result + fn public_input( + &self, + layouter: &mut impl Layouter, + mut f: F, + ) -> Result where F: FnMut() -> Value, { @@ -243,7 +256,7 @@ fn plonk_api() { &self, layouter: &mut impl Layouter, values: &[FF], - ) -> Result<(), Error> { + ) -> Result<(), ErrorFront> { layouter.assign_table( || "", |mut table| { @@ -369,7 +382,7 @@ fn plonk_api() { &self, config: PlonkConfig, mut layouter: impl Layouter, - ) -> Result<(), Error> { + ) -> Result<(), ErrorFront> { let cs = StandardPlonk::new(config); let _ = cs.public_input(&mut layouter, || Value::known(F::ONE + F::ONE))?; @@ -421,9 +434,9 @@ fn plonk_api() { let much_too_small_params= <$scheme as CommitmentScheme>::ParamsProver::new(1); assert_matches!( keygen_vk(&much_too_small_params, &empty_circuit), - Err(Error::NotEnoughRowsAvailable { + Err(Error::Frontend(ErrorFront::NotEnoughRowsAvailable { current_k, - }) if current_k == 1 + })) if current_k == 1 ); // Check that we get an error if we try to initialize the proving key with a value of @@ -431,9 +444,9 @@ fn plonk_api() { let slightly_too_small_params = <$scheme as CommitmentScheme>::ParamsProver::new(K-1); assert_matches!( keygen_vk(&slightly_too_small_params, &empty_circuit), - Err(Error::NotEnoughRowsAvailable { + Err(Error::Frontend(ErrorFront::NotEnoughRowsAvailable { current_k, - }) if current_k == K - 1 + })) if current_k == K - 1 ); }}; } @@ -619,7 +632,7 @@ fn plonk_api() { // Check that the verification key has not changed unexpectedly { - //panic!("{:#?}", pk.get_vk().pinned()); + // panic!("{:#?}", pk.get_vk().pinned()); assert_eq!( format!("{:#?}", pk.get_vk().pinned()), r#"PinnedVerificationKey { @@ -634,147 +647,221 @@ fn plonk_api() { num_fixed_columns: 7, num_advice_columns: 5, num_instance_columns: 1, - num_selectors: 0, gates: [ Sum( Sum( Sum( Sum( Product( - Advice { - query_index: 0, - column_index: 1, - rotation: Rotation( - 0, + Var( + Query( + QueryBack { + index: 0, + column_index: 1, + column_type: Advice, + rotation: Rotation( + 0, + ), + }, ), - }, - Fixed { - query_index: 0, - column_index: 2, - rotation: Rotation( - 0, + ), + Var( + Query( + QueryBack { + index: 0, + column_index: 2, + column_type: Fixed, + rotation: Rotation( + 0, + ), + }, ), - }, + ), ), Product( - Advice { - query_index: 1, - column_index: 2, - rotation: Rotation( - 0, + Var( + Query( + QueryBack { + index: 1, + column_index: 2, + column_type: Advice, + rotation: Rotation( + 0, + ), + }, ), - }, - Fixed { - query_index: 1, - column_index: 3, - rotation: Rotation( - 0, + ), + Var( + Query( + QueryBack { + index: 1, + column_index: 3, + column_type: Fixed, + rotation: Rotation( + 0, + ), + }, ), - }, + ), ), ), Product( Product( - Advice { - query_index: 0, - column_index: 1, - rotation: Rotation( - 0, + Var( + Query( + QueryBack { + index: 0, + column_index: 1, + column_type: Advice, + rotation: Rotation( + 0, + ), + }, ), - }, - Advice { - query_index: 1, - column_index: 2, - rotation: Rotation( - 0, + ), + Var( + Query( + QueryBack { + index: 1, + column_index: 2, + column_type: Advice, + rotation: Rotation( + 0, + ), + }, ), - }, + ), ), - Fixed { - query_index: 2, - column_index: 1, - rotation: Rotation( - 0, + Var( + Query( + QueryBack { + index: 2, + column_index: 1, + column_type: Fixed, + rotation: Rotation( + 0, + ), + }, ), - }, + ), ), ), Negated( Product( - Advice { - query_index: 2, - column_index: 3, - rotation: Rotation( - 0, + Var( + Query( + QueryBack { + index: 2, + column_index: 3, + column_type: Advice, + rotation: Rotation( + 0, + ), + }, ), - }, - Fixed { - query_index: 3, - column_index: 4, - rotation: Rotation( - 0, + ), + Var( + Query( + QueryBack { + index: 3, + column_index: 4, + column_type: Fixed, + rotation: Rotation( + 0, + ), + }, ), - }, + ), ), ), ), Product( - Fixed { - query_index: 4, - column_index: 0, - rotation: Rotation( - 0, + Var( + Query( + QueryBack { + index: 4, + column_index: 0, + column_type: Fixed, + rotation: Rotation( + 0, + ), + }, ), - }, + ), Product( - Advice { - query_index: 3, - column_index: 4, - rotation: Rotation( - 1, + Var( + Query( + QueryBack { + index: 3, + column_index: 4, + column_type: Advice, + rotation: Rotation( + 1, + ), + }, ), - }, - Advice { - query_index: 4, - column_index: 0, - rotation: Rotation( - -1, + ), + Var( + Query( + QueryBack { + index: 4, + column_index: 0, + column_type: Advice, + rotation: Rotation( + -1, + ), + }, ), - }, + ), ), ), ), Product( - Fixed { - query_index: 5, - column_index: 5, - rotation: Rotation( - 0, + Var( + Query( + QueryBack { + index: 5, + column_index: 5, + column_type: Fixed, + rotation: Rotation( + 0, + ), + }, ), - }, + ), Sum( - Advice { - query_index: 0, - column_index: 1, - rotation: Rotation( - 0, + Var( + Query( + QueryBack { + index: 0, + column_index: 1, + column_type: Advice, + rotation: Rotation( + 0, + ), + }, ), - }, + ), Negated( - Instance { - query_index: 0, - column_index: 0, - rotation: Rotation( - 0, + Var( + Query( + QueryBack { + index: 0, + column_index: 0, + column_type: Instance, + rotation: Rotation( + 0, + ), + }, ), - }, + ), ), ), ), ], advice_queries: [ ( - Column { + ColumnMid { index: 1, column_type: Advice, }, @@ -783,7 +870,7 @@ fn plonk_api() { ), ), ( - Column { + ColumnMid { index: 2, column_type: Advice, }, @@ -792,7 +879,7 @@ fn plonk_api() { ), ), ( - Column { + ColumnMid { index: 3, column_type: Advice, }, @@ -801,7 +888,7 @@ fn plonk_api() { ), ), ( - Column { + ColumnMid { index: 4, column_type: Advice, }, @@ -810,7 +897,7 @@ fn plonk_api() { ), ), ( - Column { + ColumnMid { index: 0, column_type: Advice, }, @@ -819,7 +906,7 @@ fn plonk_api() { ), ), ( - Column { + ColumnMid { index: 0, column_type: Advice, }, @@ -828,7 +915,7 @@ fn plonk_api() { ), ), ( - Column { + ColumnMid { index: 4, column_type: Advice, }, @@ -839,7 +926,7 @@ fn plonk_api() { ], instance_queries: [ ( - Column { + ColumnMid { index: 0, column_type: Instance, }, @@ -850,7 +937,7 @@ fn plonk_api() { ], fixed_queries: [ ( - Column { + ColumnMid { index: 2, column_type: Fixed, }, @@ -859,7 +946,7 @@ fn plonk_api() { ), ), ( - Column { + ColumnMid { index: 3, column_type: Fixed, }, @@ -868,7 +955,7 @@ fn plonk_api() { ), ), ( - Column { + ColumnMid { index: 1, column_type: Fixed, }, @@ -877,7 +964,7 @@ fn plonk_api() { ), ), ( - Column { + ColumnMid { index: 4, column_type: Fixed, }, @@ -886,7 +973,7 @@ fn plonk_api() { ), ), ( - Column { + ColumnMid { index: 0, column_type: Fixed, }, @@ -895,7 +982,7 @@ fn plonk_api() { ), ), ( - Column { + ColumnMid { index: 5, column_type: Fixed, }, @@ -904,7 +991,7 @@ fn plonk_api() { ), ), ( - Column { + ColumnMid { index: 6, column_type: Fixed, }, @@ -913,53 +1000,53 @@ fn plonk_api() { ), ), ], - permutation: Argument { + permutation: ArgumentMid { columns: [ - Column { + ColumnMid { index: 1, column_type: Advice, }, - Column { + ColumnMid { index: 2, column_type: Advice, }, - Column { + ColumnMid { index: 3, column_type: Advice, }, - Column { + ColumnMid { index: 0, column_type: Fixed, }, - Column { + ColumnMid { index: 0, column_type: Advice, }, - Column { + ColumnMid { index: 4, column_type: Advice, }, - Column { + ColumnMid { index: 0, column_type: Instance, }, - Column { + ColumnMid { index: 1, column_type: Fixed, }, - Column { + ColumnMid { index: 2, column_type: Fixed, }, - Column { + ColumnMid { index: 3, column_type: Fixed, }, - Column { + ColumnMid { index: 4, column_type: Fixed, }, - Column { + ColumnMid { index: 5, column_type: Fixed, }, @@ -967,27 +1054,37 @@ fn plonk_api() { }, lookups: [ Argument { + name: "lookup", input_expressions: [ - Advice { - query_index: 0, - column_index: 1, - rotation: Rotation( - 0, + Var( + Query( + QueryBack { + index: 0, + column_index: 1, + column_type: Advice, + rotation: Rotation( + 0, + ), + }, ), - }, + ), ], table_expressions: [ - Fixed { - query_index: 6, - column_index: 6, - rotation: Rotation( - 0, + Var( + Query( + QueryBack { + index: 6, + column_index: 6, + column_type: Fixed, + rotation: Rotation( + 0, + ), + }, ), - }, + ), ], }, ], - constants: [], minimum_degree: None, }, fixed_commitments: [