mirror of https://github.com/zcash/halo2.git
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:
parent
d6f70203d2
commit
e1f6e41757
|
@ -6,4 +6,5 @@ members = [
|
|||
"halo2_middleware",
|
||||
"halo2_backend",
|
||||
"halo2_common",
|
||||
]
|
||||
]
|
||||
resolver = "2"
|
||||
|
|
|
@ -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"]
|
||||
|
||||
|
|
|
@ -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>(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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())]
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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],
|
||||
)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"]
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
//! Implementations of common circuit floor planners.
|
||||
|
||||
pub mod single_pass;
|
||||
|
||||
pub mod v1;
|
||||
pub use v1::{V1Pass, V1};
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
®ion_columns,
|
||||
region.row_count(),
|
||||
0,
|
||||
None,
|
||||
)
|
||||
.expect("We can always fit a region somewhere");
|
||||
|
||||
(region_start.into(), region)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Return the column allocations for potential further processing.
|
||||
(regions, column_allocations)
|
||||
}
|
||||
|
||||
/// Sorts the regions by advice area and then lays them out with the [`slot_in`] strategy.
|
||||
pub fn slot_in_biggest_advice_first(
|
||||
region_shapes: Vec<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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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,
|
||||
®ion_columns,
|
||||
region.row_count(),
|
||||
0,
|
||||
None,
|
||||
)
|
||||
.expect("We can always fit a region somewhere");
|
||||
|
||||
(region_start.into(), region)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Return the column allocations for potential further processing.
|
||||
(regions, column_allocations)
|
||||
}
|
||||
|
||||
/// Sorts the regions by advice area and then lays them out with the [`slot_in`] strategy.
|
||||
pub fn slot_in_biggest_advice_first(
|
||||
region_shapes: Vec<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![
|
||||
|
|
|
@ -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.
|
|
@ -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]
|
||||
|
|
|
@ -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();
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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::{
|
||||
|
|
|
@ -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},
|
||||
/// };
|
||||
///
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
pub mod circuit;
|
||||
pub mod dev;
|
||||
pub mod plonk;
|
||||
|
|
|
@ -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::*;
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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 {
|
|
@ -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.
|
|
@ -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> {
|
|
@ -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 {
|
|
@ -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> {
|
|
@ -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,
|
||||
}),
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
pub mod circuit;
|
||||
pub mod expression;
|
||||
pub mod lookup;
|
||||
pub mod metadata;
|
||||
pub mod permutation;
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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(¶ms, &circuit).expect("vk should not fail");
|
||||
let compress_selectors = true;
|
||||
let vk = keygen_vk_custom(¶ms, &circuit, compress_selectors).expect("vk should not fail");
|
||||
let pk = keygen_pk(¶ms, vk, &circuit).expect("pk should not fail");
|
||||
|
||||
let f = File::create("serialization-test.pk").unwrap();
|
||||
|
@ -143,9 +144,10 @@ fn main() {
|
|||
let f = File::open("serialization-test.pk").unwrap();
|
||||
let mut reader = BufReader::new(f);
|
||||
#[allow(clippy::unit_arg)]
|
||||
let pk = ProvingKey::<G1Affine>::read::<_, StandardPlonk>(
|
||||
let pk = pk_read::<G1Affine, _, StandardPlonk>(
|
||||
&mut reader,
|
||||
SerdeFormat::RawBytes,
|
||||
compress_selectors,
|
||||
#[cfg(feature = "circuit-params")]
|
||||
circuit.params(),
|
||||
)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)?)
|
||||
}
|
||||
|
|
|
@ -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<_>, _, _, _, _>(
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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: [
|
||||
|
|
Loading…
Reference in New Issue