From 84003a202d6b9185c952ec91ea793dbbd6c5b200 Mon Sep 17 00:00:00 2001 From: xkx Date: Fri, 8 Dec 2023 17:22:49 +0800 Subject: [PATCH] Feat: switch to logup scheme for lookup argument (#71) * Multi-input mv-lookup. (#49) * Add mv_lookup.rs * mv_lookup::prover, mv_lookup::verifier * Replace lookup with mv_lookup * replace halo2 with mv lookup Co-authored-by: ying tong * cleanups Co-authored-by: ying tong * ConstraintSystem: setup lookup_tracker Co-authored-by: Andrija * mv_lookup::hybrid_prover Co-authored-by: Andrija * WIP * mv_multi_lookup: enable lookup caching Co-authored-by: therealyingtong * Rename hybrid_lookup -> lookup * Chunk lookups using user-provided minimum degree Co-authored-by: Andrija * mv_lookup bench Co-authored-by: Andrija * Introduce counter feature for FFTs and MSMs Co-authored-by: Andrija * Fix off-by-one errors in chunk_lookup Co-authored-by: Andrija * bench wip * time evaluate_h * KZG * more efficient batch inversion * extended lookup example * Finalize mv lookup Author: therealyingtong * Remove main/ * Fix according to the comments * replace scan with parallel grand sum computation * Revert Cargo.lock * mv lookup Argument name * parallel batch invert --------- Co-authored-by: Andrija Co-authored-by: ying tong Co-authored-by: therealyingtong * fmt * fix unit test * fix clippy errors * add todo in mv_lookup's prover * fmt and clippy * fix clippy * add detailed running time of steps in logup's prover * fmt * add more log hooks * more running time logs * use par invert * use sorted-vector to store how many times a table element occurs in input * par the process to get inputs_inv_sum * use par * fix par * add feature to skip inv sums * add new feature flag * fix clippy error --------- Co-authored-by: Sphere L Co-authored-by: Andrija Co-authored-by: ying tong Co-authored-by: therealyingtong --- Cargo.lock | 1 + halo2_proofs/Cargo.toml | 10 +- halo2_proofs/benches/lookups.rs | 239 +++++ halo2_proofs/src/arithmetic.rs | 22 +- halo2_proofs/src/dev.rs | 172 ++-- halo2_proofs/src/dev/failure.rs | 151 +-- halo2_proofs/src/lib.rs | 19 + halo2_proofs/src/plonk.rs | 2 + halo2_proofs/src/plonk/circuit.rs | 176 +++- .../src/plonk/circuit/compress_selectors.rs | 2 +- halo2_proofs/src/plonk/evaluation.rs | 266 ++++-- halo2_proofs/src/plonk/keygen.rs | 2 + halo2_proofs/src/plonk/mv_lookup.rs | 96 ++ .../src/plonk/mv_lookup/exec_info.json | 46 + halo2_proofs/src/plonk/mv_lookup/prover.rs | 529 +++++++++++ halo2_proofs/src/plonk/mv_lookup/verifier.rs | 191 ++++ halo2_proofs/src/plonk/prover.rs | 28 +- halo2_proofs/src/plonk/verifier.rs | 50 +- halo2_proofs/src/poly.rs | 2 +- halo2_proofs/tests/plonk_api.rs | 857 ++++++++++++------ 20 files changed, 2293 insertions(+), 568 deletions(-) create mode 100644 halo2_proofs/benches/lookups.rs create mode 100644 halo2_proofs/src/plonk/mv_lookup.rs create mode 100644 halo2_proofs/src/plonk/mv_lookup/exec_info.json create mode 100644 halo2_proofs/src/plonk/mv_lookup/prover.rs create mode 100644 halo2_proofs/src/plonk/mv_lookup/verifier.rs diff --git a/Cargo.lock b/Cargo.lock index bac52822ae..3a36d84c12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -886,6 +886,7 @@ dependencies = [ "group", "gumdrop", "halo2curves 0.1.0", + "lazy_static", "log", "num-bigint", "num-integer", diff --git a/halo2_proofs/Cargo.toml b/halo2_proofs/Cargo.toml index b7eb49477d..94d7df91ab 100644 --- a/halo2_proofs/Cargo.toml +++ b/halo2_proofs/Cargo.toml @@ -39,6 +39,10 @@ harness = false name = "dev_lookup" harness = false +[[bench]] +name = "lookups" +harness = false + [[bench]] name = "fft" harness = false @@ -63,11 +67,13 @@ crossbeam = "0.8.0" # Developer tooling dependencies plotters = { version = "0.3.0", optional = true } tabbycat = { version = "0.1", features = ["attributes"], optional = true } +lazy_static = { version = "1", optional = true } log = "0.4.17" # timer ark-std = { version = "0.3.0" } + [dev-dependencies] assert_matches = "1.5" criterion = "0.3" @@ -80,7 +86,7 @@ rand_core = { version = "0.6", default-features = false, features = ["getrandom" getrandom = { version = "0.2", features = ["js"] } [features] -default = ["batch", "gwc"] +default = ["batch", "gwc", "logup_skip_inv"] dev-graph = ["plotters", "tabbycat"] gadget-traces = ["backtrace"] sanity-checks = [] @@ -90,7 +96,9 @@ gwc = [] parallel_syn = [] phase-check = [] profile = ["ark-std/print-trace"] +counter = ["lazy_static"] mock-batch-inv = [] +logup_skip_inv = [] [lib] bench = false diff --git a/halo2_proofs/benches/lookups.rs b/halo2_proofs/benches/lookups.rs new file mode 100644 index 0000000000..601b1a4285 --- /dev/null +++ b/halo2_proofs/benches/lookups.rs @@ -0,0 +1,239 @@ +#[macro_use] +extern crate criterion; + +use halo2_proofs::circuit::{Layouter, SimpleFloorPlanner, Value}; +use halo2_proofs::plonk::*; +use halo2_proofs::poly::kzg::multiopen::VerifierGWC; +use halo2_proofs::poly::{commitment::ParamsProver, Rotation}; +use halo2_proofs::transcript::{Blake2bRead, Blake2bWrite, Challenge255}; +use halo2curves::bn256::{Bn256, G1Affine}; +use halo2curves::pairing::Engine; +use rand_core::OsRng; + +use halo2_proofs::{ + poly::kzg::{ + commitment::{KZGCommitmentScheme, ParamsKZG}, + multiopen::ProverGWC, + strategy::SingleStrategy, + }, + transcript::{TranscriptReadBuffer, TranscriptWriterBuffer}, +}; + +use std::marker::PhantomData; + +use criterion::{BenchmarkId, Criterion}; +use ff::PrimeField as Field; + +fn criterion_benchmark(c: &mut Criterion) { + #[derive(Clone, Default)] + struct MyCircuit { + _marker: PhantomData, + } + + #[derive(Clone)] + struct MyConfig { + selector: Selector, + table: TableColumn, + advice: Column, + other_advice: Column, + } + + impl Circuit for MyCircuit { + type Config = MyConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> MyConfig { + let config = MyConfig { + selector: meta.complex_selector(), + table: meta.lookup_table_column(), + advice: meta.advice_column(), + other_advice: meta.advice_column(), + }; + + let dummy_selector = meta.complex_selector(); + + meta.create_gate("degree 6 gate", |meta| { + let dummy_selector = meta.query_selector(dummy_selector); + let constraints = vec![dummy_selector.clone(); 4] + .iter() + .fold(dummy_selector.clone(), |acc, val| acc * val.clone()); + Constraints::with_selector(dummy_selector, Some(constraints)) + }); + + meta.lookup("lookup", |meta| { + let advice = meta.query_advice(config.advice, Rotation::cur()); + vec![(advice, config.table)] + }); + + meta.lookup("lookup", |meta| { + let advice = meta.query_advice(config.advice, Rotation::cur()); + vec![(advice, config.table)] + }); + + meta.lookup("lookup", |meta| { + let advice = meta.query_advice(config.advice, Rotation::cur()); + vec![(advice, config.table)] + }); + + meta.lookup("lookup", |meta| { + let advice = meta.query_advice(config.advice, Rotation::cur()); + vec![(advice, config.table)] + }); + + meta.lookup("lookup", |meta| { + let advice = meta.query_advice(config.advice, Rotation::cur()); + vec![(advice, config.table)] + }); + + /* + - We need degree at least 6 because 6 - 1 = 5 and we need to go to extended domain of 8n + - Our goal is to get to max degree of 9 because now 9 - 1 = 8 and that will fit into domain + + - base degree = table_deg + 2 + - if we put input_expression_degree = 1 + => degree = base + 1 = 3 + 1 = 4 + - we can batch one more with 5 more lookups + */ + + config + } + + fn synthesize( + &self, + config: MyConfig, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_table( + || "8-bit table", + |mut table| { + for row in 0u64..(1 << 8) { + table.assign_cell( + || format!("row {}", row), + config.table, + row as usize, + || Value::known(F::from(row)), + )?; + } + + Ok(()) + }, + )?; + + layouter.assign_region( + || "assign values", + |mut region| { + for offset in 0u64..(1 << 10) { + config.selector.enable(&mut region, offset as usize)?; + region.assign_advice( + || format!("offset {}", offset), + config.advice, + offset as usize, + || Value::known(F::from(offset % 256)), + )?; + } + for offset in 1u64..(1 << 10) { + config.selector.enable(&mut region, offset as usize)?; + region.assign_advice( + || format!("offset {}", offset), + config.other_advice, + offset as usize - 1, + || Value::known(F::from(offset % 256)), + )?; + } + Ok(()) + }, + ) + } + } + + fn keygen(k: u32) -> (ParamsKZG, ProvingKey) { + let params: ParamsKZG = ParamsKZG::new(k); + let empty_circuit: MyCircuit<::Scalar> = MyCircuit { + _marker: PhantomData, + }; + let vk = keygen_vk(¶ms, &empty_circuit).expect("keygen_vk should not fail"); + let pk = keygen_pk(¶ms, vk, &empty_circuit).expect("keygen_pk should not fail"); + (params, pk) + } + + fn prover(_k: u32, params: &ParamsKZG, pk: &ProvingKey) -> Vec { + let rng = OsRng; + + let circuit: MyCircuit<::Scalar> = MyCircuit { + _marker: PhantomData, + }; + + let mut transcript = Blake2bWrite::<_, _, Challenge255>::init(vec![]); + create_proof::, ProverGWC<'_, Bn256>, _, _, _, _>( + params, + pk, + &[circuit], + &[&[]], + rng, + &mut transcript, + ) + .expect("proof generation should not fail"); + transcript.finalize() + } + + fn verifier(params: &ParamsKZG, vk: &VerifyingKey, proof: &[u8]) { + let strategy = SingleStrategy::new(params); + let mut transcript = Blake2bRead::<_, _, Challenge255>::init(proof); + assert!(verify_proof::< + KZGCommitmentScheme, + VerifierGWC<'_, Bn256>, + Challenge255, + Blake2bRead<&[u8], G1Affine, Challenge255>, + SingleStrategy<'_, Bn256>, + >(params, vk, strategy, &[&[]], &mut transcript) + .is_ok()); + } + + let k_range = 16..=16; + + let mut keygen_group = c.benchmark_group("plonk-keygen"); + keygen_group.sample_size(10); + for k in k_range.clone() { + keygen_group.bench_with_input(BenchmarkId::from_parameter(k), &k, |b, &k| { + b.iter(|| keygen(k)); + }); + } + keygen_group.finish(); + + let mut prover_group = c.benchmark_group("plonk-prover"); + prover_group.sample_size(10); + for k in k_range.clone() { + let (params, pk) = keygen(k); + + prover_group.bench_with_input( + BenchmarkId::from_parameter(k), + &(k, ¶ms, &pk), + |b, &(k, params, pk)| { + b.iter(|| prover(k, params, pk)); + }, + ); + } + prover_group.finish(); + + let mut verifier_group = c.benchmark_group("plonk-verifier"); + for k in k_range { + let (params, pk) = keygen(k); + let proof = prover(k, ¶ms, &pk); + + verifier_group.bench_with_input( + BenchmarkId::from_parameter(k), + &(¶ms, pk.get_vk(), &proof[..]), + |b, &(params, vk, proof)| { + b.iter(|| verifier(params, vk, proof)); + }, + ); + } + verifier_group.finish(); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/halo2_proofs/src/arithmetic.rs b/halo2_proofs/src/arithmetic.rs index 56ca5fd90c..849a7022cc 100644 --- a/halo2_proofs/src/arithmetic.rs +++ b/halo2_proofs/src/arithmetic.rs @@ -535,9 +535,18 @@ where q } +pub fn par_invert(values: &mut [F]) { + parallelize(values, |values, _start| { + values.batch_invert(); + }); +} + /// This simple utility function will parallelize an operation that is to be /// performed over a mutable slice. -pub fn parallelize(v: &mut [T], f: F) { +pub(crate) fn parallelize_internal( + v: &mut [T], + f: F, +) -> Vec { let n = v.len(); let num_threads = multicore::current_num_threads(); let mut chunk = (n as usize) / num_threads; @@ -546,14 +555,23 @@ pub fn parallelize(v: &mu } multicore::scope(|scope| { + let mut chunk_starts = vec![]; for (chunk_num, v) in v.chunks_mut(chunk).enumerate() { let f = f.clone(); scope.spawn(move |_| { let start = chunk_num * chunk; f(v, start); }); + let start = chunk_num * chunk; + chunk_starts.push(start); } - }); + + chunk_starts + }) +} + +pub fn parallelize(v: &mut [T], f: F) { + parallelize_internal(v, f); } fn log2_floor(num: usize) -> u32 { diff --git a/halo2_proofs/src/dev.rs b/halo2_proofs/src/dev.rs index 585987cc9d..a83478366c 100644 --- a/halo2_proofs/src/dev.rs +++ b/halo2_proofs/src/dev.rs @@ -940,6 +940,7 @@ impl<'a, F: FromUniformBytes<64> + Ord> MockProver<'a, F> { let mut cs = ConstraintSystem::default(); let config = ConcreteCircuit::configure(&mut cs); + let cs = cs.chunk_lookups(); let cs = cs; if n < cs.minimum_rows() { @@ -1340,7 +1341,9 @@ impl<'a, F: FromUniformBytes<64> + Ord> MockProver<'a, F> { ) }; - assert!(lookup.table_expressions.len() == lookup.input_expressions.len()); + for input_expressions in lookup.inputs_expressions.iter() { + assert!(lookup.table_expressions.len() == input_expressions.len()); + } assert!(self.usable_rows.end > 0); // We optimize on the basis that the table might have been filled so that the last @@ -1386,49 +1389,53 @@ impl<'a, F: FromUniformBytes<64> + Ord> MockProver<'a, F> { } let table = &cached_table; - let mut inputs: Vec<(Vec<_>, usize)> = lookup_input_row_ids - .clone() - .into_iter() - .filter_map(|input_row| { - let t = lookup - .input_expressions - .iter() - .map(move |c| load(c, input_row)) - .collect(); - - if t != fill_row { - // Also keep track of the original input row, since we're going to sort. - Some((t, input_row)) - } else { - None - } - }) - .collect(); - inputs.sort_unstable(); - - let mut i = 0; - inputs + lookup + .inputs_expressions .iter() - .filter_map(move |(input, input_row)| { - while i < table.len() && &table[i] < input { - i += 1; - } - if i == table.len() || &table[i] > input { - assert!(table.binary_search(input).is_err()); + .map(|input_expressions| { + let mut inputs: Vec<(Vec<_>, usize)> = lookup_input_row_ids + .clone() + .filter_map(|input_row| { + let t = input_expressions + .iter() + .map(move |c| load(c, input_row)) + .collect(); + + if t != fill_row { + // Also keep track of the original input row, since we're going to sort. + Some((t, input_row)) + } else { + None + } + }) + .collect(); + inputs.sort_unstable(); - Some(VerifyFailure::Lookup { - name: lookup.name, - lookup_index, - location: FailureLocation::find_expressions( - &self.cs, - &self.regions, - *input_row, - lookup.input_expressions.iter(), - ), + let mut i = 0; + inputs + .iter() + .filter_map(move |(input, input_row)| { + while i < table.len() && &table[i] < input { + i += 1; + } + if i == table.len() || &table[i] > input { + assert!(table.binary_search(input).is_err()); + + Some(VerifyFailure::Lookup { + name: lookup.name, + lookup_index, + location: FailureLocation::find_expressions( + &self.cs, + &self.regions, + *input_row, + input_expressions.iter(), + ), + }) + } else { + None + } }) - } else { - None - } + .collect::>() }) .collect::>() }); @@ -1483,7 +1490,7 @@ impl<'a, F: FromUniformBytes<64> + Ord> MockProver<'a, F> { let mut errors: Vec<_> = iter::empty() .chain(selector_errors) .chain(gate_errors) - .chain(lookup_errors) + .chain(lookup_errors.flatten()) .chain(perm_errors) .collect(); if errors.is_empty() { @@ -1719,7 +1726,9 @@ impl<'a, F: FromUniformBytes<64> + Ord> MockProver<'a, F> { ) }; - assert!(lookup.table_expressions.len() == lookup.input_expressions.len()); + for input_expressions in lookup.inputs_expressions.iter() { + assert!(lookup.table_expressions.len() == input_expressions.len()); + } assert!(self.usable_rows.end > 0); // We optimize on the basis that the table might have been filled so that the last @@ -1766,43 +1775,48 @@ impl<'a, F: FromUniformBytes<64> + Ord> MockProver<'a, F> { } let table = &cached_table; - let mut inputs: Vec<(Vec<_>, usize)> = lookup_input_row_ids - .clone() - .into_par_iter() - .filter_map(|input_row| { - let t = lookup - .input_expressions - .iter() - .map(move |c| load(c, input_row)) + lookup + .inputs_expressions + .iter() + .map(|input_expressions| { + let mut inputs: Vec<(Vec<_>, usize)> = lookup_input_row_ids + .clone() + .into_par_iter() + .filter_map(|input_row| { + let t = input_expressions + .iter() + .map(move |c| load(c, input_row)) + .collect(); + + if t != fill_row { + // Also keep track of the original input row, since we're going to sort. + Some((t, input_row)) + } else { + None + } + }) .collect(); - - if t != fill_row { - // Also keep track of the original input row, since we're going to sort. - Some((t, input_row)) - } else { - None - } - }) - .collect(); - inputs.par_sort_unstable(); - - inputs - .par_iter() - .filter_map(move |(input, input_row)| { - if table.binary_search(input).is_err() { - Some(VerifyFailure::Lookup { - name: lookup.name, - lookup_index, - location: FailureLocation::find_expressions( - &self.cs, - &self.regions, - *input_row, - lookup.input_expressions.iter(), - ), + inputs.par_sort_unstable(); + + inputs + .par_iter() + .filter_map(move |(input, input_row)| { + if table.binary_search(input).is_err() { + Some(VerifyFailure::Lookup { + name: lookup.name, + lookup_index, + location: FailureLocation::find_expressions( + &self.cs, + &self.regions, + *input_row, + input_expressions.iter(), + ), + }) + } else { + None + } }) - } else { - None - } + .collect::>() }) .collect::>() }); @@ -1861,7 +1875,7 @@ impl<'a, F: FromUniformBytes<64> + Ord> MockProver<'a, F> { let mut errors: Vec<_> = iter::empty() .chain(selector_errors) .chain(gate_errors) - .chain(lookup_errors) + .chain(lookup_errors.flatten()) .chain(perm_errors) .collect(); if errors.is_empty() { diff --git a/halo2_proofs/src/dev/failure.rs b/halo2_proofs/src/dev/failure.rs index 54b7a473db..06aaafe201 100644 --- a/halo2_proofs/src/dev/failure.rs +++ b/halo2_proofs/src/dev/failure.rs @@ -158,7 +158,6 @@ pub enum VerifyFailure { }, /// A lookup input did not exist in its corresponding table. Lookup { - /// The name of the lookup that is not satisfied. name: &'static str, /// The index of the lookup that is not satisfied. These indices are assigned in /// the order in which `ConstraintSystem::lookup` is called during @@ -445,7 +444,7 @@ fn render_constraint_not_satisfied( /// ``` fn render_lookup( prover: &MockProver, - name: &str, + _name: &str, lookup_index: usize, location: &FailureLocation, ) { @@ -533,8 +532,10 @@ fn render_lookup( eprintln!("error: lookup input does not exist in table"); eprint!(" ("); - for i in 0..lookup.input_expressions.len() { - eprint!("{}L{}", if i == 0 { "" } else { ", " }, i); + for input_expressions in lookup.inputs_expressions.iter() { + for i in 0..input_expressions.len() { + eprint!("{}L{}", if i == 0 { "" } else { ", " }, i); + } } eprint!(") ∉ ("); @@ -544,79 +545,81 @@ fn render_lookup( eprintln!(")"); eprintln!(); - eprintln!(" Lookup '{}' inputs:", name); - for (i, input) in lookup.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_slice( - n, - row, - &cs.fixed_queries, - prover.fixed.as_slice(), - )), - &cell_value(&util::load_slice( - 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)); - } + eprintln!(" Lookup inputs:"); + for input_expressions in lookup.inputs_expressions.iter() { + for (i, input) in 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_slice( + n, + row, + &cs.fixed_queries, + prover.fixed.as_slice(), + )), + &cell_value(&util::load_slice( + 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!( - " L{} = {}", - i, - emitter::expression_to_string(input, &layout) - ); - eprintln!(" ^"); - - emitter::render_cell_layout(" | ", location, &columns, &layout, |_, rotation| { - if rotation == 0 { - eprint!(" <--{{ Lookup '{}' inputs queried here", name); + if i != 0 { + eprintln!(); } - }); + eprintln!( + " L{} = {}", + i, + emitter::expression_to_string(input, &layout) + ); + eprintln!(" ^"); + + emitter::render_cell_layout(" | ", location, &columns, &layout, |_, rotation| { + if rotation == 0 { + eprint!(" <--{{ Lookup inputs queried here"); + } + }); - // 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); + // 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); + } } } } diff --git a/halo2_proofs/src/lib.rs b/halo2_proofs/src/lib.rs index 6192272fc2..39fd43d4e5 100644 --- a/halo2_proofs/src/lib.rs +++ b/halo2_proofs/src/lib.rs @@ -25,6 +25,25 @@ #![allow(unused_imports)] #![allow(clippy::derive_partial_eq_without_eq)] +#[cfg(feature = "counter")] +#[macro_use] +extern crate lazy_static; + +#[cfg(feature = "counter")] +use lazy_static::lazy_static; + +#[cfg(feature = "counter")] +use std::sync::Mutex; + +#[cfg(feature = "counter")] +use std::collections::BTreeMap; + +#[cfg(feature = "counter")] +lazy_static! { + static ref FFT_COUNTER: Mutex> = Mutex::new(BTreeMap::new()); + static ref MSM_COUNTER: Mutex> = Mutex::new(BTreeMap::new()); +} + pub mod arithmetic; pub mod circuit; pub use halo2curves; diff --git a/halo2_proofs/src/plonk.rs b/halo2_proofs/src/plonk.rs index dc80092f7b..96d34992e1 100644 --- a/halo2_proofs/src/plonk.rs +++ b/halo2_proofs/src/plonk.rs @@ -26,7 +26,9 @@ mod circuit; mod error; mod evaluation; mod keygen; +#[allow(dead_code)] mod lookup; +mod mv_lookup; pub(crate) mod permutation; mod vanishing; diff --git a/halo2_proofs/src/plonk/circuit.rs b/halo2_proofs/src/plonk/circuit.rs index 85e48022f8..251c146aeb 100644 --- a/halo2_proofs/src/plonk/circuit.rs +++ b/halo2_proofs/src/plonk/circuit.rs @@ -1,14 +1,17 @@ use core::cmp::max; use core::ops::{Add, Mul}; use ff::Field; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashMap}; +use std::fmt::Debug; +use std::hash::Hasher; +use std::marker::PhantomData; use std::ops::Range; use std::{ convert::TryFrom, ops::{Neg, Sub}, }; -use super::{lookup, permutation, Assigned, Error}; +use super::{mv_lookup, permutation, Assigned, Error}; use crate::dev::metadata; use crate::{ circuit::{Layouter, Region, Value}, @@ -388,7 +391,7 @@ impl Selector { } /// Query of fixed column at a certain relative location -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct FixedQuery { /// Query index pub(crate) index: usize, @@ -419,7 +422,7 @@ impl FixedQuery { } /// Query of advice column at a certain relative location -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct AdviceQuery { /// Query index pub(crate) index: usize, @@ -457,7 +460,7 @@ impl AdviceQuery { } /// Query of instance column at a certain relative location -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct InstanceQuery { /// Query index pub(crate) index: usize, @@ -1382,9 +1385,16 @@ impl Gate { } } +/// TODO doc +#[derive(Debug, Clone)] +pub struct LookupTracker { + pub(crate) table: Vec>, + pub(crate) inputs: Vec>>, +} + /// This is a description of the circuit environment, such as the gate, column and /// permutation arrangements. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub struct ConstraintSystem { pub num_fixed_columns: usize, pub num_advice_columns: usize, @@ -1414,9 +1424,12 @@ pub struct ConstraintSystem { // Permutation argument for performing equality constraints pub permutation: permutation::Argument, + /// Map from table expression to vec of vec of input expressions + pub lookups_map: BTreeMap>, + // Vector of lookup arguments, where each corresponds to a sequence of // input expressions and a sequence of table expressions involved in the lookup. - pub lookups: Vec>, + pub lookups: Vec>, // List of indexes of Fixed columns which are associated to a circuit-general Column tied to their annotation. pub(crate) general_column_annotations: BTreeMap, @@ -1443,7 +1456,7 @@ pub struct PinnedConstraintSystem<'a, F: Field> { instance_queries: &'a Vec<(Column, Rotation)>, fixed_queries: &'a Vec<(Column, Rotation)>, permutation: &'a permutation::Argument, - lookups: &'a Vec>, + lookups_map: &'a BTreeMap>, constants: &'a Vec>, minimum_degree: &'a Option, } @@ -1469,7 +1482,7 @@ impl<'a, F: Field> std::fmt::Debug for PinnedConstraintSystem<'a, F> { .field("instance_queries", self.instance_queries) .field("fixed_queries", self.fixed_queries) .field("permutation", self.permutation) - .field("lookups", self.lookups) + .field("lookups_map", self.lookups_map) .field("constants", self.constants) .field("minimum_degree", self.minimum_degree); debug_struct.finish() @@ -1504,6 +1517,7 @@ impl Default for ConstraintSystem { num_advice_queries: Vec::new(), instance_queries: Vec::new(), permutation: permutation::Argument::new(), + lookups_map: BTreeMap::default(), lookups: Vec::new(), general_column_annotations: BTreeMap::new(), constants: vec![], @@ -1530,7 +1544,7 @@ impl ConstraintSystem { advice_queries: &self.advice_queries, instance_queries: &self.instance_queries, permutation: &self.permutation, - lookups: &self.lookups, + lookups_map: &self.lookups_map, constants: &self.constants, minimum_degree: &self.minimum_degree, } @@ -1561,11 +1575,12 @@ impl ConstraintSystem { /// they need to match. pub fn lookup( &mut self, - name: &'static str, + // FIXME use name in debug messages + _name: &'static str, table_map: impl FnOnce(&mut VirtualCells<'_, F>) -> Vec<(Expression, TableColumn)>, - ) -> usize { + ) { let mut cells = VirtualCells::new(self); - let table_map = table_map(&mut cells) + let (input_expressions, table_expressions): (Vec<_>, Vec<_>) = table_map(&mut cells) .into_iter() .map(|(input, table)| { if input.contains_simple_selector() { @@ -1576,13 +1591,90 @@ impl ConstraintSystem { (input, table) }) - .collect(); + .unzip(); - let index = self.lookups.len(); + let table_expressions_identifier = table_expressions + .iter() + .fold(String::new(), |string, expr| string + &expr.identifier()); + + self.lookups_map + .entry(table_expressions_identifier) + .and_modify(|table_tracker| table_tracker.inputs.push(input_expressions.clone())) + .or_insert(LookupTracker { + table: table_expressions, + inputs: vec![input_expressions], + }); + } - self.lookups.push(lookup::Argument::new(name, table_map)); + /// Chunk lookup arguments into pieces below a given degree bound + pub fn chunk_lookups(mut self) -> Self { + if self.lookups_map.is_empty() { + return self; + } - index + let max_gate_degree = self.max_gate_degree(); + let max_single_lookup_degree: usize = self + .lookups_map + .values() + .map(|v| { + let table_degree = v.table.iter().map(|expr| expr.degree()).max().unwrap(); + let base_lookup_degree = super::mv_lookup::base_degree(table_degree); + + let max_inputs_degree: usize = v + .inputs + .iter() + .map(|input| input.iter().map(|expr| expr.degree()).max().unwrap()) + .max() + .unwrap(); + + mv_lookup::degree_with_input(base_lookup_degree, max_inputs_degree) + }) + .max() + .unwrap(); + + let required_degree = std::cmp::max(max_gate_degree, max_single_lookup_degree); + let required_degree = (required_degree as u64 - 1).next_power_of_two() as usize; + + self.set_minimum_degree(required_degree + 1); + + // safe to unwrap here + let minimum_degree = self.minimum_degree.unwrap(); + + let mut lookups: Vec<_> = vec![]; + for v in self.lookups_map.values() { + let LookupTracker { table, inputs } = v; + let mut args = vec![super::mv_lookup::Argument::new( + "lookup", + table, + &[inputs[0].clone()], + )]; + + for input in inputs.iter().skip(1) { + let cur_input_degree = input.iter().map(|expr| expr.degree()).max().unwrap(); + let mut indicator = false; + for arg in args.iter_mut() { + // try to fit input in one of the args + let cur_argument_degree = arg.required_degree(); + let new_potential_degree = cur_argument_degree + cur_input_degree; + if new_potential_degree <= minimum_degree { + arg.inputs_expressions.push(input.clone()); + indicator = true; + break; + } + } + + if !indicator { + args.push(super::mv_lookup::Argument::new( + "dummy", + table, + &[input.clone()], + )) + } + } + lookups.append(&mut args); + } + self.lookups = lookups; + self } /// Add a lookup argument for some input expressions and table expressions. @@ -1591,17 +1683,26 @@ impl ConstraintSystem { /// they need to match. pub fn lookup_any( &mut self, - name: &'static str, + // FIXME use name in debug messages + _name: &'static str, table_map: impl FnOnce(&mut VirtualCells<'_, F>) -> Vec<(Expression, Expression)>, - ) -> usize { + ) { let mut cells = VirtualCells::new(self); let table_map = table_map(&mut cells); - let index = self.lookups.len(); - - self.lookups.push(lookup::Argument::new(name, table_map)); - - index + let (input_expressions, table_expressions): (Vec<_>, Vec<_>) = + table_map.into_iter().unzip(); + let table_expressions_identifier = table_expressions + .iter() + .fold(String::new(), |string, expr| string + &expr.identifier()); + + self.lookups_map + .entry(table_expressions_identifier) + .and_modify(|table_tracker| table_tracker.inputs.push(input_expressions.clone())) + .or_insert(LookupTracker { + table: table_expressions, + inputs: vec![input_expressions], + }); } fn query_fixed_index(&mut self, column: Column, at: Rotation) -> usize { @@ -1710,7 +1811,9 @@ impl ConstraintSystem { /// larger amount than actually needed. This can be used, for example, to /// force the permutation argument to involve more columns in the same set. pub fn set_minimum_degree(&mut self, degree: usize) { - self.minimum_degree = Some(degree); + self.minimum_degree = self + .minimum_degree + .map_or(Some(degree), |min_degree| Some(max(min_degree, degree))); } /// Creates a new gate. @@ -1855,8 +1958,9 @@ impl ConstraintSystem { // lookup expressions for expr in self.lookups.iter_mut().flat_map(|lookup| { lookup - .input_expressions + .inputs_expressions .iter_mut() + .flatten() .chain(lookup.table_expressions.iter_mut()) }) { replace_selectors(expr, &selector_replacements, true); @@ -2015,6 +2119,15 @@ impl ConstraintSystem { (0..=max_phase).map(sealed::Phase) } + /// Compute the maximum degree of gates in the constraint system + pub fn max_gate_degree(&self) -> usize { + self.gates + .iter() + .flat_map(|gate| gate.polynomials().iter().map(|poly| poly.degree())) + .max() + .unwrap_or(0) + } + /// Compute the degree of the constraint system (the maximum degree of all /// constraints). pub fn degree(&self) -> usize { @@ -2035,13 +2148,16 @@ impl ConstraintSystem { // Account for each gate to ensure our quotient polynomial is the // correct degree and that our extended domain is the right size. + degree = std::cmp::max(degree, self.max_gate_degree()); + + // Lookup degree degree = std::cmp::max( degree, - self.gates + self.lookups .iter() - .flat_map(|gate| gate.polynomials().iter().map(|poly| poly.degree())) + .map(|hl| hl.required_degree()) .max() - .unwrap_or(0), + .unwrap_or(1), ); std::cmp::max(degree, self.minimum_degree.unwrap_or(1)) @@ -2147,7 +2263,7 @@ impl ConstraintSystem { } /// Returns lookup arguments - pub fn lookups(&self) -> &Vec> { + pub fn lookups(&self) -> &Vec> { &self.lookups } diff --git a/halo2_proofs/src/plonk/circuit/compress_selectors.rs b/halo2_proofs/src/plonk/circuit/compress_selectors.rs index 60898d3950..f52a0e7c9a 100644 --- a/halo2_proofs/src/plonk/circuit/compress_selectors.rs +++ b/halo2_proofs/src/plonk/circuit/compress_selectors.rs @@ -20,7 +20,7 @@ pub struct SelectorDescription { /// This describes the assigned combination of a particular selector as well as /// the expression it should be substituted with. #[derive(Debug, Clone)] -pub struct SelectorAssignment { +pub struct SelectorAssignment { /// The selector that this structure references, by index. pub selector: usize, diff --git a/halo2_proofs/src/plonk/evaluation.rs b/halo2_proofs/src/plonk/evaluation.rs index 2f08a43e01..f626bcbb54 100644 --- a/halo2_proofs/src/plonk/evaluation.rs +++ b/halo2_proofs/src/plonk/evaluation.rs @@ -1,7 +1,9 @@ use crate::multicore; use crate::plonk::lookup::prover::Committed; use crate::plonk::permutation::Argument; -use crate::plonk::{lookup, permutation, AdviceQuery, Any, FixedQuery, InstanceQuery, ProvingKey}; +use crate::plonk::{ + mv_lookup, permutation, AdviceQuery, Any, FixedQuery, InstanceQuery, ProvingKey, +}; use crate::poly::Basis; use crate::{ arithmetic::{eval_polynomial, parallelize, CurveAffine}, @@ -16,10 +18,14 @@ use group::{ ff::{BatchInvert, Field, PrimeField, WithSmallOrderMulGroup}, Curve, }; +use rayon::prelude::IntoParallelIterator; +use rayon::prelude::ParallelIterator; use std::any::TypeId; use std::convert::TryInto; use std::num::ParseIntError; +use std::process::exit; use std::slice; +use std::sync::atomic::fence; use std::{ collections::BTreeMap, iter, @@ -51,7 +57,8 @@ pub enum ValueSource { /// beta Beta(), /// gamma - Gamma(), + // only used by the old halo2 lookup scheme + // Gamma(), /// theta Theta(), /// y @@ -78,7 +85,7 @@ impl ValueSource { instance_values: &[Polynomial], challenges: &[F], beta: &F, - gamma: &F, + _gamma: &F, theta: &F, y: &F, previous_value: &F, @@ -97,7 +104,7 @@ impl ValueSource { } ValueSource::Challenge(index) => challenges[*index], ValueSource::Beta() => *beta, - ValueSource::Gamma() => *gamma, + // ValueSource::Gamma() => *gamma, ValueSource::Theta() => *theta, ValueSource::Y() => *y, ValueSource::PreviousValue() => *previous_value, @@ -185,7 +192,7 @@ pub struct Evaluator { /// Custom gates evalution pub custom_gates: GraphEvaluator, /// Lookups evalution - pub lookups: Vec>, + pub lookups: Vec<(Vec>, GraphEvaluator)>, } /// GraphEvaluator @@ -241,9 +248,12 @@ impl Evaluator { // Lookups for lookup in cs.lookups.iter() { - let mut graph = GraphEvaluator::default(); + let mut graph_table = GraphEvaluator::default(); + let mut graph_inputs: Vec<_> = (0..lookup.inputs_expressions.len()) + .map(|_| GraphEvaluator::default()) + .collect(); - let mut evaluate_lc = |expressions: &Vec>| { + let evaluate_lc = |graph: &mut GraphEvaluator, expressions: &Vec>| { let parts = expressions .iter() .map(|expr| graph.add_expression(expr)) @@ -255,22 +265,33 @@ impl Evaluator { )) }; - // Input coset - let compressed_input_coset = evaluate_lc(&lookup.input_expressions); + // Inputs cosets + for (input_expressions, graph_input) in lookup + .inputs_expressions + .iter() + .zip(graph_inputs.iter_mut()) + { + let compressed_input_coset = evaluate_lc(graph_input, input_expressions); + + graph_input.add_calculation(Calculation::Add( + compressed_input_coset, + ValueSource::Beta(), + )); + } + // table coset - let compressed_table_coset = evaluate_lc(&lookup.table_expressions); - // z(\omega X) (a'(X) + \beta) (s'(X) + \gamma) - let right_gamma = graph.add_calculation(Calculation::Add( + let compressed_table_coset = evaluate_lc(&mut graph_table, &lookup.table_expressions); + + graph_table.add_calculation(Calculation::Add( compressed_table_coset, - ValueSource::Gamma(), - )); - let lc = graph.add_calculation(Calculation::Add( - compressed_input_coset, ValueSource::Beta(), )); - graph.add_calculation(Calculation::Mul(lc, right_gamma)); - ev.lookups.push(graph); + /* + a) f_i + beta + b) t + beta + */ + ev.lookups.push((graph_inputs.to_vec(), graph_table)); } ev @@ -287,7 +308,7 @@ impl Evaluator { beta: C::ScalarExt, gamma: C::ScalarExt, theta: C::ScalarExt, - lookups: &[Vec>], + lookups: &[Vec>], permutations: &[permutation::prover::Committed], ) -> Polynomial { let domain = &pk.vk.domain; @@ -492,33 +513,140 @@ impl Evaluator { }); } + // For lookups, compute inputs_inv_sum = ∑ 1 / (f_i(X) + beta) + // The outer vector has capacity self.lookups.len() + #[cfg(not(feature = "logup_skip_inv"))] + let inputs_inv_sum: Vec> = self + .lookups + .iter() + .map(|lookup| { + let (inputs_lookup_evaluator, _) = lookup; + + let inputs_values_for_extended_domain: Vec> = (0..size) + .into_par_iter() + .map(|idx| { + let mut inputs_eval_data: Vec<_> = inputs_lookup_evaluator + .iter() + .map(|input_lookup_evaluator| { + input_lookup_evaluator.instance() + }) + .collect(); + + inputs_lookup_evaluator + .iter() + .zip(inputs_eval_data.iter_mut()) + .map(|(input_lookup_evaluator, input_eval_data)| { + input_lookup_evaluator.evaluate( + input_eval_data, + fixed, + advice, + instance, + challenges, + &beta, + &gamma, + &theta, + &y, + &C::ScalarExt::zero(), + idx, + rot_scale, + isize, + ) + }) + .collect() + }) + .collect(); + let mut inputs_values_for_extended_domain: Vec = + inputs_values_for_extended_domain + .into_iter() + .flatten() + .collect(); + + parallelize(&mut inputs_values_for_extended_domain, |values, _| { + values.batch_invert(); + }); + + let inputs_len = inputs_lookup_evaluator.len(); + + (0..size) + .into_par_iter() + .map(|i| { + inputs_values_for_extended_domain + [i * inputs_len..(i + 1) * inputs_len] + .iter() + .fold(C::Scalar::zero(), |acc, x| acc + x) + }) + .collect::>() + }) + .collect(); + // Lookups for (n, lookup) in lookups.iter().enumerate() { // Polynomials required for this lookup. // Calculated here so these only have to be kept in memory for the short time // they are actually needed. - let product_coset = pk.vk.domain.coeff_to_extended_part( - lookup.product_poly.clone(), - current_extended_omega, - ); - let permuted_input_coset = pk.vk.domain.coeff_to_extended_part( - lookup.permuted_input_poly.clone(), - current_extended_omega, - ); - let permuted_table_coset = pk.vk.domain.coeff_to_extended_part( - lookup.permuted_table_poly.clone(), + let phi_coset = pk.vk.domain.coeff_to_extended_part( + lookup.phi_poly.clone(), current_extended_omega, ); + let m_coset = pk + .vk + .domain + .coeff_to_extended_part(lookup.m_poly.clone(), current_extended_omega); // Lookup constraints + /* + φ_i(X) = f_i(X) + beta + τ(X) = t(X) + beta + LHS = τ(X) * Π(φ_i(X)) * (ϕ(gX) - ϕ(X)) + RHS = τ(X) * Π(φ_i(X)) * (∑ 1/(φ_i(X)) - m(X) / τ(X)))) (1) + = (τ(X) * Π(φ_i(X)) * ∑ 1/(φ_i(X))) - Π(φ_i(X)) * m(X) + = Π(φ_i(X)) * (τ(X) * ∑ 1/(φ_i(X)) - m(X)) + + = ∑_i τ(X) * Π_{j != i} φ_j(X) - m(X) * Π(φ_i(X)) (2) + */ parallelize(&mut values, |values, start| { - let lookup_evaluator = &self.lookups[n]; - let mut eval_data = lookup_evaluator.instance(); + let (inputs_lookup_evaluator, table_lookup_evaluator) = + &self.lookups[n]; + let mut inputs_eval_data: Vec<_> = inputs_lookup_evaluator + .iter() + .map(|input_lookup_evaluator| input_lookup_evaluator.instance()) + .collect(); + let mut table_eval_data = table_lookup_evaluator.instance(); + for (i, value) in values.iter_mut().enumerate() { let idx = start + i; - let table_value = lookup_evaluator.evaluate( - &mut eval_data, + // f_i(X) + beta for i in expressions + let inputs_value: Vec = inputs_lookup_evaluator + .iter() + .zip(inputs_eval_data.iter_mut()) + .map(|(input_lookup_evaluator, input_eval_data)| { + input_lookup_evaluator.evaluate( + input_eval_data, + fixed, + advice, + instance, + challenges, + &beta, + &gamma, + &theta, + &y, + &C::ScalarExt::ZERO, + idx, + rot_scale, + isize, + ) + }) + .collect(); + + // Π(φ_i(X)) + let inputs_prod: C::Scalar = inputs_value + .iter() + .fold(C::Scalar::ZERO, |acc, input| acc * input); + + // t(X) + beta + let table_value = table_lookup_evaluator.evaluate( + &mut table_eval_data, fixed, advice, instance, @@ -534,41 +662,45 @@ impl Evaluator { ); let r_next = get_rotation_idx(idx, 1, rot_scale, isize); - let r_prev = get_rotation_idx(idx, -1, rot_scale, isize); - let a_minus_s = - permuted_input_coset[idx] - permuted_table_coset[idx]; - // 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) (a'(X) + \beta) (s'(X) + \gamma) - // - z(X) (\theta^{m-1} a_0(X) + ... + a_{m-1}(X) + \beta) - // (\theta^{m-1} s_0(X) + ... + s_{m-1}(X) + \gamma) - // ) = 0 - *value = *value * y - + ((product_coset[r_next] - * (permuted_input_coset[idx] + beta) - * (permuted_table_coset[idx] + gamma) - - product_coset[idx] * table_value) - * l_active_row[idx]); - // Check that the first values in the permuted input expression and permuted - // fixed expression are the same. - // l_0(X) * (a'(X) - s'(X)) = 0 - *value = *value * y + (a_minus_s * l0[idx]); - // Check that each value in the permuted lookup input expression is either - // equal to the value above it, or the value at the same index in the - // permuted table expression. - // (1 - (l_last + l_blind)) * (a′(X) − s′(X))⋅(a′(X) − a′(\omega^{-1} X)) = 0 - *value = *value * y - + (a_minus_s - * (permuted_input_coset[idx] - - permuted_input_coset[r_prev]) - * l_active_row[idx]); + let lhs = { + // τ(X) * Π(φ_i(X)) * (ϕ(gX) - ϕ(X)) + table_value * inputs_prod * (phi_coset[r_next] - phi_coset[idx]) + }; + + #[cfg(feature = "logup_skip_inv")] + let rhs = { + // τ(X) * Π(φ_i(X)) * (∑ 1/(φ_i(X)) - m(X) / τ(X)))) + // = ∑_i τ(X) * Π_{j != i} φ_j(X) - m(X) * Π(φ_i(X)) + let inputs = (0..inputs_value.len()) + .map(|i| { + inputs_value + .iter() + .enumerate() + .filter(|(j, _)| *j != i) + .fold(C::Scalar::ZERO, |acc, (_, x)| acc * *x) + }) + .fold(C::Scalar::ZERO, |acc, x| acc + x); + inputs * table_value - inputs_prod * m_coset[idx] + }; + #[cfg(not(feature = "logup_skip_inv"))] + let rhs = { + // ∑ 1 / (f_i(X) + beta) at ω^idx + let inv_sum: C::Scalar = inputs_inv_sum[n][idx]; + // τ(X) * Π(φ_i(X)) * (∑ 1/(φ_i(X)) - m(X) / τ(X)))) + // = (τ(X) * Π(φ_i(X)) * ∑ 1/(φ_i(X))) - Π(φ_i(X)) * m(X) + // = Π(φ_i(X)) * (τ(X) * ∑ 1/(φ_i(X)) - m(X)) + inputs_prod * (table_value * inv_sum - m_coset[idx]) + }; + + // phi[0] = 0 + *value = *value * y + l0[idx] * phi_coset[idx]; + + // phi[u] = 0 + *value = *value * y + l_last[idx] * phi_coset[idx]; + + // q(X) = (1 - (l_last(X) + l_blind(X))) * (LHS - RHS) + *value = *value * y + (lhs - rhs) * l_active_row[idx]; } }); } diff --git a/halo2_proofs/src/plonk/keygen.rs b/halo2_proofs/src/plonk/keygen.rs index e5459969f5..92ef096f32 100644 --- a/halo2_proofs/src/plonk/keygen.rs +++ b/halo2_proofs/src/plonk/keygen.rs @@ -41,6 +41,8 @@ where let mut cs = ConstraintSystem::default(); let config = ConcreteCircuit::configure(&mut cs); + let cs = cs.chunk_lookups(); + let degree = cs.degree(); let domain = EvaluationDomain::new(degree as u32, k); diff --git a/halo2_proofs/src/plonk/mv_lookup.rs b/halo2_proofs/src/plonk/mv_lookup.rs new file mode 100644 index 0000000000..b4fd2d825f --- /dev/null +++ b/halo2_proofs/src/plonk/mv_lookup.rs @@ -0,0 +1,96 @@ +use super::circuit::Expression; +use ff::Field; +use std::fmt::{self, Debug}; + +pub(crate) mod prover; +pub(crate) mod verifier; + +/// Degree of lookup without inputs +pub fn base_degree(table_degree: usize) -> usize { + // let lhs_degree = table_degree + inputs_expressions_degree + 1 + // let degree = lhs_degree + 1 + std::cmp::max(3, table_degree + 2) +} + +pub fn degree_with_input(base_degree: usize, input_expression_degree: usize) -> usize { + base_degree + input_expression_degree +} + +#[derive(Clone)] +pub struct Argument { + pub name: &'static str, + pub(crate) table_expressions: Vec>, + pub(crate) inputs_expressions: Vec>>, +} + +impl Debug for Argument { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Argument") + .field("table_expressions", &self.table_expressions) + .field("inputs_expressions", &self.inputs_expressions) + .finish() + } +} + +impl Argument { + /// Constructs a new lookup argument. + pub fn new(name: &'static str, table: &[Expression], input: &[Vec>]) -> Self { + Self { + name, + table_expressions: table.to_owned(), + inputs_expressions: input.to_owned(), + } + } + + pub(crate) fn required_degree(&self) -> usize { + assert!(self + .inputs_expressions + .iter() + .all(|input| input.len() == self.table_expressions.len())); + + let expr_degree = |input_expressions: &Vec>| { + let mut input_degree = 0; + for expr in input_expressions.iter() { + input_degree = std::cmp::max(input_degree, expr.degree()); + } + + input_degree + }; + + let inputs_expressions_degree: usize = self + .inputs_expressions + .iter() + .map(|input_expressions| expr_degree(input_expressions)) + .sum(); + + let table_degree = expr_degree(&self.table_expressions); + + /* + φ_i(X) = f_i(X) + α + τ(X) = t(X) + α + LHS = τ(X) * Π(φ_i(X)) * (ϕ(gX) - ϕ(X)) + = table_degree + sum(input_degree) + 1 + RHS = τ(X) * Π(φ_i(X)) * (∑ 1/(φ_i(X)) - m(X) / τ(X)))) + + deg(q(X)) = (1 - (q_last + q_blind)) * (LHS - RHS) + = 1 + LHS + */ + + let lhs_degree = table_degree + inputs_expressions_degree + 1; + let degree = lhs_degree + 1; + + // 3 = phi + q_blind + table (where table is = 1) + // + 1 for each of inputs expressions + std::cmp::max(3 + self.inputs_expressions.len(), degree) + } + + /// Returns input of this argument + pub fn input_expressions(&self) -> &Vec>> { + &self.inputs_expressions + } + + /// Returns table of this argument + pub fn table_expressions(&self) -> &Vec> { + &self.table_expressions + } +} diff --git a/halo2_proofs/src/plonk/mv_lookup/exec_info.json b/halo2_proofs/src/plonk/mv_lookup/exec_info.json new file mode 100644 index 0000000000..f7a7042c0f --- /dev/null +++ b/halo2_proofs/src/plonk/mv_lookup/exec_info.json @@ -0,0 +1,46 @@ +{ + "unit": "ms", + "non_batched": { + "k": 14, + "halo2": { + "protocol": "halo2", + "methods": { + "commit_permuted": { + "compress_expressions": 1, + "permute_expressions": 4.5, + "commit_permuted_input": 5, + "commit_permuted_table": 5 + }, + "grand_product": { + "lookup_product_denom": 2, + "lookup_product": 0.2, + "grand_prod_evals": 0.5, + "grand_prod_commit": 7.5 + }, + "h_evaluation": { + + } + } + }, + "mv": { + "protocol": "mv", + "methods": { + "compute_multiplicity": { + "compress_expressions": 1, + "compute_multiplicities": 2, + "commit_m": 1 + }, + "grand_sum": { + "inputs_log_derivatives": 2, + "table_log_derivatives": 1.8, + "log_derivatives_diff": 0.2, + "grand_sum_evals": 0.2, + "grand_sum_commit": 33 + }, + "h_evaluation": { + + } + } + } + } +} \ No newline at end of file diff --git a/halo2_proofs/src/plonk/mv_lookup/prover.rs b/halo2_proofs/src/plonk/mv_lookup/prover.rs new file mode 100644 index 0000000000..52d434289b --- /dev/null +++ b/halo2_proofs/src/plonk/mv_lookup/prover.rs @@ -0,0 +1,529 @@ +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 ark_std::{end_timer, start_timer}; +use blake2b_simd::Hash; +use ff::{BitViewSized, PrimeField, PrimeFieldBits, WithSmallOrderMulGroup}; +use group::{ + ff::{BatchInvert, Field}, + Curve, +}; +use rand_core::RngCore; +use rayon::current_num_threads; +use std::collections::{BTreeSet, HashSet}; +use std::time::Instant; +use std::{any::TypeId, convert::TryInto, num::ParseIntError, ops::Index}; +use std::{ + collections::BTreeMap, + iter, + ops::{Mul, MulAssign}, +}; + +use crate::arithmetic::{par_invert, parallelize_internal}; +use rayon::prelude::{ + IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator, ParallelSliceMut, +}; + +#[derive(Debug)] +pub(in crate::plonk) struct Prepared { + compressed_inputs_expressions: Vec>, + compressed_table_expression: Polynomial, + m_values: Polynomial, +} + +#[derive(Debug)] +pub(in crate::plonk) struct Committed { + pub(in crate::plonk) m_poly: Polynomial, + pub(in crate::plonk) phi_poly: Polynomial, +} + +pub(in crate::plonk) struct Evaluated { + constructed: Committed, +} + +impl + Ord> Argument { + pub(in crate::plonk) fn prepare< + 'a, + 'params: 'a, + C, + P: Params<'params, C>, + E: EncodedChallenge, + R: RngCore, + T: TranscriptWrite, + >( + &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], + mut rng: R, // in case we want to blind (do we actually need zk?) + transcript: &mut T, + ) -> Result, Error> + where + C: CurveAffine, + C::Curve: Mul + MulAssign, + { + let prepare_time = start_timer!(|| format!( + "prepare m(X) (inputs={:?}, table={})", + self.inputs_expressions + .iter() + .map(|e| e.len()) + .collect::>(), + self.table_expressions.len() + )); + // 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 lookup and compress them + let compressed_inputs_expressions: Vec<_> = self + .inputs_expressions + .iter() + .map(|input_expressions| compress_expressions(input_expressions)) + .collect(); + + // Get values of table expressions involved in the lookup and compress them + let compressed_table_expression = compress_expressions(&self.table_expressions); + + let blinding_factors = pk.vk.cs.blinding_factors(); + + // compute m(X) + let tivm_time = start_timer!(|| "table index value mapping"); + let mut sorted_table_with_indices = compressed_table_expression + .iter() + .take(params.n() as usize - blinding_factors - 1) + .enumerate() + .map(|(i, t)| (t, i)) + .collect::>(); + sorted_table_with_indices.par_sort_by_key(|(&t, _)| t); + end_timer!(tivm_time); + + let m_time = start_timer!(|| "m(X) values"); + let m_values: Vec = { + use std::sync::atomic::{AtomicU64, Ordering}; + use std::sync::RwLock; + let m_values: Vec = (0..params.n()).map(|_| AtomicU64::new(0)).collect(); + + for compressed_input_expression in compressed_inputs_expressions.iter() { + let _ = compressed_input_expression + .par_iter() + .take(params.n() as usize - blinding_factors - 1) + .try_for_each(|fi| -> Result<(), Error> { + let index = sorted_table_with_indices + .binary_search_by_key(&fi, |&(t, _)| t) + .map_err(|_| Error::ConstraintSystemFailure)?; + let index = sorted_table_with_indices[index].1; + + m_values[index].fetch_add(1, Ordering::Relaxed); + Ok(()) + }); + } + + m_values + .par_iter() + .map(|mi| F::from(mi.load(Ordering::Relaxed) as u64)) + .collect() + }; + end_timer!(m_time); + let m_values = pk.vk.domain.lagrange_from_vec(m_values); + + #[cfg(feature = "sanity-checks")] + { + // check that m is zero after blinders + let invalid_ms = m_values + .iter() + .skip(params.n() as usize - blinding_factors) + .collect::>(); + assert_eq!(invalid_ms.len(), blinding_factors); + for mi in invalid_ms { + assert_eq!(*mi, C::Scalar::ZERO); + } + + // check sums + let alpha = C::Scalar::random(&mut rng); + let cs_input_sum = + |compressed_input_expression: &Polynomial| { + let mut lhs_sum = C::Scalar::ZERO; + for &fi in compressed_input_expression + .iter() + .take(params.n() as usize - blinding_factors - 1) + { + lhs_sum += (fi + alpha).invert().unwrap(); + } + + lhs_sum + }; + + let mut lhs_sum = C::Scalar::ZERO; + + for compressed_input_expression in compressed_inputs_expressions.iter() { + lhs_sum += cs_input_sum(compressed_input_expression); + } + + let mut rhs_sum = C::Scalar::ZERO; + for (&ti, &mi) in compressed_table_expression.iter().zip(m_values.iter()) { + rhs_sum += mi * (ti + alpha).invert().unwrap(); + } + + assert_eq!(lhs_sum, rhs_sum); + } + + // commit to m(X) + // TODO: should we use zero instead? + let blind = Blind(C::Scalar::random(rng)); + let m_commitment = params.commit_lagrange(&m_values, blind).to_affine(); + + // write commitment of m(X) to transcript + transcript.write_point(m_commitment)?; + + end_timer!(prepare_time); + + Ok(Prepared { + compressed_inputs_expressions, + compressed_table_expression, + m_values, + }) + } +} + +impl Prepared { + pub(in crate::plonk) fn commit_grand_sum< + 'params, + P: Params<'params, C>, + E: EncodedChallenge, + R: RngCore, + T: TranscriptWrite, + >( + self, + pk: &ProvingKey, + params: &P, + beta: ChallengeBeta, + mut rng: R, + transcript: &mut T, + ) -> Result, Error> { + /* + φ_i(X) = f_i(X) + beta + τ(X) = t(X) + beta + LHS = τ(X) * Π(φ_i(X)) * (ϕ(gX) - ϕ(X)) + RHS = τ(X) * Π(φ_i(X)) * (∑ 1/(φ_i(X)) - m(X) / τ(X)))) + */ + let lookup_commit_time = start_timer!(|| "commit_grand_sum"); + + // ∑ 1/(φ_i(X)) + let inputs_log_drv_time = start_timer!(|| "inputs_log_derivative"); + let mut inputs_log_derivatives = vec![C::Scalar::ZERO; params.n() as usize]; + for compressed_input_expression in self.compressed_inputs_expressions.iter() { + let mut input_log_derivatives = vec![C::Scalar::ZERO; params.n() as usize]; + + parallelize( + &mut input_log_derivatives, + |input_log_derivatives, start| { + for (input_log_derivative, fi) in input_log_derivatives + .iter_mut() + .zip(compressed_input_expression[start..].iter()) + { + *input_log_derivative = *beta + fi; + } + }, + ); + let inputs_inv_time = start_timer!(|| "batch invert"); + par_invert(input_log_derivatives.as_mut_slice()); + end_timer!(inputs_inv_time); + + // TODO: remove last blinders from this + for i in 0..params.n() as usize { + inputs_log_derivatives[i] += input_log_derivatives[i]; + } + } + end_timer!(inputs_log_drv_time); + + // 1 / τ(X) + let table_log_drv_time = start_timer!(|| "table log derivative"); + let mut table_log_derivatives = vec![C::Scalar::ZERO; params.n() as usize]; + parallelize( + &mut table_log_derivatives, + |table_log_derivatives, start| { + for (table_log_derivative, ti) in table_log_derivatives + .iter_mut() + .zip(self.compressed_table_expression[start..].iter()) + { + *table_log_derivative = *beta + ti; + } + }, + ); + + let table_inv_time = start_timer!(|| "table batch invert"); + par_invert(table_log_derivatives.as_mut_slice()); + end_timer!(table_inv_time); + end_timer!(table_log_drv_time); + + let log_drv_diff_time = start_timer!(|| "log derivatives diff"); + // (Σ 1/(φ_i(X)) - m(X) / τ(X)) + let mut log_derivatives_diff = vec![C::Scalar::ZERO; params.n() as usize]; + parallelize(&mut log_derivatives_diff, |log_derivatives_diff, start| { + for (((log_derivative_diff, fi), ti), mi) in log_derivatives_diff + .iter_mut() + .zip(inputs_log_derivatives[start..].iter()) + .zip(table_log_derivatives[start..].iter()) + .zip(self.m_values[start..].iter()) + { + // (Σ 1/(φ_i(X)) - m(X) / τ(X)) + *log_derivative_diff = *fi - *mi * *ti; + } + }); + end_timer!(log_drv_diff_time); + + // Compute the evaluations of the lookup grand sum polynomial + // over our domain, starting with phi[0] = 0 + let blinding_factors = pk.vk.cs.blinding_factors(); + let phi_time = start_timer!(|| "par_scan(log_derivatives_diff)"); + let phi = { + // parallelized version of log_derivatives_diff.scan() + let active_size = params.n() as usize - blinding_factors; + let chunk = { + let num_threads = crate::multicore::current_num_threads(); + let mut chunk = (active_size as usize) / num_threads; + if chunk < num_threads { + chunk = 1; + } + chunk + }; + let num_chunks = (active_size as usize + chunk - 1) / chunk; + let mut segment_sum = vec![C::Scalar::ZERO; num_chunks]; + let mut grand_sum = iter::once(C::Scalar::ZERO) + .chain(log_derivatives_diff) + .take(active_size) + .collect::>(); + // TODO: remove the implicit assumption that parallelize() split the grand_sum + // into segments that each has `chunk` elements except the last. + parallelize(&mut grand_sum, |segment_grand_sum, _| { + for i in 1..segment_grand_sum.len() { + segment_grand_sum[i] += segment_grand_sum[i - 1]; + } + }); + for i in 1..segment_sum.len() { + segment_sum[i] = segment_sum[i - 1] + grand_sum[i * chunk - 1]; + } + parallelize(&mut grand_sum, |grand_sum, start| { + let prefix_sum = segment_sum[start / chunk]; + for v in grand_sum.iter_mut() { + *v += prefix_sum; + } + }); + grand_sum + .into_iter() + .chain((0..blinding_factors).map(|_| C::Scalar::random(&mut rng))) + .collect::>() + }; + end_timer!(phi_time); + assert_eq!(phi.len(), params.n() as usize); + let phi = pk.vk.domain.lagrange_from_vec(phi); + + #[cfg(feature = "sanity-checks")] + // This test works only with intermediate representations in this method. + // It can be used for debugging purposes. + { + // While in Lagrange basis, check that product is correctly constructed + let u = (params.n() as usize) - (blinding_factors + 1); + + /* + φ_i(X) = f_i(X) + α + τ(X) = t(X) + α + LHS = τ(X) * Π(φ_i(X)) * (ϕ(gX) - ϕ(X)) + RHS = τ(X) * Π(φ_i(X)) * (∑ 1/(φ_i(X)) - m(X) / τ(X)))) + */ + + // q(X) = LHS - RHS mod zH(X) + for i in 0..u { + // Π(φ_i(X)) + let fi_prod = || { + let mut prod = C::Scalar::ONE; + for compressed_input_expression in self.compressed_inputs_expressions.iter() { + prod *= *beta + compressed_input_expression[i]; + } + + prod + }; + + let fi_log_derivative = || { + let mut sum = C::Scalar::ZERO; + for compressed_input_expression in self.compressed_inputs_expressions.iter() { + sum += (*beta + compressed_input_expression[i]).invert().unwrap(); + } + + sum + }; + + // LHS = τ(X) * Π(φ_i(X)) * (ϕ(gX) - ϕ(X)) + let lhs = { + (*beta + self.compressed_table_expression[i]) + * fi_prod() + * (phi[i + 1] - phi[i]) + }; + + // RHS = τ(X) * Π(φ_i(X)) * (∑ 1/(φ_i(X)) - m(X) / τ(X)))) + let rhs = { + (*beta + self.compressed_table_expression[i]) + * fi_prod() + * (fi_log_derivative() + - self.m_values[i] + * (*beta + self.compressed_table_expression[i]) + .invert() + .unwrap()) + }; + + assert_eq!(lhs - rhs, C::Scalar::ZERO); + } + + assert_eq!(phi[u], C::Scalar::ZERO); + } + + let grand_sum_blind = Blind(C::Scalar::random(rng)); + let phi_commitment = params.commit_lagrange(&phi, grand_sum_blind).to_affine(); + + // Hash grand sum commitment + transcript.write_point(phi_commitment)?; + + end_timer!(lookup_commit_time); + Ok(Committed { + m_poly: pk.vk.domain.lagrange_to_coeff(self.m_values), + phi_poly: pk.vk.domain.lagrange_to_coeff(phi), + }) + } +} + +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 phi_eval = eval_polynomial(&self.phi_poly, *x); + let phi_next_eval = eval_polynomial(&self.phi_poly, x_next); + let m_eval = eval_polynomial(&self.m_poly, *x); + + // Hash each advice evaluation + for eval in iter::empty() + .chain(Some(phi_eval)) + .chain(Some(phi_next_eval)) + .chain(Some(m_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() + .chain(Some(ProverQuery { + point: *x, + poly: &self.constructed.phi_poly, + blind: Blind(C::Scalar::ZERO), + })) + .chain(Some(ProverQuery { + point: x_next, + poly: &self.constructed.phi_poly, + blind: Blind(C::Scalar::ZERO), + })) + .chain(Some(ProverQuery { + point: *x, + poly: &self.constructed.m_poly, + blind: Blind(C::Scalar::ZERO), + })) + } +} + +mod benches { + use ark_std::rand::thread_rng; + use ff::Field; + use halo2curves::bn256::Fr; + use std::collections::BTreeMap; + use std::time::Instant; + + // bench the time to construct a BTreeMap out of a large table + // tivm is short for table_index_value_mapping + #[ignore] + #[test] + fn bench_tivm_btree_map() { + env_logger::init(); + let mut rng = thread_rng(); + + for log_n in 20..26 { + let n = 1 << log_n; + let dur = Instant::now(); + let _table: BTreeMap = (0..n) + .into_iter() + .map(|_| Fr::random(&mut rng)) + .enumerate() + .map(|(i, x)| (x, i)) + .collect(); + log::info!( + "construct btreemap from random vec (len = {}) took {:?}", + n, + dur.elapsed() + ); + } + + for log_n in 20..26 { + let n = 1 << log_n; + let dur = Instant::now(); + let _table: BTreeMap = (0..n) + .into_iter() + .map(Fr::from) + .enumerate() + .map(|(i, x)| (x, i)) + .collect(); + log::info!( + "construct btreemap from increasing vec (len = {}) took {:?}", + n, + dur.elapsed() + ); + } + } +} diff --git a/halo2_proofs/src/plonk/mv_lookup/verifier.rs b/halo2_proofs/src/plonk/mv_lookup/verifier.rs new file mode 100644 index 0000000000..361bb5f972 --- /dev/null +++ b/halo2_proofs/src/plonk/mv_lookup/verifier.rs @@ -0,0 +1,191 @@ +use std::iter; + +use super::super::{ + circuit::Expression, ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, +}; +use super::Argument; +use crate::{ + arithmetic::CurveAffine, + plonk::{Error, VerifyingKey}, + poly::{commitment::MSM, Rotation, VerifierQuery}, + transcript::{EncodedChallenge, TranscriptRead}, +}; +use ff::{BatchInvert, Field, PrimeField, WithSmallOrderMulGroup}; + +pub struct PreparedCommitments { + m_commitment: C, +} + +pub struct Committed { + prepared: PreparedCommitments, + phi_commitment: C, +} + +pub struct Evaluated { + committed: Committed, + phi_eval: C::Scalar, + phi_next_eval: C::Scalar, + m_eval: C::Scalar, +} + +impl> Argument { + pub(in crate::plonk) fn read_prepared_commitments< + C: CurveAffine, + E: EncodedChallenge, + T: TranscriptRead, + >( + &self, + transcript: &mut T, + ) -> Result, Error> { + let m_commitment = transcript.read_point()?; + + Ok(PreparedCommitments { m_commitment }) + } +} + +impl PreparedCommitments { + pub(in crate::plonk) fn read_grand_sum_commitment< + E: EncodedChallenge, + T: TranscriptRead, + >( + self, + transcript: &mut T, + ) -> Result, Error> { + let phi_commitment = transcript.read_point()?; + + Ok(Committed { + prepared: self, + phi_commitment, + }) + } +} + +impl Committed { + pub(crate) fn evaluate, T: TranscriptRead>( + self, + transcript: &mut T, + ) -> Result, Error> { + let phi_eval = transcript.read_scalar()?; + let phi_next_eval = transcript.read_scalar()?; + let m_eval = transcript.read_scalar()?; + + Ok(Evaluated { + committed: self, + phi_eval, + phi_next_eval, + m_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, + beta: ChallengeBeta, + advice_evals: &[C::Scalar], + fixed_evals: &[C::Scalar], + instance_evals: &[C::Scalar], + challenges: &[C::Scalar], + ) -> impl Iterator + 'a { + let active_rows = C::Scalar::ZERO - (l_last + l_blind); + + /* + φ_i(X) = f_i(X) + beta + τ(X) = t(X) + beta + LHS = τ(X) * Π(φ_i(X)) * (ϕ(gX) - ϕ(X)) + RHS = τ(X) * Π(φ_i(X)) * (∑ 1/(φ_i(X)) - m(X) / τ(X)))) + */ + + let grand_sum_expression = || { + 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], + &|query| advice_evals[query.index], + &|query| instance_evals[query.index], + &|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) + }; + + // φ_i(X) = f_i(X) + beta + let mut f_evals: Vec<_> = argument + .inputs_expressions + .iter() + .map(|input_expressions| compress_expressions(input_expressions) + *beta) + .collect(); + + let t_eval = compress_expressions(&argument.table_expressions); + + let tau = t_eval + *beta; + // Π(φ_i(X)) + let prod_fi = f_evals.iter().fold(C::Scalar::ZERO, |acc, eval| acc * eval); + // ∑ 1/(φ_i(X)) + let sum_inv_fi = { + f_evals.batch_invert(); + f_evals.iter().fold(C::Scalar::ZERO, |acc, eval| acc + eval) + }; + + // LHS = τ(X) * Π(φ_i(X)) * (ϕ(gX) - ϕ(X)) + let lhs = tau * prod_fi * (self.phi_next_eval - self.phi_eval); + + // RHS = τ(X) * Π(φ_i(X)) * (∑ 1/(φ_i(X)) - m(X) / τ(X)))) + let rhs = { tau * prod_fi * (sum_inv_fi - self.m_eval * tau.invert().unwrap()) }; + + (lhs - rhs) * active_rows + }; + + std::iter::empty() + .chain( + // phi[0] = 0 + Some(l_0 * self.phi_eval), + ) + .chain( + // phi[u] = 0 + Some(l_last * self.phi_eval), + ) + .chain( + // (1 - l_last - l_blind) * (lhs - rhs) = 0 + Some(grand_sum_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() + .chain(Some(VerifierQuery::new_commitment( + &self.committed.phi_commitment, + *x, + self.phi_eval, + ))) + .chain(Some(VerifierQuery::new_commitment( + &self.committed.phi_commitment, + x_next, + self.phi_next_eval, + ))) + .chain(Some(VerifierQuery::new_commitment( + &self.committed.prepared.m_commitment, + *x, + self.m_eval, + ))) + } +} diff --git a/halo2_proofs/src/plonk/prover.rs b/halo2_proofs/src/plonk/prover.rs index 15f0438470..768216e4c5 100644 --- a/halo2_proofs/src/plonk/prover.rs +++ b/halo2_proofs/src/plonk/prover.rs @@ -16,7 +16,7 @@ use super::{ Advice, Any, Assignment, Challenge, Circuit, Column, ConstraintSystem, FirstPhase, Fixed, FloorPlanner, Instance, Selector, }, - lookup, permutation, vanishing, ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, + mv_lookup, permutation, vanishing, ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, ChallengeY, Error, Expression, ProvingKey, }; use crate::{ @@ -34,6 +34,7 @@ use crate::{ poly::batch_invert_assigned, transcript::{EncodedChallenge, TranscriptWrite}, }; +use ark_std::{end_timer, start_timer}; use group::prime::PrimeCurveAffine; /// This creates a proof for the provided `circuit` when given the public @@ -57,7 +58,7 @@ pub fn create_proof< transcript: &mut T, ) -> Result<(), Error> where - Scheme::Scalar: WithSmallOrderMulGroup<3> + FromUniformBytes<64>, + Scheme::Scalar: WithSmallOrderMulGroup<3> + FromUniformBytes<64> + Ord, { for instance in instances.iter() { if instance.len() != pk.vk.cs.num_instance_columns { @@ -546,17 +547,20 @@ where // Sample theta challenge for keeping lookup columns linearly independent let theta: ChallengeTheta<_> = transcript.squeeze_challenge_scalar(); - let lookups: Vec>> = instance + let lookups: Vec>> = instance .iter() .zip(advice.iter()) .map(|(instance, advice)| -> Result, Error> { + let lookup_get_mx_time = + start_timer!(|| format!("get m(X) in {} lookups", pk.vk.cs.lookups.len())); // Construct and commit to permuted values for each lookup - pk.vk + let mx = pk + .vk .cs .lookups .iter() .map(|lookup| { - lookup.commit_permuted( + lookup.prepare( pk, params, domain, @@ -569,7 +573,10 @@ where transcript, ) }) - .collect() + .collect(); + end_timer!(lookup_get_mx_time); + + mx }) .collect::, _>>()?; @@ -599,16 +606,18 @@ where }) .collect::, _>>()?; - let lookups: Vec>> = lookups + let lookup_commit_time = start_timer!(|| "lookup commit grand sum"); + let lookups: Vec>> = lookups .into_iter() .map(|lookups| -> Result, _> { // Construct and commit to products for each lookup lookups .into_iter() - .map(|lookup| lookup.commit_product(pk, params, beta, gamma, &mut rng, transcript)) + .map(|lookup| lookup.commit_grand_sum(pk, params, beta, &mut rng, transcript)) .collect::, _>>() }) .collect::, _>>()?; + end_timer!(lookup_commit_time); // Commit to the vanishing argument's random polynomial for blinding h(x_3) let vanishing = vanishing::Argument::commit(params, domain, &mut rng, transcript)?; @@ -728,8 +737,7 @@ where .map(|permutation| -> Result<_, _> { permutation.construct().evaluate(pk, x, transcript) }) .collect::, _>>()?; - // Evaluate the lookups, if any, at omega^i x. - let lookups: Vec>> = lookups + let lookups: Vec>> = lookups .into_iter() .map(|lookups| -> Result, _> { lookups diff --git a/halo2_proofs/src/plonk/verifier.rs b/halo2_proofs/src/plonk/verifier.rs index f5984d2c79..315a3b6ca1 100644 --- a/halo2_proofs/src/plonk/verifier.rs +++ b/halo2_proofs/src/plonk/verifier.rs @@ -131,7 +131,7 @@ where vk.cs .lookups .iter() - .map(|argument| argument.read_permuted_commitments(transcript)) + .map(|argument| argument.read_prepared_commitments(transcript)) .collect::, _>>() }) .collect::, _>>()?; @@ -152,10 +152,10 @@ where let lookups_committed = lookups_permuted .into_iter() .map(|lookups| { - // Hash each lookup product commitment + // Hash each lookup sum commitment lookups .into_iter() - .map(|lookup| lookup.read_product_commitment(transcript)) + .map(|lookup| lookup.read_grand_sum_commitment(transcript)) .collect::, _>>() }) .collect::, _>>()?; @@ -300,27 +300,22 @@ where 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(lookups.iter().zip(vk.cs.lookups.iter()).flat_map( + move |(p, argument)| { + p.expressions( + l_0, + l_last, + l_blind, + argument, + theta, + beta, + advice_evals, + fixed_evals, + instance_evals, + challenges, + ) + }, + )) }); vanishing.verify(params, expressions, y, xn) @@ -366,12 +361,7 @@ where }, )) .chain(permutation.queries(vk, x)) - .chain( - lookups - .iter() - .flat_map(move |p| p.queries(vk, x)) - .into_iter(), - ) + .chain(lookups.iter().flat_map(move |p| p.queries(vk, x))) }, ) .chain( diff --git a/halo2_proofs/src/poly.rs b/halo2_proofs/src/poly.rs index 42e43b2e29..31f6d1f71d 100644 --- a/halo2_proofs/src/poly.rs +++ b/halo2_proofs/src/poly.rs @@ -304,7 +304,7 @@ impl<'a, F: Field, B: Basis> Sub for &'a Polynomial { /// Describes the relative rotation of a vector. Negative numbers represent /// reverse (leftmost) rotations and positive numbers represent forward (rightmost) /// rotations. Zero represents no rotation. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct Rotation(pub i32); impl Rotation { diff --git a/halo2_proofs/tests/plonk_api.rs b/halo2_proofs/tests/plonk_api.rs index 187280b034..1f1f0a2aa7 100644 --- a/halo2_proofs/tests/plonk_api.rs +++ b/halo2_proofs/tests/plonk_api.rs @@ -17,252 +17,390 @@ use halo2_proofs::transcript::{ Blake2bRead, Blake2bWrite, Challenge255, EncodedChallenge, TranscriptReadBuffer, TranscriptWriterBuffer, }; +use halo2curves::bn256::Bn256; use rand_core::{OsRng, RngCore}; use std::marker::PhantomData; use std::time::Instant; #[cfg(feature = "parallel_syn")] use halo2_proofs::circuit::Region; +use halo2_proofs::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; +use halo2_proofs::poly::kzg::multiopen::{ProverGWC, VerifierGWC}; +use halo2_proofs::poly::kzg::strategy::AccumulatorStrategy; + +/// This represents an advice column at a certain row in the ConstraintSystem +#[derive(Copy, Clone, Debug)] +pub struct Variable(Column, usize); + +#[derive(Clone)] +struct PlonkConfig { + a: Column, + b: Column, + c: Column, + d: Column, + e: Column, + + sa: Column, + sb: Column, + sc: Column, + sm: Column, + sp: Column, + sl: TableColumn, +} -#[test] -fn plonk_api() { - const K: u32 = 17; - - /// This represents an advice column at a certain row in the ConstraintSystem - #[derive(Copy, Clone, Debug)] - pub struct Variable(Column, usize); - - #[derive(Clone)] - struct PlonkConfig { - a: Column, - b: Column, - c: Column, - d: Column, - e: Column, - - sa: Column, - sb: Column, - sc: Column, - sm: Column, - sp: Column, - sl: TableColumn, - } +impl PlonkConfig { + pub fn construct(meta: &mut ConstraintSystem) -> Self { + let e = meta.advice_column(); + let a = meta.advice_column(); + let b = meta.advice_column(); + let sf = meta.fixed_column(); + let c = meta.advice_column(); + let d = meta.advice_column(); + let p = meta.instance_column(); + + meta.enable_equality(a); + meta.enable_equality(b); + meta.enable_equality(c); + + let sm = meta.fixed_column(); + let sa = meta.fixed_column(); + let sb = meta.fixed_column(); + let sc = meta.fixed_column(); + let sp = meta.fixed_column(); + let sl = meta.lookup_table_column(); + + // Add to test mvlookup + let dummy = meta.complex_selector(); + let dummy_2 = meta.complex_selector(); + let dummy_3 = meta.complex_selector(); + + let dummy_table = meta.lookup_table_column(); - #[allow(clippy::type_complexity)] - trait StandardCs { - fn raw_multiply( - &self, - layouter: &mut impl Layouter, - f: F, - ) -> Result<(Cell, Cell, Cell), Error> - where - F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; - fn raw_add( - &self, - layouter: &mut impl Layouter, - f: F, - ) -> Result<(Cell, Cell, Cell), Error> - where - F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; - fn copy(&self, layouter: &mut impl Layouter, a: Cell, b: Cell) -> Result<(), Error>; - fn public_input(&self, layouter: &mut impl Layouter, f: F) -> Result - where - F: FnMut() -> Value; - fn lookup_table( - &self, - layouter: &mut impl Layouter, - values: &[FF], - ) -> Result<(), Error>; + /* + * A B ... sl + * [ + * instance 0 ... 0 + * a a ... 0 + * a a^2 ... 0 + * a a ... 0 + * a a^2 ... 0 + * ... ... ... ... + * ... ... ... instance + * ... ... ... a + * ... ... ... a + * ... ... ... 0 + * ] + */ + + meta.lookup("lookup", |meta| { + let a_ = meta.query_any(a, Rotation::cur()); + vec![(a_, sl)] + }); + + // Add to test mvlookup + meta.lookup("lookup_same", |meta| { + let a_ = meta.query_any(a, Rotation::cur()); + vec![(a_, sl)] + }); + + meta.lookup("lookup_same", |meta| { + let b_ = meta.query_any(b, Rotation::cur()); + let dummy = meta.query_selector(dummy); + let dummy_2 = meta.query_selector(dummy_2); + let dummy_3 = meta.query_selector(dummy_3); + + vec![(dummy * dummy_2 * dummy_3 * b_, dummy_table)] + }); + + meta.create_gate("Combined add-mult", |meta| { + let d = meta.query_advice(d, Rotation::next()); + let a = meta.query_advice(a, Rotation::cur()); + let sf = meta.query_fixed(sf, Rotation::cur()); + let e = meta.query_advice(e, Rotation::prev()); + let b = meta.query_advice(b, Rotation::cur()); + let c = meta.query_advice(c, Rotation::cur()); + + let sa = meta.query_fixed(sa, Rotation::cur()); + let sb = meta.query_fixed(sb, Rotation::cur()); + let sc = meta.query_fixed(sc, Rotation::cur()); + let sm = meta.query_fixed(sm, Rotation::cur()); + + vec![a.clone() * sa + b.clone() * sb + a * b * sm - (c * sc) + sf * (d * e)] + }); + + meta.create_gate("Public input", |meta| { + let a = meta.query_advice(a, Rotation::cur()); + let p = meta.query_instance(p, Rotation::cur()); + let sp = meta.query_fixed(sp, Rotation::cur()); + + vec![sp * (a - p)] + }); + + meta.enable_equality(sf); + meta.enable_equality(e); + meta.enable_equality(d); + meta.enable_equality(p); + meta.enable_equality(sm); + meta.enable_equality(sa); + meta.enable_equality(sb); + meta.enable_equality(sc); + meta.enable_equality(sp); + + PlonkConfig { + a, + b, + c, + d, + e, + sa, + sb, + sc, + sm, + sp, + sl, + } } +} - #[derive(Clone)] - struct MyCircuit { - a: Value, - lookup_table: Vec, - } +#[allow(clippy::type_complexity)] +trait StandardCs { + fn raw_multiply( + &self, + layouter: &mut impl Layouter, + f: F, + ) -> Result<(Cell, Cell, Cell), Error> + where + F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; + fn raw_add( + &self, + layouter: &mut impl Layouter, + f: F, + ) -> Result<(Cell, Cell, Cell), Error> + where + F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; + fn copy(&self, layouter: &mut impl Layouter, a: Cell, b: Cell) -> Result<(), Error>; + fn public_input(&self, layouter: &mut impl Layouter, f: F) -> Result + where + F: FnMut() -> Value; + fn lookup_table(&self, layouter: &mut impl Layouter, values: &[FF]) -> Result<(), Error>; +} - struct StandardPlonk { - config: PlonkConfig, - _marker: PhantomData, - } +struct StandardPlonk { + config: PlonkConfig, + _marker: PhantomData, +} - impl StandardPlonk { - fn new(config: PlonkConfig) -> Self { - StandardPlonk { - config, - _marker: PhantomData, - } +impl StandardPlonk { + fn new(config: PlonkConfig) -> Self { + StandardPlonk { + config, + _marker: PhantomData, } } +} +impl StandardCs for StandardPlonk { + fn raw_multiply( + &self, + layouter: &mut impl Layouter, + mut f: F, + ) -> Result<(Cell, Cell, Cell), Error> + where + F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, + { + layouter.assign_region( + || "raw_multiply", + |mut region| { + let mut value = None; + let lhs = region.assign_advice( + || "lhs", + self.config.a, + 0, + || { + value = Some(f()); + value.unwrap().map(|v| v.0) + }, + )?; + region.assign_advice( + || "lhs^4", + self.config.d, + 0, + || value.unwrap().map(|v| v.0).square().square(), + )?; + let rhs = region.assign_advice( + || "rhs", + self.config.b, + 0, + || value.unwrap().map(|v| v.1), + )?; + region.assign_advice( + || "rhs^4", + self.config.e, + 0, + || value.unwrap().map(|v| v.1).square().square(), + )?; + let out = region.assign_advice( + || "out", + self.config.c, + 0, + || value.unwrap().map(|v| v.2), + )?; + + region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::ZERO))?; + region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::ZERO))?; + region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::ONE))?; + region.assign_fixed(|| "a * b", self.config.sm, 0, || Value::known(FF::ONE))?; + Ok((lhs.cell(), rhs.cell(), out.cell())) + }, + ) + } + fn raw_add( + &self, + layouter: &mut impl Layouter, + mut f: F, + ) -> Result<(Cell, Cell, Cell), Error> + where + F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, + { + layouter.assign_region( + || "raw_add", + |mut region| { + let mut value = None; + let lhs = region.assign_advice( + || "lhs", + self.config.a, + 0, + || { + value = Some(f()); + value.unwrap().map(|v| v.0) + }, + )?; + region.assign_advice( + || "lhs^4", + self.config.d, + 0, + || value.unwrap().map(|v| v.0).square().square(), + )?; + let rhs = region.assign_advice( + || "rhs", + self.config.b, + 0, + || value.unwrap().map(|v| v.1), + )?; + region.assign_advice( + || "rhs^4", + self.config.e, + 0, + || value.unwrap().map(|v| v.1).square().square(), + )?; + let out = region.assign_advice( + || "out", + self.config.c, + 0, + || value.unwrap().map(|v| v.2), + )?; + + region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::ONE))?; + region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::ONE))?; + region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::ONE))?; + region.assign_fixed(|| "a * b", self.config.sm, 0, || Value::known(FF::ZERO))?; + Ok((lhs.cell(), rhs.cell(), out.cell())) + }, + ) + } + fn copy(&self, layouter: &mut impl Layouter, left: Cell, right: Cell) -> Result<(), Error> { + layouter.assign_region( + || "copy", + |mut region| { + region.constrain_equal(left, right)?; + region.constrain_equal(left, right) + }, + ) + } + fn public_input(&self, layouter: &mut impl Layouter, mut f: F) -> Result + where + F: FnMut() -> Value, + { + layouter.assign_region( + || "public_input", + |mut region| { + let value = region.assign_advice(|| "value", self.config.a, 0, &mut f)?; + region.assign_fixed(|| "public", self.config.sp, 0, || Value::known(FF::ONE))?; - impl StandardCs for StandardPlonk { - fn raw_multiply( - &self, - layouter: &mut impl Layouter, - mut f: F, - ) -> Result<(Cell, Cell, Cell), Error> - where - F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, - { - layouter.assign_region( - || "raw_multiply", - |mut region| { - let mut value = None; - let lhs = region.assign_advice( - || "lhs", - self.config.a, - 0, - || { - value = Some(f()); - value.unwrap().map(|v| v.0) - }, - )?; - region.assign_advice( - || "lhs^4", - self.config.d, - 0, - || value.unwrap().map(|v| v.0).square().square(), - )?; - let rhs = region.assign_advice( - || "rhs", - self.config.b, - 0, - || value.unwrap().map(|v| v.1), - )?; - region.assign_advice( - || "rhs^4", - self.config.e, - 0, - || value.unwrap().map(|v| v.1).square().square(), - )?; - let out = region.assign_advice( - || "out", - self.config.c, - 0, - || value.unwrap().map(|v| v.2), + Ok(value.cell()) + }, + ) + } + fn lookup_table(&self, layouter: &mut impl Layouter, values: &[FF]) -> Result<(), Error> { + layouter.assign_table( + || "", + |mut table| { + for (index, &value) in values.iter().enumerate() { + table.assign_cell( + || "table col", + self.config.sl, + index, + || Value::known(value), )?; + } + Ok(()) + }, + )?; + Ok(()) + } +} - region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::ZERO))?; - region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::ZERO))?; - region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::ONE))?; - region.assign_fixed(|| "a * b", self.config.sm, 0, || Value::known(FF::ONE))?; - Ok((lhs.cell(), rhs.cell(), out.cell())) - }, - ) - } - fn raw_add( - &self, - layouter: &mut impl Layouter, - mut f: F, - ) -> Result<(Cell, Cell, Cell), Error> - where - F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, - { - layouter.assign_region( - || "raw_add", - |mut region| { - let mut value = None; - let lhs = region.assign_advice( - || "lhs", - self.config.a, - 0, - || { - value = Some(f()); - value.unwrap().map(|v| v.0) - }, - )?; - region.assign_advice( - || "lhs^4", - self.config.d, - 0, - || value.unwrap().map(|v| v.0).square().square(), - )?; - let rhs = region.assign_advice( - || "rhs", - self.config.b, - 0, - || value.unwrap().map(|v| v.1), - )?; - region.assign_advice( - || "rhs^4", - self.config.e, - 0, - || value.unwrap().map(|v| v.1).square().square(), - )?; - let out = region.assign_advice( - || "out", - self.config.c, - 0, - || value.unwrap().map(|v| v.2), - )?; +macro_rules! common { + ($scheme:ident) => {{ + let a = <$scheme as CommitmentScheme>::Scalar::from(2834758237) + * <$scheme as CommitmentScheme>::Scalar::ZETA; + let instance = + <$scheme as CommitmentScheme>::Scalar::ONE + <$scheme as CommitmentScheme>::Scalar::ONE; + let lookup_table = vec![instance, a, a, <$scheme as CommitmentScheme>::Scalar::ZERO]; + (a, instance, lookup_table) + }}; +} - region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::ONE))?; - region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::ONE))?; - region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::ONE))?; - region.assign_fixed( - || "a * b", - self.config.sm, - 0, - || Value::known(FF::ZERO), - )?; - Ok((lhs.cell(), rhs.cell(), out.cell())) - }, - ) - } - fn copy( - &self, - layouter: &mut impl Layouter, - left: Cell, - right: Cell, - ) -> Result<(), Error> { - layouter.assign_region( - || "copy", - |mut region| { - region.constrain_equal(left, right)?; - region.constrain_equal(left, right) - }, - ) - } - fn public_input(&self, layouter: &mut impl Layouter, mut f: F) -> Result - where - F: FnMut() -> Value, - { - layouter.assign_region( - || "public_input", - |mut region| { - let value = region.assign_advice(|| "value", self.config.a, 0, &mut f)?; - region.assign_fixed( - || "public", - self.config.sp, - 0, - || Value::known(FF::ONE), - )?; +fn verify_proof< + 'a, + 'params, + Scheme: CommitmentScheme, + V: Verifier<'params, Scheme>, + E: EncodedChallenge, + T: TranscriptReadBuffer<&'a [u8], Scheme::Curve, E>, + Strategy: VerificationStrategy<'params, Scheme, V, Output = Strategy>, +>( + params_verifier: &'params Scheme::ParamsVerifier, + vk: &VerifyingKey, + proof: &'a [u8], +) where + Scheme::Scalar: Ord + WithSmallOrderMulGroup<3> + FromUniformBytes<64>, +{ + let (_, instance, _) = common!(Scheme); + let pubinputs = [instance]; + + let mut transcript = T::init(proof); + + let strategy = Strategy::new(params_verifier); + let strategy = verify_plonk_proof( + params_verifier, + vk, + strategy, + &[&[&pubinputs[..]], &[&pubinputs[..]]], + &mut transcript, + ) + .unwrap(); + + assert!(strategy.finalize()); +} - Ok(value.cell()) - }, - ) - } - fn lookup_table( - &self, - layouter: &mut impl Layouter, - values: &[FF], - ) -> Result<(), Error> { - layouter.assign_table( - || "", - |mut table| { - for (index, &value) in values.iter().enumerate() { - table.assign_cell( - || "table col", - self.config.sl, - index, - || Value::known(value), - )?; - } - Ok(()) - }, - )?; - Ok(()) - } +#[test] +fn plonk_api() { + const K: u32 = 17; + + #[derive(Clone)] + struct MyCircuit { + a: Value, + lookup_table: Vec, } impl Circuit for MyCircuit { @@ -469,17 +607,6 @@ fn plonk_api() { } } - macro_rules! common { - ($scheme:ident) => {{ - let a = <$scheme as CommitmentScheme>::Scalar::from(2834758237) - * <$scheme as CommitmentScheme>::Scalar::ZETA; - let instance = <$scheme as CommitmentScheme>::Scalar::ONE - + <$scheme as CommitmentScheme>::Scalar::ONE; - let lookup_table = vec![instance, a, a, <$scheme as CommitmentScheme>::Scalar::ZERO]; - (a, instance, lookup_table) - }}; - } - /* macro_rules! bad_keys { ($scheme:ident) => {{ @@ -569,39 +696,6 @@ fn plonk_api() { transcript.finalize() } - fn verify_proof< - 'a, - 'params, - Scheme: CommitmentScheme, - V: Verifier<'params, Scheme>, - E: EncodedChallenge, - T: TranscriptReadBuffer<&'a [u8], Scheme::Curve, E>, - Strategy: VerificationStrategy<'params, Scheme, V, Output = Strategy>, - >( - params_verifier: &'params Scheme::ParamsVerifier, - vk: &VerifyingKey, - proof: &'a [u8], - ) where - Scheme::Scalar: Ord + WithSmallOrderMulGroup<3> + FromUniformBytes<64>, - { - let (_, instance, _) = common!(Scheme); - let pubinputs = [instance]; - - let mut transcript = T::init(proof); - - let strategy = Strategy::new(params_verifier); - let strategy = verify_plonk_proof( - params_verifier, - vk, - strategy, - &[&[&pubinputs[..]], &[&pubinputs[..]]], - &mut transcript, - ) - .unwrap(); - - assert!(strategy.finalize()); - } - fn test_plonk_api_gwc() { use halo2_proofs::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; use halo2_proofs::poly::kzg::multiopen::{ProverGWC, VerifierGWC}; @@ -675,6 +769,7 @@ fn plonk_api() { >(verifier_params, pk.get_vk(), &proof[..]); } + #[allow(unused)] fn test_plonk_api_ipa() { use halo2_proofs::poly::ipa::commitment::{IPACommitmentScheme, ParamsIPA}; use halo2_proofs::poly::ipa::multiopen::{ProverIPA, VerifierIPA}; @@ -1109,8 +1204,224 @@ fn plonk_api() { */ } - env_logger::init(); - test_plonk_api_ipa(); + let _logger_err = env_logger::try_init(); + // TODO: fix the ipa test + // test_plonk_api_ipa(); test_plonk_api_gwc(); test_plonk_api_shplonk(); } + +#[test] +fn plonk_api_with_many_subregions() { + #[derive(Clone)] + struct MyCircuit { + a: Value, + lookup_table: Vec, + } + + impl Circuit for MyCircuit { + type Config = PlonkConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self { + a: Value::unknown(), + lookup_table: self.lookup_table.clone(), + } + } + + fn configure(meta: &mut ConstraintSystem) -> PlonkConfig { + PlonkConfig::construct(meta) + } + + fn synthesize( + &self, + config: PlonkConfig, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let cs = StandardPlonk::new(config); + + let _ = cs.public_input(&mut layouter, || Value::known(F::ONE + F::ONE))?; + + let a: Value> = self.a.into(); + let parallel_regions_time = Instant::now(); + #[cfg(feature = "parallel_syn")] + layouter.assign_regions( + || "regions", + (0..(1 << 14)) + .into_iter() + .map(|_| { + let mut is_first_pass = true; + move |mut region: Region<'_, F>| -> Result<(), Error> { + let n = 1 << 1; + for i in 0..n { + // skip the assign of rows except the last row in the first pass + if is_first_pass && i < n - 1 { + is_first_pass = false; + continue; + } + let a0 = + region.assign_advice(|| "config.a", cs.config.a, i, || a)?; + let a1 = + region.assign_advice(|| "config.b", cs.config.b, i, || a)?; + region.assign_advice( + || "config.c", + cs.config.c, + i, + || a.double(), + )?; + + region.assign_fixed( + || "a", + cs.config.sa, + i, + || Value::known(F::ONE), + )?; + region.assign_fixed( + || "b", + cs.config.sb, + i, + || Value::known(F::ONE), + )?; + region.assign_fixed( + || "c", + cs.config.sc, + i, + || Value::known(F::ONE), + )?; + region.assign_fixed( + || "a * b", + cs.config.sm, + i, + || Value::known(F::ZERO), + )?; + + region.constrain_equal(a0.cell(), a1.cell())?; + } + is_first_pass = false; + Ok(()) + } + }) + .collect(), + )?; + log::info!( + "parallel_regions assign took {:?}", + parallel_regions_time.elapsed() + ); + + for _ in 0..10 { + let a: Value> = self.a.into(); + let mut a_squared = Value::unknown(); + let (a0, _, c0) = cs.raw_multiply(&mut layouter, || { + a_squared = a.square(); + a.zip(a_squared).map(|(a, a_squared)| (a, a, a_squared)) + })?; + let (a1, b1, _) = cs.raw_add(&mut layouter, || { + let fin = a_squared + a; + a.zip(a_squared) + .zip(fin) + .map(|((a, a_squared), fin)| (a, a_squared, fin)) + })?; + cs.copy(&mut layouter, a0, a1)?; + cs.copy(&mut layouter, b1, c0)?; + } + + cs.lookup_table(&mut layouter, &self.lookup_table)?; + + Ok(()) + } + } + fn keygen(params: &Scheme::ParamsProver) -> ProvingKey + where + Scheme::Scalar: Ord + WithSmallOrderMulGroup<3> + FromUniformBytes<64>, + { + let (_, _, lookup_table) = common!(Scheme); + let empty_circuit: MyCircuit = MyCircuit { + a: Value::unknown(), + lookup_table, + }; + + // Initialize the proving key + let vk = keygen_vk(params, &empty_circuit).expect("keygen_vk should not fail"); + log::info!("keygen vk succeed"); + + let pk = keygen_pk(params, vk, &empty_circuit).expect("keygen_pk should not fail"); + log::info!("keygen pk succeed"); + + pk + } + + fn create_proof< + 'params, + Scheme: CommitmentScheme, + P: Prover<'params, Scheme>, + E: EncodedChallenge, + R: RngCore, + T: TranscriptWriterBuffer, Scheme::Curve, E>, + >( + rng: R, + params: &'params Scheme::ParamsProver, + pk: &ProvingKey, + ) -> Vec + where + Scheme::Scalar: Ord + WithSmallOrderMulGroup<3> + FromUniformBytes<64>, + { + let (a, instance, lookup_table) = common!(Scheme); + + let circuit: MyCircuit = MyCircuit { + a: Value::known(a), + lookup_table, + }; + + let mut transcript = T::init(vec![]); + + create_plonk_proof::( + params, + pk, + &[circuit.clone(), circuit], + &[&[&[instance]], &[&[instance]]], + rng, + &mut transcript, + ) + .expect("proof generation should not fail"); + + transcript.finalize() + } + + const K: u32 = 17; + type Scheme = KZGCommitmentScheme; + // bad_keys!(Scheme); + + let _logger_err = env_logger::try_init(); + let (a, instance, lookup_table) = common!(Scheme); + + let circuit: MyCircuit<::Scalar> = MyCircuit { + a: Value::known(a), + lookup_table, + }; + + // Check this circuit is satisfied. + let prover = match MockProver::run(K, &circuit, vec![vec![instance]]) { + Ok(prover) => prover, + Err(e) => panic!("{:?}", e), + }; + assert_eq!(prover.verify_par(), Ok(())); + log::info!("mock proving succeed!"); + + let params = ParamsKZG::::new(K); + let rng = OsRng; + + let pk = keygen::>(¶ms); + + let proof = create_proof::<_, ProverGWC<_>, _, _, Blake2bWrite<_, _, Challenge255<_>>>( + rng, ¶ms, &pk, + ); + + let verifier_params = params.verifier_params(); + + verify_proof::<_, VerifierGWC<_>, _, Blake2bRead<_, _, Challenge255<_>>, AccumulatorStrategy<_>>( + verifier_params, + pk.get_vk(), + &proof[..], + ); +}