From ee9f185705749c28b989e357603387f7240eda04 Mon Sep 17 00:00:00 2001 From: kilic Date: Sun, 21 May 2023 19:17:27 +0300 Subject: [PATCH 01/10] implement native shuffle argument and api --- halo2_proofs/examples/shuffle_api.rs | 222 +++++++++++++++++++ halo2_proofs/src/dev.rs | 101 +++++++++ halo2_proofs/src/dev/failure.rs | 203 ++++++++++++++++++ halo2_proofs/src/plonk.rs | 1 + halo2_proofs/src/plonk/circuit.rs | 57 ++++- halo2_proofs/src/plonk/evaluation.rs | 104 ++++++++- halo2_proofs/src/plonk/prover.rs | 55 ++++- halo2_proofs/src/plonk/shuffle.rs | 67 ++++++ halo2_proofs/src/plonk/shuffle/prover.rs | 234 +++++++++++++++++++++ halo2_proofs/src/plonk/shuffle/verifier.rs | 137 ++++++++++++ halo2_proofs/src/plonk/verifier.rs | 175 ++++++++++----- 11 files changed, 1292 insertions(+), 64 deletions(-) create mode 100644 halo2_proofs/examples/shuffle_api.rs create mode 100644 halo2_proofs/src/plonk/shuffle.rs create mode 100644 halo2_proofs/src/plonk/shuffle/prover.rs create mode 100644 halo2_proofs/src/plonk/shuffle/verifier.rs diff --git a/halo2_proofs/examples/shuffle_api.rs b/halo2_proofs/examples/shuffle_api.rs new file mode 100644 index 0000000000..5f72040804 --- /dev/null +++ b/halo2_proofs/examples/shuffle_api.rs @@ -0,0 +1,222 @@ +use std::{marker::PhantomData, vec}; + +use ff::FromUniformBytes; +use halo2_proofs::{ + arithmetic::Field, + circuit::{Layouter, SimpleFloorPlanner, Value}, + plonk::{ + create_proof, keygen_pk, keygen_vk, verify_proof, Advice, Circuit, Column, + ConstraintSystem, Error, Fixed, Selector, + }, + poly::Rotation, + poly::{ + commitment::ParamsProver, + ipa::{ + commitment::{IPACommitmentScheme, ParamsIPA}, + multiopen::{ProverIPA, VerifierIPA}, + strategy::AccumulatorStrategy, + }, + VerificationStrategy, + }, + transcript::{ + Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, + }, +}; +use halo2curves::{pasta::EqAffine, CurveAffine}; +use rand_core::OsRng; + +struct ShuffleChip { + config: ShuffleConfig, + _marker: PhantomData, +} + +#[derive(Clone, Debug)] +struct ShuffleConfig { + input_0: Column, + input_1: Column, + shuffle_0: Column, + shuffle_1: Column, + s_input: Selector, + s_shuffle: Selector, +} + +impl ShuffleChip { + fn construct(config: ShuffleConfig) -> Self { + Self { + config, + _marker: PhantomData, + } + } + + fn configure( + meta: &mut ConstraintSystem, + input_0: Column, + input_1: Column, + shuffle_0: Column, + shuffle_1: Column, + ) -> ShuffleConfig { + let s_shuffle = meta.complex_selector(); + let s_input = meta.complex_selector(); + meta.shuffle("shuffle", |meta| { + let s_input = meta.query_selector(s_input); + let s_shuffle = meta.query_selector(s_shuffle); + let input_0 = meta.query_advice(input_0, Rotation::cur()); + let input_1 = meta.query_fixed(input_1, Rotation::cur()); + let shuffle_0 = meta.query_advice(shuffle_0, Rotation::cur()); + let shuffle_1 = meta.query_advice(shuffle_1, Rotation::cur()); + vec![ + (s_input.clone() * input_0, s_shuffle.clone() * shuffle_0), + (s_input * input_1, s_shuffle * shuffle_1), + ] + }); + ShuffleConfig { + input_0, + input_1, + shuffle_0, + shuffle_1, + s_input, + s_shuffle, + } + } +} + +// ANCHOR: circuit +/// The full circuit implementation. +/// +/// In this struct we store the private input variables. We use `Option` because +/// they won't have any value during key generation. During proving, if any of these +/// were `None` we would get an error. +#[derive(Default)] +struct MyCircuit { + input_0: Vec>, + input_1: Vec, + shuffle_0: Vec>, + shuffle_1: Vec>, +} + +impl Circuit for MyCircuit { + // Since we are using a single chip for everything, we can just reuse its config. + type Config = ShuffleConfig; + type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let input_0 = meta.advice_column(); + let input_1 = meta.fixed_column(); + let shuffle_0 = meta.advice_column(); + let shuffle_1 = meta.advice_column(); + ShuffleChip::configure(meta, input_0, input_1, shuffle_0, shuffle_1) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let ch = ShuffleChip::::construct(config); + layouter.assign_region( + || "load inputs", + |mut region| { + for (i, (input_0, input_1)) in + self.input_0.iter().zip(self.input_1.iter()).enumerate() + { + region.assign_advice(|| "input_0", ch.config.input_0, i, || *input_0)?; + region.assign_fixed( + || "input_1", + ch.config.input_1, + i, + || Value::known(*input_1), + )?; + ch.config.s_input.enable(&mut region, i)?; + } + Ok(()) + }, + )?; + layouter.assign_region( + || "load shuffles", + |mut region| { + for (i, (shuffle_0, shuffle_1)) in + self.shuffle_0.iter().zip(self.shuffle_1.iter()).enumerate() + { + region.assign_advice(|| "shuffle_0", ch.config.shuffle_0, i, || *shuffle_0)?; + region.assign_advice(|| "shuffle_1", ch.config.shuffle_1, i, || *shuffle_1)?; + ch.config.s_shuffle.enable(&mut region, i)?; + } + Ok(()) + }, + )?; + Ok(()) + } +} + +fn test_prover(k: u32, circuit: MyCircuit, expected: bool) +where + C::Scalar: FromUniformBytes<64>, +{ + let params = ParamsIPA::::new(k); + let vk = keygen_vk(¶ms, &circuit).unwrap(); + let pk = keygen_pk(¶ms, vk, &circuit).unwrap(); + + let proof = { + let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); + + create_proof::, ProverIPA, _, _, _, _>( + ¶ms, + &pk, + &[circuit], + &[&[]], + OsRng, + &mut transcript, + ) + .expect("proof generation should not fail"); + + transcript.finalize() + }; + + let accepted = { + let strategy = AccumulatorStrategy::new(¶ms); + let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); + + verify_proof::, VerifierIPA, _, _, _>( + ¶ms, + pk.get_vk(), + strategy, + &[&[]], + &mut transcript, + ) + .map(|strategy| strategy.finalize()) + .unwrap_or_default() + }; + + assert_eq!(accepted, expected); +} + +fn main() { + use halo2_proofs::dev::MockProver; + use halo2curves::pasta::Fp; + const K: u32 = 4; + let input_0 = [1, 2, 4, 1] + .map(|e: u64| Value::known(Fp::from(e))) + .to_vec(); + let input_1 = [10, 20, 40, 10].map(Fp::from).to_vec(); + let shuffle_0 = [4, 1, 1, 2] + .map(|e: u64| Value::known(Fp::from(e))) + .to_vec(); + let shuffle_1 = [40, 10, 10, 20] + .map(|e: u64| Value::known(Fp::from(e))) + .to_vec(); + let circuit = MyCircuit { + input_0, + input_1, + shuffle_0, + shuffle_1, + }; + let prover = MockProver::run(K, &circuit, vec![]).unwrap(); + prover.assert_satisfied(); + test_prover::(K, circuit, true); +} diff --git a/halo2_proofs/src/dev.rs b/halo2_proofs/src/dev.rs index a1bb0610e8..ee480d14d5 100644 --- a/halo2_proofs/src/dev.rs +++ b/halo2_proofs/src/dev.rs @@ -1,5 +1,6 @@ //! Tools for developing circuits. +use std::collections::BTreeMap; use std::collections::HashMap; use std::collections::HashSet; use std::fmt; @@ -1000,6 +1001,105 @@ impl + Ord> MockProver { }) .collect::>() }); + + let shuffle_errors = + self.cs + .shuffles + .iter() + .enumerate() + .flat_map(|(shuffle_index, shuffle)| { + let load = |expression: &Expression, row| { + expression.evaluate_lazy( + &|scalar| Value::Real(scalar), + &|_| panic!("virtual selectors are removed during optimization"), + &|query| { + let query = self.cs.fixed_queries[query.index.unwrap()]; + let column_index = query.0.index(); + let rotation = query.1 .0; + self.fixed[column_index] + [(row as i32 + n + rotation) as usize % n as usize] + .into() + }, + &|query| { + let query = self.cs.advice_queries[query.index.unwrap()]; + let column_index = query.0.index(); + let rotation = query.1 .0; + self.advice[column_index] + [(row as i32 + n + rotation) as usize % n as usize] + .into() + }, + &|query| { + let query = self.cs.instance_queries[query.index.unwrap()]; + let column_index = query.0.index(); + let rotation = query.1 .0; + Value::Real( + self.instance[column_index] + [(row as i32 + n + rotation) as usize % n as usize], + ) + }, + &|challenge| Value::Real(self.challenges[challenge.index()]), + &|a| -a, + &|a, b| a + b, + &|a, b| a * b, + &|a, scalar| a * scalar, + &Value::Real(F::ZERO), + ) + }; + + assert!(shuffle.shuffle_expressions.len() == shuffle.input_expressions.len()); + assert!(self.usable_rows.end > 0); + + let mut shuffle_rows: Vec>> = self + .usable_rows + .clone() + .map(|row| { + let t = shuffle + .shuffle_expressions + .iter() + .map(move |c| load(c, row)) + .collect(); + t + }) + .collect(); + shuffle_rows.sort(); + + let mut input_rows: Vec<(Vec>, usize)> = lookup_input_row_ids + .clone() + .into_iter() + .map(|input_row| { + let t = shuffle + .input_expressions + .iter() + .map(move |c| load(c, input_row)) + .collect(); + + (t, input_row) + }) + .collect(); + input_rows.sort(); + + input_rows + .iter() + .zip(shuffle_rows.iter()) + .filter_map(|((input_value, row), shuffle_value)| { + if shuffle_value != input_value { + Some(VerifyFailure::Shuffle { + name: shuffle.name.clone(), + shuffle_index, + location: FailureLocation::find_expressions( + &self.cs, + &self.regions, + *row, + shuffle.input_expressions.iter(), + ), + }) + } else { + None + } + }) + .collect::>() + }); + let mapping = self.permutation.mapping(); // Check that permutations preserve the original values of the cells. let perm_errors = { @@ -1050,6 +1150,7 @@ impl + Ord> MockProver { .chain(gate_errors) .chain(lookup_errors) .chain(perm_errors) + .chain(shuffle_errors) .collect(); if errors.is_empty() { Ok(()) diff --git a/halo2_proofs/src/dev/failure.rs b/halo2_proofs/src/dev/failure.rs index 706ab76b4f..3cb85311af 100644 --- a/halo2_proofs/src/dev/failure.rs +++ b/halo2_proofs/src/dev/failure.rs @@ -178,6 +178,28 @@ pub enum VerifyFailure { /// lookup is active on a row adjacent to an unrelated region. location: FailureLocation, }, + /// A lookup input did not exist in its corresponding table. + Shuffle { + /// The name of the lookup that is not satisfied. + name: String, + /// The index of the lookup that is not satisfied. These indices are assigned in + /// the order in which `ConstraintSystem::lookup` is called during + /// `Circuit::configure`. + shuffle_index: usize, + /// The location at which the lookup is not satisfied. + /// + /// `FailureLocation::InRegion` is most common, and may be due to the intentional + /// use of a lookup (if its inputs are conditional on a complex selector), or an + /// unintentional lookup constraint that overlaps the region (indicating that the + /// lookup's inputs should be made conditional). + /// + /// `FailureLocation::OutsideRegion` is uncommon, and could mean that: + /// - The input expressions do not correctly constrain a default value that exists + /// in the table when the lookup is not being used. + /// - The input expressions use a column queried at a non-zero `Rotation`, and the + /// lookup is active on a row adjacent to an unrelated region. + location: FailureLocation, + }, /// A permutation did not preserve the original value of a cell. Permutation { /// The column in which this permutation is not satisfied. @@ -241,6 +263,17 @@ impl fmt::Display for VerifyFailure { name, lookup_index, location ) } + Self::Shuffle { + name, + shuffle_index, + location, + } => { + write!( + f, + "Shuffle {}(index: {}) is not satisfied {}", + name, shuffle_index, location + ) + } Self::Permutation { column, location } => { write!( f, @@ -611,6 +644,171 @@ fn render_lookup( } } +fn render_shuffle( + prover: &MockProver, + name: &str, + shuffle_index: usize, + location: &FailureLocation, +) { + let n = prover.n as i32; + let cs = &prover.cs; + let shuffle = &cs.shuffles[shuffle_index]; + + // Get the absolute row on which the lookup's inputs are being queried, so we can + // fetch the input values. + let row = match location { + FailureLocation::InRegion { region, offset } => { + prover.regions[region.index].rows.unwrap().0 + offset + } + FailureLocation::OutsideRegion { row } => *row, + } as i32; + + let shuffle_columns = shuffle.shuffle_expressions.iter().map(|expr| { + expr.evaluate( + &|f| format! {"Const: {:#?}", f}, + &|s| format! {"S{}", s.0}, + &|query| { + format!( + "{:?}", + prover + .cs + .general_column_annotations + .get(&metadata::Column::from((Any::Fixed, query.column_index))) + .cloned() + .unwrap_or_else(|| format!("F{}", query.column_index())) + ) + }, + &|query| { + format!( + "{:?}", + prover + .cs + .general_column_annotations + .get(&metadata::Column::from((Any::advice(), query.column_index))) + .cloned() + .unwrap_or_else(|| format!("A{}", query.column_index())) + ) + }, + &|query| { + format!( + "{:?}", + prover + .cs + .general_column_annotations + .get(&metadata::Column::from((Any::Instance, query.column_index))) + .cloned() + .unwrap_or_else(|| format!("I{}", query.column_index())) + ) + }, + &|challenge| format! {"C{}", challenge.index()}, + &|query| format! {"-{}", query}, + &|a, b| format! {"{} + {}", a,b}, + &|a, b| format! {"{} * {}", a,b}, + &|a, b| format! {"{} * {:?}", a, b}, + ) + }); + + fn cell_value<'a, F: Field, Q: Into + Copy>( + load: impl Fn(Q) -> Value + 'a, + ) -> impl Fn(Q) -> BTreeMap + 'a { + move |query| { + let AnyQuery { + column_type, + column_index, + rotation, + .. + } = query.into(); + Some(( + ((column_type, column_index).into(), rotation.0).into(), + match load(query) { + Value::Real(v) => util::format_value(v), + Value::Poison => unreachable!(), + }, + )) + .into_iter() + .collect() + } + } + + eprintln!("error: input does not exist in shuffle"); + eprint!(" ("); + for i in 0..shuffle.input_expressions.len() { + eprint!("{}L{}", if i == 0 { "" } else { ", " }, i); + } + eprint!(") <-> ("); + for (i, column) in shuffle_columns.enumerate() { + eprint!("{}{}", if i == 0 { "" } else { ", " }, column); + } + eprintln!(")"); + + eprintln!(); + eprintln!(" Shuffle '{}' inputs:", name); + for (i, input) in shuffle.input_expressions.iter().enumerate() { + // Fetch the cell values (since we don't store them in VerifyFailure::Lookup). + let cell_values = input.evaluate( + &|_| BTreeMap::default(), + &|_| panic!("virtual selectors are removed during optimization"), + &cell_value(&util::load(n, row, &cs.fixed_queries, &prover.fixed)), + &cell_value(&util::load(n, row, &cs.advice_queries, &prover.advice)), + &cell_value(&util::load_instance( + n, + row, + &cs.instance_queries, + &prover.instance, + )), + &|_| BTreeMap::default(), + &|a| a, + &|mut a, mut b| { + a.append(&mut b); + a + }, + &|mut a, mut b| { + a.append(&mut b); + a + }, + &|a, _| a, + ); + + // Collect the necessary rendering information: + // - The columns involved in this constraint. + // - How many cells are in each column. + // - The grid of cell values, indexed by rotation. + let mut columns = BTreeMap::::default(); + let mut layout = BTreeMap::>::default(); + for (i, (cell, _)) in cell_values.iter().enumerate() { + *columns.entry(cell.column).or_default() += 1; + layout + .entry(cell.rotation) + .or_default() + .entry(cell.column) + .or_insert(format!("x{}", i)); + } + + if i != 0 { + eprintln!(); + } + eprintln!( + " Sh{} = {}", + i, + emitter::expression_to_string(input, &layout) + ); + eprintln!(" ^"); + + emitter::render_cell_layout(" | ", location, &columns, &layout, |_, rotation| { + if rotation == 0 { + eprint!(" <--{{ Shuffle '{}' inputs queried here", name); + } + }); + + // Print the map from local variables to assigned values. + eprintln!(" |"); + eprintln!(" | Assigned cell values:"); + for (i, (_, value)) in cell_values.iter().enumerate() { + eprintln!(" | x{} = {}", i, value); + } + } +} + impl VerifyFailure { /// Emits this failure in pretty-printed format to stderr. pub(super) fn emit(&self, prover: &MockProver) { @@ -641,6 +839,11 @@ impl VerifyFailure { lookup_index, location, } => render_lookup(prover, name, *lookup_index, location), + Self::Shuffle { + name, + shuffle_index, + location, + } => render_shuffle(prover, name, *shuffle_index, location), _ => eprintln!("{}", self), } } diff --git a/halo2_proofs/src/plonk.rs b/halo2_proofs/src/plonk.rs index 5485d39fb4..c216a2fe34 100644 --- a/halo2_proofs/src/plonk.rs +++ b/halo2_proofs/src/plonk.rs @@ -27,6 +27,7 @@ mod evaluation; mod keygen; mod lookup; pub mod permutation; +mod shuffle; mod vanishing; mod prover; diff --git a/halo2_proofs/src/plonk/circuit.rs b/halo2_proofs/src/plonk/circuit.rs index 1c55eb5c8b..5f24a56d9c 100644 --- a/halo2_proofs/src/plonk/circuit.rs +++ b/halo2_proofs/src/plonk/circuit.rs @@ -1,5 +1,5 @@ -use super::{lookup, permutation, Assigned, Error}; use crate::circuit::layouter::SyncDeps; +use super::{lookup, permutation, shuffle, Assigned, Error}; use crate::dev::metadata; use crate::{ circuit::{Layouter, Region, Value}, @@ -1564,6 +1564,10 @@ pub struct ConstraintSystem { // input expressions and a sequence of table expressions involved in the lookup. pub(crate) lookups: Vec>, + // Vector of shuffle arguments, where each corresponds to a sequence of + // input expressions and a sequence of shuffle expressions involved in the shuffle. + pub(crate) shuffles: Vec>, + // List of indexes of Fixed columns which are associated to a circuit-general Column tied to their annotation. pub(crate) general_column_annotations: HashMap, @@ -1590,6 +1594,7 @@ pub struct PinnedConstraintSystem<'a, F: Field> { fixed_queries: &'a Vec<(Column, Rotation)>, permutation: &'a permutation::Argument, lookups: &'a Vec>, + shuffles: &'a Vec>, constants: &'a Vec>, minimum_degree: &'a Option, } @@ -1650,6 +1655,7 @@ impl Default for ConstraintSystem { instance_queries: Vec::new(), permutation: permutation::Argument::new(), lookups: Vec::new(), + shuffles: Vec::new(), general_column_annotations: HashMap::new(), constants: vec![], minimum_degree: None, @@ -1676,6 +1682,7 @@ impl ConstraintSystem { instance_queries: &self.instance_queries, permutation: &self.permutation, lookups: &self.lookups, + shuffles: &self.shuffles, constants: &self.constants, minimum_degree: &self.minimum_degree, } @@ -1756,6 +1763,29 @@ impl ConstraintSystem { index } + /// Add a shuffle argument for some input expressions and table expressions. + pub fn shuffle>( + &mut self, + name: S, + shuffle_map: impl FnOnce(&mut VirtualCells<'_, F>) -> Vec<(Expression, Expression)>, + ) -> usize { + let mut cells = VirtualCells::new(self); + let shuffle_map = shuffle_map(&mut cells) + .into_iter() + .map(|(mut input, mut table)| { + input.query_cells(&mut cells); + table.query_cells(&mut cells); + (input, table) + }) + .collect(); + let index = self.shuffles.len(); + + self.shuffles + .push(shuffle::Argument::new(name.as_ref(), shuffle_map)); + + index + } + fn query_fixed_index(&mut self, column: Column, at: Rotation) -> usize { // Return existing query, if it exists for (index, fixed_query) in self.fixed_queries.iter().enumerate() { @@ -2017,6 +2047,15 @@ impl ConstraintSystem { replace_selectors(expr, &selector_replacements, true); } + for expr in self.shuffles.iter_mut().flat_map(|shuffle| { + shuffle + .input_expressions + .iter_mut() + .chain(shuffle.shuffle_expressions.iter_mut()) + }) { + replace_selectors(expr, &selector_replacements, true); + } + (self, polys) } @@ -2178,6 +2217,17 @@ impl ConstraintSystem { .unwrap_or(1), ); + // The lookup argument also serves alongside the gates and must be accounted + // for. + degree = std::cmp::max( + degree, + self.shuffles + .iter() + .map(|l| l.required_degree()) + .max() + .unwrap_or(1), + ); + // Account for each gate to ensure our quotient polynomial is the // correct degree and that our extended domain is the right size. degree = std::cmp::max( @@ -2306,6 +2356,11 @@ impl ConstraintSystem { &self.lookups } + /// Returns lookup arguments + pub fn shuffles(&self) -> &Vec> { + &self.shuffles + } + /// Returns constants pub fn constants(&self) -> &Vec> { &self.constants diff --git a/halo2_proofs/src/plonk/evaluation.rs b/halo2_proofs/src/plonk/evaluation.rs index d5fb984024..5a4a0f21d5 100644 --- a/halo2_proofs/src/plonk/evaluation.rs +++ b/halo2_proofs/src/plonk/evaluation.rs @@ -11,6 +11,7 @@ use crate::{ }, transcript::{EncodedChallenge, TranscriptWrite}, }; +use core::num; use group::prime::PrimeCurve; use group::{ ff::{BatchInvert, Field, PrimeField, WithSmallOrderMulGroup}, @@ -26,7 +27,7 @@ use std::{ ops::{Index, Mul, MulAssign}, }; -use super::{ConstraintSystem, Expression}; +use super::{shuffle, ConstraintSystem, Expression}; /// Return the index in the polynomial of size `isize` after rotation `rot`. fn get_rotation_idx(idx: usize, rot: i32, rot_scale: i32, isize: i32) -> usize { @@ -186,6 +187,8 @@ pub struct Evaluator { pub custom_gates: GraphEvaluator, /// Lookups evalution pub lookups: Vec>, + /// Shuffle evalution + pub shuffles: Vec>, } /// GraphEvaluator @@ -273,6 +276,39 @@ impl Evaluator { ev.lookups.push(graph); } + // Shuffles + for shuffle in cs.shuffles.iter() { + let evaluate_lc = |expressions: &Vec>, graph: &mut GraphEvaluator| { + let parts = expressions + .iter() + .map(|expr| graph.add_expression(expr)) + .collect(); + graph.add_calculation(Calculation::Horner( + ValueSource::Constant(0), + parts, + ValueSource::Theta(), + )) + }; + + let mut graph_input = GraphEvaluator::default(); + let compressed_input_coset = evaluate_lc(&shuffle.input_expressions, &mut graph_input); + let _ = graph_input.add_calculation(Calculation::Add( + compressed_input_coset, + ValueSource::Gamma(), + )); + + let mut graph_shuffle = GraphEvaluator::default(); + let compressed_shuffle_coset = + evaluate_lc(&shuffle.shuffle_expressions, &mut graph_shuffle); + let _ = graph_shuffle.add_calculation(Calculation::Add( + compressed_shuffle_coset, + ValueSource::Gamma(), + )); + + ev.shuffles.push(graph_input); + ev.shuffles.push(graph_shuffle); + } + ev } @@ -288,6 +324,7 @@ impl Evaluator { gamma: C::ScalarExt, theta: C::ScalarExt, lookups: &[Vec>], + shuffles: &[Vec>], permutations: &[permutation::prover::Committed], ) -> Polynomial { let domain = &pk.vk.domain; @@ -326,10 +363,11 @@ impl Evaluator { // Core expression evaluations let num_threads = multicore::current_num_threads(); - for (((advice, instance), lookups), permutation) in advice + for ((((advice, instance), lookups), shuffles), permutation) in advice .iter() .zip(instance.iter()) .zip(lookups.iter()) + .zip(shuffles.iter()) .zip(permutations.iter()) { // Custom gates @@ -517,6 +555,68 @@ impl Evaluator { } }); } + + // Shuffle constraints + for (n, shuffle) in shuffles.iter().enumerate() { + let product_coset = pk.vk.domain.coeff_to_extended(shuffle.product_poly.clone()); + + // Shuffle constraints + parallelize(&mut values, |values, start| { + let input_evaluator = &self.shuffles[2 * n]; + let shuffle_evaluator = &self.shuffles[2 * n + 1]; + let mut eval_data_input = shuffle_evaluator.instance(); + let mut eval_data_shuffle = shuffle_evaluator.instance(); + for (i, value) in values.iter_mut().enumerate() { + let idx = start + i; + + let input_value = input_evaluator.evaluate( + &mut eval_data_input, + fixed, + advice, + instance, + challenges, + &beta, + &gamma, + &theta, + &y, + &C::ScalarExt::ZERO, + idx, + rot_scale, + isize, + ); + + let shuffle_value = shuffle_evaluator.evaluate( + &mut eval_data_shuffle, + fixed, + advice, + instance, + challenges, + &beta, + &gamma, + &theta, + &y, + &C::ScalarExt::ZERO, + idx, + rot_scale, + isize, + ); + + let r_next = get_rotation_idx(idx, 1, rot_scale, isize); + + // l_0(X) * (1 - z(X)) = 0 + *value = *value * y + ((one - product_coset[idx]) * l0[idx]); + // l_last(X) * (z(X)^2 - z(X)) = 0 + *value = *value * y + + ((product_coset[idx] * product_coset[idx] - product_coset[idx]) + * l_last[idx]); + // (1 - (l_last(X) + l_blind(X))) * (z(\omega X) (s(X) + \gamma) - z(X) (a(X) + \gamma) = 0 + *value = *value * y + + l_active_row[idx] + * (product_coset[r_next] * shuffle_value + - product_coset[idx] * input_value) + } + }); + } } values } diff --git a/halo2_proofs/src/plonk/prover.rs b/halo2_proofs/src/plonk/prover.rs index 6314481d96..855f2fb85f 100644 --- a/halo2_proofs/src/plonk/prover.rs +++ b/halo2_proofs/src/plonk/prover.rs @@ -15,8 +15,8 @@ use super::{ Advice, Any, Assignment, Challenge, Circuit, Column, ConstraintSystem, FirstPhase, Fixed, FloorPlanner, Instance, Selector, }, - lookup, permutation, vanishing, ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, - ChallengeY, Error, Expression, ProvingKey, + lookup, permutation, shuffle, vanishing, ChallengeBeta, ChallengeGamma, ChallengeTheta, + ChallengeX, ChallengeY, Error, Expression, ProvingKey, }; use crate::circuit::layouter::SyncDeps; use crate::{ @@ -439,6 +439,30 @@ where }) .collect::, _>>()?; + let shuffles = instance + .iter() + .zip(advice.iter()) + .map(|(instance, advice)| -> Vec<_> { + // Compress expressions for each shuffle + pk.vk + .cs + .shuffles + .iter() + .map(|shuffle| { + shuffle.compress( + pk, + params, + domain, + theta, + &advice.advice_polys, + &pk.fixed_values, + &instance.instance_values, + &challenges, + ) + }) + .collect::>() + }); + // Sample beta challenge let beta: ChallengeBeta<_> = transcript.squeeze_challenge_scalar(); @@ -476,6 +500,17 @@ where }) .collect::, _>>()?; + let shuffles: Vec>> = shuffles + .into_iter() + .map(|shuffles| -> Result, _> { + // Construct and commit to products for each shuffle + shuffles + .into_iter() + .map(|shuffle| shuffle.commit_product(pk, params, gamma, &mut rng, transcript)) + .collect::, _>>() + }) + .collect::, _>>()?; + // Commit to the vanishing argument's random polynomial for blinding h(x_3) let vanishing = vanishing::Argument::commit(params, domain, &mut rng, transcript)?; @@ -518,6 +553,7 @@ where *gamma, *theta, &lookups, + &shuffles, &permutations, ); @@ -605,12 +641,24 @@ where }) .collect::, _>>()?; + // Evaluate the shuffles, if any, at omega^i x. + let shuffles: Vec>> = shuffles + .into_iter() + .map(|shuffles| -> Result, _> { + shuffles + .into_iter() + .map(|p| p.evaluate(pk, x, transcript)) + .collect::, _>>() + }) + .collect::, _>>()?; + let instances = instance .iter() .zip(advice.iter()) .zip(permutations.iter()) .zip(lookups.iter()) - .flat_map(|(((instance, advice), permutation), lookups)| { + .zip(shuffles.iter()) + .flat_map(|((((instance, advice), permutation), lookups), shuffles)| { iter::empty() .chain( P::QUERY_INSTANCE @@ -637,6 +685,7 @@ where ) .chain(permutation.open(pk, x)) .chain(lookups.iter().flat_map(move |p| p.open(pk, x)).into_iter()) + .chain(shuffles.iter().flat_map(move |p| p.open(pk, x)).into_iter()) }) .chain( pk.vk diff --git a/halo2_proofs/src/plonk/shuffle.rs b/halo2_proofs/src/plonk/shuffle.rs new file mode 100644 index 0000000000..e32353c710 --- /dev/null +++ b/halo2_proofs/src/plonk/shuffle.rs @@ -0,0 +1,67 @@ +use super::circuit::Expression; +use ff::Field; +use std::fmt::{self, Debug}; + +pub(crate) mod prover; +pub(crate) mod verifier; + +#[derive(Clone)] +pub struct Argument { + pub(crate) name: String, + pub(crate) input_expressions: Vec>, + pub(crate) shuffle_expressions: Vec>, +} + +impl Debug for Argument { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Argument") + .field("input_expressions", &self.input_expressions) + .field("shuffle_expressions", &self.shuffle_expressions) + .finish() + } +} + +impl Argument { + /// Constructs a new shuffle argument. + /// + /// `shuffle` is a sequence of `(input, shuffle)` tuples. + pub fn new>(name: S, shuffle: Vec<(Expression, Expression)>) -> Self { + let (input_expressions, shuffle_expressions) = shuffle.into_iter().unzip(); + Argument { + name: name.as_ref().to_string(), + input_expressions, + shuffle_expressions, + } + } + + pub(crate) fn required_degree(&self) -> usize { + assert_eq!(self.input_expressions.len(), self.shuffle_expressions.len()); + + let mut input_degree = 1; + for expr in self.input_expressions.iter() { + input_degree = std::cmp::max(input_degree, expr.degree()); + } + let mut shuffle_degree = 1; + for expr in self.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) + } + + /// Returns input of this argument + pub fn input_expressions(&self) -> &Vec> { + &self.input_expressions + } + + /// Returns table of this argument + pub fn shuffle_expressions(&self) -> &Vec> { + &self.shuffle_expressions + } + + /// Returns name of this argument + pub fn name(&self) -> &str { + &self.name + } +} diff --git a/halo2_proofs/src/plonk/shuffle/prover.rs b/halo2_proofs/src/plonk/shuffle/prover.rs new file mode 100644 index 0000000000..f73803e77e --- /dev/null +++ b/halo2_proofs/src/plonk/shuffle/prover.rs @@ -0,0 +1,234 @@ +use super::super::{ + circuit::Expression, ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, Error, + ProvingKey, +}; +use super::Argument; +use crate::plonk::evaluation::evaluate; +use crate::{ + arithmetic::{eval_polynomial, parallelize, CurveAffine}, + poly::{ + commitment::{Blind, Params}, + Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, ProverQuery, + Rotation, + }, + transcript::{EncodedChallenge, TranscriptWrite}, +}; +use ff::WithSmallOrderMulGroup; +use group::{ + ff::{BatchInvert, Field}, + Curve, +}; +use rand_core::RngCore; +use std::{any::TypeId, convert::TryInto, num::ParseIntError, ops::Index}; +use std::{ + collections::BTreeMap, + iter, + ops::{Mul, MulAssign}, +}; + +#[derive(Debug)] +pub(in crate::plonk) struct Compressed { + compressed_input_expression: Polynomial, + compressed_shuffle_expression: Polynomial, +} + +#[derive(Debug)] +pub(in crate::plonk) struct Committed { + pub(in crate::plonk) product_poly: Polynomial, + product_blind: Blind, +} + +pub(in crate::plonk) struct Evaluated { + constructed: Committed, +} + +impl> Argument { + /// Given a Shuffle with input expressions [A_0, A_1, ..., A_{m-1}] and table expressions + /// [S_0, S_1, ..., S_{m-1}], this method + /// - constructs A_compressed = \theta^{m-1} A_0 + theta^{m-2} A_1 + ... + \theta A_{m-2} + A_{m-1} + /// and S_compressed = \theta^{m-1} S_0 + theta^{m-2} S_1 + ... + \theta S_{m-2} + S_{m-1}, + pub(in crate::plonk) fn compress<'a, 'params: 'a, C, P: Params<'params, C>>( + &self, + pk: &ProvingKey, + params: &P, + domain: &EvaluationDomain, + theta: ChallengeTheta, + advice_values: &'a [Polynomial], + fixed_values: &'a [Polynomial], + instance_values: &'a [Polynomial], + challenges: &'a [C::Scalar], + ) -> Compressed + where + C: CurveAffine, + C::Curve: Mul + MulAssign, + { + // Closure to get values of expressions and compress them + let compress_expressions = |expressions: &[Expression]| { + let compressed_expression = expressions + .iter() + .map(|expression| { + pk.vk.domain.lagrange_from_vec(evaluate( + expression, + params.n() as usize, + 1, + fixed_values, + advice_values, + instance_values, + challenges, + )) + }) + .fold(domain.empty_lagrange(), |acc, expression| { + acc * *theta + &expression + }); + compressed_expression + }; + + // Get values of input expressions involved in the shuffle and compress them + let compressed_input_expression = compress_expressions(&self.input_expressions); + + // Get values of table expressions involved in the shuffle and compress them + let compressed_shuffle_expression = compress_expressions(&self.shuffle_expressions); + + Compressed { + compressed_input_expression, + compressed_shuffle_expression, + } + } +} + +impl Compressed { + /// Given a Shuffle with input expressions and table expressions this method + /// constructs the grand product polynomial over the shuffle. + /// The grand product polynomial is used to populate the Product struct. + /// The Product struct is added to the Shuffle and finally returned by the method. + pub(in crate::plonk) fn commit_product< + 'params, + P: Params<'params, C>, + E: EncodedChallenge, + R: RngCore, + T: TranscriptWrite, + >( + self, + pk: &ProvingKey, + params: &P, + gamma: ChallengeGamma, + mut rng: R, + transcript: &mut T, + ) -> Result, Error> { + let blinding_factors = pk.vk.cs.blinding_factors(); + + let mut shuffle_product = vec![C::Scalar::ZERO; params.n() as usize]; + parallelize(&mut shuffle_product, |shuffle_product, start| { + for (shuffle_product, shuffle_value) in shuffle_product + .iter_mut() + .zip(self.compressed_shuffle_expression[start..].iter()) + { + *shuffle_product = *gamma + shuffle_value; + } + }); + + shuffle_product.iter_mut().batch_invert(); + + parallelize(&mut shuffle_product, |product, start| { + for (i, product) in product.iter_mut().enumerate() { + let i = i + start; + *product *= &(*gamma + self.compressed_input_expression[i]); + } + }); + + // Compute the evaluations of the shuffle product polynomial + // over our domain, starting with z[0] = 1 + let z = iter::once(C::Scalar::ONE) + .chain(shuffle_product) + .scan(C::Scalar::ONE, |state, cur| { + *state *= &cur; + Some(*state) + }) + // Take all rows including the "last" row which should + // be a boolean (and ideally 1, else soundness is broken) + .take(params.n() as usize - blinding_factors) + // Chain random blinding factors. + .chain((0..blinding_factors).map(|_| C::Scalar::random(&mut rng))) + .collect::>(); + assert_eq!(z.len(), params.n() as usize); + let z = pk.vk.domain.lagrange_from_vec(z); + + #[cfg(feature = "sanity-checks")] + { + // While in Lagrange basis, check that product is correctly constructed + let u = (params.n() as usize) - (blinding_factors + 1); + assert_eq!(z[0], C::Scalar::ONE); + for i in 0..u { + let mut left = z[i + 1]; + let input_value = &self.compressed_input_expression[i]; + let shuffle_value = &self.compressed_shuffle_expression[i]; + left *= &(*gamma + shuffle_value); + let mut right = z[i]; + right *= &(*gamma + input_value); + assert_eq!(left, right); + } + assert_eq!(z[u], C::Scalar::ONE); + } + + let product_blind = Blind(C::Scalar::random(rng)); + let product_commitment = params.commit_lagrange(&z, product_blind).to_affine(); + let z = pk.vk.domain.lagrange_to_coeff(z); + + // Hash product commitment + transcript.write_point(product_commitment)?; + + Ok(Committed:: { + product_poly: z, + product_blind, + }) + } +} + +impl Committed { + pub(in crate::plonk) fn evaluate, T: TranscriptWrite>( + self, + pk: &ProvingKey, + x: ChallengeX, + transcript: &mut T, + ) -> Result, Error> { + let domain = &pk.vk.domain; + let x_next = domain.rotate_omega(*x, Rotation::next()); + + let product_eval = eval_polynomial(&self.product_poly, *x); + let product_next_eval = eval_polynomial(&self.product_poly, x_next); + + // Hash each advice evaluation + for eval in iter::empty() + .chain(Some(product_eval)) + .chain(Some(product_next_eval)) + { + transcript.write_scalar(eval)?; + } + + Ok(Evaluated { constructed: self }) + } +} + +impl Evaluated { + pub(in crate::plonk) fn open<'a>( + &'a self, + pk: &'a ProvingKey, + x: ChallengeX, + ) -> impl Iterator> + Clone { + let x_next = pk.vk.domain.rotate_omega(*x, Rotation::next()); + + iter::empty() + // Open shuffle product commitments at x + .chain(Some(ProverQuery { + point: *x, + poly: &self.constructed.product_poly, + blind: self.constructed.product_blind, + })) + // Open shuffle product commitments at x_next + .chain(Some(ProverQuery { + point: x_next, + poly: &self.constructed.product_poly, + blind: self.constructed.product_blind, + })) + } +} diff --git a/halo2_proofs/src/plonk/shuffle/verifier.rs b/halo2_proofs/src/plonk/shuffle/verifier.rs new file mode 100644 index 0000000000..b02febb694 --- /dev/null +++ b/halo2_proofs/src/plonk/shuffle/verifier.rs @@ -0,0 +1,137 @@ +use std::iter; + +use super::super::{circuit::Expression, ChallengeGamma, ChallengeTheta, ChallengeX}; +use super::Argument; +use crate::{ + arithmetic::CurveAffine, + plonk::{Error, VerifyingKey}, + poly::{commitment::MSM, Rotation, VerifierQuery}, + transcript::{EncodedChallenge, TranscriptRead}, +}; +use ff::Field; + +pub struct Committed { + product_commitment: C, +} + +pub struct Evaluated { + committed: Committed, + product_eval: C::Scalar, + product_next_eval: C::Scalar, +} + +impl Argument { + pub(in crate::plonk) fn read_product_commitment< + C: CurveAffine, + E: EncodedChallenge, + T: TranscriptRead, + >( + &self, + transcript: &mut T, + ) -> Result, Error> { + let product_commitment = transcript.read_point()?; + + Ok(Committed { product_commitment }) + } +} + +impl Committed { + pub(crate) fn evaluate, T: TranscriptRead>( + self, + transcript: &mut T, + ) -> Result, Error> { + let product_eval = transcript.read_scalar()?; + let product_next_eval = transcript.read_scalar()?; + + Ok(Evaluated { + committed: self, + product_eval, + product_next_eval, + }) + } +} + +impl Evaluated { + pub(in crate::plonk) fn expressions<'a>( + &'a self, + l_0: C::Scalar, + l_last: C::Scalar, + l_blind: C::Scalar, + argument: &'a Argument, + theta: ChallengeTheta, + gamma: ChallengeGamma, + advice_evals: &[C::Scalar], + fixed_evals: &[C::Scalar], + instance_evals: &[C::Scalar], + challenges: &[C::Scalar], + ) -> impl Iterator + 'a { + let active_rows = C::Scalar::ONE - (l_last + l_blind); + + let product_expression = || { + // z(\omega X) (s(X) + \gamma) - z(X) (a(X) + \gamma) + let compress_expressions = |expressions: &[Expression]| { + 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()], + &|a| -a, + &|a, b| a + &b, + &|a, b| a * &b, + &|a, scalar| a * &scalar, + ) + }) + .fold(C::Scalar::ZERO, |acc, eval| acc * &*theta + &eval) + }; + // z(\omega X) (s(X) + \gamma) + let left = self.product_next_eval + * &(compress_expressions(&argument.shuffle_expressions) + &*gamma); + // z(X) (a(X) + \gamma) + let right = + self.product_eval * &(compress_expressions(&argument.input_expressions) + &*gamma); + + (left - &right) * &active_rows + }; + + std::iter::empty() + .chain( + // l_0(X) * (1 - z'(X)) = 0 + Some(l_0 * &(C::Scalar::ONE - &self.product_eval)), + ) + .chain( + // l_last(X) * (z(X)^2 - z(X)) = 0 + Some(l_last * &(self.product_eval.square() - &self.product_eval)), + ) + .chain( + // (1 - (l_last(X) + l_blind(X))) * ( z(\omega X) (s(X) + \gamma) - z(X) (a(X) + \gamma)) + Some(product_expression()), + ) + } + + pub(in crate::plonk) fn queries<'r, M: MSM + 'r>( + &'r self, + vk: &'r VerifyingKey, + x: ChallengeX, + ) -> impl Iterator> + Clone { + let x_next = vk.domain.rotate_omega(*x, Rotation::next()); + + iter::empty() + // Open shuffle product commitment at x + .chain(Some(VerifierQuery::new_commitment( + &self.committed.product_commitment, + *x, + self.product_eval, + ))) + // Open shuffle product commitment at \omega x + .chain(Some(VerifierQuery::new_commitment( + &self.committed.product_commitment, + x_next, + self.product_next_eval, + ))) + } +} diff --git a/halo2_proofs/src/plonk/verifier.rs b/halo2_proofs/src/plonk/verifier.rs index 72ec7203fb..b92c3dbef6 100644 --- a/halo2_proofs/src/plonk/verifier.rs +++ b/halo2_proofs/src/plonk/verifier.rs @@ -160,6 +160,17 @@ where }) .collect::, _>>()?; + let shuffles_committed = (0..num_proofs) + .map(|_| -> Result, _> { + // Hash each shuffle product commitment + vk.cs + .shuffles + .iter() + .map(|argument| argument.read_product_commitment(transcript)) + .collect::, _>>() + }) + .collect::, _>>()?; + let vanishing = vanishing::Argument::read_commitments_before_y(transcript)?; // Sample y challenge, which keeps the gates linearly independent. @@ -242,6 +253,16 @@ where }) .collect::, _>>()?; + let shuffles_evaluated = shuffles_committed + .into_iter() + .map(|shuffles| -> Result, _> { + shuffles + .into_iter() + .map(|shuffle| shuffle.evaluate(transcript)) + .collect::, _>>() + }) + .collect::, _>>()?; + // This check ensures the circuit is satisfied so long as the polynomial // commitments open to the correct values. let vanishing = { @@ -265,63 +286,88 @@ where .zip(instance_evals.iter()) .zip(permutations_evaluated.iter()) .zip(lookups_evaluated.iter()) - .flat_map(|(((advice_evals, instance_evals), permutation), lookups)| { - let challenges = &challenges; - 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"), - &|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, - ) - }) - })) - .chain(permutation.expressions( - vk, - &vk.cs.permutation, - &permutations_common, - advice_evals, - fixed_evals, - instance_evals, - l_0, - l_last, - l_blind, - beta, - gamma, - x, - )) - .chain( - lookups - .iter() - .zip(vk.cs.lookups.iter()) - .flat_map(move |(p, argument)| { - p.expressions( - l_0, - l_last, - l_blind, - argument, - theta, - beta, - gamma, - advice_evals, - fixed_evals, - instance_evals, - challenges, + .zip(shuffles_evaluated.iter()) + .flat_map( + |((((advice_evals, instance_evals), permutation), lookups), shuffles)| { + let challenges = &challenges; + 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") + }, + &|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, ) }) - .into_iter(), - ) - }); + })) + .chain(permutation.expressions( + vk, + &vk.cs.permutation, + &permutations_common, + advice_evals, + fixed_evals, + instance_evals, + l_0, + l_last, + l_blind, + beta, + gamma, + x, + )) + .chain( + lookups + .iter() + .zip(vk.cs.lookups.iter()) + .flat_map(move |(p, argument)| { + p.expressions( + l_0, + l_last, + l_blind, + argument, + theta, + beta, + gamma, + advice_evals, + fixed_evals, + instance_evals, + challenges, + ) + }) + .into_iter(), + ) + .chain( + shuffles + .iter() + .zip(vk.cs.shuffles.iter()) + .flat_map(move |(p, argument)| { + p.expressions( + l_0, + l_last, + l_blind, + argument, + theta, + gamma, + advice_evals, + fixed_evals, + instance_evals, + challenges, + ) + }) + .into_iter(), + ) + }, + ); vanishing.verify(params, expressions, y, xn) }; @@ -333,13 +379,20 @@ where .zip(advice_evals.iter()) .zip(permutations_evaluated.iter()) .zip(lookups_evaluated.iter()) + .zip(shuffles_evaluated.iter()) .flat_map( |( ( - (((instance_commitments, instance_evals), advice_commitments), advice_evals), - permutation, + ( + ( + ((instance_commitments, instance_evals), advice_commitments), + advice_evals, + ), + permutation, + ), + lookups, ), - lookups, + shuffles, )| { iter::empty() .chain( @@ -372,6 +425,12 @@ where .flat_map(move |p| p.queries(vk, x)) .into_iter(), ) + .chain( + shuffles + .iter() + .flat_map(move |p| p.queries(vk, x)) + .into_iter(), + ) }, ) .chain( From ab9390248e1a8174911d5f7d2b282de6f816d027 Mon Sep 17 00:00:00 2001 From: kilic Date: Sun, 21 May 2023 19:45:23 +0300 Subject: [PATCH 02/10] fix: remove nonsense comment --- halo2_proofs/examples/shuffle_api.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/halo2_proofs/examples/shuffle_api.rs b/halo2_proofs/examples/shuffle_api.rs index 5f72040804..259e038d06 100644 --- a/halo2_proofs/examples/shuffle_api.rs +++ b/halo2_proofs/examples/shuffle_api.rs @@ -80,12 +80,6 @@ impl ShuffleChip { } } -// ANCHOR: circuit -/// The full circuit implementation. -/// -/// In this struct we store the private input variables. We use `Option` because -/// they won't have any value during key generation. During proving, if any of these -/// were `None` we would get an error. #[derive(Default)] struct MyCircuit { input_0: Vec>, From cac1cec50d68a6dc65df7478d0ca30bdc9ef5ffb Mon Sep 17 00:00:00 2001 From: kilic Date: Mon, 5 Jun 2023 21:01:19 +0300 Subject: [PATCH 03/10] strictly check shuffle rows --- halo2_proofs/src/dev.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/halo2_proofs/src/dev.rs b/halo2_proofs/src/dev.rs index ee480d14d5..99684a946f 100644 --- a/halo2_proofs/src/dev.rs +++ b/halo2_proofs/src/dev.rs @@ -1063,7 +1063,8 @@ impl + Ord> MockProver { .collect(); shuffle_rows.sort(); - let mut input_rows: Vec<(Vec>, usize)> = lookup_input_row_ids + let mut input_rows: Vec<(Vec>, usize)> = self + .usable_rows .clone() .into_iter() .map(|input_row| { From 9ff57aa99a80c2d2986a92b3ff1a4bf32574d9f4 Mon Sep 17 00:00:00 2001 From: kilic Date: Mon, 5 Jun 2023 21:12:15 +0300 Subject: [PATCH 04/10] address doc typos --- halo2_proofs/src/dev/failure.rs | 4 ++-- halo2_proofs/src/plonk/circuit.rs | 2 +- halo2_proofs/src/plonk/evaluation.rs | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/halo2_proofs/src/dev/failure.rs b/halo2_proofs/src/dev/failure.rs index 3cb85311af..fd80dee25b 100644 --- a/halo2_proofs/src/dev/failure.rs +++ b/halo2_proofs/src/dev/failure.rs @@ -178,7 +178,7 @@ pub enum VerifyFailure { /// lookup is active on a row adjacent to an unrelated region. location: FailureLocation, }, - /// A lookup input did not exist in its corresponding table. + /// A shuffle input did not exist in its corresponding map. Shuffle { /// The name of the lookup that is not satisfied. name: String, @@ -744,7 +744,7 @@ fn render_shuffle( eprintln!(); eprintln!(" Shuffle '{}' inputs:", name); for (i, input) in shuffle.input_expressions.iter().enumerate() { - // Fetch the cell values (since we don't store them in VerifyFailure::Lookup). + // Fetch the cell values (since we don't store them in VerifyFailure::Shuffle). let cell_values = input.evaluate( &|_| BTreeMap::default(), &|_| panic!("virtual selectors are removed during optimization"), diff --git a/halo2_proofs/src/plonk/circuit.rs b/halo2_proofs/src/plonk/circuit.rs index 5f24a56d9c..7c4778dc12 100644 --- a/halo2_proofs/src/plonk/circuit.rs +++ b/halo2_proofs/src/plonk/circuit.rs @@ -2356,7 +2356,7 @@ impl ConstraintSystem { &self.lookups } - /// Returns lookup arguments + /// Returns shuffle arguments pub fn shuffles(&self) -> &Vec> { &self.shuffles } diff --git a/halo2_proofs/src/plonk/evaluation.rs b/halo2_proofs/src/plonk/evaluation.rs index 5a4a0f21d5..2061607982 100644 --- a/halo2_proofs/src/plonk/evaluation.rs +++ b/halo2_proofs/src/plonk/evaluation.rs @@ -11,7 +11,6 @@ use crate::{ }, transcript::{EncodedChallenge, TranscriptWrite}, }; -use core::num; use group::prime::PrimeCurve; use group::{ ff::{BatchInvert, Field, PrimeField, WithSmallOrderMulGroup}, @@ -609,7 +608,7 @@ impl Evaluator { *value = *value * y + ((product_coset[idx] * product_coset[idx] - product_coset[idx]) * l_last[idx]); - // (1 - (l_last(X) + l_blind(X))) * (z(\omega X) (s(X) + \gamma) - z(X) (a(X) + \gamma) = 0 + // (1 - (l_last(X) + l_blind(X))) * (z(\omega X) (s(X) + \gamma) - z(X) (a(X) + \gamma)) = 0 *value = *value * y + l_active_row[idx] * (product_coset[r_next] * shuffle_value From fa72b3d4725fcfeec0e7bba2e7c02d5729f265a1 Mon Sep 17 00:00:00 2001 From: kilic Date: Mon, 5 Jun 2023 22:03:18 +0300 Subject: [PATCH 05/10] move compression into product commitment --- halo2_proofs/src/plonk/prover.rs | 55 +++++++++++------------- halo2_proofs/src/plonk/shuffle/prover.rs | 51 +++++++++++++++------- 2 files changed, 60 insertions(+), 46 deletions(-) diff --git a/halo2_proofs/src/plonk/prover.rs b/halo2_proofs/src/plonk/prover.rs index 855f2fb85f..61392e95e8 100644 --- a/halo2_proofs/src/plonk/prover.rs +++ b/halo2_proofs/src/plonk/prover.rs @@ -439,30 +439,6 @@ where }) .collect::, _>>()?; - let shuffles = instance - .iter() - .zip(advice.iter()) - .map(|(instance, advice)| -> Vec<_> { - // Compress expressions for each shuffle - pk.vk - .cs - .shuffles - .iter() - .map(|shuffle| { - shuffle.compress( - pk, - params, - domain, - theta, - &advice.advice_polys, - &pk.fixed_values, - &instance.instance_values, - &challenges, - ) - }) - .collect::>() - }); - // Sample beta challenge let beta: ChallengeBeta<_> = transcript.squeeze_challenge_scalar(); @@ -500,13 +476,30 @@ where }) .collect::, _>>()?; - let shuffles: Vec>> = shuffles - .into_iter() - .map(|shuffles| -> Result, _> { - // Construct and commit to products for each shuffle - shuffles - .into_iter() - .map(|shuffle| shuffle.commit_product(pk, params, gamma, &mut rng, transcript)) + let shuffles: Vec>> = instance + .iter() + .zip(advice.iter()) + .map(|(instance, advice)| -> Result, _> { + // Compress expressions for each shuffle + pk.vk + .cs + .shuffles + .iter() + .map(|shuffle| { + shuffle.commit_product( + pk, + params, + domain, + theta, + gamma, + &advice.advice_polys, + &pk.fixed_values, + &instance.instance_values, + &challenges, + &mut rng, + transcript, + ) + }) .collect::, _>>() }) .collect::, _>>()?; diff --git a/halo2_proofs/src/plonk/shuffle/prover.rs b/halo2_proofs/src/plonk/shuffle/prover.rs index f73803e77e..90cc971f84 100644 --- a/halo2_proofs/src/plonk/shuffle/prover.rs +++ b/halo2_proofs/src/plonk/shuffle/prover.rs @@ -27,9 +27,9 @@ use std::{ }; #[derive(Debug)] -pub(in crate::plonk) struct Compressed { - compressed_input_expression: Polynomial, - compressed_shuffle_expression: Polynomial, +struct Compressed { + input_expression: Polynomial, + shuffle_expression: Polynomial, } #[derive(Debug)] @@ -47,7 +47,7 @@ impl> Argument { /// [S_0, S_1, ..., S_{m-1}], this method /// - constructs A_compressed = \theta^{m-1} A_0 + theta^{m-2} A_1 + ... + \theta A_{m-2} + A_{m-1} /// and S_compressed = \theta^{m-1} S_0 + theta^{m-2} S_1 + ... + \theta S_{m-2} + S_{m-1}, - pub(in crate::plonk) fn compress<'a, 'params: 'a, C, P: Params<'params, C>>( + fn compress<'a, 'params: 'a, C, P: Params<'params, C>>( &self, pk: &ProvingKey, params: &P, @@ -84,44 +84,65 @@ impl> Argument { }; // Get values of input expressions involved in the shuffle and compress them - let compressed_input_expression = compress_expressions(&self.input_expressions); + let input_expression = compress_expressions(&self.input_expressions); // Get values of table expressions involved in the shuffle and compress them - let compressed_shuffle_expression = compress_expressions(&self.shuffle_expressions); + let shuffle_expression = compress_expressions(&self.shuffle_expressions); Compressed { - compressed_input_expression, - compressed_shuffle_expression, + input_expression, + shuffle_expression, } } -} -impl Compressed { /// Given a Shuffle with input expressions and table expressions this method /// constructs the grand product polynomial over the shuffle. /// The grand product polynomial is used to populate the Product struct. /// The Product struct is added to the Shuffle and finally returned by the method. pub(in crate::plonk) fn commit_product< - 'params, + 'a, + 'params: 'a, + C, P: Params<'params, C>, E: EncodedChallenge, R: RngCore, T: TranscriptWrite, >( - self, + &self, pk: &ProvingKey, params: &P, + domain: &EvaluationDomain, + theta: ChallengeTheta, gamma: ChallengeGamma, + advice_values: &'a [Polynomial], + fixed_values: &'a [Polynomial], + instance_values: &'a [Polynomial], + challenges: &'a [C::Scalar], mut rng: R, transcript: &mut T, - ) -> Result, Error> { + ) -> Result, Error> + where + C: CurveAffine, + C::Curve: Mul + MulAssign, + { + let compressed = self.compress( + pk, + params, + domain, + theta, + advice_values, + fixed_values, + instance_values, + challenges, + ); + let blinding_factors = pk.vk.cs.blinding_factors(); let mut shuffle_product = vec![C::Scalar::ZERO; params.n() as usize]; parallelize(&mut shuffle_product, |shuffle_product, start| { for (shuffle_product, shuffle_value) in shuffle_product .iter_mut() - .zip(self.compressed_shuffle_expression[start..].iter()) + .zip(compressed.shuffle_expression[start..].iter()) { *shuffle_product = *gamma + shuffle_value; } @@ -132,7 +153,7 @@ impl Compressed { parallelize(&mut shuffle_product, |product, start| { for (i, product) in product.iter_mut().enumerate() { let i = i + start; - *product *= &(*gamma + self.compressed_input_expression[i]); + *product *= &(*gamma + compressed.input_expression[i]); } }); From b7b3ddf2570089321d6378fe101fa87bbbfd193c Mon Sep 17 00:00:00 2001 From: kilic Date: Mon, 5 Jun 2023 22:07:04 +0300 Subject: [PATCH 06/10] typo --- halo2_proofs/src/dev/failure.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/halo2_proofs/src/dev/failure.rs b/halo2_proofs/src/dev/failure.rs index fd80dee25b..f203cfa829 100644 --- a/halo2_proofs/src/dev/failure.rs +++ b/halo2_proofs/src/dev/failure.rs @@ -654,7 +654,7 @@ fn render_shuffle( let cs = &prover.cs; let shuffle = &cs.shuffles[shuffle_index]; - // Get the absolute row on which the lookup's inputs are being queried, so we can + // Get the absolute row on which the shuffle's inputs are being queried, so we can // fetch the input values. let row = match location { FailureLocation::InRegion { region, offset } => { From c26a93fe7dfb67e5eff6c0b960d5471fc0f35ddc Mon Sep 17 00:00:00 2001 From: kilic Date: Mon, 5 Jun 2023 22:27:08 +0300 Subject: [PATCH 07/10] add shuffle errors for `verify_at_rows_par` --- halo2_proofs/src/dev.rs | 101 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/halo2_proofs/src/dev.rs b/halo2_proofs/src/dev.rs index 99684a946f..d3d058a3eb 100644 --- a/halo2_proofs/src/dev.rs +++ b/halo2_proofs/src/dev.rs @@ -1462,6 +1462,106 @@ impl + Ord> MockProver { }) .collect::>() }); + + let shuffle_errors = + self.cs + .shuffles + .iter() + .enumerate() + .flat_map(|(shuffle_index, shuffle)| { + let load = |expression: &Expression, row| { + expression.evaluate_lazy( + &|scalar| Value::Real(scalar), + &|_| panic!("virtual selectors are removed during optimization"), + &|query| { + let query = self.cs.fixed_queries[query.index.unwrap()]; + let column_index = query.0.index(); + let rotation = query.1 .0; + self.fixed[column_index] + [(row as i32 + n + rotation) as usize % n as usize] + .into() + }, + &|query| { + let query = self.cs.advice_queries[query.index.unwrap()]; + let column_index = query.0.index(); + let rotation = query.1 .0; + self.advice[column_index] + [(row as i32 + n + rotation) as usize % n as usize] + .into() + }, + &|query| { + let query = self.cs.instance_queries[query.index.unwrap()]; + let column_index = query.0.index(); + let rotation = query.1 .0; + Value::Real( + self.instance[column_index] + [(row as i32 + n + rotation) as usize % n as usize], + ) + }, + &|challenge| Value::Real(self.challenges[challenge.index()]), + &|a| -a, + &|a, b| a + b, + &|a, b| a * b, + &|a, scalar| a * scalar, + &Value::Real(F::ZERO), + ) + }; + + assert!(shuffle.shuffle_expressions.len() == shuffle.input_expressions.len()); + assert!(self.usable_rows.end > 0); + + let mut shuffle_rows: Vec>> = self + .usable_rows + .clone() + .map(|row| { + let t = shuffle + .shuffle_expressions + .iter() + .map(move |c| load(c, row)) + .collect(); + t + }) + .collect(); + shuffle_rows.sort(); + + let mut input_rows: Vec<(Vec>, usize)> = self + .usable_rows + .clone() + .into_iter() + .map(|input_row| { + let t = shuffle + .input_expressions + .iter() + .map(move |c| load(c, input_row)) + .collect(); + + (t, input_row) + }) + .collect(); + input_rows.sort(); + + input_rows + .iter() + .zip(shuffle_rows.iter()) + .filter_map(|((input_value, row), shuffle_value)| { + if shuffle_value != input_value { + Some(VerifyFailure::Shuffle { + name: shuffle.name.clone(), + shuffle_index, + location: FailureLocation::find_expressions( + &self.cs, + &self.regions, + *row, + shuffle.input_expressions.iter(), + ), + }) + } else { + None + } + }) + .collect::>() + }); + let mapping = self.permutation.mapping(); // Check that permutations preserve the original values of the cells. let perm_errors = { @@ -1512,6 +1612,7 @@ impl + Ord> MockProver { .chain(gate_errors) .chain(lookup_errors) .chain(perm_errors) + .chain(shuffle_errors) .collect(); if errors.is_empty() { Ok(()) From 1581bb0cd55c7e09413f2b01d54197a8584b74b7 Mon Sep 17 00:00:00 2001 From: kilic Date: Mon, 5 Jun 2023 22:35:01 +0300 Subject: [PATCH 08/10] dedup expression evaluation --- halo2_proofs/src/dev.rs | 208 +++++++++++++--------------------------- 1 file changed, 65 insertions(+), 143 deletions(-) diff --git a/halo2_proofs/src/dev.rs b/halo2_proofs/src/dev.rs index d3d058a3eb..8fe602f1d0 100644 --- a/halo2_proofs/src/dev.rs +++ b/halo2_proofs/src/dev.rs @@ -862,6 +862,42 @@ impl + Ord> MockProver { }) }); + let load = |expression: &Expression, row| { + expression.evaluate_lazy( + &|scalar| Value::Real(scalar), + &|_| panic!("virtual selectors are removed during optimization"), + &|query| { + let query = self.cs.fixed_queries[query.index.unwrap()]; + let column_index = query.0.index(); + let rotation = query.1 .0; + self.fixed[column_index][(row as i32 + n + rotation) as usize % n as usize] + .into() + }, + &|query| { + let query = self.cs.advice_queries[query.index.unwrap()]; + let column_index = query.0.index(); + let rotation = query.1 .0; + self.advice[column_index][(row as i32 + n + rotation) as usize % n as usize] + .into() + }, + &|query| { + let query = self.cs.instance_queries[query.index.unwrap()]; + let column_index = query.0.index(); + let rotation = query.1 .0; + Value::Real( + self.instance[column_index] + [(row as i32 + n + rotation) as usize % n as usize], + ) + }, + &|challenge| Value::Real(self.challenges[challenge.index()]), + &|a| -a, + &|a, b| a + b, + &|a, b| a * b, + &|a, scalar| a * scalar, + &Value::Real(F::ZERO), + ) + }; + let mut cached_table = Vec::new(); let mut cached_table_identifier = Vec::new(); // Check that all lookups exist in their respective tables. @@ -871,44 +907,6 @@ impl + Ord> MockProver { .iter() .enumerate() .flat_map(|(lookup_index, lookup)| { - let load = |expression: &Expression, row| { - expression.evaluate_lazy( - &|scalar| Value::Real(scalar), - &|_| panic!("virtual selectors are removed during optimization"), - &|query| { - let query = self.cs.fixed_queries[query.index.unwrap()]; - let column_index = query.0.index(); - let rotation = query.1 .0; - self.fixed[column_index] - [(row as i32 + n + rotation) as usize % n as usize] - .into() - }, - &|query| { - let query = self.cs.advice_queries[query.index.unwrap()]; - let column_index = query.0.index(); - let rotation = query.1 .0; - self.advice[column_index] - [(row as i32 + n + rotation) as usize % n as usize] - .into() - }, - &|query| { - let query = self.cs.instance_queries[query.index.unwrap()]; - let column_index = query.0.index(); - let rotation = query.1 .0; - Value::Real( - self.instance[column_index] - [(row as i32 + n + rotation) as usize % n as usize], - ) - }, - &|challenge| Value::Real(self.challenges[challenge.index()]), - &|a| -a, - &|a, b| a + b, - &|a, b| a * b, - &|a, scalar| a * scalar, - &Value::Real(F::ZERO), - ) - }; - assert!(lookup.table_expressions.len() == lookup.input_expressions.len()); assert!(self.usable_rows.end > 0); @@ -1008,44 +1006,6 @@ impl + Ord> MockProver { .iter() .enumerate() .flat_map(|(shuffle_index, shuffle)| { - let load = |expression: &Expression, row| { - expression.evaluate_lazy( - &|scalar| Value::Real(scalar), - &|_| panic!("virtual selectors are removed during optimization"), - &|query| { - let query = self.cs.fixed_queries[query.index.unwrap()]; - let column_index = query.0.index(); - let rotation = query.1 .0; - self.fixed[column_index] - [(row as i32 + n + rotation) as usize % n as usize] - .into() - }, - &|query| { - let query = self.cs.advice_queries[query.index.unwrap()]; - let column_index = query.0.index(); - let rotation = query.1 .0; - self.advice[column_index] - [(row as i32 + n + rotation) as usize % n as usize] - .into() - }, - &|query| { - let query = self.cs.instance_queries[query.index.unwrap()]; - let column_index = query.0.index(); - let rotation = query.1 .0; - Value::Real( - self.instance[column_index] - [(row as i32 + n + rotation) as usize % n as usize], - ) - }, - &|challenge| Value::Real(self.challenges[challenge.index()]), - &|a| -a, - &|a, b| a + b, - &|a, b| a * b, - &|a, scalar| a * scalar, - &Value::Real(F::ZERO), - ) - }; - assert!(shuffle.shuffle_expressions.len() == shuffle.input_expressions.len()); assert!(self.usable_rows.end > 0); @@ -1337,6 +1297,35 @@ impl + Ord> MockProver { .collect::>() }); + let load = |expression: &Expression, row| { + expression.evaluate_lazy( + &|scalar| Value::Real(scalar), + &|_| panic!("virtual selectors are removed during optimization"), + &|query| { + self.fixed[query.column_index] + [(row as i32 + n + query.rotation.0) as usize % n as usize] + .into() + }, + &|query| { + self.advice[query.column_index] + [(row as i32 + n + query.rotation.0) as usize % n as usize] + .into() + }, + &|query| { + Value::Real( + self.instance[query.column_index] + [(row as i32 + n + query.rotation.0) as usize % n as usize], + ) + }, + &|challenge| Value::Real(self.challenges[challenge.index()]), + &|a| -a, + &|a, b| a + b, + &|a, b| a * b, + &|a, scalar| a * scalar, + &Value::Real(F::ZERO), + ) + }; + let mut cached_table = Vec::new(); let mut cached_table_identifier = Vec::new(); // Check that all lookups exist in their respective tables. @@ -1346,35 +1335,6 @@ impl + Ord> MockProver { .iter() .enumerate() .flat_map(|(lookup_index, lookup)| { - let load = |expression: &Expression, row| { - expression.evaluate_lazy( - &|scalar| Value::Real(scalar), - &|_| panic!("virtual selectors are removed during optimization"), - &|query| { - self.fixed[query.column_index] - [(row as i32 + n + query.rotation.0) as usize % n as usize] - .into() - }, - &|query| { - self.advice[query.column_index] - [(row as i32 + n + query.rotation.0) as usize % n as usize] - .into() - }, - &|query| { - Value::Real( - self.instance[query.column_index] - [(row as i32 + n + query.rotation.0) as usize % n as usize], - ) - }, - &|challenge| Value::Real(self.challenges[challenge.index()]), - &|a| -a, - &|a, b| a + b, - &|a, b| a * b, - &|a, scalar| a * scalar, - &Value::Real(F::ZERO), - ) - }; - assert!(lookup.table_expressions.len() == lookup.input_expressions.len()); assert!(self.usable_rows.end > 0); @@ -1469,44 +1429,6 @@ impl + Ord> MockProver { .iter() .enumerate() .flat_map(|(shuffle_index, shuffle)| { - let load = |expression: &Expression, row| { - expression.evaluate_lazy( - &|scalar| Value::Real(scalar), - &|_| panic!("virtual selectors are removed during optimization"), - &|query| { - let query = self.cs.fixed_queries[query.index.unwrap()]; - let column_index = query.0.index(); - let rotation = query.1 .0; - self.fixed[column_index] - [(row as i32 + n + rotation) as usize % n as usize] - .into() - }, - &|query| { - let query = self.cs.advice_queries[query.index.unwrap()]; - let column_index = query.0.index(); - let rotation = query.1 .0; - self.advice[column_index] - [(row as i32 + n + rotation) as usize % n as usize] - .into() - }, - &|query| { - let query = self.cs.instance_queries[query.index.unwrap()]; - let column_index = query.0.index(); - let rotation = query.1 .0; - Value::Real( - self.instance[column_index] - [(row as i32 + n + rotation) as usize % n as usize], - ) - }, - &|challenge| Value::Real(self.challenges[challenge.index()]), - &|a| -a, - &|a, b| a + b, - &|a, b| a * b, - &|a, scalar| a * scalar, - &Value::Real(F::ZERO), - ) - }; - assert!(shuffle.shuffle_expressions.len() == shuffle.input_expressions.len()); assert!(self.usable_rows.end > 0); From 1623a87816e1282fe4b09dc80f5df3afb152a377 Mon Sep 17 00:00:00 2001 From: kilic Date: Tue, 20 Jun 2023 22:03:56 +0300 Subject: [PATCH 09/10] cargo fmt --- halo2_proofs/src/plonk/circuit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/halo2_proofs/src/plonk/circuit.rs b/halo2_proofs/src/plonk/circuit.rs index 7c4778dc12..ad55b747bc 100644 --- a/halo2_proofs/src/plonk/circuit.rs +++ b/halo2_proofs/src/plonk/circuit.rs @@ -1,5 +1,5 @@ -use crate::circuit::layouter::SyncDeps; use super::{lookup, permutation, shuffle, Assigned, Error}; +use crate::circuit::layouter::SyncDeps; use crate::dev::metadata; use crate::{ circuit::{Layouter, Region, Value}, From 5385bdd9b44d0e2f278ce110eec4d440be04ef2d Mon Sep 17 00:00:00 2001 From: kilic Date: Tue, 20 Jun 2023 22:25:23 +0300 Subject: [PATCH 10/10] fix fields in sanity-checks feature --- halo2_proofs/src/plonk/shuffle/prover.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/halo2_proofs/src/plonk/shuffle/prover.rs b/halo2_proofs/src/plonk/shuffle/prover.rs index 90cc971f84..ef3a459f22 100644 --- a/halo2_proofs/src/plonk/shuffle/prover.rs +++ b/halo2_proofs/src/plonk/shuffle/prover.rs @@ -181,8 +181,8 @@ impl> Argument { assert_eq!(z[0], C::Scalar::ONE); for i in 0..u { let mut left = z[i + 1]; - let input_value = &self.compressed_input_expression[i]; - let shuffle_value = &self.compressed_shuffle_expression[i]; + let input_value = &compressed.input_expression[i]; + let shuffle_value = &compressed.shuffle_expression[i]; left *= &(*gamma + shuffle_value); let mut right = z[i]; right *= &(*gamma + input_value);