Remove dependency to common ConstraintSystem in the backend (#290)

* refactor: generalize ExpressionMid

* refactor: move ConstraintSystem::from(ConstraintSystemV2Backend) to backend

* refactor: generalize Expression type

* feat: define ExpressionMid as an alias

* refactor: rename ConstraintSystemV2Back to ConstraintSystemMid, GateV2Back to GateMid

* refactor: use ConstraintSystemBack in halo2_backend

* fix: warnings

* refactor: simplify Query type

* feat(backend): rewrite pk/vk serialization

- Rewrite VerifyingKey and ProvingKey methods in terms of the
  ConstraintSystemBack so that the backend becomes independent of the
  Circuit trait (which belongs to the frontend)
- Add `vk_read` and `pk_read` legacy functions in halo2_proofs for
  compatiblity.
- Split the implementation of converting selectors to fixed columns into
  two parts:
  - One part just converts the ConstraintSystem, transforming the
    selectors into fixed columns (compressed and direct versions)
  - The other part transforms the assignments of selector columns into
    assignments of fixed columns based on the mappings calculated in
    part one.

* feat: remove feature circuit-params from halo2_backend

* wip: clean up common+backend

* wip: clean up frontend

* wip: clean common plonk folder

* refactor: move Error to frontend

* refactor: move Error to backend

* refactor: clean up error types

* fix: use errors instead of temporary panics

* feat: annotate columns in test

* fix: remove unnecessary pub, set correct SelectorsToFixed.compressed value, add safety check

In selectors_to_fixed_compressed and selectors_to_fixed_direct add
safety check via the ConstraintSystem.selectors_to_fixed status field
such that the methods can only be called once.  Calling them multiple
times would lead to an invalid ConstraintSystem with unused duplicated
fixed columns.

* fix: ponk_api unit tests

* fix: clippy warnings, common dependencies

* fix: remove old TODO

* chore: bump VK version

* chore: remove unnecessary code

* chore: remove virtual variable rules in middleware Expression

* chore: remove unused evaluate_lazy, deprecate directly_convert_selectors_to_fixed
This commit is contained in:
Eduard S 2024-03-18 11:39:06 +01:00 committed by GitHub
parent d6f70203d2
commit e1f6e41757
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
90 changed files with 6979 additions and 6214 deletions

View File

@ -6,4 +6,5 @@ members = [
"halo2_middleware",
"halo2_backend",
"halo2_common",
]
]
resolver = "2"

View File

@ -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"]

View File

