diff --git a/Cargo.lock b/Cargo.lock index c23a86fc59..36299d8a0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2250,6 +2250,8 @@ dependencies = [ "plotters", "pretty_assertions", "rand", + "strum", + "strum_macros", ] [[package]] diff --git a/keccak256/Cargo.toml b/keccak256/Cargo.toml index 7c35517a70..6fc9344f1f 100644 --- a/keccak256/Cargo.toml +++ b/keccak256/Cargo.toml @@ -16,6 +16,8 @@ plotters = { version = "0.3.0", optional = true } eth-types = { path = "../eth-types" } lazy_static = "1.4" gadgets = { path = "../gadgets" } +strum = "0.24" +strum_macros = "0.24" [dev-dependencies] pretty_assertions = "1.0" diff --git a/keccak256/src/permutation/circuit.rs b/keccak256/src/permutation/circuit.rs index c1c649f139..8b1a8a62d0 100644 --- a/keccak256/src/permutation/circuit.rs +++ b/keccak256/src/permutation/circuit.rs @@ -3,15 +3,21 @@ use crate::{ common::{NEXT_INPUTS_LANES, PERMUTATION, ROUND_CONSTANTS}, keccak_arith::*, permutation::{ - base_conversion::BaseConversionConfig, generic::GenericConfig, iota::IotaConstants, - mixing::MixingConfig, pi::pi_gate_permutation, rho::RhoConfig, - tables::FromBase9TableConfig, theta::ThetaConfig, xi::XiConfig, + base_conversion::BaseConversionConfig, + generic::GenericConfig, + iota::IotaConstants, + mixing::MixingConfig, + pi::pi_gate_permutation, + rho::RhoConfig, + tables::{FromBase9TableConfig, StackableTable}, + theta::ThetaConfig, + xi::XiConfig, }, }; use eth_types::Field; use halo2_proofs::{ circuit::{AssignedCell, Layouter, Region}, - plonk::{Advice, Column, ConstraintSystem, Error, Selector}, + plonk::{Advice, Column, ConstraintSystem, Error, Selector, TableColumn}, poly::Rotation, }; use itertools::Itertools; @@ -19,6 +25,7 @@ use std::convert::TryInto; #[derive(Clone, Debug)] pub struct KeccakFConfig { generic: GenericConfig, + stackable: StackableTable, theta_config: ThetaConfig, rho_config: RhoConfig, xi_config: XiConfig, @@ -45,11 +52,19 @@ impl KeccakFConfig { let fixed = meta.fixed_column(); let generic = GenericConfig::configure(meta, state[0..3].try_into().unwrap(), fixed); + let table_cols: [TableColumn; 3] = (0..3) + .map(|_| meta.lookup_table_column()) + .collect_vec() + .try_into() + .unwrap(); + let stackable = + StackableTable::configure(meta, state[0..3].try_into().unwrap(), table_cols); // theta let theta_config = ThetaConfig::configure(meta.selector(), meta, state); // rho - let rho_config = RhoConfig::configure(meta, state, fixed, &generic); + let rho_config = + RhoConfig::configure(meta, state, fixed, generic.clone(), stackable.clone()); // xi let xi_config = XiConfig::configure(meta.selector(), meta, state); @@ -90,6 +105,7 @@ impl KeccakFConfig { KeccakFConfig { generic, + stackable, theta_config, rho_config, xi_config, @@ -103,6 +119,7 @@ impl KeccakFConfig { } pub fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { + self.stackable.load(layouter)?; self.rho_config.load(layouter)?; self.from_b9_table.load(layouter) } diff --git a/keccak256/src/permutation/rho.rs b/keccak256/src/permutation/rho.rs index 93718c8510..33029dd6a4 100644 --- a/keccak256/src/permutation/rho.rs +++ b/keccak256/src/permutation/rho.rs @@ -1,8 +1,7 @@ use crate::permutation::{ generic::GenericConfig, - rho_checks::{LaneRotateConversionConfig, OverflowCheckConfig}, - rho_helpers::{STEP2_RANGE, STEP3_RANGE}, - tables::{Base13toBase9TableConfig, RangeCheckConfig, SpecialChunkTableConfig}, + rho_checks::LaneRotateConversionConfig, + tables::{Base13toBase9TableConfig, StackableTable}, }; use eth_types::Field; @@ -15,11 +14,9 @@ use std::convert::TryInto; #[derive(Debug, Clone)] pub struct RhoConfig { lane_config: LaneRotateConversionConfig, - overflow_check_config: OverflowCheckConfig, base13_to_9_table: Base13toBase9TableConfig, - special_chunk_table: SpecialChunkTableConfig, - step2_range_table: RangeCheckConfig, - step3_range_table: RangeCheckConfig, + stackable: StackableTable, + generic: GenericConfig, } impl RhoConfig { @@ -27,37 +24,25 @@ impl RhoConfig { meta: &mut ConstraintSystem, state: [Column; 25], fixed: Column, - generic: &GenericConfig, + generic: GenericConfig, + stackable: StackableTable, ) -> Self { state.iter().for_each(|col| meta.enable_equality(*col)); let base13_to_9_table = Base13toBase9TableConfig::configure(meta); - let special_chunk_table = SpecialChunkTableConfig::configure(meta); - let step2_range_table = RangeCheckConfig::::configure(meta); - let step3_range_table = RangeCheckConfig::::configure(meta); let lane_config = LaneRotateConversionConfig::configure( meta, &base13_to_9_table, - &special_chunk_table, state[0..3].try_into().unwrap(), fixed, generic.clone(), - ); - - let overflow_check_config = OverflowCheckConfig::configure( - meta, - &step2_range_table, - &step3_range_table, - state[3], - generic.clone(), + stackable.clone(), ); Self { lane_config, - overflow_check_config, base13_to_9_table, - special_chunk_table, - step2_range_table, - step3_range_table, + stackable, + generic, } } pub fn assign_rotation_checks( @@ -93,19 +78,15 @@ impl RhoConfig { .iter() .flat_map(|(_, _, step3_od)| step3_od.clone()) .collect::>(); - self.overflow_check_config.assign_region( - &mut layouter.namespace(|| "Final overflow check"), - step2_od_join, - step3_od_join, - )?; + let step2_sum = self.generic.running_sum(layouter, step2_od_join, None)?; + let step3_sum = self.generic.running_sum(layouter, step3_od_join, None)?; + self.stackable.lookup_range_12(layouter, &[step2_sum])?; + self.stackable.lookup_range_169(layouter, &[step3_sum])?; Ok(next_state) } pub fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { self.base13_to_9_table.load(layouter)?; - self.special_chunk_table.load(layouter)?; - self.step2_range_table.load(layouter)?; - self.step3_range_table.load(layouter)?; Ok(()) } } @@ -121,7 +102,7 @@ mod tests { circuit::{Layouter, SimpleFloorPlanner}, dev::MockProver, pairing::bn256::Fr as Fp, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Selector}, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Selector, TableColumn}, poly::Rotation, }; use itertools::Itertools; @@ -139,6 +120,7 @@ mod tests { struct MyConfig { q_enable: Selector, rho_config: RhoConfig, + stackable: StackableTable, state: [Column; 25], } impl Circuit for MyCircuit { @@ -157,10 +139,18 @@ mod tests { .unwrap(); let fixed = meta.fixed_column(); + let table_cols: [TableColumn; 3] = (0..3) + .map(|_| meta.lookup_table_column()) + .collect_vec() + .try_into() + .unwrap(); + let stackable = + StackableTable::configure(meta, state[0..3].try_into().unwrap(), table_cols); let generic = GenericConfig::configure(meta, state[0..3].try_into().unwrap(), fixed); - let rho_config = RhoConfig::configure(meta, state, fixed, &generic); + let rho_config = + RhoConfig::configure(meta, state, fixed, generic, stackable.clone()); let q_enable = meta.selector(); meta.create_gate("Check states", |meta| { @@ -178,6 +168,7 @@ mod tests { MyConfig { q_enable, rho_config, + stackable, state, } } @@ -188,6 +179,7 @@ mod tests { mut layouter: impl Layouter, ) -> Result<(), Error> { config.rho_config.load(&mut layouter)?; + config.stackable.load(&mut layouter)?; let state = layouter.assign_region( || "assign input state", |mut region| { diff --git a/keccak256/src/permutation/rho_checks.rs b/keccak256/src/permutation/rho_checks.rs index e66fcda3d5..1198cea0b9 100644 --- a/keccak256/src/permutation/rho_checks.rs +++ b/keccak256/src/permutation/rho_checks.rs @@ -80,7 +80,7 @@ //! [`crate::permutation::tables::Base13toBase9TableConfig`] to lookup //! `overflow_detector`. We sum up all the overflow_detectors across 25 lanes, //! for each step 1, step 2, and step 3. At the end of the Rho step we perform -//! the final overflow detector range check in [`OverflowCheckConfig`]. +//! the final overflow detector range check for them. //! //! The `OVERFLOW_TRANSFORM` maps step 1 to 0, step 2 to 1, step 3 to 13, and //! step 4 to 170. It is defined that any possible overflow would result the @@ -110,7 +110,7 @@ use crate::gate_helpers::{biguint_to_f, f_to_biguint}; use crate::permutation::{ generic::GenericConfig, rho_helpers::*, - tables::{Base13toBase9TableConfig, RangeCheckConfig, SpecialChunkTableConfig}, + tables::{Base13toBase9TableConfig, StackableTable}, }; use eth_types::Field; use halo2_proofs::{ @@ -122,24 +122,23 @@ use halo2_proofs::{ #[derive(Debug, Clone)] pub struct LaneRotateConversionConfig { q_normal: Selector, - q_special: Selector, input_coef: Column, output_coef: Column, - pub overflow_detector: Column, + overflow_detector: Column, generic: GenericConfig, + stackable: StackableTable, } impl LaneRotateConversionConfig { pub fn configure( meta: &mut ConstraintSystem, base13_to_9_table: &Base13toBase9TableConfig, - special_chunk_table: &SpecialChunkTableConfig, advices: [Column; 3], constant: Column, generic: GenericConfig, + stackable: StackableTable, ) -> Self { let q_normal = meta.complex_selector(); - let q_special = meta.complex_selector(); let [input_coef, output_coef, overflow_detector] = advices; meta.enable_equality(overflow_detector); @@ -157,27 +156,13 @@ impl LaneRotateConversionConfig { (q_normal * od, base13_to_9_table.overflow_detector), ] }); - - meta.lookup("special chunk", |meta| { - let q_special = meta.query_selector(q_special); - let input_coef = meta.query_advice(input_coef, Rotation::cur()); - let output_coef = meta.query_advice(output_coef, Rotation::cur()); - - vec![ - ( - q_special.clone() * input_coef, - special_chunk_table.last_chunk, - ), - (q_special * output_coef, special_chunk_table.output_coef), - ] - }); Self { q_normal, - q_special, input_coef, output_coef, overflow_detector, generic, + stackable, } } @@ -252,6 +237,16 @@ impl LaneRotateConversionConfig { _ => unreachable!(), } } + // Special chunk + let final_output_coef = region.assign_advice( + || "Special output coef", + self.output_coef, + slices.len(), + || Ok(F::from(special.output_coef as u64)), + )?; + let final_output_pob = F::from(B9 as u64).pow(&[rotation.into(), 0, 0, 0]); + output_coefs.push(final_output_coef); + output_pobs.push(final_output_pob); Ok(( input_coefs, @@ -270,26 +265,8 @@ impl LaneRotateConversionConfig { .generic .sub_advice(layouter, lane_base_13, input_from_chunks)?; - let (final_output_coef, final_output_pob) = layouter.assign_region( - || "special chunks", - |mut region| { - let offset = 0; - self.q_special.enable(&mut region, offset)?; - diff.copy_advice(|| "input lane diff", &mut region, self.input_coef, offset)?; - let output_coef = region.assign_advice( - || "Special output coef", - self.output_coef, - offset, - || Ok(F::from(special.output_coef as u64)), - )?; - let final_output_pob = F::from(B9 as u64).pow(&[rotation.into(), 0, 0, 0]); - Ok((output_coef, final_output_pob)) - }, - )?; - let mut output_coefs = output_coefs; - output_coefs.push(final_output_coef); - let mut output_pobs = output_pobs; - output_pobs.push(final_output_pob); + self.stackable + .lookup_special_chunks(layouter, &diff, output_coefs.last().unwrap())?; let output_lane = self.generic @@ -297,64 +274,3 @@ impl LaneRotateConversionConfig { Ok((output_lane, step2_od, step3_od)) } } - -#[derive(Debug, Clone)] -pub struct OverflowCheckConfig { - q_step2: Selector, - q_step3: Selector, - generic: GenericConfig, - acc: Column, -} -impl OverflowCheckConfig { - pub fn configure( - meta: &mut ConstraintSystem, - step2_range_table: &RangeCheckConfig, - step3_range_table: &RangeCheckConfig, - acc: Column, - generic: GenericConfig, - ) -> Self { - let q_step2 = meta.complex_selector(); - let q_step3 = meta.complex_selector(); - meta.enable_equality(acc); - - meta.lookup("Overflow check step2", |meta| { - let q_step2 = meta.query_selector(q_step2); - let acc = meta.query_advice(acc, Rotation::cur()); - vec![(q_step2 * acc, step2_range_table.range)] - }); - meta.lookup("Overflow check step3", |meta| { - let q_step3 = meta.query_selector(q_step3); - let acc = meta.query_advice(acc, Rotation::cur()); - vec![(q_step3 * acc, step3_range_table.range)] - }); - - Self { - q_step2, - q_step3, - generic, - acc, - } - } - pub fn assign_region( - &self, - layouter: &mut impl Layouter, - step2_cells: Vec>, - step3_cells: Vec>, - ) -> Result<(), Error> { - let step2_sum = self.generic.running_sum(layouter, step2_cells, None)?; - let step3_sum = self.generic.running_sum(layouter, step3_cells, None)?; - layouter.assign_region( - || "Overflow range check", - |mut region| { - let offset = 0; - self.q_step2.enable(&mut region, offset)?; - step2_sum.copy_advice(|| "Step2 sum", &mut region, self.acc, offset)?; - let offset = 1; - self.q_step3.enable(&mut region, offset)?; - step3_sum.copy_advice(|| "Step3 sum", &mut region, self.acc, offset)?; - - Ok(()) - }, - ) - } -} diff --git a/keccak256/src/permutation/tables.rs b/keccak256/src/permutation/tables.rs index 49b825cd9f..ff8d8fcc4a 100644 --- a/keccak256/src/permutation/tables.rs +++ b/keccak256/src/permutation/tables.rs @@ -4,17 +4,203 @@ use crate::gate_helpers::f_to_biguint; use crate::permutation::rho_helpers::{get_overflow_detector, BASE_NUM_OF_CHUNKS}; use eth_types::Field; use halo2_proofs::{ - circuit::Layouter, - plonk::{ConstraintSystem, Error, TableColumn}, + circuit::{AssignedCell, Layouter, Table}, + plonk::{Advice, Column, ConstraintSystem, Error, Selector, TableColumn}, + poly::Rotation, }; use itertools::Itertools; use std::convert::TryInto; use std::marker::PhantomData; +use strum_macros::{Display, EnumIter}; + +use super::rho_helpers::{STEP2_RANGE, STEP3_RANGE}; const MAX_CHUNKS: usize = 64; const NUM_OF_BINARY_CHUNKS: usize = 16; const NUM_OF_B9_CHUNKS: usize = 5; +#[derive(EnumIter, Display, Clone, Copy)] +enum TableTags { + Range12 = 0, + Range169, + SpecialChunk, +} + +#[derive(Debug, Clone)] +pub struct StackableTable { + q_enable: Selector, + tag: (Column, TableColumn), + col1: (Column, TableColumn), + col2: (Column, TableColumn), + _marker: PhantomData, +} + +impl StackableTable { + pub(crate) fn configure( + meta: &mut ConstraintSystem, + adv_cols: [Column; 3], + table_cols: [TableColumn; 3], + ) -> Self { + let tag = (adv_cols[0], table_cols[0]); + let col1 = (adv_cols[1], table_cols[1]); + let col2 = (adv_cols[2], table_cols[2]); + let q_enable = meta.complex_selector(); + meta.lookup("stackable lookup", |meta| { + let q_enable = meta.query_selector(q_enable); + let tag_adv = meta.query_advice(tag.0, Rotation::cur()); + let col1_adv = meta.query_advice(col1.0, Rotation::cur()); + let col2_adv = meta.query_advice(col2.0, Rotation::cur()); + + vec![ + (q_enable.clone() * tag_adv, tag.1), + (q_enable.clone() * col1_adv, col1.1), + (q_enable * col2_adv, col2.1), + ] + }); + Self { + q_enable, + tag, + col1, + col2, + _marker: PhantomData, + } + } + + fn load_range( + &self, + table: &mut Table, + offset: usize, + tag: TableTags, + k: u64, + ) -> Result { + let mut offset = offset; + for i in 0..=k { + table.assign_cell( + || format!("tag range{}", tag), + self.tag.1, + offset, + || Ok(F::from(tag as u64)), + )?; + table.assign_cell( + || format!("range{}", tag), + self.col1.1, + offset, + || Ok(F::from(i)), + )?; + table.assign_cell( + || format!("dummy col range{}", tag), + self.col2.1, + offset, + || Ok(F::zero()), + )?; + offset += 1; + } + Ok(offset) + } + /// The table describes all possible combinations of these two variables: + /// - The last input accumulator: `high_value`*(13**64) + `low_value`, and + /// - The last output coef: `convert_b13_coef(high_value + low_value)` + fn load_special_chunks(&self, table: &mut Table, offset: usize) -> Result { + let mut offset = offset; + for i in 0..B13 { + for j in 0..(B13 - i) { + let (low, high) = (i, j); + let last_chunk = F::from(low as u64) + + F::from(high as u64) * F::from(B13 as u64).pow(&[LANE_SIZE as u64, 0, 0, 0]); + let output_coef = F::from(convert_b13_coef(low + high) as u64); + table.assign_cell( + || "tag special chunks", + self.tag.1, + offset, + || Ok(F::from(TableTags::SpecialChunk as u64)), + )?; + table.assign_cell(|| "last chunk", self.col1.1, offset, || Ok(last_chunk))?; + table.assign_cell(|| "output coef", self.col2.1, offset, || Ok(output_coef))?; + offset += 1; + } + } + Ok(offset) + } + pub(crate) fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { + layouter.assign_table( + || "stackable", + |mut table| { + let mut offset = 0; + for &(tag, k) in [ + (TableTags::Range12, STEP2_RANGE), + (TableTags::Range169, STEP3_RANGE), + ] + .iter() + { + offset = self.load_range(&mut table, offset, tag, k)?; + } + self.load_special_chunks(&mut table, offset)?; + Ok(()) + }, + ) + } + + fn lookup_range( + &self, + layouter: &mut impl Layouter, + values: &[AssignedCell], + tag: TableTags, + ) -> Result<(), Error> { + layouter.assign_region( + || format!("lookup for {}", tag), + |mut region| { + let tag = F::from(tag as u64); + for (offset, v) in values.iter().enumerate() { + self.q_enable.enable(&mut region, offset)?; + region.assign_advice_from_constant(|| "tag", self.tag.0, offset, tag)?; + v.copy_advice(|| "value", &mut region, self.col1.0, offset)?; + region.assign_advice_from_constant( + || "dummy", + self.col2.0, + offset, + F::zero(), + )?; + } + Ok(()) + }, + ) + } + pub(crate) fn lookup_range_12( + &self, + layouter: &mut impl Layouter, + values: &[AssignedCell], + ) -> Result<(), Error> { + self.lookup_range(layouter, values, TableTags::Range12) + } + pub(crate) fn lookup_range_169( + &self, + layouter: &mut impl Layouter, + values: &[AssignedCell], + ) -> Result<(), Error> { + self.lookup_range(layouter, values, TableTags::Range169) + } + + pub(crate) fn lookup_special_chunks( + &self, + layouter: &mut impl Layouter, + last_chunk: &AssignedCell, + output_coef: &AssignedCell, + ) -> Result<(), Error> { + layouter.assign_region( + || "lookup for special chunks", + |mut region| { + let offset = 0; + let tag = F::from(TableTags::SpecialChunk as u64); + self.q_enable.enable(&mut region, offset)?; + region.assign_advice_from_constant(|| "tag", self.tag.0, offset, tag)?; + last_chunk.copy_advice(|| "last chunk", &mut region, self.col1.0, offset)?; + output_coef.copy_advice(|| "output coef", &mut region, self.col2.0, offset)?; + Ok(()) + }, + ) + } +} + #[derive(Debug, Clone)] pub struct RangeCheckConfig { pub range: TableColumn, @@ -33,7 +219,9 @@ impl RangeCheckConfig { }, ) } - + // dead_code reason: WordBuilderConfig is using it. We defer the decision to + // remove this after WordBuilderConfig is complete + #[allow(dead_code)] pub(crate) fn configure(meta: &mut ConstraintSystem) -> Self { Self { range: meta.lookup_table_column(), @@ -107,60 +295,6 @@ impl Base13toBase9TableConfig { } } -/// The table describes all possible combinations of these two variables: -/// - The last input accumulator: `high_value`*(13**64) + `low_value`, and -/// - The last output coef: `convert_b13_coef(high_value + low_value)` -#[derive(Debug, Clone)] -pub struct SpecialChunkTableConfig { - pub last_chunk: TableColumn, - pub output_coef: TableColumn, - _marker: PhantomData, -} - -impl SpecialChunkTableConfig { - pub(crate) fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { - layouter.assign_table( - || "Special Chunks", - |mut table| { - // Iterate over all possible values less than 13 for both low - // and high - let mut offset = 0; - for i in 0..B13 { - for j in 0..(B13 - i) { - let (low, high) = (i, j); - let last_chunk = F::from(low as u64) - + F::from(high as u64) - * F::from(B13 as u64).pow(&[LANE_SIZE as u64, 0, 0, 0]); - let output_coef = F::from(convert_b13_coef(low + high) as u64); - table.assign_cell( - || "last chunk", - self.last_chunk, - offset, - || Ok(last_chunk), - )?; - table.assign_cell( - || "output coef", - self.output_coef, - offset, - || Ok(output_coef), - )?; - offset += 1; - } - } - Ok(()) - }, - ) - } - - pub(crate) fn configure(meta: &mut ConstraintSystem) -> Self { - Self { - last_chunk: meta.lookup_table_column(), - output_coef: meta.lookup_table_column(), - _marker: PhantomData, - } - } -} - #[derive(Clone, Debug)] pub(crate) struct BaseInfo { input_base: u8,