diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 354ec6ab..f1b1bddd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,33 +18,48 @@ jobs: - name: Build run: cargo build --verbose - name: Run halo2-base tests + working-directory: "halo2-base" run: | - cd halo2-base - cargo test -- --test-threads=1 - cd .. - - name: Run halo2-ecc tests MockProver + cargo test + - name: Run halo2-ecc tests (mock prover) + working-directory: "halo2-ecc" run: | - cd halo2-ecc - cargo test -- --test-threads=1 test_fp - cargo test -- test_ecc - cargo test -- test_secp - cargo test -- test_ecdsa - cargo test -- test_ec_add - cargo test -- test_fixed - cargo test -- test_msm - cargo test -- test_fb - cargo test -- test_pairing - cd .. - - name: Run halo2-ecc tests real prover + cargo test --lib -- --skip bench --test-threads=2 + - name: Run halo2-ecc tests (real prover) + working-directory: "halo2-ecc" run: | - cd halo2-ecc - cargo test --release -- test_fp_assert_eq + mv configs/bn254/bench_fixed_msm.t.config configs/bn254/bench_fixed_msm.config + mv configs/bn254/bench_msm.t.config configs/bn254/bench_msm.config + mv configs/bn254/bench_pairing.t.config configs/bn254/bench_pairing.config cargo test --release -- --nocapture bench_secp256k1_ecdsa cargo test --release -- --nocapture bench_ec_add - mv configs/bn254/bench_fixed_msm.t.config configs/bn254/bench_fixed_msm.config cargo test --release -- --nocapture bench_fixed_base_msm - mv configs/bn254/bench_msm.t.config configs/bn254/bench_msm.config cargo test --release -- --nocapture bench_msm - mv configs/bn254/bench_pairing.t.config configs/bn254/bench_pairing.config cargo test --release -- --nocapture bench_pairing - cd .. + - name: Run zkevm-keccak tests + working-directory: "hashes/zkevm-keccak" + run: | + cargo test + + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Install toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + override: false + components: rustfmt, clippy + + - uses: Swatinem/rust-cache@v1 + with: + cache-on-failure: true + + - name: Run fmt + run: cargo fmt --all -- --check + + - name: Run clippy + run: cargo clippy --all -- -D warnings diff --git a/halo2-base/Cargo.toml b/halo2-base/Cargo.toml index 66c26e91..3c568313 100644 --- a/halo2-base/Cargo.toml +++ b/halo2-base/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "halo2-base" -version = "0.3.0" +version = "0.3.1" edition = "2021" [dependencies] diff --git a/halo2-base/benches/inner_product.rs b/halo2-base/benches/inner_product.rs index 9454faa3..71702bc0 100644 --- a/halo2-base/benches/inner_product.rs +++ b/halo2-base/benches/inner_product.rs @@ -1,6 +1,6 @@ #![allow(unused_imports)] #![allow(unused_variables)] -use halo2_base::gates::builder::{GateCircuitBuilder, GateThreadBuilder}; +use halo2_base::gates::builder::{GateCircuitBuilder, GateThreadBuilder, RangeCircuitBuilder}; use halo2_base::gates::flex_gate::{FlexGateConfig, GateChip, GateInstructions, GateStrategy}; use halo2_base::halo2_proofs::{ arithmetic::Field, @@ -50,7 +50,7 @@ fn bench(c: &mut Criterion) { let mut builder = GateThreadBuilder::new(false); inner_prod_bench(builder.main(0), vec![Fr::zero(); 5], vec![Fr::zero(); 5]); builder.config(k as usize, Some(20)); - let circuit = GateCircuitBuilder::mock(builder); + let circuit = RangeCircuitBuilder::mock(builder); // check the circuit is correct just in case MockProver::run(k, &circuit, vec![]).unwrap().assert_satisfied(); @@ -59,7 +59,7 @@ fn bench(c: &mut Criterion) { let vk = keygen_vk(¶ms, &circuit).expect("vk should not fail"); let pk = keygen_pk(¶ms, vk, &circuit).expect("pk should not fail"); - let break_points = circuit.break_points.take(); + let break_points = circuit.0.break_points.take(); drop(circuit); let mut group = c.benchmark_group("plonk-prover"); @@ -73,7 +73,7 @@ fn bench(c: &mut Criterion) { let a = (0..5).map(|_| Fr::random(OsRng)).collect_vec(); let b = (0..5).map(|_| Fr::random(OsRng)).collect_vec(); inner_prod_bench(builder.main(0), a, b); - let circuit = GateCircuitBuilder::prover(builder, break_points.clone()); + let circuit = RangeCircuitBuilder::prover(builder, break_points.clone()); let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); create_proof::< diff --git a/halo2-base/benches/mul.rs b/halo2-base/benches/mul.rs index 16687e08..1099db67 100644 --- a/halo2-base/benches/mul.rs +++ b/halo2-base/benches/mul.rs @@ -1,5 +1,5 @@ use ff::Field; -use halo2_base::gates::builder::{GateCircuitBuilder, GateThreadBuilder}; +use halo2_base::gates::builder::{GateThreadBuilder, RangeCircuitBuilder}; use halo2_base::gates::flex_gate::{GateChip, GateInstructions}; use halo2_base::halo2_proofs::{ halo2curves::bn256::{Bn256, Fr, G1Affine}, @@ -37,13 +37,13 @@ fn bench(c: &mut Criterion) { let mut builder = GateThreadBuilder::new(false); mul_bench(builder.main(0), [Fr::zero(); 2]); builder.config(K as usize, Some(9)); - let circuit = GateCircuitBuilder::keygen(builder); + let circuit = RangeCircuitBuilder::keygen(builder); let params = ParamsKZG::::setup(K, OsRng); let vk = keygen_vk(¶ms, &circuit).expect("vk should not fail"); let pk = keygen_pk(¶ms, vk, &circuit).expect("pk should not fail"); - let break_points = circuit.break_points.take(); + let break_points = circuit.0.break_points.take(); let a = Fr::random(OsRng); let b = Fr::random(OsRng); @@ -56,7 +56,7 @@ fn bench(c: &mut Criterion) { let mut builder = GateThreadBuilder::new(true); // do the computation mul_bench(builder.main(0), inputs); - let circuit = GateCircuitBuilder::prover(builder, break_points.clone()); + let circuit = RangeCircuitBuilder::prover(builder, break_points.clone()); let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); create_proof::< diff --git a/halo2-base/examples/inner_product.rs b/halo2-base/examples/inner_product.rs index 8572817e..585a8b78 100644 --- a/halo2-base/examples/inner_product.rs +++ b/halo2-base/examples/inner_product.rs @@ -1,6 +1,6 @@ #![allow(unused_imports)] #![allow(unused_variables)] -use halo2_base::gates::builder::{GateCircuitBuilder, GateThreadBuilder}; +use halo2_base::gates::builder::{GateThreadBuilder, RangeCircuitBuilder}; use halo2_base::gates::flex_gate::{FlexGateConfig, GateChip, GateInstructions, GateStrategy}; use halo2_base::halo2_proofs::{ arithmetic::Field, @@ -53,7 +53,7 @@ fn main() { let mut builder = GateThreadBuilder::new(false); inner_prod_bench(builder.main(0), vec![Fr::zero(); 5], vec![Fr::zero(); 5]); builder.config(k as usize, Some(20)); - let circuit = GateCircuitBuilder::mock(builder); + let circuit = RangeCircuitBuilder::mock(builder); // check the circuit is correct just in case MockProver::run(k, &circuit, vec![]).unwrap().assert_satisfied(); @@ -62,13 +62,13 @@ fn main() { let vk = keygen_vk(¶ms, &circuit).expect("vk should not fail"); let pk = keygen_pk(¶ms, vk, &circuit).expect("pk should not fail"); - let break_points = circuit.break_points.take(); + let break_points = circuit.0.break_points.take(); let mut builder = GateThreadBuilder::new(true); let a = (0..5).map(|_| Fr::random(OsRng)).collect_vec(); let b = (0..5).map(|_| Fr::random(OsRng)).collect_vec(); inner_prod_bench(builder.main(0), a, b); - let circuit = GateCircuitBuilder::prover(builder, break_points); + let circuit = RangeCircuitBuilder::prover(builder, break_points); let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); create_proof::< diff --git a/halo2-base/src/gates/builder.rs b/halo2-base/src/gates/builder/mod.rs similarity index 90% rename from halo2-base/src/gates/builder.rs rename to halo2-base/src/gates/builder/mod.rs index 35da9642..ed20fa47 100644 --- a/halo2-base/src/gates/builder.rs +++ b/halo2-base/src/gates/builder/mod.rs @@ -1,6 +1,6 @@ use super::{ flex_gate::{FlexGateConfig, GateStrategy, MAX_PHASE}, - range::{RangeConfig, RangeStrategy}, + range::BaseConfig, }; use crate::{ halo2_proofs::{ @@ -14,7 +14,6 @@ use serde::{Deserialize, Serialize}; use std::{ cell::RefCell, collections::{HashMap, HashSet}, - env::{set_var, var}, }; mod parallelize; @@ -25,6 +24,16 @@ pub type ThreadBreakPoints = Vec; /// Vector of vectors tracking the thread break points across different halo2 phases pub type MultiPhaseThreadBreakPoints = Vec; +thread_local! { + /// This is used as a thread-safe way to auto-configure a circuit's shape and then pass the configuration to `Circuit::configure`. + pub static BASE_CONFIG_PARAMS: RefCell = RefCell::new(Default::default()); +} + +/// Sets the thread-local number of bits to be range checkable via a lookup table with entries [0, 2lookup_bits) +pub fn set_lookup_bits(lookup_bits: usize) { + BASE_CONFIG_PARAMS.with(|conf| conf.borrow_mut().lookup_bits = Some(lookup_bits)); +} + /// Stores the cell values loaded during the Keygen phase of a halo2 proof and breakpoints for multi-threading #[derive(Clone, Debug, Default)] pub struct KeygenAssignments { @@ -134,7 +143,7 @@ impl GateThreadBuilder { /// /// * `k`: The number of in the circuit (i.e. numeber of rows = 2k) /// * `minimum_rows`: The minimum number of rows in the circuit that cannot be used for witness assignments and contain random `blinding factors` to ensure zk property, defaults to 0. - pub fn config(&self, k: usize, minimum_rows: Option) -> FlexGateConfigParams { + pub fn config(&self, k: usize, minimum_rows: Option) -> BaseConfigParams { let max_rows = (1 << k) - minimum_rows.unwrap_or(0); let total_advice_per_phase = self .threads @@ -164,13 +173,18 @@ impl GateThreadBuilder { .len(); let num_fixed = (total_fixed + (1 << k) - 1) >> k; - let params = FlexGateConfigParams { + let mut params = BaseConfigParams { strategy: GateStrategy::Vertical, num_advice_per_phase, num_lookup_advice_per_phase, num_fixed, k, + lookup_bits: None, }; + BASE_CONFIG_PARAMS.with(|conf| { + params.lookup_bits = conf.borrow().lookup_bits; + *conf.borrow_mut() = params.clone(); + }); #[cfg(feature = "display")] { for phase in 0..MAX_PHASE { @@ -184,7 +198,6 @@ impl GateThreadBuilder { println!("Total {total_fixed} fixed cells"); log::info!("Auto-calculated config params:\n {params:#?}"); } - set_var("FLEX_GATE_CONFIG_PARAMS", serde_json::to_string(¶ms).unwrap()); params } @@ -453,12 +466,13 @@ pub fn assign_threads_in( } } -/// A Config struct defining the parameters for a FlexGate circuit. -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct FlexGateConfigParams { +/// A Config struct defining the parameters for a halo2-base circuit +/// - this is used to configure either FlexGateConfig or RangeConfig. +#[derive(Clone, Default, Debug, Serialize, Deserialize)] +pub struct BaseConfigParams { /// The gate strategy used for the advice column of the circuit and applied at every row. pub strategy: GateStrategy, - /// Security parameter `k` used for the keygen. + /// Specifies the number of rows in the circuit to be 2k pub k: usize, /// The number of advice columns per phase pub num_advice_per_phase: Vec, @@ -466,6 +480,9 @@ pub struct FlexGateConfigParams { pub num_lookup_advice_per_phase: Vec, /// The number of fixed columns per phase pub num_fixed: usize, + /// The number of bits that can be ranged checked using a special lookup table with values [0, 2lookup_bits), if using. + /// This is `None` if no lookup table is used. + pub lookup_bits: Option, } /// A wrapper struct to auto-build a circuit from a `GateThreadBuilder`. @@ -563,38 +580,6 @@ impl GateCircuitBuilder { } } -impl Circuit for GateCircuitBuilder { - type Config = FlexGateConfig; - type FloorPlanner = SimpleFloorPlanner; - - /// Creates a new instance of the circuit without withnesses filled in. - fn without_witnesses(&self) -> Self { - unimplemented!() - } - - /// Configures a new circuit using the the parameters specified [Config]. - fn configure(meta: &mut ConstraintSystem) -> FlexGateConfig { - let FlexGateConfigParams { - strategy, - num_advice_per_phase, - num_lookup_advice_per_phase: _, - num_fixed, - k, - } = serde_json::from_str(&var("FLEX_GATE_CONFIG_PARAMS").unwrap()).unwrap(); - FlexGateConfig::configure(meta, strategy, &num_advice_per_phase, num_fixed, k) - } - - /// Performs the actual computation on the circuit (e.g., witness generation), filling in all the advice values for a particular proof. - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - self.sub_synthesize(&config, &[], &[], &mut layouter); - Ok(()) - } -} - /// A wrapper struct to auto-build a circuit from a `GateThreadBuilder`. #[derive(Clone, Debug)] pub struct RangeCircuitBuilder(pub GateCircuitBuilder); @@ -620,7 +605,7 @@ impl RangeCircuitBuilder { } impl Circuit for RangeCircuitBuilder { - type Config = RangeConfig; + type Config = BaseConfig; type FloorPlanner = SimpleFloorPlanner; /// Creates a new instance of the [RangeCircuitBuilder] without witnesses by setting the witness_gen_only flag to false @@ -628,28 +613,12 @@ impl Circuit for RangeCircuitBuilder { unimplemented!() } - /// Configures a new circuit using the the parameters specified [Config] and environment variable `LOOKUP_BITS`. + /// Configures a new circuit using [`BaseConfigParams`] fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let FlexGateConfigParams { - strategy, - num_advice_per_phase, - num_lookup_advice_per_phase, - num_fixed, - k, - } = serde_json::from_str(&var("FLEX_GATE_CONFIG_PARAMS").unwrap()).unwrap(); - let strategy = match strategy { - GateStrategy::Vertical => RangeStrategy::Vertical, - }; - let lookup_bits = var("LOOKUP_BITS").unwrap_or_else(|_| "0".to_string()).parse().unwrap(); - RangeConfig::configure( - meta, - strategy, - &num_advice_per_phase, - &num_lookup_advice_per_phase, - num_fixed, - lookup_bits, - k, - ) + let params = BASE_CONFIG_PARAMS + .try_with(|config| config.borrow().clone()) + .expect("You need to call config() to configure the halo2-base circuit shape first"); + BaseConfig::configure(meta, params) } /// Performs the actual computation on the circuit (e.g., witness generation), populating the lookup table and filling in all the advice values for a particular proof. @@ -659,21 +628,24 @@ impl Circuit for RangeCircuitBuilder { mut layouter: impl Layouter, ) -> Result<(), Error> { // only load lookup table if we are actually doing lookups - if config.lookup_advice.iter().map(|a| a.len()).sum::() != 0 - || !config.q_lookup.iter().all(|q| q.is_none()) - { + if let BaseConfig::WithRange(config) = &config { config.load_lookup_table(&mut layouter).expect("load lookup table should not fail"); } - self.0.sub_synthesize(&config.gate, &config.lookup_advice, &config.q_lookup, &mut layouter); + self.0.sub_synthesize( + config.gate(), + config.lookup_advice(), + config.q_lookup(), + &mut layouter, + ); Ok(()) } } -/// Configuration with [`RangeConfig`] and a single public instance column. +/// Configuration with [`BaseConfig`] and a single public instance column. #[derive(Clone, Debug)] -pub struct RangeWithInstanceConfig { +pub struct PublicBaseConfig { /// The underlying range configuration - pub range: RangeConfig, + pub base: BaseConfig, /// The public instance column pub instance: Column, } @@ -719,7 +691,7 @@ impl RangeWithInstanceCircuitBuilder { } /// Calls [`GateThreadBuilder::config`] - pub fn config(&self, k: u32, minimum_rows: Option) -> FlexGateConfigParams { + pub fn config(&self, k: u32, minimum_rows: Option) -> BaseConfigParams { self.circuit.0.builder.borrow().config(k as usize, minimum_rows) } @@ -740,7 +712,7 @@ impl RangeWithInstanceCircuitBuilder { } impl Circuit for RangeWithInstanceCircuitBuilder { - type Config = RangeWithInstanceConfig; + type Config = PublicBaseConfig; type FloorPlanner = SimpleFloorPlanner; fn without_witnesses(&self) -> Self { @@ -748,10 +720,10 @@ impl Circuit for RangeWithInstanceCircuitBuilder { } fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let range = RangeCircuitBuilder::configure(meta); + let base = RangeCircuitBuilder::configure(meta); let instance = meta.instance_column(); meta.enable_equality(instance); - RangeWithInstanceConfig { range, instance } + PublicBaseConfig { base, instance } } fn synthesize( @@ -760,20 +732,19 @@ impl Circuit for RangeWithInstanceCircuitBuilder { mut layouter: impl Layouter, ) -> Result<(), Error> { // copied from RangeCircuitBuilder::synthesize but with extra logic to expose public instances - let range = config.range; + let instance_col = config.instance; + let config = config.base; let circuit = &self.circuit.0; // only load lookup table if we are actually doing lookups - if range.lookup_advice.iter().map(|a| a.len()).sum::() != 0 - || !range.q_lookup.iter().all(|q| q.is_none()) - { - range.load_lookup_table(&mut layouter).expect("load lookup table should not fail"); + if let BaseConfig::WithRange(config) = &config { + config.load_lookup_table(&mut layouter).expect("load lookup table should not fail"); } // we later `take` the builder, so we need to save this value let witness_gen_only = circuit.builder.borrow().witness_gen_only(); let assigned_advices = circuit.sub_synthesize( - &range.gate, - &range.lookup_advice, - &range.q_lookup, + config.gate(), + config.lookup_advice(), + config.q_lookup(), &mut layouter, ); @@ -785,7 +756,7 @@ impl Circuit for RangeWithInstanceCircuitBuilder { let (cell, _) = assigned_advices .get(&(cell.context_id, cell.offset)) .expect("instance not assigned"); - layouter.constrain_instance(*cell, config.instance, i); + layouter.constrain_instance(*cell, instance_col, i); } } Ok(()) diff --git a/halo2-base/src/gates/flex_gate.rs b/halo2-base/src/gates/flex_gate.rs index 1907521e..25b0da24 100644 --- a/halo2-base/src/gates/flex_gate.rs +++ b/halo2-base/src/gates/flex_gate.rs @@ -20,7 +20,7 @@ use std::{ pub const MAX_PHASE: usize = 3; /// Specifies the gate strategy for the gate chip -#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)] pub enum GateStrategy { /// # Vertical Gate Strategy: /// `q_0 * (a + b * c - d) = 0` @@ -29,6 +29,7 @@ pub enum GateStrategy { /// * q = q_enable[0] /// * q is either 0 or 1 so this is just a simple selector /// We chose `a + b * c` instead of `a * b + c` to allow "chaining" of gates, i.e., the output of one gate because `a` in the next gate. + #[default] Vertical, } diff --git a/halo2-base/src/gates/range.rs b/halo2-base/src/gates/range.rs index 2592d515..4221feb6 100644 --- a/halo2-base/src/gates/range.rs +++ b/halo2-base/src/gates/range.rs @@ -19,7 +19,7 @@ use num_integer::Integer; use num_traits::One; use std::{cmp::Ordering, ops::Shl}; -use super::flex_gate::GateChip; +use super::{builder::BaseConfigParams, flex_gate::GateChip}; /// Specifies the gate strategy for the range chip #[derive(Clone, Copy, Debug, PartialEq)] @@ -35,6 +35,73 @@ pub enum RangeStrategy { Vertical, // vanilla implementation with vertical basic gate(s) } +/// Smart Halo2 circuit config that has different variants depending on whether you need range checks or not. +/// The difference is that to enable range checks, the Halo2 config needs to add a lookup table. +#[derive(Clone, Debug)] +pub enum BaseConfig { + /// Config for a circuit that does not use range checks + WithoutRange(FlexGateConfig), + /// Config for a circuit that does use range checks + WithRange(RangeConfig), +} + +impl BaseConfig { + /// Generates a new `BaseConfig` depending on `params`. + /// - It will generate a `RangeConfig` is `params` has `lookup_bits` not None **and** `num_lookup_advice_per_phase` are not all empty or zero (i.e., if `params` indicates that the circuit actually requires a lookup table). + /// - Otherwise it will generate a `FlexGateConfig`. + pub fn configure(meta: &mut ConstraintSystem, params: BaseConfigParams) -> Self { + let total_lookup_advice_cols = params.num_lookup_advice_per_phase.iter().sum::(); + if params.lookup_bits.is_some() && total_lookup_advice_cols != 0 { + // We only add a lookup table if lookup bits is not None + Self::WithRange(RangeConfig::configure( + meta, + match params.strategy { + GateStrategy::Vertical => RangeStrategy::Vertical, + }, + ¶ms.num_advice_per_phase, + ¶ms.num_lookup_advice_per_phase, + params.num_fixed, + params.lookup_bits.unwrap(), + params.k, + )) + } else { + Self::WithoutRange(FlexGateConfig::configure( + meta, + params.strategy, + ¶ms.num_advice_per_phase, + params.num_fixed, + params.k, + )) + } + } + + /// Returns the inner [`FlexGateConfig`] + pub fn gate(&self) -> &FlexGateConfig { + match self { + Self::WithoutRange(config) => config, + Self::WithRange(config) => &config.gate, + } + } + + /// Returns a slice of the special advice columns with lookup enabled, per phase. + /// Returns empty slice if there are no lookups enabled. + pub fn lookup_advice(&self) -> &[Vec>] { + match self { + Self::WithoutRange(_) => &[], + Self::WithRange(config) => &config.lookup_advice, + } + } + + /// Returns a slice of the selector column to enable lookup -- this is only in the situation where there is a single advice column of any kind -- per phase + /// Returns empty slice if there are no lookups enabled. + pub fn q_lookup(&self) -> &[Option] { + match self { + Self::WithoutRange(_) => &[], + Self::WithRange(config) => &config.q_lookup, + } + } +} + /// Configuration for Range Chip #[derive(Clone, Debug)] pub struct RangeConfig { @@ -78,10 +145,12 @@ impl RangeConfig { num_lookup_advice: &[usize], num_fixed: usize, lookup_bits: usize, - // params.k() circuit_degree: usize, ) -> Self { assert!(lookup_bits <= 28); + // sanity check: only create lookup table if there are lookup_advice columns + assert!(!num_lookup_advice.is_empty(), "You are creating a RangeConfig but don't seem to need a lookup table, please double-check if you're using lookups correctly. Consider setting lookup_bits = None in BaseConfigParams"); + let lookup = meta.lookup_table_column(); let gate = FlexGateConfig::configure( @@ -118,11 +187,8 @@ impl RangeConfig { let mut config = Self { lookup_advice, q_lookup, lookup, lookup_bits, gate, _strategy: range_strategy }; + config.create_lookup(meta); - // sanity check: only create lookup table if there are lookup_advice columns - if !num_lookup_advice.is_empty() { - config.create_lookup(meta); - } config.gate.max_rows = (1 << circuit_degree) - meta.minimum_rows(); assert!( (1 << lookup_bits) <= config.gate.max_rows, @@ -428,13 +494,11 @@ pub trait RangeInstructions { } } -/// A chip that implements RangeInstructions which provides methods to constrain a field element `x` is within a range of bits. +/// # RangeChip +/// This chip provides methods that rely on "range checking" that a field element `x` is within a range of bits. +/// Range checks are done using a lookup table with the numbers [0, 2lookup_bits). #[derive(Clone, Debug)] pub struct RangeChip { - /// # RangeChip - /// Provides methods to constrain a field element `x` is within a range of bits. - /// Declares a lookup table of [0, 2lookup_bits) and constrains whether a field element appears in this table. - /// [GateStrategy] for advice values in this chip. strategy: RangeStrategy, /// Underlying [GateChip] for this chip. @@ -487,7 +551,7 @@ impl RangeInstructions for RangeChip { self.strategy } - /// Defines the number of bits represented in the lookup table [0,2lookup_bits). + /// Returns the number of bits represented in the lookup table [0,2lookup_bits). fn lookup_bits(&self) -> usize { self.lookup_bits } diff --git a/halo2-base/src/gates/tests/README.md b/halo2-base/src/gates/tests/README.md deleted file mode 100644 index 24f34537..00000000 --- a/halo2-base/src/gates/tests/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Tests - -For tests that use `GateCircuitBuilder` or `RangeCircuitBuilder`, we currently must use environmental variables `FLEX_GATE_CONFIG` and `LOOKUP_BITS` to pass circuit configuration parameters to the `Circuit::configure` function. This is troublesome when Rust executes tests in parallel, so we to make sure all tests pass, run - -``` -cargo test -- --test-threads=1 -``` - -to force serial execution. diff --git a/halo2-base/src/gates/tests/flex_gate.rs b/halo2-base/src/gates/tests/flex_gate.rs new file mode 100644 index 00000000..8b047504 --- /dev/null +++ b/halo2-base/src/gates/tests/flex_gate.rs @@ -0,0 +1,174 @@ +#![allow(clippy::type_complexity)] +use super::*; +use crate::utils::testing::base_test; +use crate::QuantumCell::Witness; +use crate::{gates::flex_gate::GateInstructions, QuantumCell}; +use test_case::test_case; + +#[test_case(&[10, 12].map(Fr::from).map(Witness)=> Fr::from(22); "add(): 10 + 12 == 22")] +#[test_case(&[1, 1].map(Fr::from).map(Witness)=> Fr::from(2); "add(): 1 + 1 == 2")] +pub fn test_add(inputs: &[QuantumCell]) -> Fr { + base_test().run_gate(|ctx, chip| *chip.add(ctx, inputs[0], inputs[1]).value()) +} + +#[test_case(&[10, 12].map(Fr::from).map(Witness)=> -Fr::from(2) ; "sub(): 10 - 12 == -2")] +#[test_case(&[1, 1].map(Fr::from).map(Witness)=> Fr::from(0) ; "sub(): 1 - 1 == 0")] +pub fn test_sub(inputs: &[QuantumCell]) -> Fr { + base_test().run_gate(|ctx, chip| *chip.sub(ctx, inputs[0], inputs[1]).value()) +} + +#[test_case(Witness(Fr::from(1)) => -Fr::from(1); "neg(): 1 -> -1")] +pub fn test_neg(a: QuantumCell) -> Fr { + base_test().run_gate(|ctx, chip| *chip.neg(ctx, a).value()) +} + +#[test_case(&[10, 12].map(Fr::from).map(Witness) => Fr::from(120) ; "mul(): 10 * 12 == 120")] +#[test_case(&[1, 1].map(Fr::from).map(Witness) => Fr::from(1) ; "mul(): 1 * 1 == 1")] +pub fn test_mul(inputs: &[QuantumCell]) -> Fr { + base_test().run_gate(|ctx, chip| *chip.mul(ctx, inputs[0], inputs[1]).value()) +} + +#[test_case(&[1, 1, 1].map(Fr::from).map(Witness) => Fr::from(2) ; "mul_add(): 1 * 1 + 1 == 2")] +pub fn test_mul_add(inputs: &[QuantumCell]) -> Fr { + base_test().run_gate(|ctx, chip| *chip.mul_add(ctx, inputs[0], inputs[1], inputs[2]).value()) +} + +#[test_case(&[0, 10].map(Fr::from).map(Witness) => Fr::from(10); "mul_not(): (1 - 0) * 10 == 10")] +#[test_case(&[1, 10].map(Fr::from).map(Witness) => Fr::from(0); "mul_not(): (1 - 1) * 10 == 0")] +pub fn test_mul_not(inputs: &[QuantumCell]) -> Fr { + base_test().run_gate(|ctx, chip| *chip.mul_not(ctx, inputs[0], inputs[1]).value()) +} + +#[test_case(Fr::from(0), true; "assert_bit(0)")] +#[test_case(Fr::from(1), true; "assert_bit(1)")] +#[test_case(Fr::from(2), false; "assert_bit(2)")] +pub fn test_assert_bit(input: Fr, is_bit: bool) { + base_test().expect_satisfied(is_bit).run_gate(|ctx, chip| { + let a = ctx.load_witness(input); + chip.assert_bit(ctx, a); + }); +} + +#[test_case(&[6, 2].map(Fr::from).map(Witness)=> Fr::from(3) ; "div_unsafe(): 6 / 2 == 3")] +#[test_case(&[1, 1].map(Fr::from).map(Witness)=> Fr::from(1) ; "div_unsafe(): 1 / 1 == 1")] +pub fn test_div_unsafe(inputs: &[QuantumCell]) -> Fr { + base_test().run_gate(|ctx, chip| *chip.div_unsafe(ctx, inputs[0], inputs[1]).value()) +} + +#[test_case(&[1, 1].map(Fr::from); "assert_is_const(1,1)")] +#[test_case(&[0, 1].map(Fr::from); "assert_is_const(0,1)")] +pub fn test_assert_is_const(inputs: &[Fr]) { + base_test().expect_satisfied(inputs[0] == inputs[1]).run_gate(|ctx, chip| { + let a = ctx.load_witness(inputs[0]); + chip.assert_is_const(ctx, &a, &inputs[1]); + }); +} + +#[test_case((vec![Witness(Fr::one()); 5], vec![Witness(Fr::one()); 5]) => Fr::from(5) ; "inner_product(): 1 * 1 + ... + 1 * 1 == 5")] +pub fn test_inner_product(input: (Vec>, Vec>)) -> Fr { + base_test().run_gate(|ctx, chip| *chip.inner_product(ctx, input.0, input.1).value()) +} + +#[test_case((vec![Witness(Fr::one()); 5], vec![Witness(Fr::one()); 5]) => (Fr::from(5), Fr::from(1)); "inner_product_left_last(): 1 * 1 + ... + 1 * 1 == (5, 1)")] +pub fn test_inner_product_left_last( + input: (Vec>, Vec>), +) -> (Fr, Fr) { + base_test().run_gate(|ctx, chip| { + let a = chip.inner_product_left_last(ctx, input.0, input.1); + (*a.0.value(), *a.1.value()) + }) +} + +#[test_case((vec![Witness(Fr::one()); 5], vec![Witness(Fr::one()); 5]) => (1..=5).map(Fr::from).collect::>(); "inner_product_with_sums(): 1 * 1 + ... + 1 * 1 == [1, 2, 3, 4, 5]")] +pub fn test_inner_product_with_sums( + input: (Vec>, Vec>), +) -> Vec { + base_test().run_gate(|ctx, chip| { + chip.inner_product_with_sums(ctx, input.0, input.1).map(|a| *a.value()).collect() + }) +} + +#[test_case((vec![(Fr::from(1), Witness(Fr::from(1)), Witness(Fr::from(1)))], Witness(Fr::from(1))) => Fr::from(2) ; "sum_product_with_coeff_and_var(): 1 * 1 + 1 == 2")] +pub fn test_sum_products_with_coeff_and_var( + input: (Vec<(Fr, QuantumCell, QuantumCell)>, QuantumCell), +) -> Fr { + base_test() + .run_gate(|ctx, chip| *chip.sum_products_with_coeff_and_var(ctx, input.0, input.1).value()) +} + +#[test_case(&[1, 0].map(Fr::from).map(Witness) => Fr::from(0) ; "and(): 1 && 0 == 0")] +#[test_case(&[1, 1].map(Fr::from).map(Witness) => Fr::from(1) ; "and(): 1 && 1 == 1")] +pub fn test_and(inputs: &[QuantumCell]) -> Fr { + base_test().run_gate(|ctx, chip| *chip.and(ctx, inputs[0], inputs[1]).value()) +} + +#[test_case(Witness(Fr::from(1)) => Fr::zero(); "not(): !1 == 0")] +#[test_case(Witness(Fr::from(0)) => Fr::one(); "not(): !0 == 1")] +pub fn test_not(a: QuantumCell) -> Fr { + base_test().run_gate(|ctx, chip| *chip.not(ctx, a).value()) +} + +#[test_case(&[2, 3, 1].map(Fr::from).map(Witness) => Fr::from(2); "select(): 2 ? 3 : 1 == 2")] +pub fn test_select(inputs: &[QuantumCell]) -> Fr { + base_test().run_gate(|ctx, chip| *chip.select(ctx, inputs[0], inputs[1], inputs[2]).value()) +} + +#[test_case(&[0, 1, 0].map(Fr::from).map(Witness) => Fr::from(0); "or_and(): 0 || (1 && 0) == 0")] +#[test_case(&[1, 0, 1].map(Fr::from).map(Witness) => Fr::from(1); "or_and(): 1 || (0 && 1) == 1")] +#[test_case(&[1, 1, 1].map(Fr::from).map(Witness) => Fr::from(1); "or_and(): 1 || (1 && 1) == 1")] +pub fn test_or_and(inputs: &[QuantumCell]) -> Fr { + base_test().run_gate(|ctx, chip| *chip.or_and(ctx, inputs[0], inputs[1], inputs[2]).value()) +} + +#[test_case(&[0,1] => [0,0,1,0].map(Fr::from).to_vec(); "bits_to_indicator(): bin\"10 -> [0, 0, 1, 0]")] +#[test_case(&[0] => [1,0].map(Fr::from).to_vec(); "bits_to_indicator(): 0 -> [1, 0]")] +pub fn test_bits_to_indicator(bits: &[u8]) -> Vec { + base_test().run_gate(|ctx, chip| { + let a = ctx.assign_witnesses(bits.iter().map(|x| Fr::from(*x as u64))); + chip.bits_to_indicator(ctx, &a).iter().map(|a| *a.value()).collect() + }) +} + +#[test_case(Witness(Fr::from(0)),3 => [1,0,0].map(Fr::from).to_vec(); "idx_to_indicator(): 0 -> [1, 0, 0]")] +pub fn test_idx_to_indicator(idx: QuantumCell, len: usize) -> Vec { + base_test().run_gate(|ctx, chip| { + chip.idx_to_indicator(ctx, idx, len).iter().map(|a| *a.value()).collect() + }) +} + +#[test_case((0..3).map(Fr::from).map(Witness).collect(), Witness(Fr::one()) => Fr::from(1); "select_by_indicator(1): [0, 1, 2] -> 1")] +pub fn test_select_by_indicator(array: Vec>, idx: QuantumCell) -> Fr { + base_test().run_gate(|ctx, chip| { + let a = chip.idx_to_indicator(ctx, idx, array.len()); + *chip.select_by_indicator(ctx, array, a).value() + }) +} + +#[test_case((0..3).map(Fr::from).map(Witness).collect(), Witness(Fr::from(1)) => Fr::from(1); "select_from_idx(): [0, 1, 2] -> 1")] +pub fn test_select_from_idx(array: Vec>, idx: QuantumCell) -> Fr { + base_test().run_gate(|ctx, chip| { + let a = chip.idx_to_indicator(ctx, idx, array.len()); + *chip.select_by_indicator(ctx, array, a).value() + }) +} + +#[test_case(Fr::zero() => Fr::from(1); "is_zero(): 0 -> 1")] +pub fn test_is_zero(input: Fr) -> Fr { + base_test().run_gate(|ctx, chip| { + let input = ctx.load_witness(input); + *chip.is_zero(ctx, input).value() + }) +} + +#[test_case(&[1, 1].map(Fr::from).map(Witness) => Fr::one(); "is_equal(): 1 == 1")] +pub fn test_is_equal(inputs: &[QuantumCell]) -> Fr { + base_test().run_gate(|ctx, chip| *chip.is_equal(ctx, inputs[0], inputs[1]).value()) +} + +#[test_case(6, 3 => [0,1,1].map(Fr::from).to_vec(); "num_to_bits(): 6")] +pub fn test_num_to_bits(num: usize, bits: usize) -> Vec { + base_test().run_gate(|ctx, chip| { + let num = ctx.load_witness(Fr::from(num as u64)); + chip.num_to_bits(ctx, num, bits).iter().map(|a| *a.value()).collect() + }) +} diff --git a/halo2-base/src/gates/tests/flex_gate_tests.rs b/halo2-base/src/gates/tests/flex_gate_tests.rs deleted file mode 100644 index e73c6d63..00000000 --- a/halo2-base/src/gates/tests/flex_gate_tests.rs +++ /dev/null @@ -1,267 +0,0 @@ -#![allow(clippy::type_complexity)] -use super::*; -use crate::halo2_proofs::dev::MockProver; -use crate::halo2_proofs::dev::VerifyFailure; -use crate::utils::ScalarField; -use crate::QuantumCell::Witness; -use crate::{ - gates::{ - builder::{GateCircuitBuilder, GateThreadBuilder}, - flex_gate::{GateChip, GateInstructions}, - }, - QuantumCell, -}; -use test_case::test_case; - -#[test_case(&[1, 1].map(Fr::from).map(Witness) => Fr::from(2) ; "add(): 1 + 1 == 2")] -pub fn test_add(inputs: &[QuantumCell]) -> F { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = GateChip::default(); - let a = chip.add(ctx, inputs[0], inputs[1]); - *a.value() -} - -#[test_case(&[1, 1].map(Fr::from).map(Witness) => Fr::from(0) ; "sub(): 1 - 1 == 0")] -pub fn test_sub(inputs: &[QuantumCell]) -> F { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = GateChip::default(); - let a = chip.sub(ctx, inputs[0], inputs[1]); - *a.value() -} - -#[test_case(Witness(Fr::from(1)) => -Fr::from(1) ; "neg(): 1 -> -1")] -pub fn test_neg(a: QuantumCell) -> F { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = GateChip::default(); - let a = chip.neg(ctx, a); - *a.value() -} - -#[test_case(&[1, 1].map(Fr::from).map(Witness) => Fr::from(1) ; "mul(): 1 * 1 == 1")] -pub fn test_mul(inputs: &[QuantumCell]) -> F { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = GateChip::default(); - let a = chip.mul(ctx, inputs[0], inputs[1]); - *a.value() -} - -#[test_case(&[1, 1, 1].map(Fr::from).map(Witness) => Fr::from(2) ; "mul_add(): 1 * 1 + 1 == 2")] -pub fn test_mul_add(inputs: &[QuantumCell]) -> F { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = GateChip::default(); - let a = chip.mul_add(ctx, inputs[0], inputs[1], inputs[2]); - *a.value() -} - -#[test_case(&[1, 1].map(Fr::from).map(Witness) => Fr::from(0) ; "mul_not(): 1 * 1 == 0")] -pub fn test_mul_not(inputs: &[QuantumCell]) -> F { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = GateChip::default(); - let a = chip.mul_not(ctx, inputs[0], inputs[1]); - *a.value() -} - -#[test_case(Fr::from(1) => Ok(()); "assert_bit(): 1 == bit")] -pub fn test_assert_bit(input: F) -> Result<(), Vec> { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = GateChip::default(); - let a = ctx.assign_witnesses([input])[0]; - chip.assert_bit(ctx, a); - // auto-tune circuit - builder.config(6, Some(9)); - // create circuit - let circuit = GateCircuitBuilder::mock(builder); - MockProver::run(6, &circuit, vec![]).unwrap().verify() -} - -#[test_case(&[1, 1].map(Fr::from).map(Witness) => Fr::from(1) ; "div_unsafe(): 1 / 1 == 1")] -pub fn test_div_unsafe(inputs: &[QuantumCell]) -> F { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = GateChip::default(); - let a = chip.div_unsafe(ctx, inputs[0], inputs[1]); - *a.value() -} - -#[test_case(&[1, 1].map(Fr::from); "assert_is_const()")] -pub fn test_assert_is_const(inputs: &[F]) { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = GateChip::default(); - let a = ctx.assign_witnesses([inputs[0]])[0]; - chip.assert_is_const(ctx, &a, &inputs[1]); - // auto-tune circuit - builder.config(6, Some(9)); - // create circuit - let circuit = GateCircuitBuilder::mock(builder); - MockProver::run(6, &circuit, vec![]).unwrap().assert_satisfied() -} - -#[test_case((vec![Witness(Fr::one()); 5], vec![Witness(Fr::one()); 5]) => Fr::from(5) ; "inner_product(): 1 * 1 + ... + 1 * 1 == 5")] -pub fn test_inner_product(input: (Vec>, Vec>)) -> F { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = GateChip::default(); - let a = chip.inner_product(ctx, input.0, input.1); - *a.value() -} - -#[test_case((vec![Witness(Fr::one()); 5], vec![Witness(Fr::one()); 5]) => (Fr::from(5), Fr::from(1)); "inner_product_left_last(): 1 * 1 + ... + 1 * 1 == (5, 1)")] -pub fn test_inner_product_left_last( - input: (Vec>, Vec>), -) -> (F, F) { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = GateChip::default(); - let a = chip.inner_product_left_last(ctx, input.0, input.1); - (*a.0.value(), *a.1.value()) -} - -#[test_case((vec![Witness(Fr::one()); 5], vec![Witness(Fr::one()); 5]) => vec![Fr::one(), Fr::from(2), Fr::from(3), Fr::from(4), Fr::from(5)]; "inner_product_with_sums(): 1 * 1 + ... + 1 * 1 == [1, 2, 3, 4, 5]")] -pub fn test_inner_product_with_sums( - input: (Vec>, Vec>), -) -> Vec { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = GateChip::default(); - let a = chip.inner_product_with_sums(ctx, input.0, input.1); - a.into_iter().map(|x| *x.value()).collect() -} - -#[test_case((vec![(Fr::from(1), Witness(Fr::from(1)), Witness(Fr::from(1)))], Witness(Fr::from(1))) => Fr::from(2) ; "sum_product_with_coeff_and_var(): 1 * 1 + 1 == 2")] -pub fn test_sum_products_with_coeff_and_var( - input: (Vec<(F, QuantumCell, QuantumCell)>, QuantumCell), -) -> F { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = GateChip::default(); - let a = chip.sum_products_with_coeff_and_var(ctx, input.0, input.1); - *a.value() -} - -#[test_case(&[1, 1].map(Fr::from).map(Witness) => Fr::from(1) ; "and(): 1 && 1 == 1")] -pub fn test_and(inputs: &[QuantumCell]) -> F { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = GateChip::default(); - let a = chip.and(ctx, inputs[0], inputs[1]); - *a.value() -} - -#[test_case(Witness(Fr::from(1)) => Fr::zero() ; "not(): !1 == 0")] -pub fn test_not(a: QuantumCell) -> F { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = GateChip::default(); - let a = chip.not(ctx, a); - *a.value() -} - -#[test_case(&[2, 3, 1].map(Fr::from).map(Witness) => Fr::from(2) ; "select(): 2 ? 3 : 1 == 2")] -pub fn test_select(inputs: &[QuantumCell]) -> F { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = GateChip::default(); - let a = chip.select(ctx, inputs[0], inputs[1], inputs[2]); - *a.value() -} - -#[test_case(&[1, 1, 1].map(Fr::from).map(Witness) => Fr::from(1) ; "or_and(): 1 || 1 && 1 == 1")] -pub fn test_or_and(inputs: &[QuantumCell]) -> F { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = GateChip::default(); - let a = chip.or_and(ctx, inputs[0], inputs[1], inputs[2]); - *a.value() -} - -#[test_case(Fr::zero() => vec![Fr::one(), Fr::zero()]; "bits_to_indicator(): 0 -> [1, 0]")] -pub fn test_bits_to_indicator(bits: F) -> Vec { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = GateChip::default(); - let a = ctx.assign_witnesses([bits])[0]; - let a = chip.bits_to_indicator(ctx, &[a]); - a.iter().map(|x| *x.value()).collect() -} - -#[test_case((Witness(Fr::zero()), 3) => vec![Fr::one(), Fr::zero(), Fr::zero()] ; "idx_to_indicator(): 0 -> [1, 0, 0]")] -pub fn test_idx_to_indicator(input: (QuantumCell, usize)) -> Vec { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = GateChip::default(); - let a = chip.idx_to_indicator(ctx, input.0, input.1); - a.iter().map(|x| *x.value()).collect() -} - -#[test_case((vec![Witness(Fr::zero()), Witness(Fr::one()), Witness(Fr::from(2))], Witness(Fr::one())) => Fr::from(1) ; "select_by_indicator(): [0, 1, 2] -> 1")] -pub fn test_select_by_indicator(input: (Vec>, QuantumCell)) -> F { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = GateChip::default(); - let a = chip.idx_to_indicator(ctx, input.1, input.0.len()); - let a = chip.select_by_indicator(ctx, input.0, a); - *a.value() -} - -#[test_case((vec![Witness(Fr::zero()), Witness(Fr::one()), Witness(Fr::from(2))], Witness(Fr::one())) => Fr::from(1) ; "select_from_idx(): [0, 1, 2] -> 1")] -pub fn test_select_from_idx(input: (Vec>, QuantumCell)) -> F { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = GateChip::default(); - let a = chip.idx_to_indicator(ctx, input.1, input.0.len()); - let a = chip.select_by_indicator(ctx, input.0, a); - *a.value() -} - -#[test_case(Fr::zero() => Fr::from(1) ; "is_zero(): 0 -> 1")] -pub fn test_is_zero(x: F) -> F { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = GateChip::default(); - let a = ctx.assign_witnesses([x])[0]; - let a = chip.is_zero(ctx, a); - *a.value() -} - -#[test_case(&[1, 1].map(Fr::from).map(Witness) => Fr::one() ; "is_equal(): 1 == 1")] -pub fn test_is_equal(inputs: &[QuantumCell]) -> F { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = GateChip::default(); - let a = chip.is_equal(ctx, inputs[0], inputs[1]); - *a.value() -} - -#[test_case((Fr::from(6u64), 3) => vec![Fr::zero(), Fr::one(), Fr::one()] ; "num_to_bits(): 6")] -pub fn test_num_to_bits(input: (F, usize)) -> Vec { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = GateChip::default(); - let a = ctx.assign_witnesses([input.0])[0]; - let a = chip.num_to_bits(ctx, a, input.1); - a.iter().map(|x| *x.value()).collect() -} - -#[test_case(&[0, 1, 2].map(Fr::from) => (Fr::one(), Fr::from(2)) ; "lagrange_eval(): constant fn")] -pub fn test_lagrange_eval(input: &[F]) -> (F, F) { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = GateChip::default(); - let input = ctx.assign_witnesses(input.iter().copied()); - let a = chip.lagrange_and_eval(ctx, &[(input[0], input[1])], input[2]); - (*a.0.value(), *a.1.value()) -} - -#[test_case(1 => Fr::one(); "inner_product_simple(): 1 -> 1")] -pub fn test_get_field_element(n: u64) -> F { - let chip = GateChip::default(); - chip.get_field_element(n) -} diff --git a/halo2-base/src/gates/tests/general.rs b/halo2-base/src/gates/tests/general.rs index 1c9924d5..2569096a 100644 --- a/halo2-base/src/gates/tests/general.rs +++ b/halo2-base/src/gates/tests/general.rs @@ -1,10 +1,13 @@ -use crate::gates::{ - builder::{GateCircuitBuilder, GateThreadBuilder, RangeCircuitBuilder}, - flex_gate::{GateChip, GateInstructions}, - range::{RangeChip, RangeInstructions}, -}; use crate::halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}; use crate::utils::{BigPrimeField, ScalarField}; +use crate::{ + gates::{ + builder::{GateThreadBuilder, RangeCircuitBuilder}, + flex_gate::{GateChip, GateInstructions}, + range::{RangeChip, RangeInstructions}, + }, + utils::testing::base_test, +}; use crate::{Context, QuantumCell::Constant}; use ff::Field; use rand::rngs::OsRng; @@ -34,21 +37,6 @@ fn gate_tests(ctx: &mut Context, inputs: [F; 3]) { chip.is_zero(ctx, a); } -#[test] -fn test_gates() { - let k = 6; - let inputs = [10u64, 12u64, 120u64].map(Fr::from); - let mut builder = GateThreadBuilder::mock(); - gate_tests(builder.main(0), inputs); - - // auto-tune circuit - builder.config(k, Some(9)); - // create circuit - let circuit = GateCircuitBuilder::mock(builder); - - MockProver::run(k as u32, &circuit, vec![]).unwrap().assert_satisfied(); -} - #[test] fn test_multithread_gates() { let k = 6; @@ -70,7 +58,7 @@ fn test_multithread_gates() { // auto-tune circuit builder.config(k, Some(9)); // create circuit - let circuit = GateCircuitBuilder::mock(builder); + let circuit = RangeCircuitBuilder::mock(builder); MockProver::run(k as u32, &circuit, vec![]).unwrap().assert_satisfied(); } @@ -92,21 +80,18 @@ fn plot_gates() { // auto-tune circuit builder.config(k, Some(9)); // create circuit - let circuit = GateCircuitBuilder::keygen(builder); + let circuit = RangeCircuitBuilder::keygen(builder); halo2_proofs::dev::CircuitLayout::default().render(k, &circuit, &root).unwrap(); } fn range_tests( ctx: &mut Context, - lookup_bits: usize, + chip: &RangeChip, inputs: [F; 2], range_bits: usize, lt_bits: usize, ) { let [a, b]: [_; 2] = ctx.assign_witnesses(inputs).try_into().unwrap(); - let chip = RangeChip::default(lookup_bits); - std::env::set_var("LOOKUP_BITS", lookup_bits.to_string()); - chip.range_check(ctx, a, range_bits); chip.check_less_than(ctx, a, b, lt_bits); @@ -120,37 +105,24 @@ fn range_tests( #[test] fn test_range_single() { - let k = 11; let inputs = [100, 101].map(Fr::from); - let mut builder = GateThreadBuilder::mock(); - range_tests(builder.main(0), 3, inputs, 8, 8); - - // auto-tune circuit - builder.config(k, Some(9)); - // create circuit - let circuit = RangeCircuitBuilder::mock(builder); - - MockProver::run(k as u32, &circuit, vec![]).unwrap().assert_satisfied(); + base_test().k(11).lookup_bits(3).run(|ctx, range| { + range_tests(ctx, range, inputs, 8, 8); + }) } #[test] fn test_range_multicolumn() { - let k = 5; let inputs = [100, 101].map(Fr::from); - let mut builder = GateThreadBuilder::mock(); - range_tests(builder.main(0), 3, inputs, 8, 8); - - // auto-tune circuit - builder.config(k, Some(9)); - // create circuit - let circuit = RangeCircuitBuilder::mock(builder); - - MockProver::run(k as u32, &circuit, vec![]).unwrap().assert_satisfied(); + base_test().k(5).lookup_bits(3).run(|ctx, range| { + range_tests(ctx, range, inputs, 8, 8); + }) } #[cfg(feature = "dev-graph")] #[test] fn plot_range() { + use crate::gates::builder::set_lookup_bits; use plotters::prelude::*; let root = BitMapBackend::new("layout.png", (1024, 1024)).into_drawing_area(); @@ -160,7 +132,9 @@ fn plot_range() { let k = 11; let inputs = [0, 0].map(Fr::from); let mut builder = GateThreadBuilder::new(false); - range_tests(builder.main(0), 3, inputs, 8, 8); + set_lookup_bits(3); + let range = RangeChip::default(3); + range_tests(builder.main(0), &range, inputs, 8, 8); // auto-tune circuit builder.config(k, Some(9)); diff --git a/halo2-base/src/gates/tests/idx_to_indicator.rs b/halo2-base/src/gates/tests/idx_to_indicator.rs index 33cbaa94..4b34e80c 100644 --- a/halo2-base/src/gates/tests/idx_to_indicator.rs +++ b/halo2-base/src/gates/tests/idx_to_indicator.rs @@ -1,6 +1,6 @@ use crate::{ gates::{ - builder::{GateCircuitBuilder, GateThreadBuilder}, + builder::{GateThreadBuilder, RangeCircuitBuilder}, GateChip, GateInstructions, }, halo2_proofs::{ @@ -27,7 +27,7 @@ fn test_idx_to_indicator_gen(k: u32, len: usize) { let ind_offsets = indicator.iter().map(|ind| ind.cell.unwrap().offset).collect::>(); // set env vars builder.config(k as usize, Some(9)); - let circuit = GateCircuitBuilder::keygen(builder); + let circuit = RangeCircuitBuilder::keygen(builder); let params = ParamsKZG::setup(k, OsRng); // generate proving key @@ -46,7 +46,7 @@ fn test_idx_to_indicator_gen(k: u32, len: usize) { for (offset, witness) in ind_offsets.iter().zip_eq(ind_witnesses) { builder.main(0).advice[*offset] = Assigned::Trivial(*witness); } - let circuit = GateCircuitBuilder::prover(builder, vec![vec![]]); // no break points + let circuit = RangeCircuitBuilder::prover(builder, vec![vec![]]); // no break points gen_proof(¶ms, &pk, circuit) }; diff --git a/halo2-base/src/gates/tests/mod.rs b/halo2-base/src/gates/tests/mod.rs index 02b45335..8e35b53e 100644 --- a/halo2-base/src/gates/tests/mod.rs +++ b/halo2-base/src/gates/tests/mod.rs @@ -1,9 +1,9 @@ use crate::halo2_proofs::halo2curves::bn256::Fr; -mod flex_gate_tests; +mod flex_gate; mod general; mod idx_to_indicator; -mod neg_prop_tests; -mod pos_prop_tests; -mod range_gate_tests; -mod test_ground_truths; +mod neg_prop; +mod pos_prop; +mod range; +mod utils; diff --git a/halo2-base/src/gates/tests/neg_prop_tests.rs b/halo2-base/src/gates/tests/neg_prop.rs similarity index 88% rename from halo2-base/src/gates/tests/neg_prop_tests.rs rename to halo2-base/src/gates/tests/neg_prop.rs index 226a01f9..d9548a60 100644 --- a/halo2-base/src/gates/tests/neg_prop_tests.rs +++ b/halo2-base/src/gates/tests/neg_prop.rs @@ -1,23 +1,24 @@ -use std::env::set_var; - use ff::Field; use itertools::Itertools; use num_bigint::BigUint; use proptest::{collection::vec, prelude::*}; use rand::rngs::OsRng; -use crate::halo2_proofs::{ - dev::MockProver, - halo2curves::{bn256::Fr, FieldExt}, - plonk::Assigned, +use crate::{ + gates::builder::set_lookup_bits, + halo2_proofs::{ + dev::MockProver, + halo2curves::{bn256::Fr, FieldExt}, + plonk::Assigned, + }, }; use crate::{ gates::{ - builder::{GateCircuitBuilder, GateThreadBuilder, RangeCircuitBuilder}, + builder::{GateThreadBuilder, RangeCircuitBuilder}, range::{RangeChip, RangeInstructions}, tests::{ - pos_prop_tests::{rand_bin_witness, rand_fr, rand_witness}, - test_ground_truths, + pos_prop::{rand_bin_witness, rand_fr, rand_witness}, + utils, }, GateChip, GateInstructions, }, @@ -156,8 +157,8 @@ fn neg_test_idx_to_indicator(k: usize, len: usize, idx: usize, ind_witnesses: &[ } // Get idx and indicator from advice column // Apply check instance function to `idx` and `ind_witnesses` - let circuit = GateCircuitBuilder::mock(builder); // no break points - // Check soundness of witness values + let circuit = RangeCircuitBuilder::mock(builder); // no break points + // Check soundness of witness values let is_valid_witness = check_idx_to_indicator(Fr::from(idx as u64), len, ind_witnesses); match MockProver::run(k as u32, &circuit, vec![]).unwrap().verify() { // if the proof is valid, then the instance should be valid -> return true @@ -185,8 +186,8 @@ fn neg_test_select( // Prank the output builder.main(0).advice[select_offset] = Assigned::Trivial(rand_output); - let circuit = GateCircuitBuilder::mock(builder); // no break points - // Check soundness of output + let circuit = RangeCircuitBuilder::mock(builder); // no break points + // Check soundness of output let is_valid_instance = check_select(*a.value(), *b.value(), *sel.value(), rand_output); match MockProver::run(k as u32, &circuit, vec![]).unwrap().verify() { // if the proof is valid, then the instance should be valid -> return true @@ -211,9 +212,9 @@ fn neg_test_select_by_indicator( let a_idx_offset = a_idx.cell.unwrap().offset; builder.main(0).advice[a_idx_offset] = Assigned::Trivial(rand_output); - let circuit = GateCircuitBuilder::mock(builder); // no break points - // Check soundness of witness values - // retrieve the value of a[idx] and check that it is equal to rand_output + let circuit = RangeCircuitBuilder::mock(builder); // no break points + // Check soundness of witness values + // retrieve the value of a[idx] and check that it is equal to rand_output let is_valid_witness = rand_output == *a[idx].value(); match MockProver::run(k as u32, &circuit, vec![]).unwrap().verify() { // if the proof is valid, then the instance should be valid -> return true @@ -238,8 +239,8 @@ fn neg_test_select_from_idx( let idx_offset = idx_val.cell.unwrap().offset; builder.main(0).advice[idx_offset] = Assigned::Trivial(rand_output); - let circuit = GateCircuitBuilder::mock(builder); // no break points - // Check soundness of witness values + let circuit = RangeCircuitBuilder::mock(builder); // no break points + // Check soundness of witness values let is_valid_witness = rand_output == *cells[idx].value(); match MockProver::run(k as u32, &circuit, vec![]).unwrap().verify() { // if the proof is valid, then the instance should be valid -> return true @@ -263,9 +264,9 @@ fn neg_test_inner_product( let inner_product_offset = inner_product.cell.unwrap().offset; builder.main(0).advice[inner_product_offset] = Assigned::Trivial(rand_output); - let circuit = GateCircuitBuilder::mock(builder); // no break points - // Check soundness of witness values - let is_valid_witness = rand_output == test_ground_truths::inner_product_ground_truth(&(a, b)); + let circuit = RangeCircuitBuilder::mock(builder); // no break points + // Check soundness of witness values + let is_valid_witness = rand_output == utils::inner_product_ground_truth(&(a, b)); match MockProver::run(k as u32, &circuit, vec![]).unwrap().verify() { // if the proof is valid, then the instance should be valid -> return true Ok(_) => is_valid_witness, @@ -291,11 +292,10 @@ fn neg_test_inner_product_left_last( // prank the output cells builder.main(0).advice[inner_product_offset.0] = Assigned::Trivial(rand_output.0); builder.main(0).advice[inner_product_offset.1] = Assigned::Trivial(rand_output.1); - let circuit = GateCircuitBuilder::mock(builder); // no break points - // Check soundness of witness values - // (inner_product_ground_truth, a[a.len()-1]) - let inner_product_ground_truth = - test_ground_truths::inner_product_ground_truth(&(a.clone(), b)); + let circuit = RangeCircuitBuilder::mock(builder); // no break points + // Check soundness of witness values + // (inner_product_ground_truth, a[a.len()-1]) + let inner_product_ground_truth = utils::inner_product_ground_truth(&(a.clone(), b)); let is_valid_witness = rand_output.0 == inner_product_ground_truth && rand_output.1 == *a[a.len() - 1].value(); match MockProver::run(k as u32, &circuit, vec![]).unwrap().verify() { @@ -316,7 +316,7 @@ fn neg_test_range_check(k: usize, range_bits: usize, lookup_bits: usize, rand_a: gate.range_check(builder.main(0), a_witness, range_bits); builder.config(k, Some(9)); - set_var("LOOKUP_BITS", lookup_bits.to_string()); + set_lookup_bits(lookup_bits); let circuit = RangeCircuitBuilder::mock(builder); // no break points // Check soundness of witness values let correct = fe_to_biguint(&rand_a).bits() <= range_bits as u64; @@ -343,7 +343,7 @@ fn neg_test_is_less_than_safe( ctx.advice[out_idx] = Assigned::Trivial(Fr::from(prank_out)); builder.config(k, Some(9)); - set_var("LOOKUP_BITS", lookup_bits.to_string()); + set_lookup_bits(lookup_bits); let circuit = RangeCircuitBuilder::mock(builder); // no break points // Check soundness of witness values // println!("rand_a: {rand_a:?}, b: {b:?}"); diff --git a/halo2-base/src/gates/tests/pos_prop_tests.rs b/halo2-base/src/gates/tests/pos_prop.rs similarity index 52% rename from halo2-base/src/gates/tests/pos_prop_tests.rs rename to halo2-base/src/gates/tests/pos_prop.rs index f110d12f..2d3a6cca 100644 --- a/halo2-base/src/gates/tests/pos_prop_tests.rs +++ b/halo2-base/src/gates/tests/pos_prop.rs @@ -1,19 +1,28 @@ -use crate::gates::tests::{flex_gate_tests, range_gate_tests, test_ground_truths::*, Fr}; -use crate::utils::{bit_length, fe_to_biguint}; +use std::cmp::max; + +use crate::gates::tests::{flex_gate, range, utils::*, Fr}; +use crate::utils::{biguint_to_fe, bit_length, fe_to_biguint}; use crate::{QuantumCell, QuantumCell::Witness}; + +use ff::{Field, PrimeField}; +use num_bigint::{BigUint, RandBigInt, RandomBits}; use proptest::{collection::vec, prelude::*}; +use rand::rngs::StdRng; +use rand::SeedableRng; //TODO: implement Copy for rand witness and rand fr to allow for array creation // create vec and convert to array??? //TODO: implement arbitrary for fr using looks like you'd probably need to implement your own TestFr struct to implement Arbitrary: https://docs.rs/quickcheck/latest/quickcheck/trait.Arbitrary.html , can probably just hack it from Fr = [u64; 4] prop_compose! { - pub fn rand_fr()(val in any::()) -> Fr { - Fr::from(val) + pub fn rand_fr()(seed in any::()) -> Fr { + let rng = StdRng::seed_from_u64(seed); + Fr::random(rng) } } prop_compose! { - pub fn rand_witness()(val in any::()) -> QuantumCell { - Witness(Fr::from(val)) + pub fn rand_witness()(seed in any::()) -> QuantumCell { + let rng = StdRng::seed_from_u64(seed); + Witness(Fr::random(rng)) } } @@ -30,25 +39,33 @@ prop_compose! { } prop_compose! { - pub fn rand_fr_range(lo: u32, hi: u32)(val in any::().prop_map(move |x| x % 2u64.pow(hi - lo))) -> Fr { - Fr::from(val) + pub fn rand_fr_range(bits: u64)(seed in any::()) -> Fr { + let mut rng = StdRng::seed_from_u64(seed); + let n = rng.sample(RandomBits::new(bits)); + biguint_to_fe(&n) } } prop_compose! { - pub fn rand_witness_range(lo: u32, hi: u32)(val in any::().prop_map(move |x| x % 2u64.pow(hi - lo))) -> QuantumCell { - Witness(Fr::from(val)) + pub fn rand_witness_range(bits: u64)(x in rand_fr_range(bits)) -> QuantumCell { + Witness(x) } } -// LEsson here 0..2^range_bits fails with 'Uniform::new called with `low >= high` -// therfore to still have a range of 0..2^range_bits we need on a mod it by 2^range_bits -// note k > lookup_bits prop_compose! { - fn range_check_strat((k_lo, k_hi): (usize, usize), min_lookup_bits: usize, max_range_bits: u32) - (range_bits in 2..=max_range_bits, k in k_lo..=k_hi) - (k in Just(k), lookup_bits in min_lookup_bits..(k-3), a in rand_fr_range(0, range_bits), - range_bits in Just(range_bits)) + fn lookup_strat((k_lo, k_hi): (usize, usize), min_lookup_bits: usize) + (k in k_lo..=k_hi) + (k in Just(k), lookup_bits in min_lookup_bits..k) + -> (usize, usize) { + (k, lookup_bits) + } +} +// k is in [k_lo, k_hi] +// lookup_bits is in [min_lookup_bits, k-1] +prop_compose! { + fn range_check_strat((k_lo, k_hi): (usize, usize), min_lookup_bits: usize, max_range_bits: u64) + ((k, lookup_bits) in lookup_strat((k_lo,k_hi), min_lookup_bits), range_bits in 2..=max_range_bits) + (k in Just(k), lookup_bits in Just(lookup_bits), a in rand_fr_range(range_bits), range_bits in Just(range_bits)) -> (usize, usize, Fr, usize) { (k, lookup_bits, a, range_bits as usize) } @@ -56,127 +73,131 @@ prop_compose! { prop_compose! { fn check_less_than_strat((k_lo, k_hi): (usize, usize), min_lookup_bits: usize, max_num_bits: usize) - (num_bits in 2..max_num_bits, k in k_lo..=k_hi) - (k in Just(k), a in rand_witness_range(0, num_bits as u32), b in rand_witness_range(0, num_bits as u32), - num_bits in Just(num_bits), lookup_bits in min_lookup_bits..k) - -> (usize, usize, QuantumCell, QuantumCell, usize) { + (num_bits in 2..max_num_bits, k in k_lo..=k_hi) + (k in Just(k), num_bits in Just(num_bits), lookup_bits in min_lookup_bits..k, seed in any::()) + -> (usize, usize, Fr, Fr, usize) { + let mut rng = StdRng::seed_from_u64(seed); + let mut b = rng.sample(RandomBits::new(num_bits as u64)); + if b == BigUint::from(0u32) { + b = BigUint::from(1u32) + } + let a = rng.gen_biguint_below(&b); + let [a,b] = [a,b].map(|x| biguint_to_fe(&x)); (k, lookup_bits, a, b, num_bits) } } prop_compose! { fn check_less_than_safe_strat((k_lo, k_hi): (usize, usize), min_lookup_bits: usize) - (k in k_lo..=k_hi) - (k in Just(k), b in any::(), a in rand_fr(), lookup_bits in min_lookup_bits..k) - -> (usize, usize, Fr, u64) { + (k in k_lo..=k_hi, b in any::()) + (lookup_bits in min_lookup_bits..k, k in Just(k), a in 0..b, b in Just(b)) + -> (usize, usize, u64, u64) { (k, lookup_bits, a, b) } } proptest! { - // Flex Gate Positive Tests #[test] fn prop_test_add(input in vec(rand_witness(), 2)) { let ground_truth = add_ground_truth(input.as_slice()); - let result = flex_gate_tests::test_add(input.as_slice()); + let result = flex_gate::test_add(input.as_slice()); prop_assert_eq!(result, ground_truth); } #[test] fn prop_test_sub(input in vec(rand_witness(), 2)) { let ground_truth = sub_ground_truth(input.as_slice()); - let result = flex_gate_tests::test_sub(input.as_slice()); + let result = flex_gate::test_sub(input.as_slice()); prop_assert_eq!(result, ground_truth); } #[test] fn prop_test_neg(input in rand_witness()) { let ground_truth = neg_ground_truth(input); - let result = flex_gate_tests::test_neg(input); + let result = flex_gate::test_neg(input); prop_assert_eq!(result, ground_truth); } #[test] fn prop_test_mul(inputs in vec(rand_witness(), 2)) { let ground_truth = mul_ground_truth(inputs.as_slice()); - let result = flex_gate_tests::test_mul(inputs.as_slice()); + let result = flex_gate::test_mul(inputs.as_slice()); prop_assert_eq!(result, ground_truth); } #[test] fn prop_test_mul_add(inputs in vec(rand_witness(), 3)) { let ground_truth = mul_add_ground_truth(inputs.as_slice()); - let result = flex_gate_tests::test_mul_add(inputs.as_slice()); + let result = flex_gate::test_mul_add(inputs.as_slice()); prop_assert_eq!(result, ground_truth); } #[test] fn prop_test_mul_not(inputs in vec(rand_witness(), 2)) { let ground_truth = mul_not_ground_truth(inputs.as_slice()); - let result = flex_gate_tests::test_mul_not(inputs.as_slice()); + let result = flex_gate::test_mul_not(inputs.as_slice()); prop_assert_eq!(result, ground_truth); } #[test] fn prop_test_assert_bit(input in rand_fr()) { let ground_truth = input == Fr::one() || input == Fr::zero(); - let result = flex_gate_tests::test_assert_bit(input).is_ok(); - prop_assert_eq!(result, ground_truth); + flex_gate::test_assert_bit(input, ground_truth); } // Note: due to unwrap after inversion this test will fail if the denominator is zero so we want to test for that. Therefore we do not filter for zero values. #[test] fn prop_test_div_unsafe(inputs in vec(rand_witness().prop_filter("Input cannot be 0",|x| *x.value() != Fr::zero()), 2)) { let ground_truth = div_unsafe_ground_truth(inputs.as_slice()); - let result = flex_gate_tests::test_div_unsafe(inputs.as_slice()); + let result = flex_gate::test_div_unsafe(inputs.as_slice()); prop_assert_eq!(result, ground_truth); } #[test] fn prop_test_assert_is_const(input in rand_fr()) { - flex_gate_tests::test_assert_is_const(&[input; 2]); + flex_gate::test_assert_is_const(&[input; 2]); } #[test] fn prop_test_inner_product(inputs in (vec(rand_witness(), 0..=100), vec(rand_witness(), 0..=100)).prop_filter("Input vectors must have equal length", |(a, b)| a.len() == b.len())) { let ground_truth = inner_product_ground_truth(&inputs); - let result = flex_gate_tests::test_inner_product(inputs); + let result = flex_gate::test_inner_product(inputs); prop_assert_eq!(result, ground_truth); } #[test] fn prop_test_inner_product_left_last(inputs in (vec(rand_witness(), 1..=100), vec(rand_witness(), 1..=100)).prop_filter("Input vectors must have equal length", |(a, b)| a.len() == b.len())) { let ground_truth = inner_product_left_last_ground_truth(&inputs); - let result = flex_gate_tests::test_inner_product_left_last(inputs); + let result = flex_gate::test_inner_product_left_last(inputs); prop_assert_eq!(result, ground_truth); } #[test] fn prop_test_inner_product_with_sums(inputs in (vec(rand_witness(), 0..=10), vec(rand_witness(), 1..=100)).prop_filter("Input vectors must have equal length", |(a, b)| a.len() == b.len())) { let ground_truth = inner_product_with_sums_ground_truth(&inputs); - let result = flex_gate_tests::test_inner_product_with_sums(inputs); + let result = flex_gate::test_inner_product_with_sums(inputs); prop_assert_eq!(result, ground_truth); } #[test] fn prop_test_sum_products_with_coeff_and_var(input in sum_products_with_coeff_and_var_strat(100)) { let expected = sum_products_with_coeff_and_var_ground_truth(&input); - let output = flex_gate_tests::test_sum_products_with_coeff_and_var(input); + let output = flex_gate::test_sum_products_with_coeff_and_var(input); prop_assert_eq!(expected, output); } #[test] fn prop_test_and(inputs in vec(rand_witness(), 2)) { let ground_truth = and_ground_truth(inputs.as_slice()); - let result = flex_gate_tests::test_and(inputs.as_slice()); + let result = flex_gate::test_and(inputs.as_slice()); prop_assert_eq!(result, ground_truth); } #[test] fn prop_test_not(input in rand_witness()) { let ground_truth = not_ground_truth(&input); - let result = flex_gate_tests::test_not(input); + let result = flex_gate::test_not(input); prop_assert_eq!(result, ground_truth); } @@ -184,49 +205,49 @@ proptest! { fn prop_test_select(vals in vec(rand_witness(), 2), sel in rand_bin_witness()) { let inputs = vec![vals[0], vals[1], sel]; let ground_truth = select_ground_truth(inputs.as_slice()); - let result = flex_gate_tests::test_select(inputs.as_slice()); + let result = flex_gate::test_select(inputs.as_slice()); prop_assert_eq!(result, ground_truth); } #[test] fn prop_test_or_and(inputs in vec(rand_witness(), 3)) { let ground_truth = or_and_ground_truth(inputs.as_slice()); - let result = flex_gate_tests::test_or_and(inputs.as_slice()); + let result = flex_gate::test_or_and(inputs.as_slice()); prop_assert_eq!(result, ground_truth); } #[test] fn prop_test_idx_to_indicator(input in (rand_witness(), 1..=16_usize)) { let ground_truth = idx_to_indicator_ground_truth(input); - let result = flex_gate_tests::test_idx_to_indicator((input.0, input.1)); + let result = flex_gate::test_idx_to_indicator(input.0, input.1); prop_assert_eq!(result, ground_truth); } #[test] fn prop_test_select_by_indicator(inputs in (vec(rand_witness(), 1..=10), rand_witness())) { let ground_truth = select_by_indicator_ground_truth(&inputs); - let result = flex_gate_tests::test_select_by_indicator(inputs); + let result = flex_gate::test_select_by_indicator(inputs.0, inputs.1); prop_assert_eq!(result, ground_truth); } #[test] fn prop_test_select_from_idx(inputs in (vec(rand_witness(), 1..=10), rand_witness())) { let ground_truth = select_from_idx_ground_truth(&inputs); - let result = flex_gate_tests::test_select_from_idx(inputs); + let result = flex_gate::test_select_from_idx(inputs.0, inputs.1); prop_assert_eq!(result, ground_truth); } #[test] fn prop_test_is_zero(x in rand_fr()) { let ground_truth = is_zero_ground_truth(x); - let result = flex_gate_tests::test_is_zero(x); + let result = flex_gate::test_is_zero(x); prop_assert_eq!(result, ground_truth); } #[test] fn prop_test_is_equal(inputs in vec(rand_witness(), 2)) { let ground_truth = is_equal_ground_truth(inputs.as_slice()); - let result = flex_gate_tests::test_is_equal(inputs.as_slice()); + let result = flex_gate::test_is_equal(inputs.as_slice()); prop_assert_eq!(result, ground_truth); } @@ -241,7 +262,7 @@ proptest! { bits.push(tmp & 1); tmp /= 2; } - let result = flex_gate_tests::test_num_to_bits((Fr::from(num), bits.len())); + let result = flex_gate::test_num_to_bits(num as usize, bits.len()); prop_assert_eq!(bits.into_iter().map(Fr::from).collect::>(), result); } @@ -251,76 +272,93 @@ proptest! { } */ - #[test] - fn prop_test_get_field_element(n in any::()) { - let ground_truth = get_field_element_ground_truth(n); - let result = flex_gate_tests::test_get_field_element::(n); - prop_assert_eq!(result, ground_truth); - } - // Range Check Property Tests #[test] - fn prop_test_is_less_than(a in rand_witness(), b in any::().prop_filter("not zero", |&x| x != 0), - lookup_bits in 4..=16_usize) { - let bits = std::cmp::max(fe_to_biguint(a.value()).bits() as usize, bit_length(b)); - let ground_truth = is_less_than_ground_truth((*a.value(), Fr::from(b))); - let result = range_gate_tests::test_is_less_than(([a, Witness(Fr::from(b))], bits, lookup_bits)); + fn prop_test_is_less_than( + (k, lookup_bits)in lookup_strat((10,18),4), + bits in 1..Fr::CAPACITY as usize, + seed in any::() + ) { + // current is_less_than requires bits to not be too large + prop_assume!(((bits + lookup_bits - 1) / lookup_bits + 1) * lookup_bits <= Fr::CAPACITY as usize); + let mut rng = StdRng::seed_from_u64(seed); + let a = biguint_to_fe(&rng.sample(RandomBits::new(bits as u64))); + let b = biguint_to_fe(&rng.sample(RandomBits::new(bits as u64))); + let ground_truth = is_less_than_ground_truth((a, b)); + let result = range::test_is_less_than(k, lookup_bits, [Witness(a), Witness(b)], bits); prop_assert_eq!(result, ground_truth); } #[test] - fn prop_test_is_less_than_safe(a in rand_fr().prop_filter("not zero", |&x| x != Fr::zero()), - b in any::().prop_filter("not zero", |&x| x != 0), - lookup_bits in 4..=16_usize) { - prop_assume!(fe_to_biguint(&a).bits() as usize <= bit_length(b)); + fn prop_test_is_less_than_safe( + (k, lookup_bits) in lookup_strat((10,18),4), + a in any::(), + b in any::(), + ) { + prop_assume!(bit_length(a) <= bit_length(b)); + let a = Fr::from(a); let ground_truth = is_less_than_ground_truth((a, Fr::from(b))); - let result = range_gate_tests::test_is_less_than_safe((a, b, lookup_bits)); + let result = range::test_is_less_than_safe(k, lookup_bits, a, b); prop_assert_eq!(result, ground_truth); } #[test] - fn prop_test_div_mod(inputs in (rand_witness().prop_filter("Non-zero num", |x| *x.value() != Fr::zero()), any::().prop_filter("Non-zero divisor", |x| *x != 0u64), 1..=16_usize)) { - let ground_truth = div_mod_ground_truth((*inputs.0.value(), inputs.1)); - let result = range_gate_tests::test_div_mod((inputs.0, inputs.1, inputs.2)); + fn prop_test_div_mod( + a in rand_witness(), + b in any::().prop_filter("Non-zero divisor", |x| *x != 0u64) + ) { + let ground_truth = div_mod_ground_truth((*a.value(), b)); + let num_bits = max(fe_to_biguint(a.value()).bits() as usize, bit_length(b)); + prop_assume!(num_bits <= Fr::CAPACITY as usize); + let result = range::test_div_mod(a, b, num_bits); prop_assert_eq!(result, ground_truth); } #[test] - fn prop_test_get_last_bit(input in rand_fr(), pad_bits in 0..10usize) { - let ground_truth = get_last_bit_ground_truth(input); - let bits = fe_to_biguint(&input).bits() as usize + pad_bits; - let result = range_gate_tests::test_get_last_bit((input, bits)); + fn prop_test_get_last_bit(bits in 1..Fr::CAPACITY as usize, pad_bits in 0..10usize, seed in any::()) { + prop_assume!(bits + pad_bits <= Fr::CAPACITY as usize); + let mut rng = StdRng::seed_from_u64(seed); + let a = rng.sample(RandomBits::new(bits as u64)); + let a = biguint_to_fe(&a); + let ground_truth = get_last_bit_ground_truth(a); + let bits = bits + pad_bits; + let result = range::test_get_last_bit(a, bits); prop_assert_eq!(result, ground_truth); } #[test] - fn prop_test_div_mod_var(inputs in (rand_witness(), any::(), 1..=16_usize, 1..=16_usize)) { - let ground_truth = div_mod_ground_truth((*inputs.0.value(), inputs.1)); - let result = range_gate_tests::test_div_mod_var((inputs.0, Witness(Fr::from(inputs.1)), inputs.2, inputs.3)); + fn prop_test_div_mod_var(a in rand_fr(), b in any::()) { + let ground_truth = div_mod_ground_truth((a, b)); + let a_num_bits = fe_to_biguint(&a).bits() as usize; + let lookup_bits = 9; + prop_assume!((a_num_bits + lookup_bits - 1) / lookup_bits * lookup_bits <= Fr::CAPACITY as usize); + let b_num_bits= bit_length(b); + let result = range::test_div_mod_var(Witness(a), Witness(Fr::from(b)), a_num_bits, b_num_bits); prop_assert_eq!(result, ground_truth); } #[test] - fn prop_test_range_check((k, lookup_bits, a, range_bits) in range_check_strat((14,24), 3, 63)) { - prop_assert_eq!(range_gate_tests::test_range_check(k, lookup_bits, a, range_bits), ()); + fn prop_test_range_check((k, lookup_bits, a, range_bits) in range_check_strat((14,22),3,253)) { + // current range check only works when range_bits isn't too big: + prop_assume!((range_bits + lookup_bits - 1) / lookup_bits * lookup_bits <= Fr::CAPACITY as usize); + range::test_range_check(k, lookup_bits, a, range_bits); } #[test] - fn prop_test_check_less_than((k, lookup_bits, a, b, num_bits) in check_less_than_strat((14,24), 3, 10)) { - prop_assume!(a.value() < b.value()); - prop_assert_eq!(range_gate_tests::test_check_less_than(k, lookup_bits, a, b, num_bits), ()); + fn prop_test_check_less_than((k, lookup_bits, a, b, num_bits) in check_less_than_strat((10,18),8,253)) { + prop_assume!((num_bits + lookup_bits - 1) / lookup_bits * lookup_bits <= Fr::CAPACITY as usize); + range::test_check_less_than(k, lookup_bits, Witness(a), Witness(b), num_bits); } #[test] - fn prop_test_check_less_than_safe((k, lookup_bits, a, b) in check_less_than_safe_strat((12,24),3)) { - prop_assume!(a < Fr::from(b)); - prop_assert_eq!(range_gate_tests::test_check_less_than_safe(k, lookup_bits, a, b), ()); + fn prop_test_check_less_than_safe((k, lookup_bits, a, b) in check_less_than_safe_strat((10,18),3)) { + range::test_check_less_than_safe(k, lookup_bits, Fr::from(a), b); } #[test] - fn prop_test_check_big_less_than_safe((k, lookup_bits, a, b) in check_less_than_safe_strat((12,24),3)) { - prop_assume!(a < Fr::from(b)); - prop_assert_eq!(range_gate_tests::test_check_big_less_than_safe(k, lookup_bits, a, b), ()); + fn prop_test_check_big_less_than_safe((k, lookup_bits, a, b, num_bits) in check_less_than_strat((18,22),8,253)) { + prop_assume!((num_bits + lookup_bits - 1) / lookup_bits * lookup_bits <= Fr::CAPACITY as usize); + range::test_check_big_less_than_safe(k, lookup_bits, a, fe_to_biguint(&b)); } } diff --git a/halo2-base/src/gates/tests/range.rs b/halo2-base/src/gates/tests/range.rs new file mode 100644 index 00000000..d477d3f2 --- /dev/null +++ b/halo2-base/src/gates/tests/range.rs @@ -0,0 +1,108 @@ +use super::*; +use crate::utils::biguint_to_fe; +use crate::utils::testing::base_test; +use crate::QuantumCell::Witness; +use crate::{gates::range::RangeInstructions, QuantumCell}; +use num_bigint::BigUint; +use test_case::test_case; + +#[test_case(16, 10, Fr::zero(), 0; "range_check() 0 bits")] +#[test_case(16, 10, Fr::from(100), 8; "range_check() pos")] +pub fn test_range_check(k: usize, lookup_bits: usize, a_val: Fr, range_bits: usize) { + base_test().k(k as u32).lookup_bits(lookup_bits).run(|ctx, chip| { + let a = ctx.load_witness(a_val); + chip.range_check(ctx, a, range_bits); + }) +} + +#[test_case(12, 10, Witness(Fr::zero()), Witness(Fr::one()), 64; "check_less_than() pos")] +pub fn test_check_less_than( + k: usize, + lookup_bits: usize, + a: QuantumCell, + b: QuantumCell, + num_bits: usize, +) { + base_test().k(k as u32).lookup_bits(lookup_bits).run(|ctx, chip| { + chip.check_less_than(ctx, a, b, num_bits); + }) +} + +#[test_case(10, 8, Fr::zero(), 1; "check_less_than_safe() pos")] +pub fn test_check_less_than_safe(k: usize, lookup_bits: usize, a: Fr, b: u64) { + base_test().k(k as u32).lookup_bits(lookup_bits).run(|ctx, chip| { + let a = ctx.load_witness(a); + chip.check_less_than_safe(ctx, a, b); + }) +} + +#[test_case(10, 8, biguint_to_fe(&BigUint::from(2u64).pow(239)), BigUint::from(2u64).pow(240) - 1usize; "check_big_less_than_safe() pos")] +pub fn test_check_big_less_than_safe(k: usize, lookup_bits: usize, a: Fr, b: BigUint) { + base_test().k(k as u32).lookup_bits(lookup_bits).run(|ctx, chip| { + let a = ctx.load_witness(a); + chip.check_big_less_than_safe(ctx, a, b) + }) +} + +#[test_case(10, 8, [6, 7].map(Fr::from).map(Witness), 3 => Fr::from(1); "is_less_than() pos")] +pub fn test_is_less_than( + k: usize, + lookup_bits: usize, + inputs: [QuantumCell; 2], + bits: usize, +) -> Fr { + base_test() + .k(k as u32) + .lookup_bits(lookup_bits) + .run(|ctx, chip| *chip.is_less_than(ctx, inputs[0], inputs[1], bits).value()) +} + +#[test_case(10, 8, Fr::from(2), 3 => Fr::from(1); "is_less_than_safe() pos")] +pub fn test_is_less_than_safe(k: usize, lookup_bits: usize, a: Fr, b: u64) -> Fr { + base_test().k(k as u32).lookup_bits(lookup_bits).run(|ctx, chip| { + let a = ctx.load_witness(a); + let lt = chip.is_less_than_safe(ctx, a, b); + *lt.value() + }) +} + +#[test_case(10, 8, biguint_to_fe(&BigUint::from(2u64).pow(239)), BigUint::from(2u64).pow(240) - 1usize => Fr::from(1); "is_big_less_than_safe() pos")] +pub fn test_is_big_less_than_safe(k: usize, lookup_bits: usize, a: Fr, b: BigUint) -> Fr { + base_test().k(k as u32).lookup_bits(lookup_bits).run(|ctx, chip| { + let a = ctx.load_witness(a); + *chip.is_big_less_than_safe(ctx, a, b).value() + }) +} + +#[test_case(Witness(Fr::from(3)), 2, 2 => (Fr::from(1), Fr::from(1)) ; "div_mod(3, 2)")] +pub fn test_div_mod(a: QuantumCell, b: u64, num_bits: usize) -> (Fr, Fr) { + base_test().run(|ctx, chip| { + let a = chip.div_mod(ctx, a, b, num_bits); + (*a.0.value(), *a.1.value()) + }) +} + +#[test_case(Fr::from(3), 8 => Fr::one() ; "get_last_bit(): 3, 8 bits")] +#[test_case(Fr::from(3), 2 => Fr::one() ; "get_last_bit(): 3, 2 bits")] +#[test_case(Fr::from(0), 2 => Fr::zero() ; "get_last_bit(): 0")] +#[test_case(Fr::from(1), 2 => Fr::one() ; "get_last_bit(): 1")] +#[test_case(Fr::from(2), 2 => Fr::zero() ; "get_last_bit(): 2")] +pub fn test_get_last_bit(a: Fr, bits: usize) -> Fr { + base_test().run(|ctx, chip| { + let a = ctx.load_witness(a); + *chip.get_last_bit(ctx, a, bits).value() + }) +} + +#[test_case(Witness(Fr::from(3)), Witness(Fr::from(2)), 3, 3 => (Fr::one(), Fr::one()); "div_mod_var(3 ,2)")] +pub fn test_div_mod_var( + a: QuantumCell, + b: QuantumCell, + a_num_bits: usize, + b_num_bits: usize, +) -> (Fr, Fr) { + base_test().run(|ctx, chip| { + let a = chip.div_mod_var(ctx, a, b, a_num_bits, b_num_bits); + (*a.0.value(), *a.1.value()) + }) +} diff --git a/halo2-base/src/gates/tests/range_gate_tests.rs b/halo2-base/src/gates/tests/range_gate_tests.rs deleted file mode 100644 index cd8acf52..00000000 --- a/halo2-base/src/gates/tests/range_gate_tests.rs +++ /dev/null @@ -1,156 +0,0 @@ -use std::env::set_var; - -use super::*; -use crate::halo2_proofs::dev::MockProver; -use crate::utils::{biguint_to_fe, ScalarField}; -use crate::QuantumCell::Witness; -use crate::{ - gates::{ - builder::{GateThreadBuilder, RangeCircuitBuilder}, - range::{RangeChip, RangeInstructions}, - }, - utils::BigPrimeField, - QuantumCell, -}; -use num_bigint::BigUint; -use test_case::test_case; - -#[test_case(16, 10, Fr::zero(), 0; "range_check() 0 bits")] -#[test_case(16, 10, Fr::from(100), 8; "range_check() pos")] -pub fn test_range_check(k: usize, lookup_bits: usize, a_val: F, range_bits: usize) { - set_var("LOOKUP_BITS", lookup_bits.to_string()); - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = RangeChip::default(lookup_bits); - let a = ctx.assign_witnesses([a_val])[0]; - chip.range_check(ctx, a, range_bits); - // auto-tune circuit - builder.config(k, Some(9)); - // create circuit - let circuit = RangeCircuitBuilder::mock(builder); - MockProver::run(k as u32, &circuit, vec![]).unwrap().assert_satisfied() -} - -#[test_case(12, 10, Witness(Fr::zero()), Witness(Fr::one()), 64; "check_less_than() pos")] -pub fn test_check_less_than( - k: usize, - lookup_bits: usize, - a: QuantumCell, - b: QuantumCell, - num_bits: usize, -) { - set_var("LOOKUP_BITS", lookup_bits.to_string()); - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = RangeChip::default(lookup_bits); - chip.check_less_than(ctx, a, b, num_bits); - // auto-tune circuit - builder.config(k, Some(9)); - // create circuit - let circuit = RangeCircuitBuilder::mock(builder); - MockProver::run(k as u32, &circuit, vec![]).unwrap().assert_satisfied() -} - -#[test_case(10, 8, Fr::zero(), 1; "check_less_than_safe() pos")] -pub fn test_check_less_than_safe(k: usize, lookup_bits: usize, a_val: F, b: u64) { - set_var("LOOKUP_BITS", lookup_bits.to_string()); - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = RangeChip::default(lookup_bits); - let a = ctx.assign_witnesses([a_val])[0]; - chip.check_less_than_safe(ctx, a, b); - // auto-tune circuit - builder.config(k, Some(9)); - // create circuit - let circuit = RangeCircuitBuilder::mock(builder); - MockProver::run(k as u32, &circuit, vec![]).unwrap().assert_satisfied() -} - -#[test_case(10, 8, Fr::zero(), 1; "check_big_less_than_safe() pos")] -pub fn test_check_big_less_than_safe( - k: usize, - lookup_bits: usize, - a_val: F, - b: u64, -) { - set_var("LOOKUP_BITS", lookup_bits.to_string()); - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = RangeChip::default(lookup_bits); - let a = ctx.assign_witnesses([a_val])[0]; - chip.check_big_less_than_safe(ctx, a, BigUint::from(b)); - // auto-tune circuit - builder.config(k, Some(9)); - // create circuit - let circuit = RangeCircuitBuilder::mock(builder); - MockProver::run(k as u32, &circuit, vec![]).unwrap().assert_satisfied() -} - -#[test_case(([0, 1].map(Fr::from).map(Witness), 3, 12) => Fr::from(1) ; "is_less_than() pos")] -pub fn test_is_less_than( - (inputs, bits, lookup_bits): ([QuantumCell; 2], usize, usize), -) -> F { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = RangeChip::default(lookup_bits); - let a = chip.is_less_than(ctx, inputs[0], inputs[1], bits); - *a.value() -} - -#[test_case((Fr::zero(), 3, 3) => Fr::from(1) ; "is_less_than_safe() pos")] -pub fn test_is_less_than_safe((a, b, lookup_bits): (F, u64, usize)) -> F { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = RangeChip::default(lookup_bits); - let a = ctx.load_witness(a); - let lt = chip.is_less_than_safe(ctx, a, b); - *lt.value() -} - -#[test_case((biguint_to_fe(&BigUint::from(2u64).pow(239)), BigUint::from(2u64).pow(240) - 1usize, 8) => Fr::from(1) ; "is_big_less_than_safe() pos")] -pub fn test_is_big_less_than_safe( - (a, b, lookup_bits): (F, BigUint, usize), -) -> F { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = RangeChip::default(lookup_bits); - let a = ctx.load_witness(a); - let b = chip.is_big_less_than_safe(ctx, a, b); - *b.value() -} - -#[test_case((Witness(Fr::one()), 1, 2) => (Fr::one(), Fr::zero()) ; "div_mod() pos")] -pub fn test_div_mod( - inputs: (QuantumCell, u64, usize), -) -> (F, F) { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = RangeChip::default(3); - let a = chip.div_mod(ctx, inputs.0, BigUint::from(inputs.1), inputs.2); - (*a.0.value(), *a.1.value()) -} - -#[test_case((Fr::from(3), 8) => Fr::one() ; "get_last_bit(): 3, 8 bits")] -#[test_case((Fr::from(3), 2) => Fr::one() ; "get_last_bit(): 3, 2 bits")] -#[test_case((Fr::from(0), 2) => Fr::zero() ; "get_last_bit(): 0")] -#[test_case((Fr::from(1), 2) => Fr::one() ; "get_last_bit(): 1")] -#[test_case((Fr::from(2), 2) => Fr::zero() ; "get_last_bit(): 2")] -pub fn test_get_last_bit((a, bits): (F, usize)) -> F { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = RangeChip::default(3); - let a = ctx.load_witness(a); - let b = chip.get_last_bit(ctx, a, bits); - *b.value() -} - -#[test_case((Witness(Fr::from(3)), Witness(Fr::from(2)), 3, 3) => (Fr::one(), Fr::one()) ; "div_mod_var() pos")] -pub fn test_div_mod_var( - inputs: (QuantumCell, QuantumCell, usize, usize), -) -> (F, F) { - let mut builder = GateThreadBuilder::mock(); - let ctx = builder.main(0); - let chip = RangeChip::default(3); - let a = chip.div_mod_var(ctx, inputs.0, inputs.1, inputs.2, inputs.3); - (*a.0.value(), *a.1.value()) -} diff --git a/halo2-base/src/gates/tests/test_ground_truths.rs b/halo2-base/src/gates/tests/utils.rs similarity index 98% rename from halo2-base/src/gates/tests/test_ground_truths.rs rename to halo2-base/src/gates/tests/utils.rs index 234cf636..59942637 100644 --- a/halo2-base/src/gates/tests/test_ground_truths.rs +++ b/halo2-base/src/gates/tests/utils.rs @@ -166,10 +166,6 @@ pub fn lagrange_eval_ground_truth(inputs: &[F]) -> (F, F) { } */ -pub fn get_field_element_ground_truth(n: u64) -> F { - F::from(n) -} - // Range Chip Ground Truths pub fn is_less_than_ground_truth(inputs: (F, F)) -> F { diff --git a/halo2-base/src/safe_types/tests.rs b/halo2-base/src/safe_types/tests.rs index 14480fdd..ccf49930 100644 --- a/halo2-base/src/safe_types/tests.rs +++ b/halo2-base/src/safe_types/tests.rs @@ -1,4 +1,5 @@ use crate::{ + gates::builder::set_lookup_bits, halo2_proofs::{halo2curves::bn256::Fr, poly::kzg::commitment::ParamsKZG}, utils::testing::{check_proof, gen_proof}, }; @@ -16,7 +17,6 @@ use crate::{ }; use itertools::Itertools; use rand::rngs::OsRng; -use std::env; // soundness checks for `raw_bytes_to` function fn test_raw_bytes_to_gen( @@ -28,7 +28,7 @@ fn test_raw_bytes_to_gen( // first create proving and verifying key let mut builder = GateThreadBuilder::::keygen(); let lookup_bits = 3; - env::set_var("LOOKUP_BITS", lookup_bits.to_string()); + set_lookup_bits(lookup_bits); let range_chip = RangeChip::::default(lookup_bits); let safe_type_chip = SafeTypeChip::new(&range_chip); diff --git a/halo2-base/src/utils.rs b/halo2-base/src/utils/mod.rs similarity index 91% rename from halo2-base/src/utils.rs rename to halo2-base/src/utils/mod.rs index 81397bd9..2117b1ee 100644 --- a/halo2-base/src/utils.rs +++ b/halo2-base/src/utils/mod.rs @@ -8,6 +8,9 @@ use num_bigint::Sign; use num_traits::Signed; use num_traits::{One, Zero}; +#[cfg(any(test, feature = "test-utils"))] +pub mod testing; + /// Helper trait to convert to and from a [BigPrimeField] by converting a list of [u64] digits #[cfg(feature = "halo2-axiom")] pub trait BigPrimeField: ScalarField { @@ -480,68 +483,6 @@ pub mod fs { } } -/// Utilities for testing -#[cfg(any(test, feature = "test-utils"))] -pub mod testing { - use crate::halo2_proofs::{ - halo2curves::bn256::{Bn256, Fr, G1Affine}, - plonk::{create_proof, verify_proof, Circuit, ProvingKey, VerifyingKey}, - poly::commitment::ParamsProver, - poly::kzg::{ - commitment::KZGCommitmentScheme, commitment::ParamsKZG, multiopen::ProverSHPLONK, - multiopen::VerifierSHPLONK, strategy::SingleStrategy, - }, - transcript::{ - Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, - }, - }; - use rand::rngs::OsRng; - - /// helper function to generate a proof with real prover - pub fn gen_proof( - params: &ParamsKZG, - pk: &ProvingKey, - circuit: impl Circuit, - ) -> Vec { - let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); - create_proof::< - KZGCommitmentScheme, - ProverSHPLONK<'_, Bn256>, - Challenge255<_>, - _, - Blake2bWrite, G1Affine, _>, - _, - >(params, pk, &[circuit], &[&[]], OsRng, &mut transcript) - .expect("prover should not fail"); - transcript.finalize() - } - - /// helper function to verify a proof - pub fn check_proof( - params: &ParamsKZG, - vk: &VerifyingKey, - proof: &[u8], - expect_satisfied: bool, - ) { - let verifier_params = params.verifier_params(); - let strategy = SingleStrategy::new(params); - let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(proof); - let res = verify_proof::< - KZGCommitmentScheme, - VerifierSHPLONK<'_, Bn256>, - Challenge255, - Blake2bRead<&[u8], G1Affine, Challenge255>, - SingleStrategy<'_, Bn256>, - >(verifier_params, vk, strategy, &[&[]], &mut transcript); - - if expect_satisfied { - assert!(res.is_ok()); - } else { - assert!(res.is_err()); - } - } -} - #[cfg(test)] mod tests { use crate::halo2_proofs::halo2curves::bn256::Fr; diff --git a/halo2-base/src/utils/testing.rs b/halo2-base/src/utils/testing.rs new file mode 100644 index 00000000..e51b4eef --- /dev/null +++ b/halo2-base/src/utils/testing.rs @@ -0,0 +1,163 @@ +//! Utilities for testing +use crate::{ + gates::{ + builder::{GateThreadBuilder, RangeCircuitBuilder, BASE_CONFIG_PARAMS}, + GateChip, + }, + halo2_proofs::{ + halo2curves::bn256::{Bn256, Fr, G1Affine}, + plonk::{create_proof, verify_proof, Circuit, ProvingKey, VerifyingKey}, + poly::commitment::ParamsProver, + poly::kzg::{ + commitment::KZGCommitmentScheme, commitment::ParamsKZG, multiopen::ProverSHPLONK, + multiopen::VerifierSHPLONK, strategy::SingleStrategy, + }, + transcript::{ + Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, + }, + }, + safe_types::RangeChip, + Context, +}; +use halo2_proofs_axiom::dev::MockProver; +use rand::{rngs::StdRng, SeedableRng}; + +/// helper function to generate a proof with real prover +pub fn gen_proof( + params: &ParamsKZG, + pk: &ProvingKey, + circuit: impl Circuit, +) -> Vec { + let rng = StdRng::seed_from_u64(0); + let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); + create_proof::< + KZGCommitmentScheme, + ProverSHPLONK<'_, Bn256>, + Challenge255<_>, + _, + Blake2bWrite, G1Affine, _>, + _, + >(params, pk, &[circuit], &[&[]], rng, &mut transcript) + .expect("prover should not fail"); + transcript.finalize() +} + +/// helper function to verify a proof +pub fn check_proof( + params: &ParamsKZG, + vk: &VerifyingKey, + proof: &[u8], + expect_satisfied: bool, +) { + let verifier_params = params.verifier_params(); + let strategy = SingleStrategy::new(params); + let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(proof); + let res = verify_proof::< + KZGCommitmentScheme, + VerifierSHPLONK<'_, Bn256>, + Challenge255, + Blake2bRead<&[u8], G1Affine, Challenge255>, + SingleStrategy<'_, Bn256>, + >(verifier_params, vk, strategy, &[&[]], &mut transcript); + + if expect_satisfied { + assert!(res.is_ok()); + } else { + assert!(res.is_err()); + } +} + +/// Helper to facilitate easier writing of tests using `RangeChip` and `RangeCircuitBuilder`. +/// By default, the [`MockProver`] is used. +/// +/// Currently this tester uses all private inputs. +pub struct BaseTester { + k: u32, + lookup_bits: Option, + expect_satisfied: bool, +} + +impl Default for BaseTester { + fn default() -> Self { + Self { k: 10, lookup_bits: Some(9), expect_satisfied: true } + } +} + +/// Creates a [`BaseTester`] +pub fn base_test() -> BaseTester { + BaseTester::default() +} + +impl BaseTester { + /// Changes the number of rows in the circuit to 2k. + /// By default it will also set lookup bits as large as possible, to `k - 1`. + pub fn k(mut self, k: u32) -> Self { + self.k = k; + self.lookup_bits = Some(k as usize - 1); + self + } + + /// Sets the size of the lookup table used for range checks to [0, 2lookup_bits) + pub fn lookup_bits(mut self, lookup_bits: usize) -> Self { + assert!(lookup_bits < self.k as usize, "lookup_bits must be less than k"); + self.lookup_bits = Some(lookup_bits); + self + } + + /// Specify whether you expect this test to pass or fail. Default: pass + pub fn expect_satisfied(mut self, expect_satisfied: bool) -> Self { + self.expect_satisfied = expect_satisfied; + self + } + + /// Run a mock test by providing a closure that uses a `ctx` and `RangeChip`. + /// - `expect_satisfied`: flag for whether you expect the test to pass or fail. Failure means a constraint system failure -- the tester does not catch system panics. + pub fn run(&self, f: impl FnOnce(&mut Context, &RangeChip) -> R) -> R { + self.run_builder(|builder, range| f(builder.main(0), range)) + } + + /// Run a mock test by providing a closure that uses a `ctx` and `GateChip`. + /// - `expect_satisfied`: flag for whether you expect the test to pass or fail. Failure means a constraint system failure -- the tester does not catch system panics. + pub fn run_gate(&self, f: impl FnOnce(&mut Context, &GateChip) -> R) -> R { + self.run(|ctx, range| f(ctx, &range.gate)) + } + + /// Run a mock test by providing a closure that uses a `builder` and `RangeChip`. + /// - `expect_satisfied`: flag for whether you expect the test to pass or fail. Failure means a constraint system failure -- the tester does not catch system panics. + pub fn run_builder( + &self, + f: impl FnOnce(&mut GateThreadBuilder, &RangeChip) -> R, + ) -> R { + let mut builder = GateThreadBuilder::mock(); + let range = RangeChip::default(self.lookup_bits.unwrap_or(0)); + BASE_CONFIG_PARAMS.with(|conf| { + conf.borrow_mut().k = self.k as usize; + conf.borrow_mut().lookup_bits = self.lookup_bits; + }); + // run the function, mutating `builder` + let res = f(&mut builder, &range); + + // helper check: if your function didn't use lookups, turn lookup table "off" + let t_cells_lookup = builder + .threads + .iter() + .map(|t| t.iter().map(|ctx| ctx.cells_to_lookup.len()).sum::()) + .sum::(); + if t_cells_lookup == 0 { + BASE_CONFIG_PARAMS.with(|conf| { + conf.borrow_mut().lookup_bits = None; + }) + } + + // configure the circuit shape, 9 blinding rows seems enough + builder.config(self.k as usize, Some(9)); + // create circuit + let circuit = RangeCircuitBuilder::mock(builder); + if self.expect_satisfied { + MockProver::run(self.k, &circuit, vec![]).unwrap().assert_satisfied(); + } else { + assert!(MockProver::run(self.k, &circuit, vec![]).unwrap().verify().is_err()); + } + res + } +} diff --git a/halo2-ecc/benches/fixed_base_msm.rs b/halo2-ecc/benches/fixed_base_msm.rs index b4f3df25..581835b1 100644 --- a/halo2-ecc/benches/fixed_base_msm.rs +++ b/halo2-ecc/benches/fixed_base_msm.rs @@ -1,7 +1,8 @@ use ark_std::{end_timer, start_timer}; use halo2_base::gates::{ builder::{ - CircuitBuilderStage, GateThreadBuilder, MultiPhaseThreadBreakPoints, RangeCircuitBuilder, + set_lookup_bits, CircuitBuilderStage, GateThreadBuilder, MultiPhaseThreadBreakPoints, + RangeCircuitBuilder, }, RangeChip, }; @@ -45,7 +46,7 @@ fn fixed_base_msm_bench( bases: Vec, scalars: Vec, ) { - std::env::set_var("LOOKUP_BITS", params.lookup_bits.to_string()); + set_lookup_bits(params.lookup_bits); let range = RangeChip::::default(params.lookup_bits); let fp_chip = FpChip::::new(&range, params.limb_bits, params.num_limbs); let ecc_chip = EccChip::new(&fp_chip); diff --git a/halo2-ecc/benches/fp_mul.rs b/halo2-ecc/benches/fp_mul.rs index 48351c45..10ef5f20 100644 --- a/halo2-ecc/benches/fp_mul.rs +++ b/halo2-ecc/benches/fp_mul.rs @@ -2,7 +2,7 @@ use ark_std::{end_timer, start_timer}; use halo2_base::{ gates::{ builder::{ - CircuitBuilderStage, GateThreadBuilder, MultiPhaseThreadBreakPoints, + set_lookup_bits, CircuitBuilderStage, GateThreadBuilder, MultiPhaseThreadBreakPoints, RangeCircuitBuilder, }, RangeChip, @@ -40,7 +40,7 @@ fn fp_mul_bench( _a: Fq, _b: Fq, ) { - std::env::set_var("LOOKUP_BITS", lookup_bits.to_string()); + set_lookup_bits(lookup_bits); let range = RangeChip::::default(lookup_bits); let chip = FpChip::::new(&range, limb_bits, num_limbs); diff --git a/halo2-ecc/benches/msm.rs b/halo2-ecc/benches/msm.rs index 3a98ee38..3d97e361 100644 --- a/halo2-ecc/benches/msm.rs +++ b/halo2-ecc/benches/msm.rs @@ -1,7 +1,8 @@ use ark_std::{end_timer, start_timer}; use halo2_base::gates::{ builder::{ - CircuitBuilderStage, GateThreadBuilder, MultiPhaseThreadBreakPoints, RangeCircuitBuilder, + set_lookup_bits, CircuitBuilderStage, GateThreadBuilder, MultiPhaseThreadBreakPoints, + RangeCircuitBuilder, }, RangeChip, }; @@ -51,7 +52,7 @@ fn msm_bench( bases: Vec, scalars: Vec, ) { - std::env::set_var("LOOKUP_BITS", params.lookup_bits.to_string()); + set_lookup_bits(params.lookup_bits); let range = RangeChip::::default(params.lookup_bits); let fp_chip = FpChip::::new(&range, params.limb_bits, params.num_limbs); let ecc_chip = EccChip::new(&fp_chip); diff --git a/halo2-ecc/src/bn254/tests/ec_add.rs b/halo2-ecc/src/bn254/tests/ec_add.rs index a902ce3c..a2136c9e 100644 --- a/halo2-ecc/src/bn254/tests/ec_add.rs +++ b/halo2-ecc/src/bn254/tests/ec_add.rs @@ -6,7 +6,7 @@ use super::*; use crate::fields::{FieldChip, FpStrategy}; use crate::halo2_proofs::halo2curves::bn256::G2Affine; use group::cofactor::CofactorCurveAffine; -use halo2_base::gates::builder::{GateThreadBuilder, RangeCircuitBuilder}; +use halo2_base::gates::builder::{set_lookup_bits, GateThreadBuilder, RangeCircuitBuilder}; use halo2_base::gates::RangeChip; use halo2_base::utils::fs::gen_srs; use halo2_base::Context; @@ -27,7 +27,7 @@ struct CircuitParams { } fn g2_add_test(ctx: &mut Context, params: CircuitParams, _points: Vec) { - std::env::set_var("LOOKUP_BITS", params.lookup_bits.to_string()); + set_lookup_bits(params.lookup_bits); let range = RangeChip::::default(params.lookup_bits); let fp_chip = FpChip::::new(&range, params.limb_bits, params.num_limbs); let fp2_chip = Fp2Chip::::new(&fp_chip); diff --git a/halo2-ecc/src/bn254/tests/fixed_base_msm.rs b/halo2-ecc/src/bn254/tests/fixed_base_msm.rs index 0283f672..d839049c 100644 --- a/halo2-ecc/src/bn254/tests/fixed_base_msm.rs +++ b/halo2-ecc/src/bn254/tests/fixed_base_msm.rs @@ -11,7 +11,7 @@ use ff::PrimeField as _; use halo2_base::{ gates::{ builder::{ - CircuitBuilderStage, GateThreadBuilder, MultiPhaseThreadBreakPoints, + set_lookup_bits, CircuitBuilderStage, GateThreadBuilder, MultiPhaseThreadBreakPoints, RangeCircuitBuilder, }, RangeChip, @@ -43,7 +43,7 @@ fn fixed_base_msm_test( bases: Vec, scalars: Vec, ) { - std::env::set_var("LOOKUP_BITS", params.lookup_bits.to_string()); + set_lookup_bits(params.lookup_bits); let range = RangeChip::::default(params.lookup_bits); let fp_chip = FpChip::::new(&range, params.limb_bits, params.num_limbs); let ecc_chip = EccChip::new(&fp_chip); diff --git a/halo2-ecc/src/bn254/tests/msm.rs b/halo2-ecc/src/bn254/tests/msm.rs index cfc7d40f..804638b2 100644 --- a/halo2-ecc/src/bn254/tests/msm.rs +++ b/halo2-ecc/src/bn254/tests/msm.rs @@ -3,7 +3,7 @@ use ff::{Field, PrimeField}; use halo2_base::{ gates::{ builder::{ - CircuitBuilderStage, GateThreadBuilder, MultiPhaseThreadBreakPoints, + set_lookup_bits, CircuitBuilderStage, GateThreadBuilder, MultiPhaseThreadBreakPoints, RangeCircuitBuilder, }, RangeChip, @@ -39,7 +39,7 @@ fn msm_test( scalars: Vec, window_bits: usize, ) { - std::env::set_var("LOOKUP_BITS", params.lookup_bits.to_string()); + set_lookup_bits(params.lookup_bits); let range = RangeChip::::default(params.lookup_bits); let fp_chip = FpChip::::new(&range, params.limb_bits, params.num_limbs); let ecc_chip = EccChip::new(&fp_chip); diff --git a/halo2-ecc/src/bn254/tests/msm_sum_infinity.rs b/halo2-ecc/src/bn254/tests/msm_sum_infinity.rs index 600a4931..45940c64 100644 --- a/halo2-ecc/src/bn254/tests/msm_sum_infinity.rs +++ b/halo2-ecc/src/bn254/tests/msm_sum_infinity.rs @@ -1,7 +1,8 @@ use ff::PrimeField; use halo2_base::gates::{ builder::{ - CircuitBuilderStage, GateThreadBuilder, MultiPhaseThreadBreakPoints, RangeCircuitBuilder, + set_lookup_bits, CircuitBuilderStage, GateThreadBuilder, MultiPhaseThreadBreakPoints, + RangeCircuitBuilder, }, RangeChip, }; @@ -17,7 +18,7 @@ fn msm_test( scalars: Vec, window_bits: usize, ) { - std::env::set_var("LOOKUP_BITS", params.lookup_bits.to_string()); + set_lookup_bits(params.lookup_bits); let range = RangeChip::::default(params.lookup_bits); let fp_chip = FpChip::::new(&range, params.limb_bits, params.num_limbs); let ecc_chip = EccChip::new(&fp_chip); diff --git a/halo2-ecc/src/bn254/tests/msm_sum_infinity_fixed_base.rs b/halo2-ecc/src/bn254/tests/msm_sum_infinity_fixed_base.rs index 6cf96c7f..b2eb1518 100644 --- a/halo2-ecc/src/bn254/tests/msm_sum_infinity_fixed_base.rs +++ b/halo2-ecc/src/bn254/tests/msm_sum_infinity_fixed_base.rs @@ -1,7 +1,8 @@ use ff::PrimeField; use halo2_base::gates::{ builder::{ - CircuitBuilderStage, GateThreadBuilder, MultiPhaseThreadBreakPoints, RangeCircuitBuilder, + set_lookup_bits, CircuitBuilderStage, GateThreadBuilder, MultiPhaseThreadBreakPoints, + RangeCircuitBuilder, }, RangeChip, }; @@ -17,7 +18,7 @@ fn msm_test( scalars: Vec, window_bits: usize, ) { - std::env::set_var("LOOKUP_BITS", params.lookup_bits.to_string()); + set_lookup_bits(params.lookup_bits); let range = RangeChip::::default(params.lookup_bits); let fp_chip = FpChip::::new(&range, params.limb_bits, params.num_limbs); let ecc_chip = EccChip::new(&fp_chip); diff --git a/halo2-ecc/src/bn254/tests/pairing.rs b/halo2-ecc/src/bn254/tests/pairing.rs index 37f82684..e5f3da48 100644 --- a/halo2-ecc/src/bn254/tests/pairing.rs +++ b/halo2-ecc/src/bn254/tests/pairing.rs @@ -9,7 +9,7 @@ use crate::{fields::FpStrategy, halo2_proofs::halo2curves::bn256::G2Affine}; use halo2_base::{ gates::{ builder::{ - CircuitBuilderStage, GateThreadBuilder, MultiPhaseThreadBreakPoints, + set_lookup_bits, CircuitBuilderStage, GateThreadBuilder, MultiPhaseThreadBreakPoints, RangeCircuitBuilder, }, RangeChip, @@ -38,7 +38,7 @@ fn pairing_test( P: G1Affine, Q: G2Affine, ) { - std::env::set_var("LOOKUP_BITS", params.lookup_bits.to_string()); + set_lookup_bits(params.lookup_bits); let range = RangeChip::::default(params.lookup_bits); let fp_chip = FpChip::::new(&range, params.limb_bits, params.num_limbs); let chip = PairingChip::new(&fp_chip); diff --git a/halo2-ecc/src/ecc/tests.rs b/halo2-ecc/src/ecc/tests.rs index 5bbc612e..887f7cfc 100644 --- a/halo2-ecc/src/ecc/tests.rs +++ b/halo2-ecc/src/ecc/tests.rs @@ -8,7 +8,7 @@ use crate::halo2_proofs::{ plonk::*, }; use group::Group; -use halo2_base::gates::builder::RangeCircuitBuilder; +use halo2_base::gates::builder::{set_lookup_bits, RangeCircuitBuilder}; use halo2_base::gates::RangeChip; use halo2_base::utils::bigint_to_fe; use halo2_base::SKIP_FIRST_PASS; @@ -26,7 +26,7 @@ fn basic_g1_tests( P: G1Affine, Q: G1Affine, ) { - std::env::set_var("LOOKUP_BITS", lookup_bits.to_string()); + set_lookup_bits(lookup_bits); let range = RangeChip::::default(lookup_bits); let fp_chip = FpChip::::new(&range, limb_bits, num_limbs); let chip = EccChip::new(&fp_chip); diff --git a/halo2-ecc/src/fields/tests/fp/assert_eq.rs b/halo2-ecc/src/fields/tests/fp/assert_eq.rs index a8184594..c364bb56 100644 --- a/halo2-ecc/src/fields/tests/fp/assert_eq.rs +++ b/halo2-ecc/src/fields/tests/fp/assert_eq.rs @@ -1,9 +1,7 @@ -use std::env::set_var; - use ff::Field; use halo2_base::{ gates::{ - builder::{GateThreadBuilder, RangeCircuitBuilder}, + builder::{set_lookup_bits, GateThreadBuilder, RangeCircuitBuilder}, RangeChip, }, halo2_proofs::{ @@ -19,7 +17,7 @@ use rand::thread_rng; // soundness checks for `` function fn test_fp_assert_eq_gen(k: u32, lookup_bits: usize, num_tries: usize) { let mut rng = thread_rng(); - set_var("LOOKUP_BITS", lookup_bits.to_string()); + set_lookup_bits(lookup_bits); // first create proving and verifying key let mut builder = GateThreadBuilder::keygen(); diff --git a/halo2-ecc/src/fields/tests/fp/mod.rs b/halo2-ecc/src/fields/tests/fp/mod.rs index 675aab5a..9bfb9691 100644 --- a/halo2-ecc/src/fields/tests/fp/mod.rs +++ b/halo2-ecc/src/fields/tests/fp/mod.rs @@ -1,5 +1,3 @@ -use std::env::set_var; - use crate::fields::fp::FpChip; use crate::fields::{FieldChip, PrimeField}; use crate::halo2_proofs::{ @@ -7,7 +5,7 @@ use crate::halo2_proofs::{ halo2curves::bn256::{Fq, Fr}, }; -use halo2_base::gates::builder::{GateThreadBuilder, RangeCircuitBuilder}; +use halo2_base::gates::builder::{set_lookup_bits, GateThreadBuilder, RangeCircuitBuilder}; use halo2_base::gates::RangeChip; use halo2_base::utils::biguint_to_fe; use halo2_base::utils::{fe_to_biguint, modulus}; @@ -25,7 +23,7 @@ fn fp_chip_test( num_limbs: usize, f: impl Fn(&mut Context, &FpChip), ) { - set_var("LOOKUP_BITS", lookup_bits.to_string()); + set_lookup_bits(lookup_bits); let range = RangeChip::::default(lookup_bits); let chip = FpChip::::new(&range, limb_bits, num_limbs); diff --git a/halo2-ecc/src/fields/tests/fp12/mod.rs b/halo2-ecc/src/fields/tests/fp12/mod.rs index 6fb631b9..2a743401 100644 --- a/halo2-ecc/src/fields/tests/fp12/mod.rs +++ b/halo2-ecc/src/fields/tests/fp12/mod.rs @@ -5,7 +5,7 @@ use crate::halo2_proofs::{ dev::MockProver, halo2curves::bn256::{Fq, Fq12, Fr}, }; -use halo2_base::gates::builder::{GateThreadBuilder, RangeCircuitBuilder}; +use halo2_base::gates::builder::{set_lookup_bits, GateThreadBuilder, RangeCircuitBuilder}; use halo2_base::gates::RangeChip; use halo2_base::Context; use rand_core::OsRng; @@ -20,7 +20,7 @@ fn fp12_mul_test( _a: Fq12, _b: Fq12, ) { - std::env::set_var("LOOKUP_BITS", lookup_bits.to_string()); + set_lookup_bits(lookup_bits); let range = RangeChip::::default(lookup_bits); let fp_chip = FpChip::::new(&range, limb_bits, num_limbs); let chip = Fp12Chip::::new(&fp_chip); diff --git a/halo2-ecc/src/secp256k1/tests/ecdsa.rs b/halo2-ecc/src/secp256k1/tests/ecdsa.rs index af7050f9..b4e07a8b 100644 --- a/halo2-ecc/src/secp256k1/tests/ecdsa.rs +++ b/halo2-ecc/src/secp256k1/tests/ecdsa.rs @@ -24,7 +24,8 @@ use crate::{ }; use ark_std::{end_timer, start_timer}; use halo2_base::gates::builder::{ - CircuitBuilderStage, GateThreadBuilder, MultiPhaseThreadBreakPoints, RangeCircuitBuilder, + set_lookup_bits, CircuitBuilderStage, GateThreadBuilder, MultiPhaseThreadBreakPoints, + RangeCircuitBuilder, }; use halo2_base::gates::RangeChip; use halo2_base::utils::fs::gen_srs; @@ -57,7 +58,7 @@ fn ecdsa_test( msghash: Fq, pk: Secp256k1Affine, ) { - std::env::set_var("LOOKUP_BITS", params.lookup_bits.to_string()); + set_lookup_bits(params.lookup_bits); let range = RangeChip::::default(params.lookup_bits); let fp_chip = FpChip::::new(&range, params.limb_bits, params.num_limbs); let fq_chip = FqChip::::new(&range, params.limb_bits, params.num_limbs); diff --git a/halo2-ecc/src/secp256k1/tests/ecdsa_tests.rs b/halo2-ecc/src/secp256k1/tests/ecdsa_tests.rs index 45e251f3..da55f3df 100644 --- a/halo2-ecc/src/secp256k1/tests/ecdsa_tests.rs +++ b/halo2-ecc/src/secp256k1/tests/ecdsa_tests.rs @@ -12,7 +12,8 @@ use crate::{ }; use ark_std::{end_timer, start_timer}; use halo2_base::gates::builder::{ - CircuitBuilderStage, GateThreadBuilder, MultiPhaseThreadBreakPoints, RangeCircuitBuilder, + set_lookup_bits, CircuitBuilderStage, GateThreadBuilder, MultiPhaseThreadBreakPoints, + RangeCircuitBuilder, }; use halo2_base::gates::RangeChip; @@ -33,7 +34,7 @@ fn ecdsa_test( msghash: Fq, pk: Secp256k1Affine, ) { - std::env::set_var("LOOKUP_BITS", params.lookup_bits.to_string()); + set_lookup_bits(params.lookup_bits); let range = RangeChip::::default(params.lookup_bits); let fp_chip = FpChip::::new(&range, params.limb_bits, params.num_limbs); let fq_chip = FqChip::::new(&range, params.limb_bits, params.num_limbs); diff --git a/halo2-ecc/src/secp256k1/tests/mod.rs b/halo2-ecc/src/secp256k1/tests/mod.rs index 803ac232..997b432e 100644 --- a/halo2-ecc/src/secp256k1/tests/mod.rs +++ b/halo2-ecc/src/secp256k1/tests/mod.rs @@ -6,7 +6,7 @@ use group::Curve; use halo2_base::{ gates::{ builder::{ - CircuitBuilderStage, GateThreadBuilder, MultiPhaseThreadBreakPoints, + set_lookup_bits, CircuitBuilderStage, GateThreadBuilder, MultiPhaseThreadBreakPoints, RangeCircuitBuilder, }, RangeChip, @@ -53,7 +53,7 @@ fn sm_test( scalar: Fq, window_bits: usize, ) { - std::env::set_var("LOOKUP_BITS", params.lookup_bits.to_string()); + set_lookup_bits(params.lookup_bits); let range = RangeChip::::default(params.lookup_bits); let fp_chip = FpChip::::new(&range, params.limb_bits, params.num_limbs); let fq_chip = FqChip::::new(&range, params.limb_bits, params.num_limbs); diff --git a/hashes/zkevm-keccak/Cargo.toml b/hashes/zkevm-keccak/Cargo.toml index 3b35b7a3..abbad893 100644 --- a/hashes/zkevm-keccak/Cargo.toml +++ b/hashes/zkevm-keccak/Cargo.toml @@ -25,6 +25,7 @@ pretty_assertions = "1.0.0" rand_core = "0.6.4" rand_xorshift = "0.3" env_logger = "0.10" +test-case = "3.1.0" [features] default = ["halo2-axiom", "display"] diff --git a/hashes/zkevm-keccak/src/keccak_packed_multi.rs b/hashes/zkevm-keccak/src/keccak_packed_multi.rs index 55be8306..f7df8cd5 100644 --- a/hashes/zkevm-keccak/src/keccak_packed_multi.rs +++ b/hashes/zkevm-keccak/src/keccak_packed_multi.rs @@ -20,8 +20,7 @@ use halo2_base::halo2_proofs::{circuit::AssignedCell, plonk::Assigned}; use itertools::Itertools; use log::{debug, info}; use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; -use std::env::var; -use std::marker::PhantomData; +use std::{cell::RefCell, marker::PhantomData}; #[cfg(test)] mod tests; @@ -32,36 +31,33 @@ const THETA_C_LOOKUP_RANGE: usize = 6; const RHO_PI_LOOKUP_RANGE: usize = 4; const CHI_BASE_LOOKUP_RANGE: usize = 5; -pub fn get_num_rows_per_round() -> usize { - var("KECCAK_ROWS") - .unwrap_or_else(|_| "25".to_string()) - .parse() - .expect("Cannot parse KECCAK_ROWS env var as usize") +thread_local! { + pub static KECCAK_CONFIG_PARAMS: RefCell = RefCell::new(Default::default()); } -fn get_num_bits_per_absorb_lookup() -> usize { - get_num_bits_per_lookup(ABSORB_LOOKUP_RANGE) +fn get_num_bits_per_absorb_lookup(k: u32) -> usize { + get_num_bits_per_lookup(ABSORB_LOOKUP_RANGE, k) } -fn get_num_bits_per_theta_c_lookup() -> usize { - get_num_bits_per_lookup(THETA_C_LOOKUP_RANGE) +fn get_num_bits_per_theta_c_lookup(k: u32) -> usize { + get_num_bits_per_lookup(THETA_C_LOOKUP_RANGE, k) } -fn get_num_bits_per_rho_pi_lookup() -> usize { - get_num_bits_per_lookup(CHI_BASE_LOOKUP_RANGE.max(RHO_PI_LOOKUP_RANGE)) +fn get_num_bits_per_rho_pi_lookup(k: u32) -> usize { + get_num_bits_per_lookup(CHI_BASE_LOOKUP_RANGE.max(RHO_PI_LOOKUP_RANGE), k) } -fn get_num_bits_per_base_chi_lookup() -> usize { - get_num_bits_per_lookup(CHI_BASE_LOOKUP_RANGE.max(RHO_PI_LOOKUP_RANGE)) +fn get_num_bits_per_base_chi_lookup(k: u32) -> usize { + get_num_bits_per_lookup(CHI_BASE_LOOKUP_RANGE.max(RHO_PI_LOOKUP_RANGE), k) } /// The number of keccak_f's that can be done in this circuit /// /// `num_rows` should be number of usable rows without blinding factors -pub fn get_keccak_capacity(num_rows: usize) -> usize { +pub fn get_keccak_capacity(num_rows: usize, rows_per_round: usize) -> usize { // - 1 because we have a dummy round at the very beginning of multi_keccak - // - NUM_WORDS_TO_ABSORB because `absorb_data_next` and `absorb_result_next` query `NUM_WORDS_TO_ABSORB * get_num_rows_per_round()` beyond any row where `q_absorb == 1` - (num_rows / get_num_rows_per_round() - 1 - NUM_WORDS_TO_ABSORB) / (NUM_ROUNDS + 1) + // - NUM_WORDS_TO_ABSORB because `absorb_data_next` and `absorb_result_next` query `NUM_WORDS_TO_ABSORB * num_rows_per_round` beyond any row where `q_absorb == 1` + (num_rows / rows_per_round - 1 - NUM_WORDS_TO_ABSORB) / (NUM_ROUNDS + 1) } pub fn get_num_keccak_f(byte_length: usize) -> usize { @@ -814,6 +810,15 @@ mod transform_to { } } +/// Configuration parameters to define [`KeccakCircuitConfig`] +#[derive(Copy, Clone, Debug, Default)] +pub struct KeccakConfigParams { + /// The circuit degree, i.e., circuit has 2k rows + pub k: u32, + /// The number of rows to use for each round in the keccak_f permutation + pub rows_per_round: usize, +} + /// KeccakConfig #[derive(Clone, Debug)] pub struct KeccakCircuitConfig { @@ -836,6 +841,10 @@ pub struct KeccakCircuitConfig { normalize_6: [TableColumn; 2], chi_base_table: [TableColumn; 2], pack_table: [TableColumn; 2], + + // config parameters for convenience + pub parameters: KeccakConfigParams, + _marker: PhantomData, } @@ -844,7 +853,14 @@ impl KeccakCircuitConfig { self.challenge } /// Return a new KeccakCircuitConfig - pub fn new(meta: &mut ConstraintSystem, challenge: Challenge) -> Self { + pub fn new( + meta: &mut ConstraintSystem, + challenge: Challenge, + parameters: KeccakConfigParams, + ) -> Self { + let k = parameters.k; + let num_rows_per_round = parameters.rows_per_round; + let q_enable = meta.fixed_column(); // let q_enable_row = meta.fixed_column(); let q_first = meta.fixed_column(); @@ -867,8 +883,7 @@ impl KeccakCircuitConfig { let chi_base_table = array_init::array_init(|_| meta.lookup_table_column()); let pack_table = array_init::array_init(|_| meta.lookup_table_column()); - let num_rows_per_round = get_num_rows_per_round(); - let mut cell_manager = CellManager::new(get_num_rows_per_round()); + let mut cell_manager = CellManager::new(num_rows_per_round); let mut cb = BaseConstraintBuilder::new(MAX_DEGREE); let mut total_lookup_counter = 0; @@ -919,7 +934,7 @@ impl KeccakCircuitConfig { // rlc. cell_manager.start_region(); let mut lookup_counter = 0; - let part_size = get_num_bits_per_absorb_lookup(); + let part_size = get_num_bits_per_absorb_lookup(k); let input = absorb_from.expr() + absorb_data.expr(); let absorb_fat = split::expr(meta, &mut cell_manager, &mut cb, input, 0, part_size, false, None); @@ -978,7 +993,7 @@ impl KeccakCircuitConfig { // that allows us to also calculate the rotated value "for free". cell_manager.start_region(); let mut lookup_counter = 0; - let part_size_c = get_num_bits_per_theta_c_lookup(); + let part_size_c = get_num_bits_per_theta_c_lookup(k); let mut c_parts = Vec::new(); for s in s.iter() { // Calculate c and split into parts @@ -1038,7 +1053,7 @@ impl KeccakCircuitConfig { // `s[j][2 * i + 3 * j) % 5] = normalize(rot(s[i][j], RHOM[i][j]))`. cell_manager.start_region(); let mut lookup_counter = 0; - let part_size = get_num_bits_per_base_chi_lookup(); + let part_size = get_num_bits_per_base_chi_lookup(k); // To combine the rho/pi/chi steps we have to ensure a specific layout so // query those cells here first. // For chi we have to do `s[i][j] ^ ((~s[(i+1)%5][j]) & s[(i+2)%5][j])`. `j` @@ -1123,7 +1138,7 @@ impl KeccakCircuitConfig { // s[(i+2)%5][j])` five times, on each row (no selector needed). // This is calculated by making use of `CHI_BASE_LOOKUP_TABLE`. let mut lookup_counter = 0; - let part_size_base = get_num_bits_per_base_chi_lookup(); + let part_size_base = get_num_bits_per_base_chi_lookup(k); for idx in 0..num_columns { // First fetch the cells we wan to use let mut input: [Expression; 5] = array_init::array_init(|_| 0.expr()); @@ -1165,7 +1180,7 @@ impl KeccakCircuitConfig { // iota // Simply do the single xor on state [0][0]. cell_manager.start_region(); - let part_size = get_num_bits_per_absorb_lookup(); + let part_size = get_num_bits_per_absorb_lookup(k); let input = s[0][0].clone() + round_cst_expr.clone(); let iota_parts = split::expr(meta, &mut cell_manager, &mut cb, input, 0, part_size, false, None); @@ -1508,13 +1523,13 @@ impl KeccakCircuitConfig { #[cfg(not(feature = "display"))] info!("Total Keccak Columns: {}", cell_manager.get_width()); info!("num unused cells: {}", cell_manager.get_num_unused_cells()); - info!("part_size absorb: {}", get_num_bits_per_absorb_lookup()); - info!("part_size theta: {}", get_num_bits_per_theta_c_lookup()); - info!("part_size theta c: {}", get_num_bits_per_lookup(THETA_C_LOOKUP_RANGE)); - info!("part_size theta t: {}", get_num_bits_per_lookup(4)); - info!("part_size rho/pi: {}", get_num_bits_per_rho_pi_lookup()); - info!("part_size chi base: {}", get_num_bits_per_base_chi_lookup()); - info!("uniform part sizes: {:?}", target_part_sizes(get_num_bits_per_theta_c_lookup())); + info!("part_size absorb: {}", get_num_bits_per_absorb_lookup(k)); + info!("part_size theta: {}", get_num_bits_per_theta_c_lookup(k)); + info!("part_size theta c: {}", get_num_bits_per_lookup(THETA_C_LOOKUP_RANGE, k)); + info!("part_size theta t: {}", get_num_bits_per_lookup(4, k)); + info!("part_size rho/pi: {}", get_num_bits_per_rho_pi_lookup(k)); + info!("part_size chi base: {}", get_num_bits_per_base_chi_lookup(k)); + info!("uniform part sizes: {:?}", target_part_sizes(get_num_bits_per_theta_c_lookup(k))); KeccakCircuitConfig { challenge, @@ -1534,6 +1549,7 @@ impl KeccakCircuitConfig { normalize_6, chi_base_table, pack_table, + parameters, _marker: PhantomData, } } @@ -1576,15 +1592,15 @@ impl KeccakCircuitConfig { assign_fixed_custom(region, self.round_cst, offset, row.round_cst); } - pub fn load_aux_tables(&self, layouter: &mut impl Layouter) -> Result<(), Error> { - load_normalize_table(layouter, "normalize_6", &self.normalize_6, 6u64)?; - load_normalize_table(layouter, "normalize_4", &self.normalize_4, 4u64)?; - load_normalize_table(layouter, "normalize_3", &self.normalize_3, 3u64)?; + pub fn load_aux_tables(&self, layouter: &mut impl Layouter, k: u32) -> Result<(), Error> { + load_normalize_table(layouter, "normalize_6", &self.normalize_6, 6u64, k)?; + load_normalize_table(layouter, "normalize_4", &self.normalize_4, 4u64, k)?; + load_normalize_table(layouter, "normalize_3", &self.normalize_3, 3u64, k)?; load_lookup_table( layouter, "chi base", &self.chi_base_table, - get_num_bits_per_base_chi_lookup(), + get_num_bits_per_base_chi_lookup(k), &CHI_BASE_LOOKUP_TABLE, )?; load_pack_table(layouter, &self.pack_table) @@ -1600,9 +1616,9 @@ pub fn keccak_phase1<'v, F: Field>( challenge: Value, input_rlcs: &mut Vec>, offset: &mut usize, + rows_per_round: usize, ) { let num_chunks = get_num_keccak_f(bytes.len()); - let num_rows_per_round = get_num_rows_per_round(); let mut byte_idx = 0; let mut data_rlc = Value::known(F::zero()); @@ -1629,7 +1645,7 @@ pub fn keccak_phase1<'v, F: Field>( input_rlcs.push(input_rlc); } - *offset += num_rows_per_round; + *offset += rows_per_round; } } } @@ -1640,12 +1656,15 @@ pub fn keccak_phase0( rows: &mut Vec>, squeeze_digests: &mut Vec<[F; NUM_WORDS_TO_SQUEEZE]>, bytes: &[u8], + parameters: KeccakConfigParams, ) { + let k = parameters.k; + let num_rows_per_round = parameters.rows_per_round; + let mut bits = into_bits(bytes); let mut s = [[F::zero(); 5]; 5]; let absorb_positions = get_absorb_positions(); let num_bytes_in_last_block = bytes.len() % RATE; - let num_rows_per_round = get_num_rows_per_round(); let two = F::from(2u64); // Padding @@ -1705,7 +1724,7 @@ pub fn keccak_phase0( // Absorb cell_manager.start_region(); - let part_size = get_num_bits_per_absorb_lookup(); + let part_size = get_num_bits_per_absorb_lookup(k); let input = absorb_row.from + absorb_row.absorb; let absorb_fat = split::value(&mut cell_manager, &mut region, input, 0, part_size, false, None); @@ -1743,7 +1762,7 @@ pub fn keccak_phase0( if round != NUM_ROUNDS { // Theta - let part_size = get_num_bits_per_theta_c_lookup(); + let part_size = get_num_bits_per_theta_c_lookup(k); let mut bcf = Vec::new(); for s in &s { let c = s[0] + s[1] + s[2] + s[3] + s[4]; @@ -1777,7 +1796,7 @@ pub fn keccak_phase0( cell_manager.start_region(); // Rho/Pi - let part_size = get_num_bits_per_base_chi_lookup(); + let part_size = get_num_bits_per_base_chi_lookup(k); let target_word_sizes = target_part_sizes(part_size); let num_word_parts = target_word_sizes.len(); let mut rho_pi_chi_cells: [[[Vec>; 5]; 5]; 3] = @@ -1826,7 +1845,7 @@ pub fn keccak_phase0( cell_manager.start_region(); // Chi - let part_size_base = get_num_bits_per_base_chi_lookup(); + let part_size_base = get_num_bits_per_base_chi_lookup(k); let three_packed = pack::(&vec![3u8; part_size_base]); let mut os = [[F::zero(); 5]; 5]; for j in 0..5 { @@ -1858,7 +1877,7 @@ pub fn keccak_phase0( cell_manager.start_region(); // iota - let part_size = get_num_bits_per_absorb_lookup(); + let part_size = get_num_bits_per_absorb_lookup(k); let input = s[0][0] + pack_u64::(ROUND_CST[round]); let iota_parts = split::value::( &mut cell_manager, @@ -1961,28 +1980,45 @@ pub fn multi_keccak_phase1<'a, 'v, F: Field>( bytes: impl IntoIterator, challenge: Value, squeeze_digests: Vec<[F; NUM_WORDS_TO_SQUEEZE]>, + parameters: KeccakConfigParams, ) -> (Vec>, Vec>) { let mut input_rlcs = Vec::with_capacity(squeeze_digests.len()); let mut output_rlcs = Vec::with_capacity(squeeze_digests.len()); - let num_rows_per_round = get_num_rows_per_round(); - for idx in 0..num_rows_per_round { + let rows_per_round = parameters.rows_per_round; + for idx in 0..rows_per_round { [keccak_table.input_rlc, keccak_table.output_rlc] .map(|column| assign_advice_custom(region, column, idx, Value::known(F::zero()))); } - let mut offset = num_rows_per_round; + let mut offset = rows_per_round; for bytes in bytes { - keccak_phase1(region, keccak_table, bytes, challenge, &mut input_rlcs, &mut offset); + keccak_phase1( + region, + keccak_table, + bytes, + challenge, + &mut input_rlcs, + &mut offset, + rows_per_round, + ); } debug_assert!(input_rlcs.len() <= squeeze_digests.len()); while input_rlcs.len() < squeeze_digests.len() { - keccak_phase1(region, keccak_table, &[], challenge, &mut input_rlcs, &mut offset); + keccak_phase1( + region, + keccak_table, + &[], + challenge, + &mut input_rlcs, + &mut offset, + rows_per_round, + ); } - offset = num_rows_per_round; + offset = rows_per_round; for hash_words in squeeze_digests { - offset += num_rows_per_round * NUM_ROUNDS; + offset += rows_per_round * NUM_ROUNDS; let hash_rlc = hash_words .into_iter() .flat_map(|a| to_bytes::value(&unpack(a))) @@ -1991,7 +2027,7 @@ pub fn multi_keccak_phase1<'a, 'v, F: Field>( .unwrap(); let output_rlc = assign_advice_custom(region, keccak_table.output_rlc, offset, hash_rlc); output_rlcs.push(output_rlc); - offset += num_rows_per_round; + offset += rows_per_round; } (input_rlcs, output_rlcs) @@ -2001,8 +2037,9 @@ pub fn multi_keccak_phase1<'a, 'v, F: Field>( pub fn multi_keccak_phase0( bytes: &[Vec], capacity: Option, + parameters: KeccakConfigParams, ) -> (Vec>, Vec<[F; NUM_WORDS_TO_SQUEEZE]>) { - let num_rows_per_round = get_num_rows_per_round(); + let num_rows_per_round = parameters.rows_per_round; let mut rows = Vec::with_capacity((1 + capacity.unwrap_or(0) * (NUM_ROUNDS + 1)) * num_rows_per_round); // Dummy first row so that the initial data is absorbed @@ -2015,7 +2052,7 @@ pub fn multi_keccak_phase0( let num_keccak_f = get_num_keccak_f(bytes.len()); let mut squeeze_digests = Vec::with_capacity(num_keccak_f); let mut rows = Vec::with_capacity(num_keccak_f * (NUM_ROUNDS + 1) * num_rows_per_round); - keccak_phase0(&mut rows, &mut squeeze_digests, bytes); + keccak_phase0(&mut rows, &mut squeeze_digests, bytes, parameters); (rows, squeeze_digests) }) .collect::>(); @@ -2028,11 +2065,11 @@ pub fn multi_keccak_phase0( if let Some(capacity) = capacity { // Pad with no data hashes to the expected capacity - while rows.len() < (1 + capacity * (NUM_ROUNDS + 1)) * get_num_rows_per_round() { - keccak_phase0(&mut rows, &mut squeeze_digests, &[]); + while rows.len() < (1 + capacity * (NUM_ROUNDS + 1)) * num_rows_per_round { + keccak_phase0(&mut rows, &mut squeeze_digests, &[], parameters); } // Check that we are not over capacity - if rows.len() > (1 + capacity * (NUM_ROUNDS + 1)) * get_num_rows_per_round() { + if rows.len() > (1 + capacity * (NUM_ROUNDS + 1)) * num_rows_per_round { panic!("{:?}", Error::BoundsFailure); } } diff --git a/hashes/zkevm-keccak/src/keccak_packed_multi/tests.rs b/hashes/zkevm-keccak/src/keccak_packed_multi/tests.rs index 4619a197..a3df0b0e 100644 --- a/hashes/zkevm-keccak/src/keccak_packed_multi/tests.rs +++ b/hashes/zkevm-keccak/src/keccak_packed_multi/tests.rs @@ -18,7 +18,9 @@ use crate::halo2_proofs::{ Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, }, }; +use halo2_base::SKIP_FIRST_PASS; use rand_core::OsRng; +use test_case::test_case; /// KeccakCircuit #[derive(Default, Clone, Debug)] @@ -42,7 +44,8 @@ impl Circuit for KeccakCircuit { meta.advice_column(); let challenge = meta.challenge_usable_after(FirstPhase); - KeccakCircuitConfig::new(meta, challenge) + let params = KECCAK_CONFIG_PARAMS.with(|conf| *conf.borrow()); + KeccakCircuitConfig::new(meta, challenge, params) } fn synthesize( @@ -50,9 +53,10 @@ impl Circuit for KeccakCircuit { config: Self::Config, mut layouter: impl Layouter, ) -> Result<(), Error> { - config.load_aux_tables(&mut layouter)?; + let params = config.parameters; + config.load_aux_tables(&mut layouter, params.k)?; let mut challenge = layouter.get_challenge(config.challenge); - let mut first_pass = true; + let mut first_pass = SKIP_FIRST_PASS; layouter.assign_region( || "keccak circuit", |mut region| { @@ -60,7 +64,11 @@ impl Circuit for KeccakCircuit { first_pass = false; return Ok(()); } - let (witness, squeeze_digests) = multi_keccak_phase0(&self.inputs, self.capacity()); + let (witness, squeeze_digests) = multi_keccak_phase0( + &self.inputs, + self.num_rows.map(|nr| get_keccak_capacity(nr, params.rows_per_round)), + params, + ); config.assign(&mut region, &witness); #[cfg(feature = "halo2-axiom")] @@ -74,7 +82,9 @@ impl Circuit for KeccakCircuit { self.inputs.iter().map(|v| v.as_slice()), challenge, squeeze_digests, + params, ); + println!("finished keccak circuit"); Ok(()) }, )?; @@ -88,12 +98,6 @@ impl KeccakCircuit { pub fn new(num_rows: Option, inputs: Vec>) -> Self { KeccakCircuit { inputs, num_rows, _marker: PhantomData } } - - /// The number of keccak_f's that can be done in this circuit - pub fn capacity(&self) -> Option { - // Subtract two for unusable rows - self.num_rows.map(|num_rows| num_rows / ((NUM_ROUNDS + 1) * get_num_rows_per_round()) - 2) - } } fn verify(k: u32, inputs: Vec>, _success: bool) { @@ -103,12 +107,14 @@ fn verify(k: u32, inputs: Vec>, _success: bool) { prover.assert_satisfied(); } -/// Cmdline: KECCAK_ROWS=28 KECCAK_DEGREE=14 RUST_LOG=info cargo test -- --nocapture packed_multi_keccak_simple -#[test] -fn packed_multi_keccak_simple() { +#[test_case(14, 28; "k: 14, rows_per_round: 28")] +fn packed_multi_keccak_simple(k: u32, rows_per_round: usize) { + KECCAK_CONFIG_PARAMS.with(|conf| { + conf.borrow_mut().k = k; + conf.borrow_mut().rows_per_round = rows_per_round; + }); let _ = env_logger::builder().is_test(true).try_init(); - let k = 14; let inputs = vec![ vec![], (0u8..1).collect::>(), @@ -119,11 +125,14 @@ fn packed_multi_keccak_simple() { verify::(k, inputs, true); } -#[test] -fn packed_multi_keccak_prover() { +#[test_case(14, 25 ; "k: 14, rows_per_round: 25")] +fn packed_multi_keccak_prover(k: u32, rows_per_round: usize) { + KECCAK_CONFIG_PARAMS.with(|conf| { + conf.borrow_mut().k = k; + conf.borrow_mut().rows_per_round = rows_per_round; + }); let _ = env_logger::builder().is_test(true).try_init(); - let k: u32 = var("KECCAK_DEGREE").unwrap_or_else(|_| "14".to_string()).parse().unwrap(); let params = ParamsKZG::::setup(k, OsRng); let inputs = vec![ diff --git a/hashes/zkevm-keccak/src/util.rs b/hashes/zkevm-keccak/src/util.rs index b3e2e2b5..4ddf8590 100644 --- a/hashes/zkevm-keccak/src/util.rs +++ b/hashes/zkevm-keccak/src/util.rs @@ -5,7 +5,6 @@ use crate::halo2_proofs::{ plonk::{Error, TableColumn}, }; use itertools::Itertools; -use std::env::var; pub mod constraint_builder; pub mod eth_types; @@ -286,21 +285,12 @@ impl WordParts { } } -/// Get the degree of the circuit from the KECCAK_DEGREE env variable -pub fn get_degree() -> usize { - var("KECCAK_DEGREE") - .expect("Need to set KECCAK_DEGREE to log_2(rows) of circuit") - .parse() - .expect("Cannot parse KECCAK_DEGREE env var as usize") -} - /// Returns how many bits we can process in a single lookup given the range of /// values the bit can have and the height of the circuit. -pub fn get_num_bits_per_lookup(range: usize) -> usize { +pub fn get_num_bits_per_lookup(range: usize, k: u32) -> usize { let num_unusable_rows = 31; - let degree = get_degree() as u32; let mut num_bits = 1; - while range.pow(num_bits + 1) + num_unusable_rows <= 2usize.pow(degree) { + while range.pow(num_bits + 1) + num_unusable_rows <= 2usize.pow(k) { num_bits += 1; } num_bits as usize @@ -312,8 +302,9 @@ pub(crate) fn load_normalize_table( name: &str, tables: &[TableColumn; 2], range: u64, + k: u32, ) -> Result<(), Error> { - let part_size = get_num_bits_per_lookup(range as usize); + let part_size = get_num_bits_per_lookup(range as usize, k); layouter.assign_table( || format!("{name} table"), |mut table| {