@ -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<R: io::Read, F: SerdePrimeField, B>(

View File

@ -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;

View File

@ -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<C: CurveAffine> {
/// Permutation verifying key
permutation: permutation::VerifyingKey<C>,
/// Constraint system
cs: ConstraintSystem<C::Scalar>,
cs: ConstraintSystemBack<C::Scalar>,
/// 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<Vec<bool>>,
// 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<bool>,
}
// Current version of the VK
const VERSION: u8 = 0x03;
const VERSION: u8 = 0x04;
impl<C: SerdeCurveAffine> VerifyingKey<C>
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<R: io::Read, ConcreteCircuit: Circuit<C::Scalar>>(
pub fn read<R: io::Read>(
reader: &mut R,
format: SerdeFormat,
#[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params,
cs: ConstraintSystemBack<C::Scalar>,
) -> io::Result<Self> {
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::<C, ConcreteCircuit>(
k as u32,
#[cfg(feature = "circuit-params")]
params,
);
let domain = keygen::create_domain::<C>(&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<bool>> = 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::<io::Result<_>>()?;
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<ConcreteCircuit: Circuit<C::Scalar>>(
pub fn from_bytes(
mut bytes: &[u8],
format: SerdeFormat,
#[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params,
cs: ConstraintSystemBack<C::Scalar>,
) -> io::Result<Self> {
Self::read::<_, ConcreteCircuit>(
&mut bytes,
format,
#[cfg(feature = "circuit-params")]
params,
)
Self::read(&mut bytes, format, cs)
}
}
@ -216,21 +158,13 @@ impl<C: CurveAffine> VerifyingKey<C> {
{
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<C::Scalar>,
fixed_commitments: Vec<C>,
permutation: permutation::VerifyingKey<C>,
cs: ConstraintSystem<C::Scalar>,
selectors: Vec<Vec<bool>>,
compress_selectors: bool,
cs: ConstraintSystemBack<C::Scalar>,
) -> Self
where
C::ScalarExt: FromUniformBytes<64>,
@ -246,8 +180,7 @@ impl<C: CurveAffine> VerifyingKey<C> {
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<C: CurveAffine> VerifyingKey<C> {
}
/// Returns `ConstraintSystem`
pub fn cs(&self) -> &ConstraintSystem<C::Scalar> {
pub fn cs(&self) -> &ConstraintSystemBack<C::Scalar> {
&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<R: io::Read, ConcreteCircuit: Circuit<C::Scalar>>(
pub fn read<R: io::Read>(
reader: &mut R,
format: SerdeFormat,
#[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params,
cs: ConstraintSystemBack<C::Scalar>,
) -> io::Result<Self> {
let vk = VerifyingKey::<C>::read::<R, ConcreteCircuit>(
reader,
format,
#[cfg(feature = "circuit-params")]
params,
)?;
let vk = VerifyingKey::<C>::read::<R>(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<ConcreteCircuit: Circuit<C::Scalar>>(
pub fn from_bytes(
mut bytes: &[u8],
format: SerdeFormat,
#[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params,
cs: ConstraintSystemBack<C::Scalar>,
) -> io::Result<Self> {
Self::read::<_, ConcreteCircuit>(
&mut bytes,
format,
#[cfg(feature = "circuit-params")]
params,
)
Self::read(&mut bytes, format, cs)
}
}

View File

@ -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<W: std::io::Write>(&self, _writer: &mut W) -> std::io::Result<()> {
unimplemented!("unused method")
}
}
pub(crate) type ExpressionBack<F> = Expression<F, VarBack>;
pub(crate) type GateBack<F> = Gate<F, VarBack>;
pub(crate) type LookupArgumentBack<F> = lookup::Argument<F, VarBack>;
pub(crate) type ShuffleArgumentBack<F> = shuffle::Argument<F, VarBack>;
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<F: Field> {
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<usize>,
/// Contains the phase for each advice column. Should have same length as num_advice_columns.
pub(crate) advice_column_phase: Vec<u8>,
/// Contains the phase for each challenge. Should have same length as num_challenges.
pub(crate) challenge_phase: Vec<u8>,
pub(crate) gates: Vec<GateBack<F>>,
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<usize>,
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<LookupArgumentBack<F>>,
// 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<ShuffleArgumentBack<F>>,
// 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<usize>,
}
impl<F: Field> ConstraintSystemBack<F> {
/// 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<Item = u8> {
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<GateBack<F>>);
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<u8>,
challenge_phase: &'a Vec<u8>,
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<LookupArgumentBack<F>>,
shuffles: &'a Vec<ShuffleArgumentBack<F>>,
minimum_degree: &'a Option<usize>,
}
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<F: Field, V: Variable>(arg: &lookup::Argument<F, V>) -> 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<F: Field, V: Variable>(arg: &shuffle::Argument<F, V>) -> 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)
}

View File

@ -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<io::Error> 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,
}
}
}

View File

@ -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<C: CurveAffine> Evaluator<C> {
/// Creates a new evaluation structure from a [`ConstraintSystem`]
pub fn new(cs: &ConstraintSystem<C::ScalarExt>) -> Self {
/// Creates a new evaluation structure from a [`ConstraintSystemBack`]
pub fn new(cs: &ConstraintSystemBack<C::ScalarExt>) -> 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<C: CurveAffine> Evaluator<C> {
for lookup in cs.lookups.iter() {
let mut graph = GraphEvaluator::default();
let mut evaluate_lc = |expressions: &Vec<Expression<_>>| {
let mut evaluate_lc = |expressions: &Vec<ExpressionBack<_>>| {
let parts = expressions
.iter()
.map(|expr| graph.add_expression(expr))
@ -280,7 +277,8 @@ impl<C: CurveAffine> Evaluator<C> {
// Shuffles
for shuffle in cs.shuffles.iter() {
let evaluate_lc = |expressions: &Vec<Expression<_>>, graph: &mut GraphEvaluator<C>| {
let evaluate_lc = |expressions: &Vec<ExpressionBack<_>>,
graph: &mut GraphEvaluator<C>| {
let parts = expressions
.iter()
.map(|expr| graph.add_expression(expr))
@ -457,10 +455,10 @@ impl<C: CurveAffine> Evaluator<C> {
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<C: CurveAffine> Evaluator<C> {
}
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<C: CurveAffine> GraphEvaluator<C> {
}
/// Generates an optimized evaluation for the expression
fn add_expression(&mut self, expr: &Expression<C::ScalarExt>) -> ValueSource {
fn add_expression(&mut self, expr: &ExpressionBack<C::ScalarExt>) -> ValueSource {
match expr {
Expression::Constant(scalar) => self.add_constant(scalar),
Expression::Selector(_selector) => unreachable!(),
Expression::Fixed(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::Fixed(
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::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) => {
let rot_idx = self.add_rotation(&query.rotation);
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<C: CurveAffine> GraphEvaluator<C> {
}
}
},
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<C: CurveAffine> GraphEvaluator<C> {
}
}
}
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<C: CurveAffine> GraphEvaluator<C> {
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<C: CurveAffine> GraphEvaluator<C> {
}
}
/// Simple evaluation of an [`Expression`] over the provided lagrange polynomials
/// Simple evaluation of an [`ExpressionBack`] over the provided lagrange polynomials
pub fn evaluate<F: Field, B: LagrangeBasis>(
expression: &Expression<F>,
expression: &ExpressionBack<F>,
size: usize,
rot_scale: i32,
fixed: &[Polynomial<F, B>],
@ -870,20 +861,17 @@ pub fn evaluate<F: Field, B: LagrangeBasis>(
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)]
&|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],
}
}
},
&|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)]
},
&|challenge| challenges[challenge.index()],
&|a| -a,
&|a, b| a + b,
&|a, b| a * b,
@ -896,17 +884,15 @@ pub fn evaluate<F: Field, B: LagrangeBasis>(
#[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<Calculation>, expr: Option<Expression<Scalar>>, expected: i64) {
fn check(calc: Option<Calculation>, expr: Option<ExpressionBack<Scalar>>, expected: i64) {
let lagranges = |v: &[&[u64]]| -> Vec<Polynomial<Scalar, LagrangeCoeff>> {
v.iter()
.map(|vv| {
@ -956,7 +942,7 @@ mod test {
expected, result
);
}
fn check_expr(expr: Expression<Scalar>, expected: i64) {
fn check_expr(expr: ExpressionBack<Scalar>, 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::<Scalar>::Fixed(FixedQuery {
index: None,
Box::new(ExpressionBack::<Scalar>::Var(Query(QueryBack {
index: 0,
column_index: 0,
column_type: Any::Fixed,
rotation: Rotation(0),
}))
})))
};
let three = || {
Box::new(Expression::<Scalar>::Fixed(FixedQuery {
index: None,
Box::new(ExpressionBack::<Scalar>::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,
);
}

View File

@ -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<C, ConcreteCircuit>(
pub(crate) fn create_domain<C>(
cs: &ConstraintSystemBack<C::Scalar>,
k: u32,
#[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params,
) -> (
EvaluationDomain<C::Scalar>,
ConstraintSystem<C::Scalar>,
ConcreteCircuit::Config,
)
) -> EvaluationDomain<C::Scalar>
where
C: CurveAffine,
ConcreteCircuit: Circuit<C::Scalar>,
{
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<C::Scalar> = cs_backend.clone().into();
let cs_mid = &circuit.cs;
let cs: ConstraintSystemBack<C::Scalar> = 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<F: Field>(&mut self, expr: &ExpressionMid<F>) -> ExpressionBack<F> {
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<F: Field>(
cs_mid: &ConstraintSystemMid<F>,
queries: &mut QueriesMap,
) -> Vec<GateBack<F>> {
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<F: Field>(
cs_mid: &ConstraintSystemMid<F>,
queries: &mut QueriesMap,
) -> Vec<LookupArgumentBack<F>> {
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<F: Field>(
cs_mid: &ConstraintSystemMid<F>,
queries: &mut QueriesMap,
) -> Vec<ShuffleArgumentBack<F>> {
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<F: Field>(
cs_mid: &ConstraintSystemMid<F>,
) -> (
Queries,
Vec<GateBack<F>>,
Vec<LookupArgumentBack<F>>,
Vec<ShuffleArgumentBack<F>>,
) {
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<F: Field> From<ConstraintSystemMid<F>> for ConstraintSystemBack<F> {
fn from(cs_mid: ConstraintSystemMid<F>) -> 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,
}
}
}

View File

@ -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;

View File

@ -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<F, Output = C::Curve> + MulAssign<F>,
{
// Closure to get values of expressions and compress them
let compress_expressions = |expressions: &[Expression<C::Scalar>]| {
let compress_expressions = |expressions: &[ExpressionBack<C::Scalar>]| {
let compressed_expression = expressions
.iter()
.map(|expression| {

View File

@ -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<C: CurveAffine> Evaluated<C> {
* (self.permuted_input_eval + *beta)
* (self.permuted_table_eval + *gamma);
let compress_expressions = |expressions: &[Expression<C::Scalar>]| {
let compress_expressions = |expressions: &[ExpressionBack<C::Scalar>]| {
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,

View File

@ -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;

View File

@ -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<Self, Error> {
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()

View File

@ -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;

View File

@ -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<C: CurveAffine> Evaluated<C> {
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<C: CurveAffine> Evaluated<C> {
let mut current_delta = (*beta * *x)
* (<C::Scalar as PrimeField>::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())]
}

View File

@ -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<Scheme::Curve>,
// Phases
phases: Vec<sealed::Phase>,
phases: Vec<u8>,
// Polynomials (Lagrange and Coeff) for all circuits instances
instances: Vec<InstanceSingle<Scheme::Curve>>,
// 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()
}
}

View File

@ -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;

View File

@ -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<F, Output = C::Curve> + MulAssign<F>,
{
// Closure to get values of expressions and compress them
let compress_expressions = |expressions: &[Expression<C::Scalar>]| {
let compress_expressions = |expressions: &[ExpressionBack<C::Scalar>]| {
let compressed_expression = expressions
.iter()
.map(|expression| {

View File

@ -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<C: CurveAffine> Evaluated<C> {
let product_expression = || {
// z(\omega X) (s(X) + \gamma) - z(X) (a(X) + \gamma)
let compress_expressions = |expressions: &[Expression<C::Scalar>]| {
let compress_expressions = |expressions: &[ExpressionBack<C::Scalar>]| {
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,

View File

@ -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};

View File

@ -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,

View File

@ -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],
)

View File

@ -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;

View File

@ -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"]

View File

@ -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<F: Field>: 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<usize> 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<usize> 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<Any>,
}
/// An assigned cell.
#[derive(Clone, Debug)]
pub struct AssignedCell<V, F: Field> {
value: Value<V>,
cell: Cell,
_marker: PhantomData<F>,
}
impl<V, F: Field> AssignedCell<V, F> {
/// 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<V, F: Field> AssignedCell<V, F>
where
for<'v> Assigned<F>: From<&'v V>,
{
/// Returns the field element value of the [`AssignedCell`].
pub fn value_field(&self) -> Value<Assigned<F>> {
self.value.to_field()
}
}
impl<F: Field> AssignedCell<Assigned<F>, 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<F, F> {
AssignedCell {
value: self.value.evaluate(),
cell: self.cell,
_marker: Default::default(),
}
}
}
impl<V: Clone, F: Field> AssignedCell<V, F>
where
for<'v> Assigned<F>: 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<A, AR>(
&self,
annotation: A,
region: &mut Region<'_, F>,
column: Column<Advice>,
offset: usize,
) -> Result<Self, Error>
where
A: Fn() -> AR,
AR: Into<String>,
{
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<F>,
}
impl<'r, F: Field> From<&'r mut dyn layouter::RegionLayouter<F>> for Region<'r, F> {
fn from(region: &'r mut dyn layouter::RegionLayouter<F>) -> Self {
Region { region }
}
}
impl<'r, F: Field> Region<'r, F> {
/// Enables a selector at the given offset.
pub fn enable_selector<A, AR>(
&mut self,
annotation: A,
selector: &Selector,
offset: usize,
) -> Result<(), Error>
where
A: Fn() -> AR,
AR: Into<String>,
{
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<A, AR, T>(&mut self, annotation: A, column: T)
where
A: Fn() -> AR,
AR: Into<String>,
T: Into<Column<Any>>,
{
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<Advice>,
offset: usize,
mut to: V,
) -> Result<AssignedCell<VR, F>, Error>
where
V: FnMut() -> Value<VR> + 'v,
for<'vr> Assigned<F>: From<&'vr VR>,
A: Fn() -> AR,
AR: Into<String>,
{
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<VR, A, AR>(
&mut self,
annotation: A,
column: Column<Advice>,
offset: usize,
constant: VR,
) -> Result<AssignedCell<VR, F>, Error>
where
for<'vr> Assigned<F>: From<&'vr VR>,
A: Fn() -> AR,
AR: Into<String>,
{
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<A, AR>(
&mut self,
annotation: A,
instance: Column<Instance>,
row: usize,
advice: Column<Advice>,
offset: usize,
) -> Result<AssignedCell<F, F>, Error>
where
A: Fn() -> AR,
AR: Into<String>,
{
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<Instance>,
row: usize,
) -> Result<Value<F>, 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<Fixed>,
offset: usize,
mut to: V,
) -> Result<AssignedCell<VR, F>, Error>
where
V: FnMut() -> Value<VR> + 'v,
for<'vr> Assigned<F>: From<&'vr VR>,
A: Fn() -> AR,
AR: Into<String>,
{
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<VR>(&mut self, cell: Cell, constant: VR) -> Result<(), Error>
where
VR: Into<Assigned<F>>,
{
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<F>,
}
impl<'r, F: Field> From<&'r mut dyn TableLayouter<F>> for Table<'r, F> {
fn from(table: &'r mut dyn TableLayouter<F>) -> 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<VR> + 'v,
VR: Into<Assigned<F>>,
A: Fn() -> AR,
AR: Into<String>,
{
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<F: Field> {
/// Represents the type of the "root" of this layouter, so that nested namespaces
/// can minimize indirection.
type Root: Layouter<F>;
/// 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<A, AR, N, NR>(&mut self, name: N, assignment: A) -> Result<AR, Error>
where
A: FnMut(Region<'_, F>) -> Result<AR, Error>,
N: Fn() -> NR,
NR: Into<String>;
/// 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<A, N, NR>(&mut self, name: N, assignment: A) -> Result<(), Error>
where
A: FnMut(Table<'_, F>) -> Result<(), Error>,
N: Fn() -> NR,
NR: Into<String>;
/// Constrains a [`Cell`] to equal an instance column's row value at an
/// absolute position.
fn constrain_instance(
&mut self,
cell: Cell,
column: Column<Instance>,
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<F>;
/// 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<NR, N>(&mut self, name_fn: N)
where
NR: Into<String>,
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<String>);
/// Enters into a namespace.
fn namespace<NR, N>(&mut self, name_fn: N) -> NamespacedLayouter<'_, F, Self::Root>
where
NR: Into<String>,
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<F> + 'a>(&'a mut L, PhantomData<F>);
impl<'a, F: Field, L: Layouter<F> + 'a> Layouter<F> for NamespacedLayouter<'a, F, L> {
type Root = L::Root;
fn assign_region<A, AR, N, NR>(&mut self, name: N, assignment: A) -> Result<AR, Error>
where
A: FnMut(Region<'_, F>) -> Result<AR, Error>,
N: Fn() -> NR,
NR: Into<String>,
{
self.0.assign_region(name, assignment)
}
fn assign_table<A, N, NR>(&mut self, name: N, assignment: A) -> Result<(), Error>
where
A: FnMut(Table<'_, F>) -> Result<(), Error>,
N: Fn() -> NR,
NR: Into<String>,
{
self.0.assign_table(name, assignment)
}
fn constrain_instance(
&mut self,
cell: Cell,
column: Column<Instance>,
row: usize,
) -> Result<(), Error> {
self.0.constrain_instance(cell, column, row)
}
fn get_challenge(&self, challenge: Challenge) -> Value<F> {
self.0.get_challenge(challenge)
}
fn get_root(&mut self) -> &mut Self::Root {
self.0.get_root()
}
fn push_namespace<NR, N>(&mut self, _name_fn: N)
where
NR: Into<String>,
N: FnOnce() -> NR,
{
panic!("Only the root's push_namespace should be called");
}
fn pop_namespace(&mut self, _gadget_name: Option<String>) {
panic!("Only the root's pop_namespace should be called");
}
}
impl<'a, F: Field, L: Layouter<F> + '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);
}
}

View File

@ -1,6 +0,0 @@
//! Implementations of common circuit floor planners.
pub mod single_pass;
pub mod v1;
pub use v1::{V1Pass, V1};

View File

@ -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<F: Field, CS: Assignment<F> + SyncDeps, C: Circuit<F>>(
cs: &mut CS,
circuit: &C,
config: C::Config,
constants: Vec<Column<Fixed>>,
) -> 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<F> + 'a> {
cs: &'a mut CS,
constants: Vec<Column<Fixed>>,
/// Stores the starting row for each region.
regions: Vec<RegionStart>,
/// Stores the first empty row for each column.
columns: HashMap<RegionColumn, usize>,
/// Stores the table fixed columns.
table_columns: Vec<TableColumn>,
_marker: PhantomData<F>,
}
impl<'a, F: Field, CS: Assignment<F> + '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<F>> SingleChipLayouter<'a, F, CS> {
/// Creates a new single-chip layouter.
pub fn new(cs: &'a mut CS, constants: Vec<Column<Fixed>>) -> Result<Self, Error> {
let ret = SingleChipLayouter {
cs,
constants,
regions: vec![],
columns: HashMap::default(),
table_columns: vec![],
_marker: PhantomData,
};
Ok(ret)
}
}
impl<'a, F: Field, CS: Assignment<F> + 'a + SyncDeps> Layouter<F>
for SingleChipLayouter<'a, F, CS>
{
type Root = Self;
fn assign_region<A, AR, N, NR>(&mut self, name: N, mut assignment: A) -> Result<AR, Error>
where
A: FnMut(Region<'_, F>) -> Result<AR, Error>,
N: Fn() -> NR,
NR: Into<String>,
{
let region_index = self.regions.len();
// Get shape of the region.
let mut shape = RegionShape::new(region_index.into());
{
let region: &mut dyn RegionLayouter<F> = &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<F> = &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::<Any>::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<A, N, NR>(&mut self, name: N, mut assignment: A) -> Result<(), Error>
where
A: FnMut(Table<'_, F>) -> Result<(), Error>,
N: Fn() -> NR,
NR: Into<String>,
{
// 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<F> = &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<Instance>,
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<F> {
self.cs.get_challenge(challenge)
}
fn get_root(&mut self) -> &mut Self::Root {
self
}
fn push_namespace<NR, N>(&mut self, name_fn: N)
where
NR: Into<String>,
N: FnOnce() -> NR,
{
self.cs.push_namespace(name_fn)
}
fn pop_namespace(&mut self, gadget_name: Option<String>) {
self.cs.pop_namespace(gadget_name)
}
}
struct SingleChipLayouterRegion<'r, 'a, F: Field, CS: Assignment<F> + '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<F>, Cell)>,
}
impl<'r, 'a, F: Field, CS: Assignment<F> + '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<F> + '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<F> + 'a + SyncDeps> RegionLayouter<F>
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<Any>,
) {
self.layouter.cs.annotate_column(annotation, column);
}
fn assign_advice<'v>(
&'v mut self,
annotation: &'v (dyn Fn() -> String + 'v),
column: Column<Advice>,
offset: usize,
to: &'v mut (dyn FnMut() -> Value<Assigned<F>> + 'v),
) -> Result<Cell, Error> {
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<Advice>,
offset: usize,
constant: Assigned<F>,
) -> Result<Cell, Error> {
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<Instance>,
row: usize,
advice: Column<Advice>,
offset: usize,
) -> Result<(Cell, Value<F>), 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<Instance>,
row: usize,
) -> Result<Value<F>, Error> {
self.layouter.cs.query_instance(instance, row)
}
fn assign_fixed<'v>(
&'v mut self,
annotation: &'v (dyn Fn() -> String + 'v),
column: Column<Fixed>,
offset: usize,
to: &'v mut (dyn FnMut() -> Value<Assigned<F>> + 'v),
) -> Result<Cell, Error> {
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<F>) -> 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(())
}
}

View File

@ -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<F> + 'a> {
cs: &'a mut CS,
/// Stores the starting row for each region.
regions: Vec<RegionStart>,
/// Stores the constants to be assigned, and the cells to which they are copied.
constants: Vec<(Assigned<F>, Cell)>,
/// Stores the table fixed columns.
table_columns: Vec<TableColumn>,
}
impl<'a, F: Field, CS: Assignment<F> + '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<F> + SyncDeps> V1Plan<'a, F, CS> {
/// Creates a new v1 layouter.
pub fn new(cs: &'a mut CS) -> Result<Self, Error> {
let ret = V1Plan {
cs,
regions: vec![],
constants: vec![],
table_columns: vec![],
};
Ok(ret)
}
}
impl FloorPlanner for V1 {
fn synthesize<F: Field, CS: Assignment<F> + SyncDeps, C: Circuit<F>>(
cs: &mut CS,
circuit: &C,
config: C::Config,
constants: Vec<Column<Fixed>>,
) -> 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::<Any>::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<F> + '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<F> + 'a>(Pass<'p, 'a, F, CS>);
impl<'p, 'a, F: Field, CS: Assignment<F> + '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<F> + SyncDeps> Layouter<F> for V1Pass<'p, 'a, F, CS> {
type Root = Self;
fn assign_region<A, AR, N, NR>(&mut self, name: N, assignment: A) -> Result<AR, Error>
where
A: FnMut(Region<'_, F>) -> Result<AR, Error>,
N: Fn() -> NR,
NR: Into<String>,
{
match &mut self.0 {
Pass::Measurement(pass) => pass.assign_region(assignment),
Pass::Assignment(pass) => pass.assign_region(name, assignment),
}
}
fn assign_table<A, N, NR>(&mut self, name: N, assignment: A) -> Result<(), Error>
where
A: FnMut(Table<'_, F>) -> Result<(), Error>,
N: Fn() -> NR,
NR: Into<String>,
{
match &mut self.0 {
Pass::Measurement(_) => Ok(()),
Pass::Assignment(pass) => pass.assign_table(name, assignment),
}
}
fn constrain_instance(
&mut self,
cell: Cell,
instance: Column<Instance>,
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<F> {
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<NR, N>(&mut self, name_fn: N)
where
NR: Into<String>,
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<String>) {
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<RegionShape>,
}
impl MeasurementPass {
fn new() -> Self {
MeasurementPass { regions: vec![] }
}
fn assign_region<F: Field, A, AR>(&mut self, mut assignment: A) -> Result<AR, Error>
where
A: FnMut(Region<'_, F>) -> Result<AR, Error>,
{
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<F> = &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<F> + '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<F> + SyncDeps> AssignmentPass<'p, 'a, F, CS> {
fn new(plan: &'p mut V1Plan<'a, F, CS>) -> Self {
AssignmentPass {
plan,
region_index: 0,
}
}
fn assign_region<A, AR, N, NR>(&mut self, name: N, mut assignment: A) -> Result<AR, Error>
where
A: FnMut(Region<'_, F>) -> Result<AR, Error>,
N: Fn() -> NR,
NR: Into<String>,
{
// 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<F> = &mut region;
assignment(region.into())
}?;
self.plan.cs.exit_region();
Ok(result)
}
fn assign_table<A, AR, N, NR>(&mut self, name: N, mut assignment: A) -> Result<AR, Error>
where
A: FnMut(Table<'_, F>) -> Result<AR, Error>,
N: Fn() -> NR,
NR: Into<String>,
{
// 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<F> = &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<Instance>,
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<F> + 'a> {
plan: &'r mut V1Plan<'a, F, CS>,
region_index: RegionIndex,
}
impl<'r, 'a, F: Field, CS: Assignment<F> + '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<F> + '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<F> + SyncDeps> RegionLayouter<F> 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<Advice>,
offset: usize,
to: &'v mut (dyn FnMut() -> Value<Assigned<F>> + 'v),
) -> Result<Cell, Error> {
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<Advice>,
offset: usize,
constant: Assigned<F>,
) -> Result<Cell, Error> {
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<Instance>,
row: usize,
advice: Column<Advice>,
offset: usize,
) -> Result<(Cell, Value<F>), 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<Instance>,
row: usize,
) -> Result<Value<F>, Error> {
self.plan.cs.query_instance(instance, row)
}
fn assign_fixed<'v>(
&'v mut self,
annotation: &'v (dyn Fn() -> String + 'v),
column: Column<Fixed>,
offset: usize,
to: &'v mut (dyn FnMut() -> Value<Assigned<F>> + 'v),
) -> Result<Cell, Error> {
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<F>) -> Result<(), Error> {
self.plan.constants.push((constant, cell));
Ok(())
}
fn name_column<'v>(
&'v mut self,
annotation: &'v (dyn Fn() -> String + 'v),
column: Column<Any>,
) {
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(())
}
}

View File

@ -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<cmp::Ordering> {
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<usize>,
}
impl EmptySpace {
pub(crate) fn range(&self) -> Option<Range<usize>> {
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<AllocatedRegion>);
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<usize>,
) -> impl Iterator<Item = EmptySpace> + '_ {
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<RegionColumn, Allocations>;
/// - `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<usize>,
) -> Option<usize> {
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<RegionShape>,
) -> (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,
&region_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<RegionShape>,
) -> (Vec<RegionStart>, 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)
}

View File

@ -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<F: Field>: 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<Assigned<F>> + '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<F> = Option<Value<Assigned<F>>>;
/// A table layouter that can be used to assign values to a table.
pub struct SimpleTableLayouter<'r, 'a, F: Field, CS: Assignment<F> + '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<TableColumn, (DefaultTableValue<F>, Vec<bool>)>,
}
impl<'r, 'a, F: Field, CS: Assignment<F> + '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<F> + '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<F> + 'a> TableLayouter<F>
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<Assigned<F>> + '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<F: Debug>(
default_and_assigned: &HashMap<TableColumn, (DefaultTableValue<F>, Vec<bool>)>,
) -> Result<usize, Error> {
let column_lengths: Result<Vec<_>, 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)
}

View File

@ -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

View File

@ -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<F> {
/// 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<F: Field> From<&Assigned<F>> for Assigned<F> {
fn from(val: &Assigned<F>) -> Self {
*val
}
}
impl<F: Field> From<&F> for Assigned<F> {
fn from(numerator: &F) -> Self {
Assigned::Trivial(*numerator)
}
}
impl<F: Field> From<F> for Assigned<F> {
fn from(numerator: F) -> Self {
Assigned::Trivial(numerator)
}
}
impl<F: Field> From<(F, F)> for Assigned<F> {
fn from((numerator, denominator): (F, F)) -> Self {
Assigned::Rational(numerator, denominator)
}
}
impl<F: Field> PartialEq for Assigned<F> {
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<F: Field> Eq for Assigned<F> {}
impl<F: Field> Neg for Assigned<F> {
type Output = Assigned<F>;
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<F: Field> Neg for &Assigned<F> {
type Output = Assigned<F>;
fn neg(self) -> Self::Output {
-*self
}
}
impl<F: Field> Add for Assigned<F> {
type Output = Assigned<F>;
fn add(self, rhs: Assigned<F>) -> Assigned<F> {
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<F: Field> Add<F> for Assigned<F> {
type Output = Assigned<F>;
fn add(self, rhs: F) -> Assigned<F> {
self + Self::Trivial(rhs)
}
}
impl<F: Field> Add<F> for &Assigned<F> {
type Output = Assigned<F>;
fn add(self, rhs: F) -> Assigned<F> {
*self + rhs
}
}
impl<F: Field> Add<&Assigned<F>> for Assigned<F> {
type Output = Assigned<F>;
fn add(self, rhs: &Self) -> Assigned<F> {
self + *rhs
}
}
impl<F: Field> Add<Assigned<F>> for &Assigned<F> {
type Output = Assigned<F>;
fn add(self, rhs: Assigned<F>) -> Assigned<F> {
*self + rhs
}
}
impl<F: Field> Add<&Assigned<F>> for &Assigned<F> {
type Output = Assigned<F>;
fn add(self, rhs: &Assigned<F>) -> Assigned<F> {
*self + *rhs
}
}
impl<F: Field> AddAssign for Assigned<F> {
fn add_assign(&mut self, rhs: Self) {
*self = *self + rhs;
}
}
impl<F: Field> AddAssign<&Assigned<F>> for Assigned<F> {
fn add_assign(&mut self, rhs: &Self) {
*self = *self + rhs;
}
}
impl<F: Field> Sub for Assigned<F> {
type Output = Assigned<F>;
fn sub(self, rhs: Assigned<F>) -> Assigned<F> {
self + (-rhs)
}
}
impl<F: Field> Sub<F> for Assigned<F> {
type Output = Assigned<F>;
fn sub(self, rhs: F) -> Assigned<F> {
self + (-rhs)
}
}
impl<F: Field> Sub<F> for &Assigned<F> {
type Output = Assigned<F>;
fn sub(self, rhs: F) -> Assigned<F> {
*self - rhs
}
}
impl<F: Field> Sub<&Assigned<F>> for Assigned<F> {
type Output = Assigned<F>;
fn sub(self, rhs: &Self) -> Assigned<F> {
self - *rhs
}
}
impl<F: Field> Sub<Assigned<F>> for &Assigned<F> {
type Output = Assigned<F>;
fn sub(self, rhs: Assigned<F>) -> Assigned<F> {
*self - rhs
}
}
impl<F: Field> Sub<&Assigned<F>> for &Assigned<F> {
type Output = Assigned<F>;
fn sub(self, rhs: &Assigned<F>) -> Assigned<F> {
*self - *rhs
}
}
impl<F: Field> SubAssign for Assigned<F> {
fn sub_assign(&mut self, rhs: Self) {
*self = *self - rhs;
}
}
impl<F: Field> SubAssign<&Assigned<F>> for Assigned<F> {
fn sub_assign(&mut self, rhs: &Self) {
*self = *self - rhs;
}
}
impl<F: Field> Mul for Assigned<F> {
type Output = Assigned<F>;
fn mul(self, rhs: Assigned<F>) -> Assigned<F> {
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<F: Field> Mul<F> for Assigned<F> {
type Output = Assigned<F>;
fn mul(self, rhs: F) -> Assigned<F> {
self * Self::Trivial(rhs)
}
}
impl<F: Field> Mul<F> for &Assigned<F> {
type Output = Assigned<F>;
fn mul(self, rhs: F) -> Assigned<F> {
*self * rhs
}
}
impl<F: Field> Mul<&Assigned<F>> for Assigned<F> {
type Output = Assigned<F>;
fn mul(self, rhs: &Assigned<F>) -> Assigned<F> {
self * *rhs
}
}
impl<F: Field> MulAssign for Assigned<F> {
fn mul_assign(&mut self, rhs: Self) {
*self = *self * rhs;
}
}
impl<F: Field> MulAssign<&Assigned<F>> for Assigned<F> {
fn mul_assign(&mut self, rhs: &Self) {
*self = *self * rhs;
}
}
impl<F: Field> Assigned<F> {
/// 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<F> {
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<Advice>, Rotation)>,
pub advice: Vec<(ColumnMid, Rotation)>,
/// List of unique instance queries
pub instance: Vec<(Column<Instance>, Rotation)>,
pub instance: Vec<(ColumnMid, Rotation)>,
/// List of unique fixed queries
pub fixed: Vec<(Column<Fixed>, 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<Output = Self> {
fn double(&self) -> Self;
fn square(&self) -> Self;
fn cube(&self) -> Self;
fn inv0(&self) -> Self;
}
impl<F: Field> 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<F: Field> UnaryOperand for Assigned<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()
}
}
#[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<F: UnaryOperand>(&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<Output = Self> + Sub<Output = Self> + Mul<Output = Self> {}
impl<F: Field> BinaryOperand for F {}
impl<F: Field> BinaryOperand for Assigned<F> {}
#[derive(Clone, Debug)]
enum BinaryOperator {
Add,
Sub,
Mul,
}
const BINARY_OPERATORS: &[BinaryOperator] = &[
BinaryOperator::Add,
BinaryOperator::Sub,
BinaryOperator::Mul,
];
impl BinaryOperator {
fn apply<F: BinaryOperand>(&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::<u64>()) -> Fp {
Fp::from(val)
}
}
prop_compose! {
fn arb_trivial()(element in arb_element()) -> Assigned<Fp> {
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<Fp> {
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<Operator> {
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<Assigned<Fp>>, Vec<Operator>) {
(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<F: UnaryOperand + BinaryOperand>(
items: Vec<F>,
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);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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<F>` and `SelectorsToFixed` are outputs for the
/// frontend itself, which will be used for witness generation and fixed column assignment.
/// The `ConstraintSystem<F>` can be converted to `ConstraintSystemMid<F>` to be used to interface
/// with the backend.
pub fn compile_circuit_cs<F: Field, ConcreteCircuit: Circuit<F>>(
compress_selectors: bool,
#[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params,
) -> (
ConcreteCircuit::Config,
ConstraintSystem<F>,
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<F: Field, ConcreteCircuit: Circuit<F>>(
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<F: Field, ConcreteCircuit: Circuit<F>>(
)?;
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<F: Field>(
.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<F: Field>: 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<usize> 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<usize> 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<Any>,
}
/// An assigned cell.
#[derive(Clone, Debug)]
pub struct AssignedCell<V, F: Field> {
value: Value<V>,
cell: Cell,
_marker: PhantomData<F>,
}
impl<V, F: Field> AssignedCell<V, F> {
/// 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<V, F: Field> AssignedCell<V, F>
where
for<'v> Assigned<F>: From<&'v V>,
{
/// Returns the field element value of the [`AssignedCell`].
pub fn value_field(&self) -> Value<Assigned<F>> {
self.value.to_field()
}
}
impl<F: Field> AssignedCell<Assigned<F>, 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<F, F> {
AssignedCell {
value: self.value.evaluate(),
cell: self.cell,
_marker: Default::default(),
}
}
}
impl<V: Clone, F: Field> AssignedCell<V, F>
where
for<'v> Assigned<F>: 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<A, AR>(
&self,
annotation: A,
region: &mut Region<'_, F>,
column: Column<Advice>,
offset: usize,
) -> Result<Self, Error>
where
A: Fn() -> AR,
AR: Into<String>,
{
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<F>,
}
impl<'r, F: Field> From<&'r mut dyn layouter::RegionLayouter<F>> for Region<'r, F> {
fn from(region: &'r mut dyn layouter::RegionLayouter<F>) -> Self {
Region { region }
}
}
impl<'r, F: Field> Region<'r, F> {
/// Enables a selector at the given offset.
pub fn enable_selector<A, AR>(
&mut self,
annotation: A,
selector: &Selector,
offset: usize,
) -> Result<(), Error>
where
A: Fn() -> AR,
AR: Into<String>,
{
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<A, AR, T>(&mut self, annotation: A, column: T)
where
A: Fn() -> AR,
AR: Into<String>,
T: Into<Column<Any>>,
{
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<Advice>,
offset: usize,
mut to: V,
) -> Result<AssignedCell<VR, F>, Error>
where
V: FnMut() -> Value<VR> + 'v,
for<'vr> Assigned<F>: From<&'vr VR>,
A: Fn() -> AR,
AR: Into<String>,
{
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<VR, A, AR>(
&mut self,
annotation: A,
column: Column<Advice>,
offset: usize,
constant: VR,
) -> Result<AssignedCell<VR, F>, Error>
where
for<'vr> Assigned<F>: From<&'vr VR>,
A: Fn() -> AR,
AR: Into<String>,
{
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<A, AR>(
&mut self,
annotation: A,
instance: Column<Instance>,
row: usize,
advice: Column<Advice>,
offset: usize,
) -> Result<AssignedCell<F, F>, Error>
where
A: Fn() -> AR,
AR: Into<String>,
{
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<Instance>,
row: usize,
) -> Result<Value<F>, 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<Fixed>,
offset: usize,
mut to: V,
) -> Result<AssignedCell<VR, F>, Error>
where
V: FnMut() -> Value<VR> + 'v,
for<'vr> Assigned<F>: From<&'vr VR>,
A: Fn() -> AR,
AR: Into<String>,
{
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<VR>(&mut self, cell: Cell, constant: VR) -> Result<(), Error>
where
VR: Into<Assigned<F>>,
{
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<F>,
}
impl<'r, F: Field> From<&'r mut dyn TableLayouter<F>> for Table<'r, F> {
fn from(table: &'r mut dyn TableLayouter<F>) -> 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<VR> + 'v,
VR: Into<Assigned<F>>,
A: Fn() -> AR,
AR: Into<String>,
{
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<F: Field> {
/// Represents the type of the "root" of this layouter, so that nested namespaces
/// can minimize indirection.
type Root: Layouter<F>;
/// 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<A, AR, N, NR>(&mut self, name: N, assignment: A) -> Result<AR, Error>
where
A: FnMut(Region<'_, F>) -> Result<AR, Error>,
N: Fn() -> NR,
NR: Into<String>;
/// 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<A, N, NR>(&mut self, name: N, assignment: A) -> Result<(), Error>
where
A: FnMut(Table<'_, F>) -> Result<(), Error>,
N: Fn() -> NR,
NR: Into<String>;
/// Constrains a [`Cell`] to equal an instance column's row value at an
/// absolute position.
fn constrain_instance(
&mut self,
cell: Cell,
column: Column<Instance>,
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<F>;
/// 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<NR, N>(&mut self, name_fn: N)
where
NR: Into<String>,
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<String>);
/// Enters into a namespace.
fn namespace<NR, N>(&mut self, name_fn: N) -> NamespacedLayouter<'_, F, Self::Root>
where
NR: Into<String>,
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<F> + 'a>(&'a mut L, PhantomData<F>);
impl<'a, F: Field, L: Layouter<F> + 'a> Layouter<F> for NamespacedLayouter<'a, F, L> {
type Root = L::Root;
fn assign_region<A, AR, N, NR>(&mut self, name: N, assignment: A) -> Result<AR, Error>
where
A: FnMut(Region<'_, F>) -> Result<AR, Error>,
N: Fn() -> NR,
NR: Into<String>,
{
self.0.assign_region(name, assignment)
}
fn assign_table<A, N, NR>(&mut self, name: N, assignment: A) -> Result<(), Error>
where
A: FnMut(Table<'_, F>) -> Result<(), Error>,
N: Fn() -> NR,
NR: Into<String>,
{
self.0.assign_table(name, assignment)
}
fn constrain_instance(
&mut self,
cell: Cell,
column: Column<Instance>,
row: usize,
) -> Result<(), Error> {
self.0.constrain_instance(cell, column, row)
}
fn get_challenge(&self, challenge: Challenge) -> Value<F> {
self.0.get_challenge(challenge)
}
fn get_root(&mut self) -> &mut Self::Root {
self.0.get_root()
}
fn push_namespace<NR, N>(&mut self, _name_fn: N)
where
NR: Into<String>,
N: FnOnce() -> NR,
{
panic!("Only the root's push_namespace should be called");
}
fn pop_namespace(&mut self, _gadget_name: Option<String>) {
panic!("Only the root's pop_namespace should be called");
}
}
impl<'a, F: Field, L: Layouter<F> + '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);
}
}

View File

@ -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};

View File

@ -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<F: Field, CS: Assignment<F> + SyncDeps, C: Circuit<F>>(
cs: &mut CS,
circuit: &C,
config: C::Config,
constants: Vec<Column<Fixed>>,
) -> 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<F> + 'a> {
cs: &'a mut CS,
constants: Vec<Column<Fixed>>,
/// Stores the starting row for each region.
regions: Vec<RegionStart>,
/// Stores the first empty row for each column.
columns: HashMap<RegionColumn, usize>,
/// Stores the table fixed columns.
table_columns: Vec<TableColumn>,
_marker: PhantomData<F>,
}
impl<'a, F: Field, CS: Assignment<F> + '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<F>> SingleChipLayouter<'a, F, CS> {
/// Creates a new single-chip layouter.
pub fn new(cs: &'a mut CS, constants: Vec<Column<Fixed>>) -> Result<Self, Error> {
let ret = SingleChipLayouter {
cs,
constants,
regions: vec![],
columns: HashMap::default(),
table_columns: vec![],
_marker: PhantomData,
};
Ok(ret)
}
}
impl<'a, F: Field, CS: Assignment<F> + 'a + SyncDeps> Layouter<F>
for SingleChipLayouter<'a, F, CS>
{
type Root = Self;
fn assign_region<A, AR, N, NR>(&mut self, name: N, mut assignment: A) -> Result<AR, Error>
where
A: FnMut(Region<'_, F>) -> Result<AR, Error>,
N: Fn() -> NR,
NR: Into<String>,
{
let region_index = self.regions.len();
// Get shape of the region.
let mut shape = RegionShape::new(region_index.into());
{
let region: &mut dyn RegionLayouter<F> = &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<F> = &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::<Any>::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<A, N, NR>(&mut self, name: N, mut assignment: A) -> Result<(), Error>
where
A: FnMut(Table<'_, F>) -> Result<(), Error>,
N: Fn() -> NR,
NR: Into<String>,
{
// 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<F> = &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<Instance>,
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<F> {
self.cs.get_challenge(challenge)
}
fn get_root(&mut self) -> &mut Self::Root {
self
}
fn push_namespace<NR, N>(&mut self, name_fn: N)
where
NR: Into<String>,
N: FnOnce() -> NR,
{
self.cs.push_namespace(name_fn)
}
fn pop_namespace(&mut self, gadget_name: Option<String>) {
self.cs.pop_namespace(gadget_name)
}
}
struct SingleChipLayouterRegion<'r, 'a, F: Field, CS: Assignment<F> + '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<F>, Cell)>,
}
impl<'r, 'a, F: Field, CS: Assignment<F> + '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<F> + '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<F> + 'a + SyncDeps> RegionLayouter<F>
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<Any>,
) {
self.layouter.cs.annotate_column(annotation, column);
}
fn assign_advice<'v>(
&'v mut self,
annotation: &'v (dyn Fn() -> String + 'v),
column: Column<Advice>,
offset: usize,
to: &'v mut (dyn FnMut() -> Value<Assigned<F>> + 'v),
) -> Result<Cell, Error> {
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<Advice>,
offset: usize,
constant: Assigned<F>,
) -> Result<Cell, Error> {
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<Instance>,
row: usize,
advice: Column<Advice>,
offset: usize,
) -> Result<(Cell, Value<F>), 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<Instance>,
row: usize,
) -> Result<Value<F>, Error> {
self.layouter.cs.query_instance(instance, row)
}
fn assign_fixed<'v>(
&'v mut self,
annotation: &'v (dyn Fn() -> String + 'v),
column: Column<Fixed>,
offset: usize,
to: &'v mut (dyn FnMut() -> Value<Assigned<F>> + 'v),
) -> Result<Cell, Error> {
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<F>) -> 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]

View File

@ -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<F> + 'a> {
cs: &'a mut CS,
/// Stores the starting row for each region.
regions: Vec<RegionStart>,
/// Stores the constants to be assigned, and the cells to which they are copied.
constants: Vec<(Assigned<F>, Cell)>,
/// Stores the table fixed columns.
table_columns: Vec<TableColumn>,
}
impl<'a, F: Field, CS: Assignment<F> + '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<F> + SyncDeps> V1Plan<'a, F, CS> {
/// Creates a new v1 layouter.
pub fn new(cs: &'a mut CS) -> Result<Self, Error> {
let ret = V1Plan {
cs,
regions: vec![],
constants: vec![],
table_columns: vec![],
};
Ok(ret)
}
}
impl FloorPlanner for V1 {
fn synthesize<F: Field, CS: Assignment<F> + SyncDeps, C: Circuit<F>>(
cs: &mut CS,
circuit: &C,
config: C::Config,
constants: Vec<Column<Fixed>>,
) -> 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::<Any>::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<F> + '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<F> + 'a>(Pass<'p, 'a, F, CS>);
impl<'p, 'a, F: Field, CS: Assignment<F> + '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<F> + SyncDeps> Layouter<F> for V1Pass<'p, 'a, F, CS> {
type Root = Self;
fn assign_region<A, AR, N, NR>(&mut self, name: N, assignment: A) -> Result<AR, Error>
where
A: FnMut(Region<'_, F>) -> Result<AR, Error>,
N: Fn() -> NR,
NR: Into<String>,
{
match &mut self.0 {
Pass::Measurement(pass) => pass.assign_region(assignment),
Pass::Assignment(pass) => pass.assign_region(name, assignment),
}
}
fn assign_table<A, N, NR>(&mut self, name: N, assignment: A) -> Result<(), Error>
where
A: FnMut(Table<'_, F>) -> Result<(), Error>,
N: Fn() -> NR,
NR: Into<String>,
{
match &mut self.0 {
Pass::Measurement(_) => Ok(()),
Pass::Assignment(pass) => pass.assign_table(name, assignment),
}
}
fn constrain_instance(
&mut self,
cell: Cell,
instance: Column<Instance>,
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<F> {
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<NR, N>(&mut self, name_fn: N)
where
NR: Into<String>,
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<String>) {
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<RegionShape>,
}
impl MeasurementPass {
fn new() -> Self {
MeasurementPass { regions: vec![] }
}
fn assign_region<F: Field, A, AR>(&mut self, mut assignment: A) -> Result<AR, Error>
where
A: FnMut(Region<'_, F>) -> Result<AR, Error>,
{
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<F> = &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<F> + '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<F> + SyncDeps> AssignmentPass<'p, 'a, F, CS> {
fn new(plan: &'p mut V1Plan<'a, F, CS>) -> Self {
AssignmentPass {
plan,
region_index: 0,
}
}
fn assign_region<A, AR, N, NR>(&mut self, name: N, mut assignment: A) -> Result<AR, Error>
where
A: FnMut(Region<'_, F>) -> Result<AR, Error>,
N: Fn() -> NR,
NR: Into<String>,
{
// 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<F> = &mut region;
assignment(region.into())
}?;
self.plan.cs.exit_region();
Ok(result)
}
fn assign_table<A, AR, N, NR>(&mut self, name: N, mut assignment: A) -> Result<AR, Error>
where
A: FnMut(Table<'_, F>) -> Result<AR, Error>,
N: Fn() -> NR,
NR: Into<String>,
{
// 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<F> = &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<Instance>,
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<F> + 'a> {
plan: &'r mut V1Plan<'a, F, CS>,
region_index: RegionIndex,
}
impl<'r, 'a, F: Field, CS: Assignment<F> + '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<F> + '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<F> + SyncDeps> RegionLayouter<F> 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<Advice>,
offset: usize,
to: &'v mut (dyn FnMut() -> Value<Assigned<F>> + 'v),
) -> Result<Cell, Error> {
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<Advice>,
offset: usize,
constant: Assigned<F>,
) -> Result<Cell, Error> {
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<Instance>,
row: usize,
advice: Column<Advice>,
offset: usize,
) -> Result<(Cell, Value<F>), 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<Instance>,
row: usize,
) -> Result<Value<F>, Error> {
self.plan.cs.query_instance(instance, row)
}
fn assign_fixed<'v>(
&'v mut self,
annotation: &'v (dyn Fn() -> String + 'v),
column: Column<Fixed>,
offset: usize,
to: &'v mut (dyn FnMut() -> Value<Assigned<F>> + 'v),
) -> Result<Cell, Error> {
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<F>) -> Result<(), Error> {
self.plan.constants.push((constant, cell));
Ok(())
}
fn name_column<'v>(
&'v mut self,
annotation: &'v (dyn Fn() -> String + 'v),
column: Column<Any>,
) {
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]

View File

@ -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<cmp::Ordering> {
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<usize>,
}
impl EmptySpace {
pub(crate) fn range(&self) -> Option<Range<usize>> {
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<AllocatedRegion>);
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<usize>,
) -> impl Iterator<Item = EmptySpace> + '_ {
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<RegionColumn, Allocations>;
/// - `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<usize>,
) -> Option<usize> {
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<RegionShape>,
) -> (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,
&region_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<RegionShape>,
) -> (Vec<RegionStart>, 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![

View File

@ -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.

View File

@ -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<F: Field>: 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<Assigned<F>> + '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<F> = Option<Value<Assigned<F>>>;
/// A table layouter that can be used to assign values to a table.
pub struct SimpleTableLayouter<'r, 'a, F: Field, CS: Assignment<F> + '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<TableColumn, (DefaultTableValue<F>, Vec<bool>)>,
}
impl<'r, 'a, F: Field, CS: Assignment<F> + '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<F> + '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<F> + 'a> TableLayouter<F>
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<Assigned<F>> + '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<F: Debug>(
default_and_assigned: &HashMap<TableColumn, (DefaultTableValue<F>, Vec<bool>)>,
) -> Result<usize, Error> {
let column_lengths: Result<Vec<_>, 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]

View File

@ -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<V> Value<V> {
/// # Examples
///
/// ```
/// use halo2_common::circuit::Value;
/// use halo2_frontend::circuit::Value;
///
/// let v = Value::known(37);
/// ```
@ -641,8 +640,8 @@ impl<V> Value<V> {
/// If you have a `Value<F: Field>`, convert it to `Value<Assigned<F>>` 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<Assigned<F>> = v.into();

View File

@ -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<F: Field> Mul<F> for Value<F> {
/// 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<F: Field> Assignment<F> for MockProver<F> {
left_row: usize,
right_column: Column<Any>,
right_row: usize,
) -> Result<(), halo2_common::plonk::Error> {
) -> Result<(), crate::plonk::Error> {
if !self.in_phase(FirstPhase) {
return Ok(());
}
@ -744,7 +739,8 @@ impl<F: FromUniformBytes<64> + Ord> MockProver<F> {
)?;
}
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;

View File

@ -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<F: Field> Assignment<F> for Layout {
l_row: usize,
r_col: Column<Any>,
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<G: PrimeGroup, ConcreteCircuit: Circuit<G::Scalar>> CircuitCost<G, Concrete
cs.constants.clone(),
)
.unwrap();
let (cs, _) = cs.compress_selectors(layout.selectors);
let (cs, _) = cs.selectors_to_fixed_compressed();
assert!((1 << k) >= cs.minimum_rows());

View File

@ -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;

View File

@ -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;

View File

@ -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 {

View File

@ -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;

View File

@ -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<F: Field> Assignment<F> for Graph {
_: usize,
_: Column<Any>,
_: usize,
) -> Result<(), halo2_common::plonk::Error> {
) -> Result<(), crate::plonk::Error> {
// Do nothing; we don't care about permutations in this context.
Ok(())
}

View File

@ -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::<F>(layout.selectors);
let non_selector_fixed_columns = cs.num_fixed_columns - selector_polys.len();
// Figure out what order to render the columns in.

View File

@ -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::{

View File

@ -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},
/// };
///

View File

@ -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;

View File

@ -1,2 +1,3 @@
pub mod circuit;
pub mod dev;
pub mod plonk;

View File

@ -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::*;

View File

@ -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<F> {
/// 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<F: Field> From<&Assigned<F>> for Assigned<F> {
fn from(val: &Assigned<F>) -> Self {
*val
}
}
impl<F: Field> From<&F> for Assigned<F> {
fn from(numerator: &F) -> Self {
Assigned::Trivial(*numerator)
}
}
impl<F: Field> From<F> for Assigned<F> {
fn from(numerator: F) -> Self {
Assigned::Trivial(numerator)
}
}
impl<F: Field> From<(F, F)> for Assigned<F> {
fn from((numerator, denominator): (F, F)) -> Self {
Assigned::Rational(numerator, denominator)
}
}
impl<F: Field> PartialEq for Assigned<F> {
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<F: Field> Eq for Assigned<F> {}
impl<F: Field> Neg for Assigned<F> {
type Output = Assigned<F>;
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<F: Field> Neg for &Assigned<F> {
type Output = Assigned<F>;
fn neg(self) -> Self::Output {
-*self
}
}
impl<F: Field> Add for Assigned<F> {
type Output = Assigned<F>;
fn add(self, rhs: Assigned<F>) -> Assigned<F> {
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<F: Field> Add<F> for Assigned<F> {
type Output = Assigned<F>;
fn add(self, rhs: F) -> Assigned<F> {
self + Self::Trivial(rhs)
}
}
impl<F: Field> Add<F> for &Assigned<F> {
type Output = Assigned<F>;
fn add(self, rhs: F) -> Assigned<F> {
*self + rhs
}
}
impl<F: Field> Add<&Assigned<F>> for Assigned<F> {
type Output = Assigned<F>;
fn add(self, rhs: &Self) -> Assigned<F> {
self + *rhs
}
}
impl<F: Field> Add<Assigned<F>> for &Assigned<F> {
type Output = Assigned<F>;
fn add(self, rhs: Assigned<F>) -> Assigned<F> {
*self + rhs
}
}
impl<F: Field> Add<&Assigned<F>> for &Assigned<F> {
type Output = Assigned<F>;
fn add(self, rhs: &Assigned<F>) -> Assigned<F> {
*self + *rhs
}
}
impl<F: Field> AddAssign for Assigned<F> {
fn add_assign(&mut self, rhs: Self) {
*self = *self + rhs;
}
}
impl<F: Field> AddAssign<&Assigned<F>> for Assigned<F> {
fn add_assign(&mut self, rhs: &Self) {
*self = *self + rhs;
}
}
impl<F: Field> Sub for Assigned<F> {
type Output = Assigned<F>;
fn sub(self, rhs: Assigned<F>) -> Assigned<F> {
self + (-rhs)
}
}
impl<F: Field> Sub<F> for Assigned<F> {
type Output = Assigned<F>;
fn sub(self, rhs: F) -> Assigned<F> {
self + (-rhs)
}
}
impl<F: Field> Sub<F> for &Assigned<F> {
type Output = Assigned<F>;
fn sub(self, rhs: F) -> Assigned<F> {
*self - rhs
}
}
impl<F: Field> Sub<&Assigned<F>> for Assigned<F> {
type Output = Assigned<F>;
fn sub(self, rhs: &Self) -> Assigned<F> {
self - *rhs
}
}
impl<F: Field> Sub<Assigned<F>> for &Assigned<F> {
type Output = Assigned<F>;
fn sub(self, rhs: Assigned<F>) -> Assigned<F> {
*self - rhs
}
}
impl<F: Field> Sub<&Assigned<F>> for &Assigned<F> {
type Output = Assigned<F>;
fn sub(self, rhs: &Assigned<F>) -> Assigned<F> {
*self - *rhs
}
}
impl<F: Field> SubAssign for Assigned<F> {
fn sub_assign(&mut self, rhs: Self) {
*self = *self - rhs;
}
}
impl<F: Field> SubAssign<&Assigned<F>> for Assigned<F> {
fn sub_assign(&mut self, rhs: &Self) {
*self = *self - rhs;
}
}
impl<F: Field> Mul for Assigned<F> {
type Output = Assigned<F>;
fn mul(self, rhs: Assigned<F>) -> Assigned<F> {
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<F: Field> Mul<F> for Assigned<F> {
type Output = Assigned<F>;
fn mul(self, rhs: F) -> Assigned<F> {
self * Self::Trivial(rhs)
}
}
impl<F: Field> Mul<F> for &Assigned<F> {
type Output = Assigned<F>;
fn mul(self, rhs: F) -> Assigned<F> {
*self * rhs
}
}
impl<F: Field> Mul<&Assigned<F>> for Assigned<F> {
type Output = Assigned<F>;
fn mul(self, rhs: &Assigned<F>) -> Assigned<F> {
self * *rhs
}
}
impl<F: Field> MulAssign for Assigned<F> {
fn mul_assign(&mut self, rhs: Self) {
*self = *self * rhs;
}
}
impl<F: Field> MulAssign<&Assigned<F>> for Assigned<F> {
fn mul_assign(&mut self, rhs: &Self) {
*self = *self * rhs;
}
}
impl<F: Field> Assigned<F> {
/// 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<F> {
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<Output = Self> {
fn double(&self) -> Self;
fn square(&self) -> Self;
fn cube(&self) -> Self;
fn inv0(&self) -> Self;
}
impl<F: Field> 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<F: Field> UnaryOperand for Assigned<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()
}
}
#[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<F: UnaryOperand>(&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<Output = Self> + Sub<Output = Self> + Mul<Output = Self> {}
impl<F: Field> BinaryOperand for F {}
impl<F: Field> BinaryOperand for Assigned<F> {}
#[derive(Clone, Debug)]
enum BinaryOperator {
Add,
Sub,
Mul,
}
const BINARY_OPERATORS: &[BinaryOperator] = &[
BinaryOperator::Add,
BinaryOperator::Sub,
BinaryOperator::Mul,
];
impl BinaryOperator {
fn apply<F: BinaryOperand>(&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::<u64>()) -> Fp {
Fp::from(val)
}
}
prop_compose! {
fn arb_trivial()(element in arb_element()) -> Assigned<Fp> {
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<Fp> {
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<Operator> {
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<Assigned<Fp>>, Vec<Operator>) {
(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<F: UnaryOperand + BinaryOperand>(
items: Vec<F>,
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());
}
}

View File

@ -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<F: Field> {
/// 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<NR, N>(&mut self, name_fn: N)
where
NR: Into<String>,
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<A, AR>(&mut self, annotation: A, column: Column<Any>)
where
A: FnOnce() -> AR,
AR: Into<String>;
/// 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<A, AR>(
&mut self,
annotation: A,
selector: &Selector,
row: usize,
) -> Result<(), Error>
where
A: FnOnce() -> AR,
AR: Into<String>;
/// 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<Instance>, row: usize) -> Result<Value<F>, Error>;
/// Assign an advice column value (witness)
fn assign_advice<V, VR, A, AR>(
&mut self,
annotation: A,
column: Column<Advice>,
row: usize,
to: V,
) -> Result<(), Error>
where
V: FnOnce() -> Value<VR>,
VR: Into<Assigned<F>>,
A: FnOnce() -> AR,
AR: Into<String>;
/// Assign a fixed value
fn assign_fixed<V, VR, A, AR>(
&mut self,
annotation: A,
column: Column<Fixed>,
row: usize,
to: V,
) -> Result<(), Error>
where
V: FnOnce() -> Value<VR>,
VR: Into<Assigned<F>>,
A: FnOnce() -> AR,
AR: Into<String>;
/// Assign two cells to have the same value
fn copy(
&mut self,
left_column: Column<Any>,
left_row: usize,
right_column: Column<Any>,
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<Fixed>,
row: usize,
to: Value<Assigned<F>>,
) -> 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<F>;
/// 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<NR, N>(&mut self, name_fn: N)
where
NR: Into<String>,
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<String>);
}
/// 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<F: Field, CS: Assignment<F> + SyncDeps, C: Circuit<F>>(
cs: &mut CS,
circuit: &C,
config: C::Config,
constants: Vec<Column<Fixed>>,
) -> 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<F: Field> {
/// 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<F>,
_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<F>) -> 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<F>) -> Result<(), Error>;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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<io::Error> 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 {

View File

@ -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.

View File

@ -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<F: Field> {
pub name: String,
pub input_expressions: Vec<Expression<F>>,
pub table_expressions: Vec<Expression<F>>,
pub(crate) name: String,
pub(crate) input_expressions: Vec<Expression<F>>,
pub(crate) table_expressions: Vec<Expression<F>>,
}
impl<F: Field> Debug for Argument<F> {

View File

@ -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<Column<Any>>,
pub(crate) columns: Vec<Column<Any>>,
}
impl From<ArgumentV2> for Argument {
fn from(arg: ArgumentV2) -> Self {
impl From<ArgumentMid> 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<Column<Any>>,
pub copies: Vec<(Cell, Cell)>,
pub(crate) n: usize,
pub(crate) columns: Vec<Column<Any>>,
pub(crate) copies: Vec<(Cell, Cell)>,
}
impl Assembly {

View File

@ -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<F: Field> {
pub name: String,
pub input_expressions: Vec<Expression<F>>,
pub shuffle_expressions: Vec<Expression<F>>,
pub(crate) name: String,
pub(crate) input_expressions: Vec<Expression<F>>,
pub(crate) shuffle_expressions: Vec<Expression<F>>,
}
impl<F: Field> Debug for Argument<F> {

View File

@ -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<F> {
/// 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),
/// This is a challenge
Challenge(ChallengeMid),
/// This is a negated polynomial
Negated(Box<ExpressionMid<F>>),
/// This is the sum of two polynomials
Sum(Box<ExpressionMid<F>>, Box<ExpressionMid<F>>),
/// This is the product of two polynomials
Product(Box<ExpressionMid<F>>, Box<ExpressionMid<F>>),
/// This is a scaled polynomial
Scaled(Box<ExpressionMid<F>>, F),
#[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,
}
impl<F: Field> ExpressionMid<F> {
/// Compute the degree of this polynomial
pub fn degree(&self) -> usize {
use ExpressionMid::*;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum VarMid {
/// This is a generic column query
Query(QueryMid),
/// This is a challenge
Challenge(ChallengeMid),
}
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<W: std::io::Write>(&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<F> = Expression<F, VarMid>;
/// A Gate contains a single polynomial identity with a name as metadata.
#[derive(Clone, Debug)]
pub struct GateV2Backend<F: Field> {
pub struct Gate<F: Field, V: Variable> {
pub name: String,
pub poly: ExpressionMid<F>,
pub poly: Expression<F, V>,
}
impl<F: Field> GateV2Backend<F> {
impl<F: Field, V: Variable> Gate<F, V> {
/// 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<F> {
pub fn polynomial(&self) -> &Expression<F, V> {
&self.poly
}
}
pub type GateMid<F> = Gate<F, VarMid>;
/// This is a description of the circuit environment, such as the gate, column and
/// permutation arrangements.
#[derive(Debug, Clone)]
pub struct ConstraintSystemV2Backend<F: Field> {
pub struct ConstraintSystemMid<F: Field> {
pub num_fixed_columns: usize,
pub num_advice_columns: usize,
pub num_instance_columns: usize,
@ -129,21 +113,26 @@ pub struct ConstraintSystemV2Backend<F: Field> {
/// Contains the phase for each challenge. Should have same length as num_challenges.
pub challenge_phase: Vec<u8>,
pub gates: Vec<GateV2Backend<F>>,
pub gates: Vec<GateMid<F>>,
// 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<lookup::ArgumentV2<F>>,
pub lookups: Vec<lookup::ArgumentMid<F>>,
// 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<shuffle::ArgumentV2<F>>,
pub shuffles: Vec<shuffle::ArgumentMid<F>>,
// List of indexes of Fixed columns which are associated to a circuit-general Column tied to their annotation.
pub general_column_annotations: HashMap<metadata::Column, String>,
// 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<usize>,
}
/// Data that needs to be preprocessed from a circuit
@ -158,7 +147,7 @@ pub struct PreprocessingV2<F: Field> {
#[derive(Debug, Clone)]
pub struct CompiledCircuitV2<F: Field> {
pub preprocessing: PreprocessingV2<F>,
pub cs: ConstraintSystemV2Backend<F>,
pub cs: ConstraintSystemMid<F>,
}
// 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<F: Field>(&self, index: usize, at: Rotation) -> ExpressionMid<F> {
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<F: Field>(&self, index: usize, at: Rotation) -> ExpressionMid<F> {
ExpressionMid::Fixed(FixedQueryMid {
ExpressionMid::Var(VarMid::Query(QueryMid {
column_index: index,
column_type: Any::Fixed,
rotation: at,
})
}))
}
}
impl ColumnType for Instance {
fn query_cell<F: Field>(&self, index: usize, at: Rotation) -> ExpressionMid<F> {
ExpressionMid::Instance(InstanceQueryMid {
ExpressionMid::Var(VarMid::Query(QueryMid {
column_index: index,
column_type: Any::Instance,
rotation: at,
})
}))
}
}
impl ColumnType for Any {
fn query_cell<F: Field>(&self, index: usize, at: Rotation) -> ExpressionMid<F> {
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,
}),
})),
}
}
}

View File

@ -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<W: std::io::Write>(&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<F, V: Variable> {
/// This is a constant polynomial
Constant(F),
/// This is a variable
Var(V),
/// This is a negated polynomial
Negated(Box<Expression<F, V>>),
/// This is the sum of two polynomials
Sum(Box<Expression<F, V>>, Box<Expression<F, V>>),
/// This is the product of two polynomials
Product(Box<Expression<F, V>>, Box<Expression<F, V>>),
/// This is a scaled polynomial
Scaled(Box<Expression<F, V>>, F),
}
impl<F: Field, V: Variable> Expression<F, V> {
/// Evaluate the polynomial using the provided closures to perform the
/// operations.
#[allow(clippy::too_many_arguments)]
pub fn evaluate<T>(
&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<W: std::io::Write>(&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<F: Field, V: Variable> Neg for Expression<F, V> {
type Output = Expression<F, V>;
fn neg(self) -> Self::Output {
Expression::Negated(Box::new(self))
}
}
impl<F: Field, V: Variable> Add for Expression<F, V> {
type Output = Expression<F, V>;
fn add(self, rhs: Expression<F, V>) -> Expression<F, V> {
Expression::Sum(Box::new(self), Box::new(rhs))
}
}
impl<F: Field, V: Variable> Sub for Expression<F, V> {
type Output = Expression<F, V>;
fn sub(self, rhs: Expression<F, V>) -> Expression<F, V> {
Expression::Sum(Box::new(self), Box::new(-rhs))
}
}
impl<F: Field, V: Variable> Mul for Expression<F, V> {
type Output = Expression<F, V>;
fn mul(self, rhs: Expression<F, V>) -> Expression<F, V> {
Expression::Product(Box::new(self), Box::new(rhs))
}
}
impl<F: Field, V: Variable> Mul<F> for Expression<F, V> {
type Output = Expression<F, V>;
fn mul(self, rhs: F) -> Expression<F, V> {
Expression::Scaled(Box::new(self), rhs)
}
}
impl<F: Field, V: Variable> Sum<Self> for Expression<F, V> {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.reduce(|acc, x| acc + x)
.unwrap_or(Expression::Constant(F::ZERO))
}
}
impl<F: Field, V: Variable> Product<Self> for Expression<F, V> {
fn product<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.reduce(|acc, x| acc * x)
.unwrap_or(Expression::Constant(F::ONE))
}
}

View File

@ -1,4 +1,5 @@
pub mod circuit;
pub mod expression;
pub mod lookup;
pub mod metadata;
pub mod permutation;

View File

@ -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<F: Field> {
pub struct Argument<F: Field, V: Variable> {
pub name: String,
pub input_expressions: Vec<ExpressionMid<F>>,
pub table_expressions: Vec<ExpressionMid<F>>,
pub input_expressions: Vec<Expression<F, V>>,
pub table_expressions: Vec<Expression<F, V>>,
}
pub type ArgumentMid<F> = Argument<F, VarMid>;

View File

@ -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<ColumnMid>,
}

View File

@ -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<F: Field> {
pub struct Argument<F: Field, V: Variable> {
pub name: String,
pub input_expressions: Vec<ExpressionMid<F>>,
pub shuffle_expressions: Vec<ExpressionMid<F>>,
pub input_expressions: Vec<Expression<F, V>>,
pub shuffle_expressions: Vec<Expression<F, V>>,
}
pub type ArgumentMid<F> = Argument<F, VarMid>;

View File

@ -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"]

View File

@ -56,7 +56,7 @@ fn criterion_benchmark(c: &mut Criterion) {
&self,
config: MyConfig,
mut layouter: impl Layouter<F>,
) -> Result<(), Error> {
) -> Result<(), ErrorFront> {
layouter.assign_table(
|| "8-bit table",
|mut table| {

View File

@ -47,17 +47,22 @@ fn criterion_benchmark(c: &mut Criterion) {
&self,
layouter: &mut impl Layouter<FF>,
f: F,
) -> Result<(Cell, Cell, Cell), Error>
) -> Result<(Cell, Cell, Cell), ErrorFront>
where
F: FnMut() -> Value<(Assigned<FF>, Assigned<FF>, Assigned<FF>)>;
fn raw_add<F>(
&self,
layouter: &mut impl Layouter<FF>,
f: F,
) -> Result<(Cell, Cell, Cell), Error>
) -> Result<(Cell, Cell, Cell), ErrorFront>
where
F: FnMut() -> Value<(Assigned<FF>, Assigned<FF>, Assigned<FF>)>;
fn copy(&self, layouter: &mut impl Layouter<FF>, a: Cell, b: Cell) -> Result<(), Error>;
fn copy(
&self,
layouter: &mut impl Layouter<FF>,
a: Cell,
b: Cell,
) -> Result<(), ErrorFront>;
}
#[derive(Clone)]
@ -85,7 +90,7 @@ fn criterion_benchmark(c: &mut Criterion) {
&self,
layouter: &mut impl Layouter<FF>,
mut f: F,
) -> Result<(Cell, Cell, Cell), Error>
) -> Result<(Cell, Cell, Cell), ErrorFront>
where
F: FnMut() -> Value<(Assigned<FF>, Assigned<FF>, Assigned<FF>)>,
{
@ -127,7 +132,7 @@ fn criterion_benchmark(c: &mut Criterion) {
&self,
layouter: &mut impl Layouter<FF>,
mut f: F,
) -> Result<(Cell, Cell, Cell), Error>
) -> Result<(Cell, Cell, Cell), ErrorFront>
where
F: FnMut() -> Value<(Assigned<FF>, Assigned<FF>, Assigned<FF>)>,
{
@ -175,7 +180,7 @@ fn criterion_benchmark(c: &mut Criterion) {
layouter: &mut impl Layouter<FF>,
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<F>,
) -> Result<(), Error> {
) -> Result<(), ErrorFront> {
let cs = StandardPlonk::new(config);
for _ in 0..((1 << (self.k - 1)) - 3) {

View File

@ -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<FF: Field> {
fn raw_multiply<F>(&self, region: &mut Region<FF>, f: F) -> Result<(Cell, Cell, Cell), Error>
fn raw_multiply<F>(
&self,
region: &mut Region<FF>,
f: F,
) -> Result<(Cell, Cell, Cell), ErrorFront>
where
F: FnMut() -> Value<(Assigned<FF>, Assigned<FF>, Assigned<FF>)>;
fn raw_add<F>(&self, region: &mut Region<FF>, f: F) -> Result<(Cell, Cell, Cell), Error>
fn raw_add<F>(&self, region: &mut Region<FF>, f: F) -> Result<(Cell, Cell, Cell), ErrorFront>
where
F: FnMut() -> Value<(Assigned<FF>, Assigned<FF>, Assigned<FF>)>;
fn copy(&self, region: &mut Region<FF>, a: Cell, b: Cell) -> Result<(), Error>;
fn lookup_table(&self, layouter: &mut impl Layouter<FF>, values: &[FF]) -> Result<(), Error>;
fn copy(&self, region: &mut Region<FF>, a: Cell, b: Cell) -> Result<(), ErrorFront>;
fn lookup_table(
&self,
layouter: &mut impl Layouter<FF>,
values: &[FF],
) -> Result<(), ErrorFront>;
}
struct MyCircuit<F: Field> {
@ -62,7 +70,7 @@ impl<FF: Field> StandardCs<FF> for StandardPlonk<FF> {
&self,
region: &mut Region<FF>,
mut f: F,
) -> Result<(Cell, Cell, Cell), Error>
) -> Result<(Cell, Cell, Cell), ErrorFront>
where
F: FnMut() -> Value<(Assigned<FF>, Assigned<FF>, Assigned<FF>)>,
{
@ -99,7 +107,11 @@ impl<FF: Field> StandardCs<FF> for StandardPlonk<FF> {
region.assign_fixed(|| "a * b", self.config.sm, 0, || Value::known(FF::ONE))?;
Ok((lhs.cell(), rhs.cell(), out.cell()))
}
fn raw_add<F>(&self, region: &mut Region<FF>, mut f: F) -> Result<(Cell, Cell, Cell), Error>
fn raw_add<F>(
&self,
region: &mut Region<FF>,
mut f: F,
) -> Result<(Cell, Cell, Cell), ErrorFront>
where
F: FnMut() -> Value<(Assigned<FF>, Assigned<FF>, Assigned<FF>)>,
{
@ -136,10 +148,14 @@ impl<FF: Field> StandardCs<FF> for StandardPlonk<FF> {
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<FF>, left: Cell, right: Cell) -> Result<(), Error> {
fn copy(&self, region: &mut Region<FF>, left: Cell, right: Cell) -> Result<(), ErrorFront> {
region.constrain_equal(left, right)
}
fn lookup_table(&self, layouter: &mut impl Layouter<FF>, values: &[FF]) -> Result<(), Error> {
fn lookup_table(
&self,
layouter: &mut impl Layouter<FF>,
values: &[FF],
) -> Result<(), ErrorFront> {
layouter.assign_table(
|| "",
|mut table| {
@ -240,7 +256,11 @@ impl<F: Field> Circuit<F> for MyCircuit<F> {
}
}
fn synthesize(&self, config: PlonkConfig, mut layouter: impl Layouter<F>) -> Result<(), Error> {
fn synthesize(
&self,
config: PlonkConfig,
mut layouter: impl Layouter<F>,
) -> Result<(), ErrorFront> {
let cs = StandardPlonk::new(config);
for i in 0..10 {

View File

@ -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<Fp> for TestCircuit {
config
}
fn synthesize(&self, config: MyConfig, mut layouter: impl Layouter<Fp>) -> Result<(), Error> {
fn synthesize(
&self,
config: MyConfig,
mut layouter: impl Layouter<Fp>,
) -> Result<(), ErrorFront> {
layouter.assign_table(
|| "8-bit table",
|mut table| {

View File

@ -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<Fr> for StandardPlonk {
&self,
config: Self::Config,
mut layouter: impl Layouter<Fr>,
) -> 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::<Bn256>::setup(k, OsRng);
let vk = keygen_vk(&params, &circuit).expect("vk should not fail");
let compress_selectors = true;
let vk = keygen_vk_custom(&params, &circuit, compress_selectors).expect("vk should not fail");
let pk = keygen_pk(&params, 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::<G1Affine>::read::<_, StandardPlonk>(
let pk = pk_read::<G1Affine, _, StandardPlonk>(
&mut reader,
SerdeFormat::RawBytes,
compress_selectors,
#[cfg(feature = "circuit-params")]
circuit.params(),
)

View File

@ -145,7 +145,7 @@ impl<F: Field, const W: usize, const H: usize> Circuit<F> for MyCircuit<F, W, H>
&self,
config: Self::Config,
mut layouter: impl Layouter<F>,
) -> Result<(), Error> {
) -> Result<(), ErrorFront> {
let theta = layouter.get_challenge(config.theta);
let gamma = layouter.get_challenge(config.gamma);

View File

@ -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<F: Field> Circuit<F> for MyCircuit<F> {
&self,
config: Self::Config,
mut layouter: impl Layouter<F>,
) -> Result<(), Error> {
) -> Result<(), ErrorFront> {
let ch = ShuffleChip::<F>::construct(config);
layouter.assign_region(
|| "load inputs",

View File

@ -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<F: Field>: Chip<F> {
type Num;
/// Loads a number into the circuit as a private input.
fn load_private(&self, layouter: impl Layouter<F>, a: Value<F>) -> Result<Self::Num, Error>;
fn load_private(
&self,
layouter: impl Layouter<F>,
a: Value<F>,
) -> Result<Self::Num, ErrorFront>;
/// Loads a number into the circuit as a fixed constant.
fn load_constant(&self, layouter: impl Layouter<F>, constant: F) -> Result<Self::Num, Error>;
fn load_constant(
&self,
layouter: impl Layouter<F>,
constant: F,
) -> Result<Self::Num, ErrorFront>;
/// Returns `c = a * b`.
fn mul(
@ -24,7 +32,7 @@ trait NumericInstructions<F: Field>: Chip<F> {
layouter: impl Layouter<F>,
a: Self::Num,
b: Self::Num,
) -> Result<Self::Num, Error>;
) -> Result<Self::Num, ErrorFront>;
/// Exposes a number as a public input to the circuit.
fn expose_public(
@ -32,7 +40,7 @@ trait NumericInstructions<F: Field>: Chip<F> {
layouter: impl Layouter<F>,
num: Self::Num,
row: usize,
) -> Result<(), Error>;
) -> Result<(), ErrorFront>;
}
// ANCHOR_END: instructions
@ -152,7 +160,7 @@ impl<F: Field> NumericInstructions<F> for FieldChip<F> {
&self,
mut layouter: impl Layouter<F>,
value: Value<F>,
) -> Result<Self::Num, Error> {
) -> Result<Self::Num, ErrorFront> {
let config = self.config();
layouter.assign_region(
@ -169,7 +177,7 @@ impl<F: Field> NumericInstructions<F> for FieldChip<F> {
&self,
mut layouter: impl Layouter<F>,
constant: F,
) -> Result<Self::Num, Error> {
) -> Result<Self::Num, ErrorFront> {
let config = self.config();
layouter.assign_region(
@ -187,7 +195,7 @@ impl<F: Field> NumericInstructions<F> for FieldChip<F> {
mut layouter: impl Layouter<F>,
a: Self::Num,
b: Self::Num,
) -> Result<Self::Num, Error> {
) -> Result<Self::Num, ErrorFront> {
let config = self.config();
layouter.assign_region(
@ -223,7 +231,7 @@ impl<F: Field> NumericInstructions<F> for FieldChip<F> {
mut layouter: impl Layouter<F>,
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<F: Field> Circuit<F> for MyCircuit<F> {
&self,
config: Self::Config,
mut layouter: impl Layouter<F>,
) -> Result<(), Error> {
) -> Result<(), ErrorFront> {
let field_chip = FieldChip::<F>::construct(config);
// Load our private values into the circuit.

View File

@ -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<F: Field>: AddInstructions<F> + MulInstructions<F> {
&self,
layouter: impl Layouter<F>,
a: Value<F>,
) -> Result<<Self as FieldInstructions<F>>::Num, Error>;
) -> Result<<Self as FieldInstructions<F>>::Num, ErrorFront>;
/// Returns `d = (a + b) * c`.
fn add_and_mul(
@ -30,7 +30,7 @@ trait FieldInstructions<F: Field>: AddInstructions<F> + MulInstructions<F> {
a: <Self as FieldInstructions<F>>::Num,
b: <Self as FieldInstructions<F>>::Num,
c: <Self as FieldInstructions<F>>::Num,
) -> Result<<Self as FieldInstructions<F>>::Num, Error>;
) -> Result<<Self as FieldInstructions<F>>::Num, ErrorFront>;
/// Exposes a number as a public input to the circuit.
fn expose_public(
@ -38,7 +38,7 @@ trait FieldInstructions<F: Field>: AddInstructions<F> + MulInstructions<F> {
layouter: impl Layouter<F>,
num: <Self as FieldInstructions<F>>::Num,
row: usize,
) -> Result<(), Error>;
) -> Result<(), ErrorFront>;
}
// ANCHOR_END: field-instructions
@ -53,7 +53,7 @@ trait AddInstructions<F: Field>: Chip<F> {
layouter: impl Layouter<F>,
a: Self::Num,
b: Self::Num,
) -> Result<Self::Num, Error>;
) -> Result<Self::Num, ErrorFront>;
}
// ANCHOR_END: add-instructions
@ -68,7 +68,7 @@ trait MulInstructions<F: Field>: Chip<F> {
layouter: impl Layouter<F>,
a: Self::Num,
b: Self::Num,
) -> Result<Self::Num, Error>;
) -> Result<Self::Num, ErrorFront>;
}
// ANCHOR_END: mul-instructions
@ -181,7 +181,7 @@ impl<F: Field> AddInstructions<F> for FieldChip<F> {
layouter: impl Layouter<F>,
a: Self::Num,
b: Self::Num,
) -> Result<Self::Num, Error> {
) -> Result<Self::Num, ErrorFront> {
let config = self.config().add_config.clone();
let add_chip = AddChip::<F>::construct(config, ());
@ -197,7 +197,7 @@ impl<F: Field> AddInstructions<F> for AddChip<F> {
mut layouter: impl Layouter<F>,
a: Self::Num,
b: Self::Num,
) -> Result<Self::Num, Error> {
) -> Result<Self::Num, ErrorFront> {
let config = self.config();
layouter.assign_region(
@ -303,7 +303,7 @@ impl<F: Field> MulInstructions<F> for FieldChip<F> {
layouter: impl Layouter<F>,
a: Self::Num,
b: Self::Num,
) -> Result<Self::Num, Error> {
) -> Result<Self::Num, ErrorFront> {
let config = self.config().mul_config.clone();
let mul_chip = MulChip::<F>::construct(config, ());
mul_chip.mul(layouter, a, b)
@ -318,7 +318,7 @@ impl<F: Field> MulInstructions<F> for MulChip<F> {
mut layouter: impl Layouter<F>,
a: Self::Num,
b: Self::Num,
) -> Result<Self::Num, Error> {
) -> Result<Self::Num, ErrorFront> {
let config = self.config();
layouter.assign_region(
@ -403,7 +403,7 @@ impl<F: Field> FieldInstructions<F> for FieldChip<F> {
&self,
mut layouter: impl Layouter<F>,
value: Value<F>,
) -> Result<<Self as FieldInstructions<F>>::Num, Error> {
) -> Result<<Self as FieldInstructions<F>>::Num, ErrorFront> {
let config = self.config();
layouter.assign_region(
@ -423,7 +423,7 @@ impl<F: Field> FieldInstructions<F> for FieldChip<F> {
a: <Self as FieldInstructions<F>>::Num,
b: <Self as FieldInstructions<F>>::Num,
c: <Self as FieldInstructions<F>>::Num,
) -> Result<<Self as FieldInstructions<F>>::Num, Error> {
) -> Result<<Self as FieldInstructions<F>>::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<F: Field> FieldInstructions<F> for FieldChip<F> {
mut layouter: impl Layouter<F>,
num: <Self as FieldInstructions<F>>::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<F: Field> Circuit<F> for MyCircuit<F> {
&self,
config: Self::Config,
mut layouter: impl Layouter<F>,
) -> Result<(), Error> {
) -> Result<(), ErrorFront> {
let field_chip = FieldChip::<F>::construct(config, ());
// Load our private values into the circuit.

View File

@ -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<F: Field>: Chip<F> {
&self,
layouter: impl Layouter<F>,
a: &[Value<F>],
) -> Result<Vec<Self::Num>, Error>;
) -> Result<Vec<Self::Num>, ErrorFront>;
/// Returns `c = a * b`. The caller is responsible for ensuring that `a.len() == b.len()`.
fn mul(
@ -25,7 +25,7 @@ trait NumericInstructions<F: Field>: Chip<F> {
layouter: impl Layouter<F>,
a: &[Self::Num],
b: &[Self::Num],
) -> Result<Vec<Self::Num>, Error>;
) -> Result<Vec<Self::Num>, ErrorFront>;
/// Exposes a number as a public input to the circuit.
fn expose_public(
@ -33,7 +33,7 @@ trait NumericInstructions<F: Field>: Chip<F> {
layouter: impl Layouter<F>,
num: &Self::Num,
row: usize,
) -> Result<(), Error>;
) -> Result<(), ErrorFront>;
}
// ANCHOR_END: instructions
@ -150,7 +150,7 @@ impl<F: Field> NumericInstructions<F> for FieldChip<F> {
&self,
mut layouter: impl Layouter<F>,
values: &[Value<F>],
) -> Result<Vec<Self::Num>, Error> {
) -> Result<Vec<Self::Num>, ErrorFront> {
let config = self.config();
layouter.assign_region(
@ -174,7 +174,7 @@ impl<F: Field> NumericInstructions<F> for FieldChip<F> {
mut layouter: impl Layouter<F>,
a: &[Self::Num],
b: &[Self::Num],
) -> Result<Vec<Self::Num>, Error> {
) -> Result<Vec<Self::Num>, ErrorFront> {
let config = self.config();
assert_eq!(a.len(), b.len());
@ -208,7 +208,7 @@ impl<F: Field> NumericInstructions<F> for FieldChip<F> {
mut layouter: impl Layouter<F>,
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<F: Field> Circuit<F> for MyCircuit<F> {
&self,
config: Self::Config,
mut layouter: impl Layouter<F>,
) -> Result<(), Error> {
) -> Result<(), ErrorFront> {
let field_chip = FieldChip::<F>::construct(config);
// Load our private values into the circuit.

View File

@ -33,7 +33,7 @@ trait NumericInstructions<F: Field>: Chip<F> {
&self,
layouter: impl Layouter<F>,
a: &[Value<F>],
) -> Result<Vec<Self::Num>, Error>;
) -> Result<Vec<Self::Num>, ErrorFront>;
/// Returns `c = a * b`. The caller is responsible for ensuring that `a.len() == b.len()`.
fn mul(
@ -41,7 +41,7 @@ trait NumericInstructions<F: Field>: Chip<F> {
layouter: impl Layouter<F>,
a: &[Self::Num],
b: &[Self::Num],
) -> Result<Vec<Self::Num>, Error>;
) -> Result<Vec<Self::Num>, ErrorFront>;
/// Returns `c = a + b`. The caller is responsible for ensuring that `a.len() == b.len()`.
fn add(
@ -49,7 +49,7 @@ trait NumericInstructions<F: Field>: Chip<F> {
layouter: impl Layouter<F>,
a: &[Self::Num],
b: &[Self::Num],
) -> Result<Vec<Self::Num>, Error>;
) -> Result<Vec<Self::Num>, ErrorFront>;
/// Exposes a number as a public input to the circuit.
fn expose_public(
@ -57,7 +57,7 @@ trait NumericInstructions<F: Field>: Chip<F> {
layouter: impl Layouter<F>,
num: &Self::Num,
row: usize,
) -> Result<(), Error>;
) -> Result<(), ErrorFront>;
}
// ANCHOR_END: instructions
@ -204,7 +204,7 @@ impl<F: Field> NumericInstructions<F> for MultChip<F> {
&self,
mut layouter: impl Layouter<F>,
values: &[Value<F>],
) -> Result<Vec<Self::Num>, Error> {
) -> Result<Vec<Self::Num>, ErrorFront> {
let config = self.config();
layouter.assign_region(
@ -228,7 +228,7 @@ impl<F: Field> NumericInstructions<F> for MultChip<F> {
_: impl Layouter<F>,
_: &[Self::Num],
_: &[Self::Num],
) -> Result<Vec<Self::Num>, Error> {
) -> Result<Vec<Self::Num>, ErrorFront> {
panic!("Not implemented")
}
@ -237,7 +237,7 @@ impl<F: Field> NumericInstructions<F> for MultChip<F> {
mut layouter: impl Layouter<F>,
a: &[Self::Num],
b: &[Self::Num],
) -> Result<Vec<Self::Num>, Error> {
) -> Result<Vec<Self::Num>, ErrorFront> {
let config = self.config();
assert_eq!(a.len(), b.len());
@ -271,7 +271,7 @@ impl<F: Field> NumericInstructions<F> for MultChip<F> {
mut layouter: impl Layouter<F>,
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<F: Field> NumericInstructions<F> for AddChip<F> {
&self,
mut layouter: impl Layouter<F>,
values: &[Value<F>],
) -> Result<Vec<Self::Num>, Error> {
) -> Result<Vec<Self::Num>, ErrorFront> {
let config = self.config();
layouter.assign_region(
@ -310,7 +310,7 @@ impl<F: Field> NumericInstructions<F> for AddChip<F> {
_: impl Layouter<F>,
_: &[Self::Num],
_: &[Self::Num],
) -> Result<Vec<Self::Num>, Error> {
) -> Result<Vec<Self::Num>, ErrorFront> {
panic!("Not implemented")
}
@ -319,7 +319,7 @@ impl<F: Field> NumericInstructions<F> for AddChip<F> {
mut layouter: impl Layouter<F>,
a: &[Self::Num],
b: &[Self::Num],
) -> Result<Vec<Self::Num>, Error> {
) -> Result<Vec<Self::Num>, ErrorFront> {
let config = self.config();
assert_eq!(a.len(), b.len());
@ -353,7 +353,7 @@ impl<F: Field> NumericInstructions<F> for AddChip<F> {
mut layouter: impl Layouter<F>,
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<F: Field> Circuit<F> for MulCircuit<F> {
&self,
config: Self::Config,
mut layouter: impl Layouter<F>,
) -> Result<(), Error> {
) -> Result<(), ErrorFront> {
let field_chip = MultChip::<F>::construct(config);
// Load our unblinded values into the circuit.
@ -448,7 +448,7 @@ impl<F: Field> Circuit<F> for AddCircuit<F> {
&self,
config: Self::Config,
mut layouter: impl Layouter<F>,
) -> Result<(), Error> {
) -> Result<(), ErrorFront> {
let field_chip = AddChip::<F>::construct(config);
// Load our unblinded values into the circuit.

View File

@ -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 {

View File

@ -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<C: SerdeCurveAffine, R: io::Read, ConcreteCircuit: Circuit<C::Scalar>>(
reader: &mut R,
format: SerdeFormat,
compress_selectors: bool,
#[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params,
) -> io::Result<VerifyingKey<C>>
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<C: SerdeCurveAffine, R: io::Read, ConcreteCircuit: Circuit<C::Scalar>>(
reader: &mut R,
format: SerdeFormat,
compress_selectors: bool,
#[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params,
) -> io::Result<ProvingKey<C>>
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())
}

View File

@ -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<ErrorFront> for Error {
fn from(err: ErrorFront) -> Self {
Error::Frontend(err)
}
}
impl From<ErrorBack> for Error {
fn from(err: ErrorBack) -> Self {
Error::Backend(err)
}
}

View File

@ -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<C::Scalar>,
{
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)?)
}

View File

@ -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<F>,
) -> 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::<KZGCommitmentScheme<_>, ProverSHPLONK<_>, _, _, _, _>(

View File

@ -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<AssignedCell<F, F>>,
abcd: [u64; 4],
) -> Result<(AssignedCell<F, F>, [AssignedCell<F, F>; 4]), Error> {
) -> Result<(AssignedCell<F, F>, [AssignedCell<F, F>; 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<F: Field + From<u64>, const WIDTH_FACTOR: usize> MyCircuit<F, WIDTH_FACTOR>
(0..WIDTH_FACTOR).map(|_| instance.clone()).collect()
}
fn configure_single(meta: &mut ConstraintSystem<F>) -> MyCircuitConfig {
fn configure_single(meta: &mut ConstraintSystem<F>, id: usize) -> MyCircuitConfig {
let s_gate = meta.selector();
let a = meta.advice_column();
let b = meta.advice_column();
@ -166,7 +167,7 @@ impl<F: Field + From<u64>, const WIDTH_FACTOR: usize> MyCircuit<F, WIDTH_FACTOR>
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<F: Field + From<u64>, const WIDTH_FACTOR: usize> MyCircuit<F, WIDTH_FACTOR>
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<F: Field + From<u64>, const WIDTH_FACTOR: usize> MyCircuit<F, WIDTH_FACTOR>
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<F: Field + From<u64>, const WIDTH_FACTOR: usize> MyCircuit<F, WIDTH_FACTOR>
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<F: Field + From<u64>, const WIDTH_FACTOR: usize> MyCircuit<F, WIDTH_FACTOR>
&self,
config: &MyCircuitConfig,
layouter: &mut impl Layouter<F>,
) -> Result<(usize, Vec<AssignedCell<F, F>>), Error> {
id: usize,
unit_id: usize,
) -> Result<(usize, Vec<AssignedCell<F, F>>), 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<F: Field + From<u64>, const WIDTH_FACTOR: usize> Circuit<F> for MyCircuit<F
fn configure(meta: &mut ConstraintSystem<F>) -> Vec<MyCircuitConfig> {
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<F: Field + From<u64>, const WIDTH_FACTOR: usize> Circuit<F> for MyCircuit<F
&self,
config: Vec<MyCircuitConfig>,
mut layouter: impl Layouter<F>,
) -> Result<(), Error> {
) -> Result<(), ErrorFront> {
// - 2 queries from first gate
// - 3 for permutation argument
// - 1 for multipoen
@ -432,11 +447,13 @@ impl<F: Field + From<u64>, const WIDTH_FACTOR: usize> Circuit<F> for MyCircuit<F
// - 1 for off-by-one errors
let unusable_rows = 2 + 3 + 1 + 1 + 1;
let max_rows = 2usize.pow(self.k) - unusable_rows;
for config in &config {
for (id, config) in config.iter().enumerate() {
let mut total_rows = 0;
let mut unit_id = 0;
loop {
let (rows, instance_copy) =
self.synthesize_unit(config, &mut layouter).expect("todo");
let (rows, instance_copy) = self
.synthesize_unit(config, &mut layouter, id, unit_id)
.expect("todo");
if total_rows == 0 {
for (i, instance) in instance_copy.iter().enumerate() {
layouter.constrain_instance(instance.cell(), config.instance, 1 + i)?;
@ -446,6 +463,7 @@ impl<F: Field + From<u64>, const WIDTH_FACTOR: usize> Circuit<F> for MyCircuit<F
if total_rows + rows > 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() {

View File

@ -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<FF>,
f: F,
) -> Result<(Cell, Cell, Cell), Error>
) -> Result<(Cell, Cell, Cell), ErrorFront>
where
F: FnMut() -> Value<(Assigned<FF>, Assigned<FF>, Assigned<FF>)>;
fn raw_add<F>(
&self,
layouter: &mut impl Layouter<FF>,
f: F,
) -> Result<(Cell, Cell, Cell), Error>
) -> Result<(Cell, Cell, Cell), ErrorFront>
where
F: FnMut() -> Value<(Assigned<FF>, Assigned<FF>, Assigned<FF>)>;
fn copy(&self, layouter: &mut impl Layouter<FF>, a: Cell, b: Cell) -> Result<(), Error>;
fn public_input<F>(&self, layouter: &mut impl Layouter<FF>, f: F) -> Result<Cell, Error>
fn copy(
&self,
layouter: &mut impl Layouter<FF>,
a: Cell,
b: Cell,
) -> Result<(), ErrorFront>;
fn public_input<F>(
&self,
layouter: &mut impl Layouter<FF>,
f: F,
) -> Result<Cell, ErrorFront>
where
F: FnMut() -> Value<FF>;
fn lookup_table(
&self,
layouter: &mut impl Layouter<FF>,
values: &[FF],
) -> Result<(), Error>;
) -> Result<(), ErrorFront>;
}
#[derive(Clone)]
@ -97,7 +106,7 @@ fn plonk_api() {
&self,
layouter: &mut impl Layouter<FF>,
mut f: F,
) -> Result<(Cell, Cell, Cell), Error>
) -> Result<(Cell, Cell, Cell), ErrorFront>
where
F: FnMut() -> Value<(Assigned<FF>, Assigned<FF>, Assigned<FF>)>,
{
@ -151,7 +160,7 @@ fn plonk_api() {
&self,
layouter: &mut impl Layouter<FF>,
mut f: F,
) -> Result<(Cell, Cell, Cell), Error>
) -> Result<(Cell, Cell, Cell), ErrorFront>
where
F: FnMut() -> Value<(Assigned<FF>, Assigned<FF>, Assigned<FF>)>,
{
@ -211,7 +220,7 @@ fn plonk_api() {
layouter: &mut impl Layouter<FF>,
left: Cell,
right: Cell,
) -> Result<(), Error> {
) -> Result<(), ErrorFront> {
layouter.assign_region(
|| "copy",
|mut region| {
@ -220,7 +229,11 @@ fn plonk_api() {
},
)
}
fn public_input<F>(&self, layouter: &mut impl Layouter<FF>, mut f: F) -> Result<Cell, Error>
fn public_input<F>(
&self,
layouter: &mut impl Layouter<FF>,
mut f: F,
) -> Result<Cell, ErrorFront>
where
F: FnMut() -> Value<FF>,
{
@ -243,7 +256,7 @@ fn plonk_api() {
&self,
layouter: &mut impl Layouter<FF>,
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<F>,
) -> 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,
),
},
),
Fixed {
query_index: 2,
column_index: 1,
rotation: Rotation(
0,
),
},
Var(
Query(
QueryBack {
index: 1,
column_index: 2,
column_type: Advice,
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,
),
},
Sum(
Advice {
query_index: 0,
column_index: 1,
rotation: Rotation(
0,
),
},
Negated(
Instance {
query_index: 0,
column_index: 0,
Var(
Query(
QueryBack {
index: 5,
column_index: 5,
column_type: Fixed,
rotation: Rotation(
0,
),
},
),
),
Sum(
Var(
Query(
QueryBack {
index: 0,
column_index: 1,
column_type: Advice,
rotation: Rotation(
0,
),
},
),
),
Negated(
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: [