diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e2b85e7a..83bfc0bb 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -24,7 +24,7 @@ jobs: cache-on-failure: true - name: Run test - run: cargo test --all -- --nocapture + run: cargo test --all --features test -- --nocapture lint: diff --git a/Cargo.toml b/Cargo.toml index 2a996930..644e01ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] ff = "0.12.0" group = "0.12.0" +itertools = "0.10.3" lazy_static = "1.4.0" num-bigint = "0.4" num-traits = "0.2" @@ -34,8 +35,11 @@ paste = "1.0.7" [features] default = ["halo2", "evm"] +test = ["halo2", "evm"] + halo2 = ["dep:blake2b_simd", "dep:halo2_proofs", "dep:halo2_wrong", "dep:halo2_wrong_ecc", "dep:halo2_wrong_maingate", "dep:halo2_wrong_transcript", "dep:poseidon"] evm = ["dep:foundry_evm", "dep:crossterm", "dep:tui", "dep:ethereum_types", "dep:sha3"] +sanity-check = [] [patch.crates-io] halo2_proofs = { git = "https://github.com/han0110/halo2", branch = "experiment", package = "halo2_proofs" } diff --git a/src/loader/evm/test.rs b/src/loader/evm/test.rs index 87ca3366..34cab3c7 100644 --- a/src/loader/evm/test.rs +++ b/src/loader/evm/test.rs @@ -1,6 +1,6 @@ use crate::loader::evm::test::tui::Tui; use foundry_evm::{ - executor::{builder::Backend, ExecutorBuilder}, + executor::{backend::Backend, fork::MultiFork, ExecutorBuilder}, revm::AccountInfo, utils::h256_to_u256_be, Address, @@ -27,14 +27,13 @@ pub fn execute(code: Vec, calldata: Vec) -> (bool, u64, Vec) { let caller = small_address(0xfe); let callee = small_address(0xff); - let builder = ExecutorBuilder::default() + let mut evm = ExecutorBuilder::default() .with_gas_limit(u64::MAX.into()) .set_tracing(debug) - .set_debugger(debug); + .set_debugger(debug) + .build(Backend::new(MultiFork::new().0, None)); - let mut evm = builder.build(Backend::simple()); - - evm.db + evm.backend_mut() .insert_account_info(callee, AccountInfo::new(0.into(), 1, code.into())); let result = evm diff --git a/src/protocol.rs b/src/protocol.rs index 1cd44832..43faa879 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -1,4 +1,4 @@ -use crate::util::{Curve, Domain, Expression, Query}; +use crate::util::{Curve, Domain, Expression, Group, Query}; #[cfg(feature = "halo2")] pub mod halo2; @@ -23,3 +23,23 @@ impl Protocol { self.preprocessed.len() + self.num_statement + self.num_auxiliary.iter().sum::() } } + +pub struct Snark { + pub protocol: Protocol, + pub statements: Vec::Scalar>>, + pub proof: Vec, +} + +impl Snark { + pub fn new( + protocol: Protocol, + statements: Vec::Scalar>>, + proof: Vec, + ) -> Self { + Snark { + protocol, + statements, + proof, + } + } +} diff --git a/src/protocol/halo2.rs b/src/protocol/halo2.rs index 8bdd2904..02cb481a 100644 --- a/src/protocol/halo2.rs +++ b/src/protocol/halo2.rs @@ -4,7 +4,7 @@ use crate::{ }; use halo2_proofs::{ arithmetic::{CurveAffine, CurveExt, FieldExt}, - plonk::{self, Advice, Any, ConstraintSystem, Fixed, Instance, VerifyingKey}, + plonk::{self, Any, ConstraintSystem, FirstPhase, SecondPhase, ThirdPhase, VerifyingKey}, poly, transcript::{EncodedChallenge, Transcript}, }; @@ -111,7 +111,10 @@ struct Polynomials<'a, F: FieldExt> { num_fixed: usize, num_permutation_fixed: usize, num_instance: usize, - num_advice: usize, + num_advice: Vec, + num_challenge: Vec, + advice_index: Vec, + challenge_index: Vec, num_lookup_permuted: usize, permutation_chunk_size: usize, num_permutation_z: usize, @@ -130,6 +133,29 @@ impl<'a, F: FieldExt> Polynomials<'a, F> { } else { degree - 1 }; + + let num_phase = *cs.advice_column_phase().iter().max().unwrap() as usize + 1; + let remapping = |phase: Vec| { + let num = phase.iter().fold(vec![0; num_phase], |mut num, phase| { + num[*phase as usize] += 1; + num + }); + let index = phase + .iter() + .scan(vec![0; num_phase], |state, phase| { + let index = state[*phase as usize]; + state[*phase as usize] += 1; + Some(index) + }) + .collect::>(); + (num, index) + }; + + let (num_advice, advice_index) = remapping(cs.advice_column_phase()); + let (num_challenge, challenge_index) = remapping(cs.challenge_phase()); + assert_eq!(num_advice.iter().sum::(), cs.num_advice_columns()); + assert_eq!(num_challenge.iter().sum::(), cs.num_challenges()); + Self { cs, zk, @@ -138,7 +164,10 @@ impl<'a, F: FieldExt> Polynomials<'a, F> { num_fixed: cs.num_fixed_columns(), num_permutation_fixed: cs.permutation().get_columns().len(), num_instance: cs.num_instance_columns(), - num_advice: cs.num_advice_columns(), + num_advice, + num_challenge, + advice_index, + challenge_index, num_lookup_permuted: 2 * cs.lookups().len(), permutation_chunk_size, num_permutation_z: cs @@ -159,19 +188,30 @@ impl<'a, F: FieldExt> Polynomials<'a, F> { } fn num_auxiliary(&self) -> Vec { - vec![ - self.num_proof * self.num_advice, - self.num_proof * self.num_lookup_permuted, - self.num_proof * (self.num_permutation_z + self.num_lookup_z) + self.zk as usize, - ] + iter::empty() + .chain( + self.num_advice + .clone() + .iter() + .map(|num| self.num_proof * num), + ) + .chain([ + self.num_proof * self.num_lookup_permuted, + self.num_proof * (self.num_permutation_z + self.num_lookup_z) + self.zk as usize, + ]) + .collect() } fn num_challenge(&self) -> Vec { - vec![ - 1, // theta - 2, // beta, gamma - 0, - ] + let mut num_challenge = self.num_challenge.clone(); + *num_challenge.last_mut().unwrap() += 1; // theta + iter::empty() + .chain(num_challenge) + .chain([ + 2, // beta, gamma + 0, + ]) + .collect() } fn instance_offset(&self) -> usize { @@ -182,17 +222,35 @@ impl<'a, F: FieldExt> Polynomials<'a, F> { self.instance_offset() + self.num_statement() } + fn cs_auxiliary_offset(&self) -> usize { + self.auxiliary_offset() + + self + .num_auxiliary() + .iter() + .take(self.num_advice.len()) + .sum::() + } + fn query + Copy, R: Into>( &self, column_type: C, - column_index: usize, + mut column_index: usize, rotation: R, t: usize, ) -> Query { let offset = match column_type.into() { Any::Fixed => 0, Any::Instance => self.instance_offset() + t * self.num_instance, - Any::Advice => self.auxiliary_offset() + t * self.num_advice, + Any::Advice(advice) => { + column_index = self.advice_index[column_index]; + let phase_offset = self.num_proof + * self.num_advice[..advice.phase() as usize] + .iter() + .sum::(); + self.auxiliary_offset() + + phase_offset + + t * self.num_advice[advice.phase() as usize] + } }; Query::new(offset + column_index, rotation.into()) } @@ -234,7 +292,7 @@ impl<'a, F: FieldExt> Polynomials<'a, F> { } fn permutation_poly(&'a self, t: usize, i: usize) -> usize { - let z_offset = self.auxiliary_offset() + self.num_auxiliary().iter().take(2).sum::(); + let z_offset = self.cs_auxiliary_offset() + self.num_auxiliary()[self.num_advice.len()]; z_offset + t * self.num_permutation_z + i } @@ -275,9 +333,10 @@ impl<'a, F: FieldExt> Polynomials<'a, F> { } fn lookup_poly(&'a self, t: usize, i: usize) -> (usize, usize, usize) { - let permuted_offset = self.auxiliary_offset() + self.num_auxiliary()[0]; - let z_offset = - permuted_offset + self.num_auxiliary()[1] + self.num_proof * self.num_permutation_z; + let permuted_offset = self.cs_auxiliary_offset(); + let z_offset = permuted_offset + + self.num_auxiliary()[self.num_advice.len()] + + self.num_proof * self.num_permutation_z; let z = z_offset + t * self.num_lookup_z + i; let permuted_input = permuted_offset + 2 * (t * self.num_lookup_z + i); let permuted_table = permuted_input + 1; @@ -328,9 +387,34 @@ impl<'a, F: FieldExt> Polynomials<'a, F> { expression.evaluate( &|scalar| Expression::Constant(scalar), &|_| unreachable!(), - &|_, index, rotation| self.query(Fixed, index, rotation, t).into(), - &|_, index, rotation| self.query(Advice, index, rotation, t).into(), - &|_, index, rotation| self.query(Instance, index, rotation, t).into(), + &|query| { + self.query(Any::Fixed, query.column_index(), query.rotation(), t) + .into() + }, + &|query| { + self.query( + match query.phase() { + 0 => Any::advice_in(FirstPhase), + 1 => Any::advice_in(SecondPhase), + 2 => Any::advice_in(ThirdPhase), + _ => unreachable!(), + }, + query.column_index(), + query.rotation(), + t, + ) + .into() + }, + &|query| { + self.query(Any::Instance, query.column_index(), query.rotation(), t) + .into() + }, + &|challenge| { + let phase_offset = self.num_challenge[..challenge.phase() as usize] + .iter() + .sum::(); + Expression::Challenge(phase_offset + self.challenge_index[challenge.index()]) + }, &|a| -a, &|a, b| a + b, &|a, b| a * b, diff --git a/src/protocol/halo2/test.rs b/src/protocol/halo2/test.rs index 2f7de080..4314a4da 100644 --- a/src/protocol/halo2/test.rs +++ b/src/protocol/halo2/test.rs @@ -1,19 +1,12 @@ use crate::{ - protocol::{ - halo2::{compile, Config}, - Protocol, - }, - util::{CommonPolynomial, Curve, Expression, Group, Query}, + protocol::halo2::{compile, Config}, + util::{CommonPolynomial, Expression, Query}, }; use halo2_curves::bn256::{Bn256, Fr, G1}; use halo2_proofs::{ arithmetic::FieldExt, - circuit::{floor_planner::V1, Layouter, Value}, dev::MockProver, - plonk::{ - create_proof, keygen_pk, keygen_vk, verify_proof, Advice, Any, Circuit, Column, - ConstraintSystem, Error, Fixed, Instance, ProvingKey, - }, + plonk::{create_proof, keygen_pk, keygen_vk, verify_proof, Circuit, ProvingKey}, poly::{ commitment::{CommitmentScheme, Params, ParamsProver, Prover, Verifier}, kzg::commitment::KZGCommitmentScheme, @@ -21,254 +14,19 @@ use halo2_proofs::{ }, transcript::{EncodedChallenge, TranscriptReadBuffer, TranscriptWriterBuffer}, }; -use halo2_wrong_ecc::EccConfig; -use halo2_wrong_maingate::{ - MainGate, MainGateConfig, MainGateInstructions, RangeChip, RangeConfig, RangeInstructions, - RegionCtx, -}; use rand_chacha::{ rand_core::{RngCore, SeedableRng}, ChaCha20Rng, }; +mod circuit; mod kzg; -#[allow(dead_code)] -#[derive(Clone)] -pub struct StandardPlonkConfig { - a: Column, - b: Column, - c: Column, - q_a: Column, - q_b: Column, - q_c: Column, - q_ab: Column, - constant: Column, - instance: Column, -} - -impl StandardPlonkConfig { - pub fn configure(meta: &mut ConstraintSystem) -> Self { - let a = meta.advice_column(); - let b = meta.advice_column(); - let c = meta.advice_column(); - - let q_a = meta.fixed_column(); - let q_b = meta.fixed_column(); - let q_c = meta.fixed_column(); - - let q_ab = meta.fixed_column(); - - let constant = meta.fixed_column(); - let instance = meta.instance_column(); - - meta.enable_equality(a); - meta.enable_equality(b); - meta.enable_equality(c); - - meta.create_gate("", |meta| { - let [a, b, c, q_a, q_b, q_c, q_ab, constant, instance] = [ - a.into(), - b.into(), - c.into(), - q_a.into(), - q_b.into(), - q_c.into(), - q_ab.into(), - constant.into(), - instance.into(), - ] - .map(|column: Column| meta.query_any(column, Rotation::cur())); - - vec![q_a * a.clone() + q_b * b.clone() + q_c * c + q_ab * a * b + constant + instance] - }); - - StandardPlonkConfig { - a, - b, - c, - q_a, - q_b, - q_c, - q_ab, - constant, - instance, - } - } -} - -#[derive(Clone, Default)] -pub struct StandardPlonk(F); - -impl StandardPlonk { - pub fn rand(mut rng: R) -> Self { - Self(F::from(rng.next_u32() as u64)) - } - - pub fn instances(&self) -> Vec> { - vec![vec![self.0]] - } -} - -impl Circuit for StandardPlonk { - type Config = StandardPlonkConfig; - type FloorPlanner = V1; - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - meta.set_minimum_degree(4); - StandardPlonkConfig::configure(meta) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - layouter.assign_region( - || "", - |mut region| { - region.assign_advice(|| "", config.a, 0, || Value::known(self.0))?; - region.assign_fixed(|| "", config.q_a, 0, || Value::known(-F::one()))?; - - region.assign_advice(|| "", config.a, 1, || Value::known(-F::from(5)))?; - for (column, idx) in [ - config.q_a, - config.q_b, - config.q_c, - config.q_ab, - config.constant, - ] - .iter() - .zip(1..) - { - region.assign_fixed(|| "", *column, 1, || Value::known(F::from(idx)))?; - } - - let a = region.assign_advice(|| "", config.a, 2, || Value::known(F::one()))?; - a.copy_advice(|| "", &mut region, config.b, 3)?; - a.copy_advice(|| "", &mut region, config.c, 4)?; - - Ok(()) - }, - ) - } -} - -#[derive(Clone)] -pub struct MainGateWithRangeConfig { - main_gate_config: MainGateConfig, - range_config: RangeConfig, -} - -impl MainGateWithRangeConfig { - fn ecc_config(&self) -> EccConfig { - EccConfig::new(self.range_config.clone(), self.main_gate_config.clone()) - } - - fn configure( - meta: &mut ConstraintSystem, - composition_bits: Vec, - overflow_bits: Vec, - ) -> Self { - let main_gate_config = MainGate::::configure(meta); - let range_config = - RangeChip::::configure(meta, &main_gate_config, composition_bits, overflow_bits); - MainGateWithRangeConfig { - main_gate_config, - range_config, - } - } - - fn load_table(&self, layouter: &mut impl Layouter) -> Result<(), Error> { - let range_chip = RangeChip::::new(self.range_config.clone()); - range_chip.load_table(layouter)?; - Ok(()) - } -} - -#[derive(Clone, Default)] -pub struct MainGateWithRange(Vec); - -impl MainGateWithRange { - pub fn new(inner: Vec) -> Self { - Self(inner) - } - - pub fn rand(mut rng: R) -> Self { - Self::new(vec![F::from(rng.next_u32() as u64)]) - } - - pub fn instances(&self) -> Vec> { - vec![self.0.clone()] - } -} - -impl Circuit for MainGateWithRange { - type Config = MainGateWithRangeConfig; - type FloorPlanner = V1; - - fn without_witnesses(&self) -> Self { - Self(vec![F::zero()]) - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - MainGateWithRangeConfig::configure(meta, vec![8], vec![1, 7]) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - let main_gate = MainGate::new(config.main_gate_config); - let range_chip = RangeChip::new(config.range_config); - range_chip.load_table(&mut layouter)?; - - let a = layouter.assign_region( - || "", - |mut region| { - let mut offset = 0; - let mut ctx = RegionCtx::new(&mut region, &mut offset); - range_chip.decompose(&mut ctx, Value::known(F::from(u64::MAX)), 8, 64)?; - range_chip.decompose(&mut ctx, Value::known(self.0[0]), 8, 33)?; - let (a, _) = range_chip.decompose(&mut ctx, Value::known(self.0[0]), 8, 39)?; - let b = main_gate.sub_sub_with_constant(&mut ctx, &a, &a, &a, F::from(2))?; - let cond = main_gate.assign_bit(&mut ctx, Value::known(F::one()))?; - main_gate.select(&mut ctx, &a, &b, &cond)?; - - Ok(a) - }, - )?; - - main_gate.expose_public(layouter, a, 0)?; - - Ok(()) - } -} - -pub struct Snark { - protocol: Protocol, - statements: Vec::Scalar>>, - proof: Vec, -} - -impl Snark { - pub fn new( - protocol: Protocol, - statements: Vec::Scalar>>, - proof: Vec, - ) -> Self { - Snark { - protocol, - statements, - proof, - } - } -} +pub use circuit::{ + maingate::{MainGateWithRange, MainGateWithRangeConfig}, + plookup::Plookuper, + standard::StandardPlonk, +}; pub fn create_proof_checked<'a, S, C, P, V, VS, TW, TR, EC, R, const ZK: bool>( params: &'a S::ParamsProver, diff --git a/src/protocol/halo2/test/circuit.rs b/src/protocol/halo2/test/circuit.rs new file mode 100644 index 00000000..a87cb997 --- /dev/null +++ b/src/protocol/halo2/test/circuit.rs @@ -0,0 +1,3 @@ +pub mod maingate; +pub mod plookup; +pub mod standard; diff --git a/src/protocol/halo2/test/circuit/maingate.rs b/src/protocol/halo2/test/circuit/maingate.rs new file mode 100644 index 00000000..bc92aeb2 --- /dev/null +++ b/src/protocol/halo2/test/circuit/maingate.rs @@ -0,0 +1,103 @@ +use halo2_proofs::{ + arithmetic::FieldExt, + circuit::{floor_planner::V1, Layouter, Value}, + plonk::{Circuit, ConstraintSystem, Error}, +}; +use halo2_wrong_ecc::EccConfig; +use halo2_wrong_maingate::{ + MainGate, MainGateConfig, MainGateInstructions, RangeChip, RangeConfig, RangeInstructions, + RegionCtx, +}; +use rand::RngCore; + +#[derive(Clone)] +pub struct MainGateWithRangeConfig { + main_gate_config: MainGateConfig, + range_config: RangeConfig, +} + +impl MainGateWithRangeConfig { + pub fn ecc_config(&self) -> EccConfig { + EccConfig::new(self.range_config.clone(), self.main_gate_config.clone()) + } + + pub fn configure( + meta: &mut ConstraintSystem, + composition_bits: Vec, + overflow_bits: Vec, + ) -> Self { + let main_gate_config = MainGate::::configure(meta); + let range_config = + RangeChip::::configure(meta, &main_gate_config, composition_bits, overflow_bits); + MainGateWithRangeConfig { + main_gate_config, + range_config, + } + } + + pub fn load_table(&self, layouter: &mut impl Layouter) -> Result<(), Error> { + let range_chip = RangeChip::::new(self.range_config.clone()); + range_chip.load_table(layouter)?; + Ok(()) + } +} + +#[derive(Clone, Default)] +pub struct MainGateWithRange(Vec); + +impl MainGateWithRange { + pub fn new(inner: Vec) -> Self { + Self(inner) + } + + pub fn rand(mut rng: R) -> Self { + Self::new(vec![F::from(rng.next_u32() as u64)]) + } + + pub fn instances(&self) -> Vec> { + vec![self.0.clone()] + } +} + +impl Circuit for MainGateWithRange { + type Config = MainGateWithRangeConfig; + type FloorPlanner = V1; + + fn without_witnesses(&self) -> Self { + Self(vec![F::zero()]) + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + MainGateWithRangeConfig::configure(meta, vec![8], vec![1, 7]) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let main_gate = MainGate::new(config.main_gate_config); + let range_chip = RangeChip::new(config.range_config); + range_chip.load_table(&mut layouter)?; + + let a = layouter.assign_region( + || "", + |mut region| { + let mut offset = 0; + let mut ctx = RegionCtx::new(&mut region, &mut offset); + range_chip.decompose(&mut ctx, Value::known(F::from(u64::MAX)), 8, 64)?; + range_chip.decompose(&mut ctx, Value::known(self.0[0]), 8, 33)?; + let (a, _) = range_chip.decompose(&mut ctx, Value::known(self.0[0]), 8, 39)?; + let b = main_gate.sub_sub_with_constant(&mut ctx, &a, &a, &a, F::from(2))?; + let cond = main_gate.assign_bit(&mut ctx, Value::known(F::one()))?; + main_gate.select(&mut ctx, &a, &b, &cond)?; + + Ok(a) + }, + )?; + + main_gate.expose_public(layouter, a, 0)?; + + Ok(()) + } +} diff --git a/src/protocol/halo2/test/circuit/plookup.rs b/src/protocol/halo2/test/circuit/plookup.rs new file mode 100644 index 00000000..889d2fcc --- /dev/null +++ b/src/protocol/halo2/test/circuit/plookup.rs @@ -0,0 +1,1067 @@ +use crate::util::{BatchInvert, Field}; +use halo2_proofs::{ + arithmetic::FieldExt, + circuit::{floor_planner::V1, Layouter, Value}, + plonk::{ + Advice, Any, Challenge, Circuit, Column, ConstraintSystem, Error, Expression, FirstPhase, + Fixed, SecondPhase, Selector, ThirdPhase, VirtualCells, + }, + poly::Rotation, +}; +use itertools::Itertools; +use rand::RngCore; +use std::{collections::BTreeMap, convert::TryFrom, iter, mem, ops::Mul}; + +fn first_fit_packing(cap: usize, weights: Vec) -> Vec> { + let mut bins = Vec::<(usize, Vec)>::new(); + + weights.into_iter().enumerate().for_each(|(idx, weight)| { + for (remaining, indices) in bins.iter_mut() { + if *remaining >= weight { + *remaining -= weight; + indices.push(idx); + return; + } + } + bins.push((cap - weight, vec![idx])); + }); + + bins.into_iter().map(|(_, indices)| indices).collect() +} + +fn max_advice_phase(expression: &Expression) -> u8 { + expression.evaluate( + &|_| 0, + &|_| 0, + &|_| 0, + &|query| query.phase(), + &|_| 0, + &|_| 0, + &|a| a, + &|a, b| a.max(b), + &|a, b| a.max(b), + &|a, _| a, + ) +} + +fn min_challenge_phase(expression: &Expression) -> Option { + expression.evaluate( + &|_| None, + &|_| None, + &|_| None, + &|_| None, + &|_| None, + &|challenge| Some(challenge.phase()), + &|a| a, + &|a, b| match (a, b) { + (Some(a), Some(b)) => Some(a.min(b)), + (Some(phase), None) | (None, Some(phase)) => Some(phase), + (None, None) => None, + }, + &|a, b| match (a, b) { + (Some(a), Some(b)) => Some(a.min(b)), + (Some(phase), None) | (None, Some(phase)) => Some(phase), + (None, None) => None, + }, + &|a, _| a, + ) +} + +fn advice_column_in(meta: &mut ConstraintSystem, phase: u8) -> Column { + match phase { + 0 => meta.advice_column_in(FirstPhase), + 1 => meta.advice_column_in(SecondPhase), + 2 => meta.advice_column_in(ThirdPhase), + _ => unreachable!(), + } +} + +fn challenge_usable_after(meta: &mut ConstraintSystem, phase: u8) -> Challenge { + match phase { + 0 => meta.challenge_usable_after(FirstPhase), + 1 => meta.challenge_usable_after(SecondPhase), + 2 => meta.challenge_usable_after(ThirdPhase), + _ => unreachable!(), + } +} + +#[derive(Clone)] +pub struct ShuffleConfig { + l_0: Selector, + zs: Vec>, + gamma: Option, + lhs_bins: Vec>, + rhs_bins: Vec>, +} + +impl ShuffleConfig { + pub fn configure( + meta: &mut ConstraintSystem, + lhs: impl Clone + FnOnce(&mut VirtualCells<'_, F>) -> Vec>, + rhs: impl Clone + FnOnce(&mut VirtualCells<'_, F>) -> Vec>, + l_0: Option, + ) -> Self { + let gamma = { + let (lhs, rhs) = { + let mut tmp = None; + meta.create_gate("", |meta| { + let (lhs, rhs) = (lhs.clone()(meta), rhs.clone()(meta)); + assert_eq!(lhs.len(), rhs.len()); + tmp = Some((lhs, rhs)); + Some(Expression::Constant(F::zero())) + }); + tmp.unwrap() + }; + let phase = iter::empty() + .chain(lhs.iter()) + .chain(rhs.iter()) + .map(max_advice_phase) + .max() + .unwrap(); + + challenge_usable_after(meta, phase) + }; + let lhs_with_gamma = |meta: &mut VirtualCells<'_, F>| { + let lhs = lhs(meta); + let gamma = meta.query_challenge(gamma); + lhs.into_iter().zip(iter::repeat(gamma)).collect() + }; + let rhs_with_gamma = |meta: &mut VirtualCells<'_, F>| { + let rhs = rhs(meta); + let gamma = meta.query_challenge(gamma); + rhs.into_iter().zip(iter::repeat(gamma)).collect() + }; + let mut config = Self::configure_with_gamma( + meta, + lhs_with_gamma, + rhs_with_gamma, + |_| None, + |_| None, + l_0, + ); + config.gamma = Some(gamma); + config + } + + pub fn configure_with_gamma( + meta: &mut ConstraintSystem, + lhs_with_gamma: impl FnOnce(&mut VirtualCells<'_, F>) -> Vec<(Expression, Expression)>, + rhs_with_gamma: impl FnOnce(&mut VirtualCells<'_, F>) -> Vec<(Expression, Expression)>, + lhs_coeff: impl FnOnce(&mut VirtualCells<'_, F>) -> Option>, + rhs_coeff: impl FnOnce(&mut VirtualCells<'_, F>) -> Option>, + l_0: Option, + ) -> Self { + if ZK { + todo!() + } + + let (lhs_with_gamma, rhs_with_gamma, lhs_coeff, rhs_coeff) = { + let mut tmp = None; + meta.create_gate("", |meta| { + let lhs_with_gamma = lhs_with_gamma(meta); + let rhs_with_gamma = rhs_with_gamma(meta); + let lhs_coeff = lhs_coeff(meta); + let rhs_coeff = rhs_coeff(meta); + assert_eq!(lhs_with_gamma.len(), rhs_with_gamma.len()); + tmp = Some((lhs_with_gamma, rhs_with_gamma, lhs_coeff, rhs_coeff)); + Some(Expression::Constant(F::zero())) + }); + tmp.unwrap() + }; + let gamma_phase = iter::empty() + .chain(lhs_with_gamma.iter()) + .chain(rhs_with_gamma.iter()) + .map(|(value, _)| max_advice_phase(value)) + .max() + .unwrap(); + let z_phase = gamma_phase + 1; + assert!(!lhs_with_gamma + .iter() + .any(|(_, gamma)| gamma.degree() != 0 + || min_challenge_phase(gamma).unwrap() < gamma_phase)); + assert!(!rhs_with_gamma + .iter() + .any(|(_, gamma)| gamma.degree() != 0 + || min_challenge_phase(gamma).unwrap() < gamma_phase)); + + let [lhs_bins, rhs_bins] = [&lhs_with_gamma, &rhs_with_gamma].map(|value_with_gamma| { + first_fit_packing( + meta.degree::() - 1, + value_with_gamma + .iter() + .map(|(value, _)| value.degree()) + .collect(), + ) + }); + let num_z = lhs_bins.len().max(rhs_bins.len()); + + let l_0 = l_0.unwrap_or_else(|| meta.selector()); + let zs = iter::repeat_with(|| advice_column_in(meta, z_phase)) + .take(num_z) + .collect::>(); + + meta.create_gate("Shuffle", |meta| { + let l_0 = meta.query_selector(l_0); + let zs = iter::empty() + .chain(zs.iter().cloned().zip(iter::repeat(Rotation::cur()))) + .chain(Some((zs[0], Rotation::next()))) + .map(|(z, at)| meta.query_advice(z, at)) + .collect::>(); + + let one = Expression::Constant(F::one()); + let z_0 = zs[0].clone(); + + let collect_contribution = + |value_with_gamma: Vec<(Expression, Expression)>, + coeff: Option>, + bins: &[Vec]| { + let mut contribution = bins + .iter() + .chain(iter::repeat(&Vec::new()).take(num_z - bins.len())) + .map(|bin| { + bin.iter() + .map(|idx| value_with_gamma[*idx].clone()) + .map(|(value, gamma)| value + gamma) + .reduce(|acc, expr| acc * expr) + }) + .collect::>(); + + if let Some(coeff) = coeff { + contribution[0] = contribution[0].take().map(|value| coeff * value) + } + + contribution + }; + let lhs_contributions = collect_contribution(lhs_with_gamma, lhs_coeff, &lhs_bins); + let rhs_contributions = collect_contribution(rhs_with_gamma, rhs_coeff, &rhs_bins); + + iter::once(l_0 * (one - z_0)).chain( + lhs_contributions + .into_iter() + .zip(rhs_contributions) + .zip(zs.clone().into_iter().zip(zs.into_iter().skip(1))) + .map(|((lhs, rhs), (z_i, z_j))| { + lhs.map(|lhs| z_i.clone() * lhs).unwrap_or_else(|| z_i) + - rhs.map(|rhs| z_j.clone() * rhs).unwrap_or_else(|| z_j) + }), + ) + }); + + ShuffleConfig { + l_0, + zs, + gamma: None, + lhs_bins, + rhs_bins, + } + } + + fn assign( + &self, + layouter: impl Layouter, + lhs: Value>>, + rhs: Value>>, + n: usize, + ) -> Result<(), Error> { + let gamma = layouter.get_challenge(self.gamma.unwrap()); + let lhs_gammas = lhs + .zip(gamma) + .map(|(lhs, gamma)| lhs.into_iter().zip(iter::repeat(gamma)).collect::>()); + let rhs_gammas = rhs + .zip(gamma) + .map(|(rhs, gamma)| rhs.into_iter().zip(iter::repeat(gamma)).collect::>()); + self.assign_with_gamma(layouter, lhs_gammas, rhs_gammas, None, None, n) + } + + fn assign_with_gamma( + &self, + mut layouter: impl Layouter, + lhs_with_gamma: Value, F)>>, + rhs_with_gamma: Value, F)>>, + lhs_coeff: Option>, + rhs_coeff: Option>, + n: usize, + ) -> Result<(), Error> { + if ZK { + todo!() + } + + let lhs_coeff = lhs_coeff + .map(|lhs_coeff| lhs_coeff.map(|lhs_coeff| Some(lhs_coeff))) + .unwrap_or_else(|| Value::known(None)); + let rhs_coeff = rhs_coeff + .map(|rhs_coeff| rhs_coeff.map(|rhs_coeff| Some(rhs_coeff))) + .unwrap_or_else(|| Value::known(None)); + let z = lhs_with_gamma + .zip(rhs_with_gamma) + .zip(lhs_coeff) + .zip(rhs_coeff) + .map( + |(((lhs_with_gamma, rhs_with_gamma), lhs_coeff), rhs_coeff)| { + let collect_contribution = + |mut value_with_gamma: Vec<(Vec, F)>, + coeff: Option, + bins: &[Vec]| { + let mut contribution = bins + .iter() + .map(|bin| { + bin.iter() + .map(|idx| mem::take(&mut value_with_gamma[*idx])) + .map(|(mut values, gamma)| { + values.iter_mut().for_each(|value| *value += gamma); + values + }) + .reduce(|mut acc, values| { + acc.iter_mut() + .zip(values) + .for_each(|(acc, value)| *acc *= value); + acc + }) + .unwrap() + }) + .collect::>(); + + if let Some(coeff) = coeff { + contribution[0].iter_mut().for_each(|value| *value *= coeff); + } + + contribution.into_iter().flatten().collect::>() + }; + + let numers = collect_contribution(lhs_with_gamma, lhs_coeff, &self.lhs_bins); + let mut denoms = + collect_contribution(rhs_with_gamma, rhs_coeff, &self.rhs_bins); + denoms.iter_mut().batch_invert(); + + let products = numers + .into_iter() + .zip(denoms) + .map(|(numer, denom)| numer * denom) + .collect::>(); + + let mut z = vec![F::one()]; + for i in 0..n { + for j in (i..).step_by(n).take(self.zs.len()) { + z.push(products[j] * z.last().unwrap()); + } + } + + let _last = z.pop().unwrap(); + #[cfg(feature = "sanity-check")] + assert_eq!(_last, F::one()); + + z + }, + ) + .transpose_vec(self.zs.len() * n); + + layouter.assign_region( + || "zs", + |mut region| { + self.l_0.enable(&mut region, 0)?; + + let mut z = z.iter(); + for offset in 0..n { + for column in self.zs.iter() { + region.assign_advice(|| "", *column, offset, || *z.next().unwrap())?; + } + } + + Ok(()) + }, + ) + } +} + +fn binomial_coeffs(n: usize) -> Vec { + debug_assert!(n > 0); + + match n { + 1 => vec![1], + _ => { + let last_row = binomial_coeffs(n - 1); + iter::once(0) + .chain(last_row.iter().cloned()) + .zip(last_row.iter().cloned().chain(iter::once(0))) + .map(|(n, m)| n + m) + .collect() + } + } +} + +fn powers>(one: T, base: T) -> impl Iterator { + iter::successors(Some(one), move |power| Some(base.clone() * power.clone())) +} + +fn ordered_multiset(inputs: &[Vec], table: &[F]) -> Vec { + let mut input_counts = + inputs + .iter() + .flatten() + .fold(BTreeMap::<_, usize>::new(), |mut map, value| { + map.entry(value) + .and_modify(|count| *count += 1) + .or_insert(1); + map + }); + + let mut ordered = Vec::with_capacity((inputs.len() + 1) * inputs[0].len()); + for (count, value) in table.iter().dedup_with_count() { + let count = input_counts + .remove(value) + .map(|input_count| input_count + count) + .unwrap_or(count); + ordered.extend(iter::repeat(*value).take(count)); + } + + #[cfg(feature = "sanity-check")] + { + assert_eq!(input_counts.len(), 0); + assert_eq!(ordered.len(), ordered.capacity()); + } + + ordered.extend(iter::repeat(*ordered.last().unwrap()).take(ordered.capacity() - ordered.len())); + + ordered +} + +#[derive(Clone)] +pub struct PlookupConfig { + shuffle: ShuffleConfig, + mixes: Vec>, + theta: Option, + beta: Challenge, + gamma: Challenge, +} + +impl PlookupConfig { + pub fn configure( + meta: &mut ConstraintSystem, + inputs: impl FnOnce(&mut VirtualCells<'_, F>) -> Vec<[Expression; W]>, + table: [Column; W], + l_0: Option, + theta: Option, + beta: Option, + gamma: Option, + ) -> Self { + if ZK { + todo!() + } + + let inputs = { + let mut tmp = None; + meta.create_gate("", |meta| { + tmp = Some(inputs(meta)); + Some(Expression::Constant(F::zero())) + }); + tmp.unwrap() + }; + let t = inputs.len(); + let theta_phase = iter::empty() + .chain(inputs.iter().flatten()) + .map(max_advice_phase) + .chain(table.iter().map(|column| { + Column::::try_from(*column) + .map(|column| column.column_type().phase()) + .unwrap_or_default() + })) + .max() + .unwrap(); + let mixes_phase = theta_phase + 1; + + let theta = if W > 1 { + Some(match theta { + Some(theta) => { + assert!(theta.phase() >= theta_phase); + theta + } + None => challenge_usable_after(meta, theta_phase), + }) + } else { + assert!(theta.is_none()); + None + }; + let mixes = iter::repeat_with(|| advice_column_in(meta, mixes_phase)) + .take(t + 1) + .collect::>(); + let [beta, gamma] = [beta, gamma].map(|challenge| match challenge { + Some(challenge) => { + assert!(challenge.phase() >= mixes_phase); + challenge + } + None => challenge_usable_after(meta, mixes_phase), + }); + assert_ne!(theta, Some(beta)); + assert_ne!(theta, Some(gamma)); + assert_ne!(beta, gamma); + + let lhs_with_gamma = |meta: &mut VirtualCells<'_, F>| { + let [table, table_w] = [Rotation::cur(), Rotation::next()] + .map(|at| table.map(|column| meta.query_any(column, at))); + let theta = theta.map(|theta| meta.query_challenge(theta)); + let [beta, gamma] = [beta, gamma].map(|challenge| meta.query_challenge(challenge)); + let one = Expression::Constant(F::one()); + let gamma_prime = (one + beta.clone()) * gamma.clone(); + + let table = table + .iter() + .cloned() + .reduce(|acc, expr| acc * theta.clone().unwrap() + expr) + .unwrap(); + let table_w = table_w + .iter() + .cloned() + .reduce(|acc, expr| acc * theta.clone().unwrap() + expr) + .unwrap(); + let inputs = inputs.iter().map(|input| { + input + .iter() + .cloned() + .reduce(|acc, expr| acc * theta.clone().unwrap() + expr) + .unwrap() + }); + + let values = inputs.chain(Some(table + table_w * beta)); + let gammas = iter::empty() + .chain(iter::repeat(gamma).take(t)) + .chain(Some(gamma_prime)); + values.zip(gammas).collect() + }; + let rhs_with_gamma = |meta: &mut VirtualCells<'_, F>| { + let mixes = iter::empty() + .chain(mixes.iter().cloned().zip(iter::repeat(Rotation::cur()))) + .chain(Some((mixes[0], Rotation::next()))) + .map(|(column, at)| meta.query_advice(column, at)) + .collect::>(); + let [beta, gamma] = [beta, gamma].map(|challenge| meta.query_challenge(challenge)); + let one = Expression::Constant(F::one()); + let gamma_prime = (one + beta.clone()) * gamma; + + let values = mixes + .iter() + .cloned() + .zip(mixes.iter().skip(1).cloned()) + .zip(iter::repeat(beta)) + .map(|((mix_i, mix_j), beta)| mix_i + mix_j * beta); + let gammas = iter::repeat(gamma_prime).take(t + 1); + values.zip(gammas).collect() + }; + let lhs_coeff = |meta: &mut VirtualCells<'_, F>| { + let beta = meta.query_challenge(beta); + let one = Expression::Constant(F::one()); + binomial_coeffs(t + 1) + .into_iter() + .zip(powers(one, beta)) + .map(|(coeff, power_of_beta)| Expression::Constant(F::from(coeff)) * power_of_beta) + .reduce(|acc, expr| acc + expr) + }; + let shuffle = ShuffleConfig::configure_with_gamma( + meta, + lhs_with_gamma, + rhs_with_gamma, + lhs_coeff, + |_| None, + l_0, + ); + + Self { + shuffle, + mixes, + theta, + beta, + gamma, + } + } + + fn assign( + &self, + mut layouter: impl Layouter, + inputs: Value>>, + table: Value>, + n: usize, + ) -> Result<(), Error> { + if ZK { + todo!() + } + + let (compressed_inputs, compressed_table, mix) = { + let compress = |values: Vec<[F; W]>, theta: Option| { + if W > 1 { + let theta = theta.unwrap(); + values + .into_iter() + .map(|value| { + value + .into_iter() + .reduce(|acc, value| acc * theta + value) + .unwrap() + }) + .collect::>() + } else { + values.into_iter().map(|value| value[0]).collect::>() + } + }; + + let theta = self + .theta + .map(|theta| layouter.get_challenge(theta).map(Some)) + .unwrap_or_else(|| Value::known(None)); + let compressed_inputs = inputs.zip(theta).map(|(inputs, theta)| { + inputs + .into_iter() + .map(|input| compress(input, theta)) + .collect::>() + }); + let compressed_table = table + .zip(theta) + .map(|(table, theta)| compress(table, theta)); + + let mix = compressed_inputs + .as_ref() + .zip(compressed_table.as_ref()) + .map(|(compressed_inputs, compressed_table)| { + ordered_multiset(compressed_inputs, compressed_table) + }); + + (compressed_inputs, compressed_table, mix) + }; + + let (lhs_with_gamma, rhs_with_gamma, lhs_coeff) = { + let [beta, gamma] = + [self.beta, self.gamma].map(|challenge| layouter.get_challenge(challenge)); + let gamma_prime = beta + .zip(gamma) + .map(|(beta, gamma)| (F::one() + beta) * gamma); + + let lhs_with_gamma = compressed_inputs + .zip(compressed_table) + .zip(beta) + .zip(gamma) + .zip(gamma_prime) + .map( + |((((compressed_inputs, compressed_table), beta), gamma), gamma_prime)| { + let values = compressed_inputs.into_iter().chain(Some( + compressed_table + .iter() + .zip(compressed_table.iter().cycle().skip(1)) + .map(|(table, table_w)| *table + beta * table_w) + .collect::>(), + )); + let gammas = iter::empty() + .chain(iter::repeat(gamma).take(self.mixes.len() - 1)) + .chain(Some(gamma_prime)); + + values.zip(gammas).collect::>() + }, + ); + let rhs_with_gamma = + mix.as_ref() + .zip(beta) + .zip(gamma_prime) + .map(|((mix, beta), gamma_prime)| { + let mut values = vec![Vec::with_capacity(n); self.mixes.len()]; + for (idx, value) in (0..values.len()).cycle().zip( + mix.iter() + .zip(mix.iter().cycle().skip(1)) + .map(|(mix_i, mix_j)| *mix_i + beta * mix_j), + ) { + values[idx].push(value); + } + let gammas = iter::repeat(gamma_prime).take(self.mixes.len()); + + values.into_iter().zip(gammas).collect::>() + }); + let lhs_coeff = beta.map(|beta| { + binomial_coeffs(self.mixes.len()) + .into_iter() + .zip(powers(F::one(), beta)) + .map(|(coeff, power_of_beta)| F::from(coeff) * power_of_beta) + .reduce(|acc, value| acc + value) + .unwrap() + }); + + (lhs_with_gamma, rhs_with_gamma, lhs_coeff) + }; + + let mix = mix.transpose_vec(self.mixes.len() * n); + layouter.assign_region( + || "mixes", + |mut region| { + let mut mix = mix.iter(); + for offset in 0..n { + for column in self.mixes.iter() { + region.assign_advice(|| "", *column, offset, || *mix.next().unwrap())?; + } + } + + Ok(()) + }, + )?; + + self.shuffle.assign_with_gamma( + layouter.namespace(|| "Shuffle"), + lhs_with_gamma, + rhs_with_gamma, + Some(lhs_coeff), + None, + n, + )?; + + Ok(()) + } +} + +#[derive(Clone)] +pub struct Plookuper { + n: usize, + inputs: Value<[Vec<[F; W]>; T]>, + table: Vec<[F; W]>, +} + +impl Plookuper { + pub fn rand(mut rng: R, n: usize) -> Self { + let m = rng.next_u32() as usize % n; + let mut table = iter::repeat_with(|| [(); W].map(|_| F::random(&mut rng))) + .take(m) + .collect::>(); + table.extend( + iter::repeat( + table + .first() + .cloned() + .unwrap_or_else(|| [(); W].map(|_| F::random(&mut rng))), + ) + .take(n - m), + ); + let inputs = [(); T].map(|_| { + iter::repeat_with(|| table[rng.next_u32() as usize % n]) + .take(n) + .collect() + }); + Self { + n, + inputs: Value::known(inputs), + table, + } + } + + pub fn instances(&self) -> Vec> { + Vec::new() + } +} + +impl Circuit + for Plookuper +{ + type Config = ( + [[Column; W]; T], + [Column; W], + PlookupConfig, + ); + type FloorPlanner = V1; + + fn without_witnesses(&self) -> Self { + Self { + n: self.n, + inputs: Value::unknown(), + table: self.table.clone(), + } + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let inputs = [(); T].map(|_| [(); W].map(|_| meta.advice_column())); + let table = [(); W].map(|_| meta.fixed_column()); + let plookup = PlookupConfig::configure( + meta, + |meta| { + inputs + .iter() + .map(|input| input.map(|column| meta.query_advice(column, Rotation::cur()))) + .collect() + }, + table.map(|fixed| fixed.into()), + None, + None, + None, + None, + ); + + (inputs, table, plookup) + } + + fn synthesize( + &self, + (inputs, table, plookup): Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_region( + || "", + |mut region| { + for (offset, value) in self.table.iter().enumerate() { + for (column, value) in table.iter().zip(value.iter()) { + region.assign_fixed(|| "", *column, offset, || Value::known(*value))?; + } + } + Ok(()) + }, + )?; + layouter.assign_region( + || "", + |mut region| { + for (idx, columns) in inputs.iter().enumerate() { + let values = self.inputs.as_ref().map(|inputs| inputs[idx].clone()); + for (offset, value) in values.transpose_vec(self.n).into_iter().enumerate() { + for (column, value) in columns.iter().zip(value.transpose_array()) { + region.assign_advice(|| "", *column, offset, || value)?; + } + } + } + Ok(()) + }, + )?; + plookup.assign( + layouter.namespace(|| "Plookup"), + self.inputs.as_ref().map(|input| input.to_vec()), + Value::known(self.table.clone()), + self.n, + )?; + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::{Plookuper, ShuffleConfig}; + use halo2_curves::{bn256::Fr, FieldExt}; + use halo2_proofs::{ + circuit::{floor_planner::V1, Layouter, Value}, + dev::{metadata::Constraint, FailureLocation, MockProver, VerifyFailure}, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error}, + poly::Rotation, + }; + use rand::{rngs::OsRng, RngCore}; + use std::{iter, mem}; + + fn shuffled( + mut values: [Vec; W], + mut rng: R, + ) -> [Vec; W] { + let n = values[0].len(); + let mut swap = |lhs: usize, rhs: usize| { + let tmp = mem::take(&mut values[lhs / n][lhs % n]); + values[lhs / n][lhs % n] = mem::replace(&mut values[rhs / n][rhs % n], tmp); + }; + + for row in (1..n * W).rev() { + let rand_row = (rng.next_u32() as usize) % row; + swap(row, rand_row); + } + + values + } + + #[derive(Clone)] + pub struct Shuffler { + n: usize, + lhs: Value<[Vec; T]>, + rhs: Value<[Vec; T]>, + } + + impl Shuffler { + pub fn rand(mut rng: R, n: usize) -> Self { + let lhs = [(); T].map(|_| { + let rng = &mut rng; + iter::repeat_with(|| F::random(&mut *rng)) + .take(n) + .collect::>() + }); + let rhs = shuffled(lhs.clone(), rng); + Self { + n, + lhs: Value::known(lhs), + rhs: Value::known(rhs), + } + } + } + + impl Circuit for Shuffler { + type Config = ([Column; T], [Column; T], ShuffleConfig); + type FloorPlanner = V1; + + fn without_witnesses(&self) -> Self { + Self { + n: self.n, + lhs: Value::unknown(), + rhs: Value::unknown(), + } + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let lhs = [(); T].map(|_| meta.advice_column()); + let rhs = [(); T].map(|_| meta.advice_column()); + let shuffle = ShuffleConfig::configure( + meta, + |meta| { + lhs.map(|column| meta.query_advice(column, Rotation::cur())) + .to_vec() + }, + |meta| { + rhs.map(|column| meta.query_advice(column, Rotation::cur())) + .to_vec() + }, + None, + ); + + (lhs, rhs, shuffle) + } + + fn synthesize( + &self, + (lhs, rhs, shuffle): Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_region( + || "", + |mut region| { + for (idx, column) in lhs.into_iter().enumerate() { + let values = self.lhs.as_ref().map(|lhs| lhs[idx].clone()); + for (offset, value) in + values.clone().transpose_vec(self.n).into_iter().enumerate() + { + region.assign_advice(|| "", column, offset, || value)?; + } + } + for (idx, column) in rhs.into_iter().enumerate() { + let values = self.rhs.as_ref().map(|rhs| rhs[idx].clone()); + for (offset, value) in + values.clone().transpose_vec(self.n).into_iter().enumerate() + { + region.assign_advice(|| "", column, offset, || value)?; + } + } + Ok(()) + }, + )?; + shuffle.assign( + layouter.namespace(|| "Shuffle"), + self.lhs.as_ref().map(|lhs| lhs.to_vec()), + self.rhs.as_ref().map(|rhs| rhs.to_vec()), + self.n, + )?; + + Ok(()) + } + } + + #[allow(dead_code)] + fn assert_constraint_not_satisfied( + result: Result<(), Vec>, + failures: Vec<(Constraint, FailureLocation)>, + ) { + match result { + Err(expected) => { + assert_eq!( + expected + .into_iter() + .map(|failure| match failure { + VerifyFailure::ConstraintNotSatisfied { + constraint, + location, + .. + } => (constraint, location), + _ => panic!("MockProver::verify has unexpected failure"), + }) + .collect::>(), + failures + ) + } + Ok(_) => { + panic!("MockProver::verify unexpectedly succeeds") + } + } + } + + #[test] + fn test_shuffle() { + const T: usize = 9; + const ZK: bool = false; + + let k = 9; + let n = 1 << k; + let circuit = Shuffler::::rand(OsRng, n); + + let mut cs = ConstraintSystem::default(); + Shuffler::::configure(&mut cs); + assert_eq!(cs.degree::(), 3); + + MockProver::run::<_, ZK>(k, &circuit, Vec::new()) + .unwrap() + .assert_satisfied(); + + #[cfg(not(feature = "sanity-check"))] + { + let mut circuit = circuit; + circuit.lhs = mem::take(&mut circuit.lhs).map(|mut value| { + value[0][0] += Fr::one(); + value + }); + assert_constraint_not_satisfied( + MockProver::run::<_, ZK>(k, &circuit, Vec::new()) + .unwrap() + .verify(), + vec![( + ((2, "Shuffle").into(), T.div_ceil(cs.degree::() - 1), "").into(), + FailureLocation::InRegion { + region: (0, "").into(), + offset: n - 1, + }, + )], + ); + } + } + + #[test] + fn test_plookup() { + const W: usize = 2; + const T: usize = 5; + const ZK: bool = false; + + let k = 9; + let n = 1 << k; + let circuit = Plookuper::::rand(OsRng, n); + + let mut cs = ConstraintSystem::default(); + Plookuper::::configure(&mut cs); + assert_eq!(cs.degree::(), 3); + + MockProver::run::<_, ZK>(k, &circuit, Vec::new()) + .unwrap() + .assert_satisfied(); + + #[cfg(not(feature = "sanity-check"))] + { + let mut circuit = circuit; + circuit.inputs = mem::take(&mut circuit.inputs).map(|mut inputs| { + inputs[0][0][0] += Fr::one(); + inputs + }); + assert_constraint_not_satisfied( + MockProver::run::<_, ZK>(k, &circuit, Vec::new()) + .unwrap() + .verify(), + vec![( + ( + (2, "Shuffle").into(), + (T + 1).div_ceil(cs.degree::() - 1), + "", + ) + .into(), + FailureLocation::InRegion { + region: (0, "").into(), + offset: n - 1, + }, + )], + ); + } + } +} diff --git a/src/protocol/halo2/test/circuit/standard.rs b/src/protocol/halo2/test/circuit/standard.rs new file mode 100644 index 00000000..0773f360 --- /dev/null +++ b/src/protocol/halo2/test/circuit/standard.rs @@ -0,0 +1,132 @@ +use halo2_proofs::{ + arithmetic::FieldExt, + circuit::{floor_planner::V1, Layouter, Value}, + plonk::{Advice, Any, Circuit, Column, ConstraintSystem, Error, Fixed, Instance}, + poly::Rotation, +}; +use rand::RngCore; + +#[allow(dead_code)] +#[derive(Clone)] +pub struct StandardPlonkConfig { + a: Column, + b: Column, + c: Column, + q_a: Column, + q_b: Column, + q_c: Column, + q_ab: Column, + constant: Column, + instance: Column, +} + +impl StandardPlonkConfig { + pub fn configure(meta: &mut ConstraintSystem) -> Self { + let a = meta.advice_column(); + let b = meta.advice_column(); + let c = meta.advice_column(); + + let q_a = meta.fixed_column(); + let q_b = meta.fixed_column(); + let q_c = meta.fixed_column(); + + let q_ab = meta.fixed_column(); + + let constant = meta.fixed_column(); + let instance = meta.instance_column(); + + meta.enable_equality(a); + meta.enable_equality(b); + meta.enable_equality(c); + + meta.create_gate("", |meta| { + let [a, b, c, q_a, q_b, q_c, q_ab, constant, instance] = [ + a.into(), + b.into(), + c.into(), + q_a.into(), + q_b.into(), + q_c.into(), + q_ab.into(), + constant.into(), + instance.into(), + ] + .map(|column: Column| meta.query_any(column, Rotation::cur())); + + vec![q_a * a.clone() + q_b * b.clone() + q_c * c + q_ab * a * b + constant + instance] + }); + + StandardPlonkConfig { + a, + b, + c, + q_a, + q_b, + q_c, + q_ab, + constant, + instance, + } + } +} + +#[derive(Clone, Default)] +pub struct StandardPlonk(F); + +impl StandardPlonk { + pub fn rand(mut rng: R) -> Self { + Self(F::from(rng.next_u32() as u64)) + } + + pub fn instances(&self) -> Vec> { + vec![vec![self.0]] + } +} + +impl Circuit for StandardPlonk { + type Config = StandardPlonkConfig; + type FloorPlanner = V1; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + meta.set_minimum_degree(4); + StandardPlonkConfig::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_region( + || "", + |mut region| { + region.assign_advice(|| "", config.a, 0, || Value::known(self.0))?; + region.assign_fixed(|| "", config.q_a, 0, || Value::known(-F::one()))?; + + region.assign_advice(|| "", config.a, 1, || Value::known(-F::from(5)))?; + for (column, idx) in [ + config.q_a, + config.q_b, + config.q_c, + config.q_ab, + config.constant, + ] + .iter() + .zip(1..) + { + region.assign_fixed(|| "", *column, 1, || Value::known(F::from(idx)))?; + } + + let a = region.assign_advice(|| "", config.a, 2, || Value::known(F::one()))?; + a.copy_advice(|| "", &mut region, config.b, 3)?; + a.copy_advice(|| "", &mut region, config.c, 4)?; + + Ok(()) + }, + ) + } +} diff --git a/src/protocol/halo2/test/kzg.rs b/src/protocol/halo2/test/kzg.rs index 0d499e81..ca6bd62d 100644 --- a/src/protocol/halo2/test/kzg.rs +++ b/src/protocol/halo2/test/kzg.rs @@ -128,7 +128,7 @@ macro_rules! halo2_kzg_create_snark { use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; use $crate::{ collect_slice, - protocol::halo2::test::{create_proof_checked, Snark}, + protocol::{halo2::test::create_proof_checked, Snark}, }; let instances = $circuits diff --git a/src/protocol/halo2/test/kzg/halo2.rs b/src/protocol/halo2/test/kzg/halo2.rs index d9067cf0..96fdc6b7 100644 --- a/src/protocol/halo2/test/kzg/halo2.rs +++ b/src/protocol/halo2/test/kzg/halo2.rs @@ -6,11 +6,11 @@ use crate::{ halo2::{ test::{ kzg::{BITS, LIMBS}, - MainGateWithRange, MainGateWithRangeConfig, Snark, StandardPlonk, + MainGateWithRange, MainGateWithRangeConfig, StandardPlonk, }, util::halo2::ChallengeScalar, }, - Protocol, + Protocol, Snark, }, scheme::kzg::{self, AccumulationScheme, ShplonkAccumulationScheme}, util::{fe_to_limbs, Curve, Group, PrimeCurveAffine}, diff --git a/src/protocol/halo2/test/kzg/native.rs b/src/protocol/halo2/test/kzg/native.rs index 82bf6237..f9520bb3 100644 --- a/src/protocol/halo2/test/kzg/native.rs +++ b/src/protocol/halo2/test/kzg/native.rs @@ -3,7 +3,7 @@ use crate::{ halo2_kzg_prepare, protocol::halo2::test::{ kzg::{main_gate_with_range_with_mock_kzg_accumulator, LIMBS}, - MainGateWithRange, + Plookuper, StandardPlonk, }, scheme::kzg::{PlonkAccumulationScheme, ShplonkAccumulationScheme}, }; @@ -57,10 +57,10 @@ macro_rules! test { } test!( - zk_main_gate_with_range_rand, + zk_standard_plonk_rand, 9, halo2_kzg_config!(true, 2), - MainGateWithRange::<_>::rand(ChaCha20Rng::from_seed(Default::default())) + StandardPlonk::<_>::rand(ChaCha20Rng::from_seed(Default::default())) ); test!( zk_main_gate_with_range_with_mock_kzg_accumulator, @@ -69,10 +69,10 @@ test!( main_gate_with_range_with_mock_kzg_accumulator::() ); test!( - main_gate_with_range_rand, + standard_plonk_rand, 9, halo2_kzg_config!(false, 2), - MainGateWithRange::<_>::rand(ChaCha20Rng::from_seed(Default::default())) + StandardPlonk::<_>::rand(ChaCha20Rng::from_seed(Default::default())) ); test!( main_gate_with_range_with_mock_kzg_accumulator, @@ -80,3 +80,9 @@ test!( halo2_kzg_config!(false, 2, (0..4 * LIMBS).map(|idx| (0, idx + 1)).collect()), main_gate_with_range_with_mock_kzg_accumulator::() ); +test!( + plookup_rand, + 9, + halo2_kzg_config!(false, 2), + Plookuper::<_, 2, 5, false>::rand(ChaCha20Rng::from_seed(Default::default()), 1 << 9) +); diff --git a/src/util.rs b/src/util.rs index 1b1d9390..34021b47 100644 --- a/src/util.rs +++ b/src/util.rs @@ -3,9 +3,9 @@ mod expression; mod transcript; pub use arithmetic::{ - batch_invert, batch_invert_and_mul, fe_from_limbs, fe_to_limbs, Curve, Domain, Field, FieldOps, - Fraction, Group, GroupEncoding, GroupOps, PrimeCurveAffine, PrimeField, Rotation, - UncompressedEncoding, + batch_invert, batch_invert_and_mul, fe_from_limbs, fe_to_limbs, BatchInvert, Curve, Domain, + Field, FieldOps, Fraction, Group, GroupEncoding, GroupOps, PrimeCurveAffine, PrimeField, + Rotation, UncompressedEncoding, }; pub use expression::{CommonPolynomial, CommonPolynomialEvaluation, Expression, Query}; pub use transcript::{Transcript, TranscriptRead}; diff --git a/src/util/arithmetic.rs b/src/util/arithmetic.rs index f9dff8a4..6bfc5bc0 100644 --- a/src/util/arithmetic.rs +++ b/src/util/arithmetic.rs @@ -7,7 +7,7 @@ use std::{ ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}, }; -pub use ff::{Field, PrimeField}; +pub use ff::{BatchInvert, Field, PrimeField}; pub use group::{prime::PrimeCurveAffine, Curve, Group, GroupEncoding}; pub trait GroupOps: