diff --git a/circuit-benchmarks/src/keccak_permutation.rs b/circuit-benchmarks/src/keccak_permutation.rs index 587cf9afd4..657e299054 100644 --- a/circuit-benchmarks/src/keccak_permutation.rs +++ b/circuit-benchmarks/src/keccak_permutation.rs @@ -5,16 +5,12 @@ use halo2_proofs::{ circuit::{AssignedCell, Layouter, SimpleFloorPlanner}, plonk::{Circuit, ConstraintSystem, Error}, }; -use keccak256::{ - common::NEXT_INPUTS_LANES, keccak_arith::KeccakFArith, permutation::circuit::KeccakFConfig, -}; +use keccak256::{common::NEXT_INPUTS_LANES, permutation::circuit::KeccakFConfig}; #[derive(Default, Clone)] struct KeccakRoundTestCircuit { in_state: [F; 25], - out_state: [F; 25], next_mixing: Option<[F; NEXT_INPUTS_LANES]>, - is_mixing: bool, } impl Circuit for KeccakRoundTestCircuit { @@ -44,12 +40,12 @@ impl Circuit for KeccakRoundTestCircuit { // Witness `state` let in_state: [AssignedCell; 25] = { let mut state: Vec> = Vec::with_capacity(25); - for (idx, val) in self.in_state.iter().enumerate() { + for &val in self.in_state.iter() { let cell = region.assign_advice( || "witness input state", - config.state[idx], + config.advice, offset, - || Ok(*val), + || Ok(val), )?; state.push(cell) } @@ -59,13 +55,7 @@ impl Circuit for KeccakRoundTestCircuit { }, )?; - config.assign_all( - &mut layouter, - in_state, - self.out_state, - self.is_mixing, - self.next_mixing, - )?; + config.assign_all(&mut layouter, in_state, self.next_mixing)?; Ok(()) } } @@ -97,14 +87,6 @@ mod tests { [0, 0, 0, 0, 0], ]; - let next_input: State = [ - [2, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - ]; - let mut in_state_biguint = StateBigInt::default(); // Generate in_state as `[Fr;25]` @@ -114,23 +96,10 @@ mod tests { in_state_biguint[(x, y)] = convert_b2_to_b13(in_state[x][y]); } - // Compute out_state_mix - let mut out_state_mix = in_state_biguint.clone(); - KeccakFArith::permute_and_absorb(&mut out_state_mix, Some(&next_input)); - - // Compute out_state_non_mix - let mut out_state_non_mix = in_state_biguint.clone(); - KeccakFArith::permute_and_absorb(&mut out_state_non_mix, None); - - // Generate out_state as `[Fr;25]` - let out_state_non_mix: [Fr; 25] = state_bigint_to_field(out_state_non_mix); - // Build the circuit let circuit = KeccakRoundTestCircuit:: { in_state: in_state_fp, - out_state: out_state_non_mix, next_mixing: None, - is_mixing: false, }; let degree: u32 = var("DEGREE") diff --git a/keccak256/src/arith_helpers.rs b/keccak256/src/arith_helpers.rs index 62d5b8207b..0104d81a32 100644 --- a/keccak256/src/arith_helpers.rs +++ b/keccak256/src/arith_helpers.rs @@ -175,6 +175,10 @@ pub fn convert_b9_lane_to_b2(x: Lane9) -> u64 { .unwrap_or(0) } +pub fn convert_b9_lane_to_b2_biguint(x: Lane9) -> BigUint { + convert_lane(x, B9, 2, convert_b9_coef) +} + pub fn convert_b9_lane_to_b2_normal(x: Lane9) -> u64 { convert_lane(x, B9, 2, |y| y) .iter_u64_digits() diff --git a/keccak256/src/permutation.rs b/keccak256/src/permutation.rs index 6339f9dfce..71ccfc205f 100644 --- a/keccak256/src/permutation.rs +++ b/keccak256/src/permutation.rs @@ -1,14 +1,7 @@ #![allow(clippy::type_complexity)] #![allow(clippy::too_many_arguments)] -pub(crate) mod absorb; -pub(crate) mod base_conversion; pub mod circuit; +pub(crate) mod components; pub(crate) mod generic; -pub(crate) mod iota; -pub(crate) mod mixing; -pub(crate) mod pi; -pub(crate) mod rho; pub(crate) mod rho_helpers; pub(crate) mod tables; -pub(crate) mod theta; -pub(crate) mod xi; diff --git a/keccak256/src/permutation/absorb.rs b/keccak256/src/permutation/absorb.rs deleted file mode 100644 index b81e459e4e..0000000000 --- a/keccak256/src/permutation/absorb.rs +++ /dev/null @@ -1,346 +0,0 @@ -use crate::arith_helpers::*; -use crate::common::*; -use eth_types::Field; -use halo2_proofs::circuit::{AssignedCell, Layouter, Region}; -use halo2_proofs::{ - plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector}, - poly::Rotation, -}; -use itertools::Itertools; -use std::marker::PhantomData; - -#[derive(Clone, Debug)] -pub struct AbsorbConfig { - q_mixing: Selector, - state: [Column; 25], - _marker: PhantomData, -} - -impl AbsorbConfig { - // We assume state is recieved in base-9. - // Rows are assigned as: - // 1) STATE (25 columns) (offset -1) - // 2) NEXT_INPUTS (17 columns) + is_mixing flag (1 column) (offset +0) - // (current rotation) - // 3) OUT_STATE (25 columns) (offset +1) - pub fn configure( - meta: &mut ConstraintSystem, - state: [Column; 25], - ) -> AbsorbConfig { - // def absorb(state: List[List[int], next_input: List[List[int]): - // for x in range(5): - // for y in range(5): - // # state[x][y] has 2*a + b + 3*c already, now add 2*d to - // make it 2*a + b + 3*c + 2*d # coefficient in 0~8 - // state[x][y] += 2 * next_input[x][y] - // return state - - // Declare the q_mixing. - let q_mixing = meta.selector(); - state - .iter() - .for_each(|column| meta.enable_equality(*column)); - - meta.create_gate("absorb", |meta| { - // We do a trick which consists on multiplying an internal selector - // which is always active by the actual `is_mixing` flag - // which will then enable or disable the gate. - let q_enable = { - // We query the flag value from the `state` `Advice` column at - // rotation curr and position = `NEXT_INPUTS_LANES + 1` - // and multiply to it the active selector so that we avoid the - // `PoisonedConstraints` and each gate equation - // can be satisfied while enforcing the correct gate logic. - // - // This is boolean-constrained outside of `AbsorbConfig` by `MixingConfig`. - let flag = meta.query_advice(state[NEXT_INPUTS_LANES], Rotation::cur()); - // Note also that we want to enable the gate when `is_mixing` is - // true. (flag = 1). See the flag computation above. - meta.query_selector(q_mixing) * flag - }; - - (0..NEXT_INPUTS_LANES) - .map(|idx| { - let val = meta.query_advice(state[idx], Rotation::prev()) - + (Expression::Constant(F::from(A4)) - * meta.query_advice(state[idx], Rotation::cur())); - - let next_lane = meta.query_advice(state[idx], Rotation::next()); - - q_enable.clone() * (val - next_lane) - }) - .collect::>() - }); - - AbsorbConfig { - q_mixing, - state, - _marker: PhantomData, - } - } - - fn assign_next_inp_and_flag( - &self, - region: &mut Region, - offset: usize, - flag: AssignedCell, - next_input: [F; NEXT_INPUTS_LANES], - ) -> Result, Error> { - // Generate next_input in base-9. - let mut next_mixing = state_to_biguint::(next_input); - for (x, y) in (0..5).cartesian_product(0..5) { - // Assign only first 17 values. - if x >= 3 && y >= 1 { - break; - } - next_mixing[(x, y)] = convert_b2_to_b9(next_mixing[(x, y)].clone().try_into().unwrap()) - } - let next_input = state_bigint_to_field::(next_mixing); - - // Assign next_mixing. - for (idx, lane) in next_input.iter().enumerate() { - region.assign_advice( - || format!("assign next_input {}", idx), - self.state[idx], - offset, - || Ok(*lane), - )?; - } - - // Assign flag at last column(17th). - let flag_assig_cell = flag.copy_advice( - || format!("assign next_input {}", NEXT_INPUTS_LANES), - region, - self.state[NEXT_INPUTS_LANES], - offset, - )?; - - Ok(flag_assig_cell) - } - - /// Doc this $ - pub fn copy_state_flag_next_inputs( - &self, - layouter: &mut impl Layouter, - in_state: &[AssignedCell; 25], - out_state: [F; 25], - // Passed in base-2 and converted internally after witnessing it. - next_input: [F; NEXT_INPUTS_LANES], - flag: AssignedCell, - ) -> Result<([AssignedCell; 25], AssignedCell), Error> { - layouter.assign_region( - || "Absorb state assignations", - |mut region| { - let mut offset = 0; - // State at offset + 0 - for (idx, in_state) in in_state.iter().enumerate() { - in_state.copy_advice( - || format!("assign state {}", idx), - &mut region, - self.state[idx], - offset, - )?; - } - - offset += 1; - // Enable `q_mixing` at `offset + 1` - self.q_mixing.enable(&mut region, offset)?; - - // Assign `next_inputs` and flag. - let flag = - self.assign_next_inp_and_flag(&mut region, offset, flag.clone(), next_input)?; - - offset += 1; - // Assign out_state at offset + 2 - let mut state: Vec> = Vec::with_capacity(25); - for (idx, lane) in out_state.iter().enumerate() { - let assig_cell = region.assign_advice( - || format!("assign state {}", idx), - self.state[idx], - offset, - || Ok(*lane), - )?; - state.push(assig_cell); - } - let out_state: [AssignedCell; 25] = state - .try_into() - .expect("Unexpected into_slice conversion err"); - - Ok((out_state, flag)) - }, - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::keccak_arith::KeccakFArith; - use halo2_proofs::pairing; - use halo2_proofs::{circuit::SimpleFloorPlanner, dev::MockProver, plonk::Circuit}; - use pairing::bn256::Fr as Fp; - use pairing::group::ff::PrimeField; - use pretty_assertions::assert_eq; - - #[test] - fn test_absorb_gate() { - #[derive(Default)] - struct MyCircuit { - in_state: [F; 25], - out_state: [F; 25], - next_input: [F; NEXT_INPUTS_LANES], - is_mixing: bool, - _marker: PhantomData, - } - impl Circuit for MyCircuit - where - F: PrimeField, - { - type Config = AbsorbConfig; - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let state: [Column; 25] = (0..25) - .map(|_| { - let column = meta.advice_column(); - meta.enable_equality(column); - column - }) - .collect::>() - .try_into() - .unwrap(); - - AbsorbConfig::configure(meta, state) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - let val: F = (self.is_mixing as u64).into(); - let flag: AssignedCell = layouter.assign_region( - || "witness_is_mixing_flag", - |mut region| { - let offset = 1; - region.assign_advice( - || "assign is_mixing", - config.state[NEXT_INPUTS_LANES + 1], - offset, - || Ok(val), - ) - }, - )?; - - // Witness `in_state`. - let in_state: [AssignedCell; 25] = layouter.assign_region( - || "Witness input state", - |mut region| { - let mut state: Vec> = Vec::with_capacity(25); - for (idx, val) in self.in_state.iter().enumerate() { - let cell = region.assign_advice( - || "witness input state", - config.state[idx], - 0, - || Ok(*val), - )?; - state.push(cell) - } - - Ok(state.try_into().unwrap()) - }, - )?; - - config.copy_state_flag_next_inputs( - &mut layouter, - &in_state, - self.out_state, - self.next_input, - flag, - )?; - - Ok(()) - } - } - - let input1: State = [ - [1, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - ]; - - let input2: State = [ - [2, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - ]; - - // Convert the input to base9 as the gadget already expects it like this - // since it's always the output of IotaB9. - let mut in_state = StateBigInt::from(input1); - for (x, y) in (0..5).cartesian_product(0..5) { - in_state[(x, y)] = convert_b2_to_b9(input1[x][y]) - } - - let in_state = state_bigint_to_field(in_state); - let out_state = - state_bigint_to_field(KeccakFArith::absorb(&StateBigInt::from(input1), &input2)); - - let next_input = state_bigint_to_field(StateBigInt::from(input2)); - - // With flag set to true, the gate should trigger. - { - // With the correct input and output witnesses, the proof should - // pass. - let circuit = MyCircuit:: { - in_state, - out_state, - next_input, - is_mixing: true, - _marker: PhantomData, - }; - - let prover = MockProver::::run(9, &circuit, vec![]).unwrap(); - - assert_eq!(prover.verify(), Ok(())); - - // With wrong input and/or output witnesses, the proof should fail - // to be verified. - let circuit = MyCircuit:: { - in_state, - out_state: in_state, - next_input, - is_mixing: true, - _marker: PhantomData, - }; - - let prover = MockProver::::run(9, &circuit, vec![]).unwrap(); - - assert!(prover.verify().is_err()); - } - - // With flag set to `false`, the gate shouldn't trigger. - // And so we can pass any witness data and the proof should pass. - { - let circuit = MyCircuit:: { - in_state, - out_state: in_state, - next_input, - is_mixing: false, - _marker: PhantomData, - }; - - let prover = MockProver::::run(9, &circuit, vec![]).unwrap(); - - assert_eq!(prover.verify(), Ok(())); - } - } -} diff --git a/keccak256/src/permutation/base_conversion.rs b/keccak256/src/permutation/base_conversion.rs deleted file mode 100644 index b9bae03ded..0000000000 --- a/keccak256/src/permutation/base_conversion.rs +++ /dev/null @@ -1,564 +0,0 @@ -use halo2_proofs::{ - circuit::{AssignedCell, Layouter}, - plonk::{Advice, Column, ConstraintSystem, Error, Selector}, - poly::Rotation, -}; - -use super::tables::BaseInfo; -use eth_types::Field; - -#[derive(Clone, Debug)] -pub(crate) struct BaseConversionConfig { - q_running_sum: Selector, - q_lookup: Selector, - base_info: BaseInfo, - // Flag is copied from the parent flag. Parent flag is assumed to be binary - // constrained. - flag: Column, - input_coef: Column, - input_acc: Column, - output_coef: Column, - output_acc: Column, -} - -impl BaseConversionConfig { - /// Side effect: lane and parent_flag is equality enabled - pub(crate) fn configure( - meta: &mut ConstraintSystem, - base_info: BaseInfo, - input_lane: Column, - parent_flag: Column, - advices: [Column; 5], - ) -> Self { - let q_running_sum = meta.selector(); - let q_lookup = meta.complex_selector(); - let [flag, input_coef, input_acc, output_coef, output_acc] = advices; - - meta.enable_equality(flag); - meta.enable_equality(input_coef); - meta.enable_equality(input_acc); - meta.enable_equality(output_coef); - meta.enable_equality(output_acc); - meta.enable_equality(input_lane); - meta.enable_equality(parent_flag); - - meta.create_gate("input running sum", |meta| { - let q_enable = meta.query_selector(q_running_sum); - let flag = meta.query_advice(flag, Rotation::cur()); - let coef = meta.query_advice(input_coef, Rotation::cur()); - let acc_prev = meta.query_advice(input_acc, Rotation::prev()); - let acc = meta.query_advice(input_acc, Rotation::cur()); - let power_of_base = base_info.input_pob(); - vec![q_enable * flag * (acc - acc_prev * power_of_base - coef)] - }); - meta.create_gate("output running sum", |meta| { - let q_enable = meta.query_selector(q_running_sum); - let flag = meta.query_advice(flag, Rotation::cur()); - let coef = meta.query_advice(output_coef, Rotation::cur()); - let acc_prev = meta.query_advice(output_acc, Rotation::prev()); - let acc = meta.query_advice(output_acc, Rotation::cur()); - let power_of_base = base_info.output_pob(); - vec![q_enable * flag * (acc - acc_prev * power_of_base - coef)] - }); - meta.lookup("Lookup i/o_coeff at Base conversion table", |meta| { - let q_enable = meta.query_selector(q_lookup); - let flag = meta.query_advice(flag, Rotation::cur()); - let input_slices = meta.query_advice(input_coef, Rotation::cur()); - let output_slices = meta.query_advice(output_coef, Rotation::cur()); - vec![ - ( - q_enable.clone() * flag.clone() * input_slices, - base_info.input_tc, - ), - (q_enable * flag * output_slices, base_info.output_tc), - ] - }); - - Self { - q_running_sum, - q_lookup, - base_info, - flag, - input_coef, - input_acc, - output_coef, - output_acc, - } - } - - pub(crate) fn assign_lane( - &self, - layouter: &mut impl Layouter, - input: AssignedCell, - flag: AssignedCell, - ) -> Result, Error> { - let (input_coefs, output_coefs, _) = self - .base_info - .compute_coefs(input.value().copied().unwrap_or_default())?; - - layouter.assign_region( - || "Base conversion", - |mut region| { - let mut input_acc = F::zero(); - let input_pob = self.base_info.input_pob(); - let mut output_acc = F::zero(); - let output_pob = self.base_info.output_pob(); - for (offset, (&input_coef, &output_coef)) in - input_coefs.iter().zip(output_coefs.iter()).enumerate() - { - self.q_lookup.enable(&mut region, offset)?; - if offset != 0 { - self.q_running_sum.enable(&mut region, offset)?; - } - flag.copy_advice(|| "Base conv flag", &mut region, self.flag, offset)?; - - let input_coef_cell = region.assign_advice( - || "Input Coef", - self.input_coef, - offset, - || Ok(input_coef), - )?; - input_acc = input_acc * input_pob + input_coef; - let input_acc_cell = region.assign_advice( - || "Input Acc", - self.input_acc, - offset, - || Ok(input_acc), - )?; - let output_coef_cell = region.assign_advice( - || "Output Coef", - self.output_coef, - offset, - || Ok(output_coef), - )?; - output_acc = output_acc * output_pob + output_coef; - let output_acc_cell = region.assign_advice( - || "Output Acc", - self.output_acc, - offset, - || Ok(output_acc), - )?; - - if offset == 0 { - // bind first acc to first coef - region.constrain_equal(input_acc_cell.cell(), input_coef_cell.cell())?; - region.constrain_equal(output_acc_cell.cell(), output_coef_cell.cell())?; - } else if offset == input_coefs.len() - 1 { - //region.constrain_equal(input_acc_cell, input.0)?; - return Ok(output_acc_cell); - } - } - unreachable!(); - }, - ) - } - - pub(crate) fn assign_state( - &self, - layouter: &mut impl Layouter, - state: &[AssignedCell; 25], - flag: AssignedCell, - ) -> Result<[AssignedCell; 25], Error> { - let state: Result>, Error> = state - .iter() - .map(|lane| { - let output = self.assign_lane(layouter, lane.clone(), flag.clone())?; - Ok(output) - }) - .into_iter() - .collect(); - let state = state?; - let state: [AssignedCell; 25] = state.try_into().unwrap(); - Ok(state) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::arith_helpers::{convert_b2_to_b13, convert_b9_lane_to_b13}; - use crate::gate_helpers::biguint_to_f; - use crate::permutation::tables::{FromBase9TableConfig, FromBinaryTableConfig}; - use halo2_proofs::{ - circuit::{Layouter, SimpleFloorPlanner}, - dev::MockProver, - pairing::bn256::Fr as Fp, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error}, - }; - use itertools::Itertools; - use num_bigint::BigUint; - use pretty_assertions::assert_eq; - #[test] - fn test_base_conversion_from_b2() { - // We have to use a MyConfig because: - // We need to load the table - #[derive(Debug, Clone)] - struct MyConfig { - lane: Column, - flag: Column, - table: FromBinaryTableConfig, - conversion: BaseConversionConfig, - } - impl MyConfig { - pub fn configure(meta: &mut ConstraintSystem) -> Self { - let table = FromBinaryTableConfig::configure(meta); - let lane = meta.advice_column(); - let flag = meta.advice_column(); - let advices = (0..5) - .map(|_| meta.advice_column()) - .collect_vec() - .try_into() - .unwrap(); - let base_info = table.get_base_info(false); - let conversion = - BaseConversionConfig::configure(meta, base_info, lane, flag, advices); - Self { - lane, - flag, - table, - conversion, - } - } - - pub fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { - self.table.load(layouter) - } - - pub fn assign_region( - &self, - layouter: &mut impl Layouter, - input: F, - ) -> Result, Error> { - // The main flag is enabled - let flag_value = F::one(); - let (lane, flag) = layouter.assign_region( - || "Input lane", - |mut region| { - let lane = - region.assign_advice(|| "Input lane", self.lane, 0, || Ok(input))?; - let flag = region.assign_advice( - || "main flag", - self.flag, - 0, - || Ok(flag_value), - )?; - Ok((lane, flag)) - }, - )?; - let output = self.conversion.assign_lane(layouter, lane, flag)?; - layouter.assign_region( - || "Input lane", - |mut region| output.copy_advice(|| "Output lane", &mut region, self.lane, 0), - )?; - Ok(output) - } - } - - #[derive(Default)] - struct MyCircuit { - input_b2_lane: F, - output_b13_lane: F, - } - impl Circuit for MyCircuit { - type Config = MyConfig; - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - Self::Config::configure(meta) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - config.load(&mut layouter)?; - let output = config.assign_region(&mut layouter, self.input_b2_lane)?; - if output.value().is_some() { - assert_eq!(output.value(), Some(&self.output_b13_lane)); - } - Ok(()) - } - } - let input = 12345678u64; - let circuit = MyCircuit:: { - input_b2_lane: Fp::from(input), - output_b13_lane: biguint_to_f::(&convert_b2_to_b13(input)), - }; - let k = 17; - - #[cfg(feature = "dev-graph")] - { - use plotters::prelude::*; - let root = BitMapBackend::new("base-conversion.png", (1024, 32768)).into_drawing_area(); - root.fill(&WHITE).unwrap(); - let root = root.titled("Base conversion", ("sans-serif", 60)).unwrap(); - halo2_proofs::dev::CircuitLayout::default() - .mark_equality_cells(true) - .render(k, &circuit, &root) - .unwrap(); - } - let prover = MockProver::::run(k, &circuit, vec![]).unwrap(); - assert_eq!(prover.verify(), Ok(())); - } - - #[test] - fn test_base_conversion_from_b9() { - #[derive(Debug, Clone)] - struct MyConfig { - lane: Column, - flag: Column, - table: FromBase9TableConfig, - conversion: BaseConversionConfig, - } - impl MyConfig { - pub fn configure(meta: &mut ConstraintSystem) -> Self { - let table = FromBase9TableConfig::configure(meta); - let lane = meta.advice_column(); - let flag = meta.advice_column(); - let advices = (0..5) - .map(|_| meta.advice_column()) - .collect_vec() - .try_into() - .unwrap(); - let base_info = table.get_base_info(false); - let conversion = - BaseConversionConfig::configure(meta, base_info, lane, flag, advices); - Self { - lane, - flag, - table, - conversion, - } - } - - pub fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { - self.table.load(layouter) - } - - pub fn assign_region( - &self, - layouter: &mut impl Layouter, - input: F, - ) -> Result, Error> { - // The main flag is enabled - let flag_value = F::one(); - let (lane, flag) = layouter.assign_region( - || "Input lane", - |mut region| { - let lane = - region.assign_advice(|| "Input lane", self.lane, 0, || Ok(input))?; - let flag = region.assign_advice( - || "main flag", - self.flag, - 0, - || Ok(flag_value), - )?; - Ok((lane, flag)) - }, - )?; - - let output = self.conversion.assign_lane(layouter, lane, flag)?; - layouter.assign_region( - || "Input lane", - |mut region| output.copy_advice(|| "Output lane", &mut region, self.lane, 0), - )?; - - Ok(output) - } - } - - #[derive(Default)] - struct MyCircuit { - input_lane: F, - output_lane: F, - } - impl Circuit for MyCircuit { - type Config = MyConfig; - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - Self::Config::configure(meta) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - config.load(&mut layouter)?; - let output = config.assign_region(&mut layouter, self.input_lane)?; - if output.value().is_some() { - assert_eq!(output.value(), Some(&self.output_lane)); - } - Ok(()) - } - } - let input = BigUint::parse_bytes(b"02939a42ef593e37757abe328e9e409e75dcd76cf1b3427bc3", 16) - .unwrap(); - let circuit = MyCircuit:: { - input_lane: biguint_to_f::(&input), - output_lane: biguint_to_f::(&convert_b9_lane_to_b13(input)), - }; - let k = 16; - let prover = MockProver::::run(k, &circuit, vec![]).unwrap(); - assert_eq!(prover.verify(), Ok(())); - } - #[test] - fn test_state_base_conversion() { - // We have to use a MyConfig because: - // We need to load the table - #[derive(Debug, Clone)] - struct MyConfig { - flag: Column, - state: [Column; 25], - table: FromBinaryTableConfig, - conversion: BaseConversionConfig, - } - impl MyConfig { - pub fn configure(meta: &mut ConstraintSystem) -> Self { - let table = FromBinaryTableConfig::configure(meta); - let state: [Column; 25] = (0..25) - .map(|_| meta.advice_column()) - .collect::>() - .try_into() - .unwrap(); - let flag = meta.advice_column(); - let lane = meta.advice_column(); - let advices = (0..5) - .map(|_| meta.advice_column()) - .collect_vec() - .try_into() - .unwrap(); - let bi = table.get_base_info(false); - let conversion = BaseConversionConfig::configure(meta, bi, lane, flag, advices); - Self { - flag, - state, - table, - conversion, - } - } - - pub fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { - self.table.load(layouter) - } - - pub fn assign_region( - &self, - layouter: &mut impl Layouter, - input: [F; 25], - ) -> Result<[F; 25], Error> { - let flag_value = F::one(); - let (state, flag) = layouter.assign_region( - || "Input state", - |mut region| { - let state: [AssignedCell; 25] = input - .iter() - .enumerate() - .map(|(idx, &value)| { - region - .assign_advice( - || format!("State {}", idx), - self.state[idx], - 0, - || Ok(value), - ) - .unwrap() - }) - .collect::>() - .try_into() - .unwrap(); - let flag = - region.assign_advice(|| "Flag", self.flag, 0, || Ok(flag_value))?; - Ok((state, flag)) - }, - )?; - let output_state = self.conversion.assign_state(layouter, &state, flag)?; - let output_state: [F; 25] = output_state - .iter() - .map(|cell| cell.value().copied().unwrap_or_default()) - .collect::>() - .try_into() - .unwrap(); - Ok(output_state) - } - } - - #[derive(Default)] - struct MyCircuit { - in_state: [F; 25], - out_state: [F; 25], - } - impl Circuit for MyCircuit { - type Config = MyConfig; - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - Self::Config::configure(meta) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - config.load(&mut layouter)?; - let out_state = config.assign_region(&mut layouter, self.in_state)?; - assert_eq!(out_state, self.out_state); - Ok(()) - } - } - let in_state: [[u64; 5]; 5] = [ - [4398046511105, 8, 2, 268436480, 2305844108725321728], - [ - 17592186044416, - 52776560230400, - 544, - 68719493120, - 2199023255552, - ], - [ - 4398046543872, - 1152921504606846984, - 262144, - 1024, - 1099511627780, - ], - [0, 52776558133248, 514, 268451840, 2305845208236949504], - [17592186077184, 1152921504608944128, 262176, 68719476736, 4], - ]; - - let in_state_flat = in_state.iter().flatten().collect::>(); - let in_state: [Fp; 25] = in_state_flat - .iter() - .map(|&x| Fp::from(*x)) - .collect::>() - .try_into() - .unwrap(); - let out_state: [Fp; 25] = in_state_flat - .iter() - .map(|&x| biguint_to_f::(&convert_b2_to_b13(*x))) - .collect::>() - .try_into() - .unwrap(); - let circuit = MyCircuit:: { - in_state, - out_state, - }; - let prover = MockProver::::run(17, &circuit, vec![]).unwrap(); - assert_eq!(prover.verify(), Ok(())); - } -} diff --git a/keccak256/src/permutation/circuit.rs b/keccak256/src/permutation/circuit.rs index 6ff04313e9..e36320e418 100644 --- a/keccak256/src/permutation/circuit.rs +++ b/keccak256/src/permutation/circuit.rs @@ -1,45 +1,39 @@ use crate::{ - arith_helpers::*, - common::{NEXT_INPUTS_LANES, PERMUTATION, ROUND_CONSTANTS}, - keccak_arith::*, + common::{NEXT_INPUTS_LANES, PERMUTATION}, permutation::{ - base_conversion::BaseConversionConfig, generic::GenericConfig, - iota::IotaConstants, - mixing::MixingConfig, - pi::pi_gate_permutation, - rho::assign_rho, tables::{Base13toBase9TableConfig, FromBase9TableConfig, StackableTable}, - theta::ThetaConfig, - xi::XiConfig, }, }; use eth_types::Field; use halo2_proofs::{ - circuit::{AssignedCell, Layouter, Region}, - plonk::{Advice, Column, ConstraintSystem, Error, Selector, TableColumn}, - poly::Rotation, + circuit::{AssignedCell, Layouter}, + plonk::{Advice, Column, ConstraintSystem, Error, TableColumn}, }; use itertools::Itertools; + +use super::{ + components::{ + assign_next_input, assign_rho, assign_theta, assign_xi, convert_from_b9_to_b13, + convert_to_b9_mul_a4, pi_gate_permutation, IotaConstants, + }, + tables::FromBinaryTableConfig, +}; + #[derive(Clone, Debug)] pub struct KeccakFConfig { generic: GenericConfig, stackable: StackableTable, - theta_config: ThetaConfig, - xi_config: XiConfig, base13to9_config: Base13toBase9TableConfig, from_b9_table: FromBase9TableConfig, - base_conversion_config: BaseConversionConfig, - mixing_config: MixingConfig, - pub state: [Column; 25], - q_out: Selector, - base_conv_activator: Column, + from_b2_table: FromBinaryTableConfig, + pub advice: Column, } impl KeccakFConfig { // We assume state is received in base-9. pub fn configure(meta: &mut ConstraintSystem) -> Self { - let state: [Column; 25] = (0..25) + let advices: [Column; 3] = (0..3) .map(|_| { let column = meta.advice_column(); meta.enable_equality(column); @@ -50,7 +44,7 @@ impl KeccakFConfig { .unwrap(); let fixed = meta.fixed_column(); - let generic = GenericConfig::configure(meta, state[0..3].try_into().unwrap(), fixed); + let generic = GenericConfig::configure(meta, advices, fixed); let stackable_cols: [TableColumn; 3] = (0..3) .map(|_| meta.lookup_table_column()) .collect_vec() @@ -61,106 +55,52 @@ impl KeccakFConfig { .collect_vec() .try_into() .unwrap(); - let stackable = - StackableTable::configure(meta, state[0..3].try_into().unwrap(), stackable_cols); - let base13to9_config = Base13toBase9TableConfig::configure( - meta, - state[0..3].try_into().unwrap(), - base13to9_cols, - ); - - // theta - let theta_config = ThetaConfig::configure(meta.selector(), meta, state); - // xi - let xi_config = XiConfig::configure(meta.selector(), meta, state); - - // Allocate space for the activation flag of the base_conversion. - let base_conv_activator = meta.advice_column(); - meta.enable_equality(base_conv_activator); - // Base conversion config. - let from_b9_table = FromBase9TableConfig::configure(meta); - let base_info = from_b9_table.get_base_info(false); - let base_conv_lane = meta.advice_column(); - let base_conversion_config = BaseConversionConfig::configure( - meta, - base_info, - base_conv_lane, - base_conv_activator, - state[0..5].try_into().unwrap(), - ); - - // Mixing will make sure that the flag is binary constrained and that - // the out state matches the expected result. - let mixing_config = MixingConfig::configure( - meta, - &from_b9_table, - state, - generic.clone(), - stackable.clone(), - ); - - // Allocate the `out state correctness` gate selector - let q_out = meta.selector(); - // Constraint the out of the mixing gate to be equal to the out state - // announced. - meta.create_gate("Constraint out_state correctness", |meta| { - (0..25usize) - .into_iter() - .map(|idx| { - let q_out = meta.query_selector(q_out); - let out_mixing = meta.query_advice(state[idx], Rotation::cur()); - let out_expected_state = meta.query_advice(state[idx], Rotation::next()); - q_out * (out_mixing - out_expected_state) - }) - .collect_vec() - }); - - KeccakFConfig { + let from_base9_cols: [TableColumn; 3] = (0..3) + .map(|_| meta.lookup_table_column()) + .collect_vec() + .try_into() + .unwrap(); + let from_base2_cols: [TableColumn; 3] = (0..3) + .map(|_| meta.lookup_table_column()) + .collect_vec() + .try_into() + .unwrap(); + let stackable = StackableTable::configure(meta, advices, stackable_cols); + let base13to9_config = Base13toBase9TableConfig::configure(meta, advices, base13to9_cols); + let from_b9_table = FromBase9TableConfig::configure(meta, advices, from_base9_cols); + let from_b2_table = FromBinaryTableConfig::configure(meta, advices, from_base2_cols); + + Self { generic, stackable, - theta_config, - xi_config, base13to9_config, from_b9_table, - base_conversion_config, - mixing_config, - state, - q_out, - base_conv_activator, + from_b2_table, + advice: advices[0], } } pub fn load(&mut self, layouter: &mut impl Layouter) -> Result<(), Error> { self.stackable.load(layouter)?; self.base13to9_config.load(layouter)?; - self.from_b9_table.load(layouter) + self.from_b9_table.load(layouter)?; + self.from_b2_table.load(layouter) } + // Result b13 state for next round, b2 state for end result pub fn assign_all( &self, layouter: &mut impl Layouter, in_state: [AssignedCell; 25], - out_state: [F; 25], - flag: bool, next_mixing: Option<[F; NEXT_INPUTS_LANES]>, - ) -> Result<[AssignedCell; 25], Error> { + ) -> Result<([AssignedCell; 25], [AssignedCell; 25]), Error> { + let iota_constants = IotaConstants::default(); let mut state = in_state; // First 23 rounds for round_idx in 0..PERMUTATION { // State in base-13 - // theta - state = { - // Apply theta outside circuit - let out_state = - KeccakFArith::theta(&state_to_biguint(split_state_cells(state.clone()))); - let out_state = state_bigint_to_field(out_state); - // assignment - self.theta_config - .assign_state(layouter, &state, out_state)? - }; - - // rho + state = assign_theta(&self.generic, layouter, &state)?; state = assign_rho( layouter, &self.base13to9_config, @@ -169,143 +109,79 @@ impl KeccakFConfig { &state, )?; // Outputs in base-9 which is what Pi requires - - // Apply Pi permutation - state = pi_gate_permutation(state.clone()); - - // xi - state = { - // Apply xi outside circuit - let out_state = - KeccakFArith::xi(&state_to_biguint(split_state_cells(state.clone()))); - let out_state = state_bigint_to_field(out_state); - // assignment - self.xi_config.assign_state(layouter, &state, out_state)? - }; + state = pi_gate_permutation(&state); + state = assign_xi(&self.generic, layouter, &state)?; // Last round before Mixing does not run IotaB9 nor BaseConversion if round_idx == PERMUTATION - 1 { break; } - // iota_b9 - let iota_constants = IotaConstants::default(); state[0] = self.generic.add_fixed( layouter, - state[0].clone(), - iota_constants.a4_times_round_constants_b9[round_idx], + &state[0], + &iota_constants.a4_times_round_constants_b9[round_idx], )?; // The resulting state is in Base-9 now. We now convert it to // base_13 which is what Theta requires again at the // start of the loop. - state = { - let activation_flag = layouter.assign_region( - || "Base conversion enable", - |mut region| { - region.assign_advice( - || "Enable base conversion", - self.base_conv_activator, - 0, - || Ok(F::one()), - ) - }, - )?; - - self.base_conversion_config - .assign_state(layouter, &state, activation_flag)? - } + state = + convert_from_b9_to_b13(layouter, &self.from_b9_table, &self.generic, state, false)? + .0; } - - // Mixing step - let mix_res = KeccakFArith::mixing( - &state_to_biguint(split_state_cells(state.clone())), - next_mixing - .map(|state| state_to_state_bigint::(state)) - .as_ref(), - *ROUND_CONSTANTS.last().unwrap(), - ); - - let mix_res = self.mixing_config.assign_state( + let (f_mix, f_no_mix) = self + .stackable + .assign_boolean_flag(layouter, next_mixing.is_some())?; + state[0] = self.generic.conditional_add_const( layouter, - &state, - state_bigint_to_field(mix_res), - flag, - next_mixing, + &state[0], + &f_no_mix, + &iota_constants.a4_times_round_constants_b9[PERMUTATION - 1], )?; + let next_input = assign_next_input(layouter, &self.advice, &next_mixing)?; - self.constrain_out_state(layouter, &mix_res, out_state) - } + // Convert to base 9 and multiply by A4 + let next_input = + convert_to_b9_mul_a4(layouter, &self.from_b2_table, &self.generic, &next_input)?; - pub fn constrain_out_state( - &self, - layouter: &mut impl Layouter, - out_mixing: &[AssignedCell; 25], - out_state: [F; 25], - ) -> Result<[AssignedCell; 25], Error> { - layouter.assign_region( - || "Constraint out_state and out_mixing", - |mut region| { - // Enable selector at offset = 0 - self.q_out.enable(&mut region, 0)?; - - // Allocate out_mixing at offset = 0 in `state` column. - self.copy_state(&mut region, 0, self.state, out_mixing)?; - - // Witness out_state at offset = 1 in `state` column. - let out_state: [AssignedCell; 25] = { - let mut out_vec: Vec> = vec![]; - for (idx, lane) in out_state.iter().enumerate() { - let out_cell = region.assign_advice( - || format!("assign out_state [{}]", idx), - self.state[idx], - 1, - || Ok(*lane), - )?; - out_vec.push(out_cell); - } - out_vec.try_into().unwrap() - }; - - Ok(out_state) - }, - ) - } - - /// Copies the `state` cells to the passed [Column; 25]. - fn copy_state( - &self, - region: &mut Region<'_, F>, - offset: usize, - columns: [Column; 25], - state: &[AssignedCell; 25], - ) -> Result<(), Error> { - for (idx, cell) in state.iter().enumerate() { - cell.copy_advice( - || format!("Copy state {}", idx), - region, - columns[idx], - offset, - )?; + for (i, input) in next_input.iter().enumerate() { + state[i] = self + .generic + .conditional_add_advice(layouter, &state[i], &f_mix, input)?; } - - Ok(()) + let (mut state_b13, state_b2) = + convert_from_b9_to_b13(layouter, &self.from_b9_table, &self.generic, state, true)?; + let state_b2 = state_b2.unwrap(); + state_b13[0] = self.generic.conditional_add_const( + layouter, + &state_b13[0], + &f_mix, + &iota_constants.round_constant_b13, + )?; + Ok((state_b13, state_b2)) } } #[cfg(test)] mod tests { use super::*; - use crate::common::{State, NEXT_INPUTS_LANES}; - use crate::gate_helpers::biguint_to_f; - use halo2_proofs::circuit::Layouter; - use halo2_proofs::pairing::bn256::Fr as Fp; - use halo2_proofs::plonk::{ConstraintSystem, Error}; - use halo2_proofs::{circuit::SimpleFloorPlanner, dev::MockProver, plonk::Circuit}; - use pretty_assertions::assert_eq; - - // TODO: Remove ignore once this can run in the CI without hanging. - #[ignore] + use crate::{ + arith_helpers::{ + convert_b2_to_b13, convert_b9_lane_to_b2_biguint, state_bigint_to_field, StateBigInt, + }, + common::{State, NEXT_INPUTS_LANES}, + gate_helpers::biguint_to_f, + keccak_arith::KeccakFArith, + }; + + use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner}, + dev::MockProver, + pairing::bn256::Fr as Fp, + plonk::{Circuit, ConstraintSystem, Error}, + }; + #[test] fn test_keccak_round() { #[derive(Default)] @@ -313,8 +189,6 @@ mod tests { in_state: [F; 25], out_state: [F; 25], next_mixing: Option<[F; NEXT_INPUTS_LANES]>, - // flag - is_mixing: bool, } impl Circuit for MyCircuit { @@ -336,38 +210,51 @@ mod tests { ) -> Result<(), Error> { // Load the table config.load(&mut layouter)?; - let offset: usize = 0; - let in_state = layouter.assign_region( + let state: [AssignedCell; 25] = layouter.assign_region( || "Keccak round Wittnes & flag assignation", |mut region| { - // Witness `state` - let in_state: [AssignedCell; 25] = { - let mut state: Vec> = Vec::with_capacity(25); - for (idx, val) in self.in_state.iter().enumerate() { - let cell = region.assign_advice( + let state = self + .in_state + .iter() + .enumerate() + .map(|(offset, val)| { + region.assign_advice( || "witness input state", - config.state[idx], + config.advice, offset, || Ok(*val), - )?; - state.push(cell) - } - state.try_into().unwrap() - }; + ) + }) + .collect::>, Error>>()?; - Ok(in_state) + Ok(state.try_into().unwrap()) }, )?; - config.assign_all( - &mut layouter, - in_state, - self.out_state, - self.is_mixing, - self.next_mixing, - )?; - Ok(()) + let (state_b13, state_b2) = + config.assign_all(&mut layouter, state, self.next_mixing)?; + if self.next_mixing.is_some() { + layouter.assign_region( + || "check final states", + |mut region| { + for (assigned, value) in state_b13.iter().zip(self.out_state.iter()) { + region.constrain_constant(assigned.cell(), value)?; + } + Ok(()) + }, + ) + } else { + layouter.assign_region( + || "check final states", + |mut region| { + for (assigned, value) in state_b2.iter().zip(self.out_state.iter()) { + region.constrain_constant(assigned.cell(), value)?; + } + Ok(()) + }, + ) + } } } @@ -404,6 +291,11 @@ mod tests { let mut out_state_non_mix = in_state_biguint.clone(); KeccakFArith::permute_and_absorb(&mut out_state_non_mix, None); + for (x, y) in (0..5).cartesian_product(0..5) { + out_state_non_mix[(x, y)] = + convert_b9_lane_to_b2_biguint(out_state_non_mix[(x, y)].clone()) + } + // Generate out_state as `[Fp;25]` let out_state_mix: [Fp; 25] = state_bigint_to_field(out_state_mix); let out_state_non_mix: [Fp; 25] = state_bigint_to_field(out_state_non_mix); @@ -422,12 +314,11 @@ mod tests { in_state: in_state_fp, out_state: out_state_non_mix, next_mixing: None, - is_mixing: false, }; let prover = MockProver::::run(17, &circuit, vec![]).unwrap(); - assert_eq!(prover.verify(), Ok(())); + assert_eq!(prover.verify(), Ok(()), "is_mixing: false"); // With wrong input and/or output witnesses, the proof should fail // to be verified. @@ -435,7 +326,6 @@ mod tests { in_state: out_state_non_mix, out_state: out_state_non_mix, next_mixing: None, - is_mixing: true, }; let k = 17; let prover = MockProver::::run(k, &circuit, vec![]).unwrap(); @@ -462,12 +352,11 @@ mod tests { in_state: in_state_fp, out_state: out_state_mix, next_mixing: Some(next_input_fp), - is_mixing: true, }; let prover = MockProver::::run(17, &circuit, vec![]).unwrap(); - assert_eq!(prover.verify(), Ok(())); + assert_eq!(prover.verify(), Ok(()), "is_mixing: true"); // With wrong input and/or output witnesses, the proof should fail // to be verified. @@ -475,7 +364,6 @@ mod tests { in_state: out_state_non_mix, out_state: out_state_non_mix, next_mixing: Some(next_input_fp), - is_mixing: true, }; let prover = MockProver::::run(17, &circuit, vec![]).unwrap(); diff --git a/keccak256/src/permutation/components.rs b/keccak256/src/permutation/components.rs new file mode 100644 index 0000000000..7529681974 --- /dev/null +++ b/keccak256/src/permutation/components.rs @@ -0,0 +1,710 @@ +use eth_types::Field; +use halo2_proofs::{ + circuit::{AssignedCell, Layouter}, + plonk::{Advice, Column, Error}, +}; +use itertools::Itertools; +use std::convert::TryInto; +use std::vec; + +use super::tables::{FromBinaryTableConfig, NUM_OF_BINARY_CHUNKS_PER_SLICE, NUM_OF_BINARY_SLICES}; + +use crate::{ + arith_helpers::{convert_b2_to_b13, convert_b2_to_b9, A1, A2, A3, A4, B13, B2, B9}, + common::{NEXT_INPUTS_LANES, PERMUTATION, ROTATION_CONSTANTS, ROUND_CONSTANTS}, + gate_helpers::{biguint_to_f, f_to_biguint}, + permutation::{ + generic::GenericConfig, + rho_helpers::{slice_lane, RhoLane}, + tables::{ + Base13toBase9TableConfig, FromBase9TableConfig, StackableTable, + NUM_OF_B9_CHUNKS_PER_SLICE, NUM_OF_B9_SLICES, + }, + }, +}; + +use num_bigint::BigUint; + +pub fn assign_theta( + generic: &GenericConfig, + layouter: &mut impl Layouter, + state: &[AssignedCell; 25], +) -> Result<[AssignedCell; 25], Error> { + let theta_col_sums = (0..5) + .map(|x| { + generic.running_sum( + layouter, + (0..5).map(|y| state[5 * x + y].clone()).collect(), + None, + ) + }) + .collect::, Error>>()?; + + let out_state = (0..5) + .cartesian_product(0..5) + .map(|(x, y)| { + let cells = vec![ + state[5 * x + y].clone(), + theta_col_sums[(x + 4) % 5].clone(), + theta_col_sums[(x + 1) % 5].clone(), + ]; + let vs = vec![F::one(), F::one(), F::from(B13 as u64)]; + generic.linear_combine_consts(layouter, cells, vs, None) + }) + .collect::, Error>>()?; + + Ok(out_state.try_into().unwrap()) +} + +pub fn assign_rho( + layouter: &mut impl Layouter, + base13to9_config: &Base13toBase9TableConfig, + generic: &GenericConfig, + stackable: &StackableTable, + state: &[AssignedCell; 25], +) -> Result<[AssignedCell; 25], Error> { + let mut next_state = vec![]; + let mut step2_od_join = vec![]; + let mut step3_od_join = vec![]; + for (lane_idx, lane) in state.iter().enumerate() { + let rotation = { + let x = lane_idx / 5; + let y = lane_idx % 5; + ROTATION_CONSTANTS[x][y] + }; + let (conversions, special) = + RhoLane::new(f_to_biguint(*lane.value().unwrap_or(&F::zero())), rotation) + .get_full_witness(); + let slices = slice_lane(rotation); + + let (input_coefs, mut output_coefs, step2_od, step3_od) = + base13to9_config.assign_region(layouter, &slices, &conversions)?; + + let input_pobs = conversions + .iter() + .map(|c| biguint_to_f::(&c.input.power_of_base)) + .collect_vec(); + + let mut output_pobs = conversions + .iter() + .map(|c| biguint_to_f::(&c.output.power_of_base)) + .collect_vec(); + // Final output power of base + output_pobs.push(biguint_to_f::(&special.output_pob)); + + let input_from_chunks = + generic.linear_combine_consts(layouter, input_coefs, input_pobs, None)?; + let last_chunk = generic.sub_advice(layouter, lane, &input_from_chunks)?; + + let final_output_coef = stackable.lookup_special_chunks(layouter, &last_chunk)?; + output_coefs.push(final_output_coef); + + let output_lane = + generic.linear_combine_consts(layouter, output_coefs, output_pobs, None)?; + next_state.push(output_lane); + step2_od_join.extend(step2_od); + step3_od_join.extend(step3_od); + } + let step2_sum = generic.running_sum(layouter, step2_od_join, None)?; + let step3_sum = generic.running_sum(layouter, step3_od_join, None)?; + stackable.lookup_range_12(layouter, &[step2_sum])?; + stackable.lookup_range_169(layouter, &[step3_sum])?; + Ok(next_state.try_into().unwrap()) +} + +/// The Keccak Pi step +/// +/// It has no gates. We just have to permute the previous state into the correct +/// order. The copy constrain in the next gate can then enforce the Pi step +/// permutation. +pub fn pi_gate_permutation(state: &[AssignedCell; 25]) -> [AssignedCell; 25] { + (0..5) + .cartesian_product(0..5) + .map(|(x, y)| state[5 * ((x + 3 * y) % 5) + x].clone()) + .collect::>() + .try_into() + .unwrap() +} + +pub fn assign_xi( + generic: &GenericConfig, + layouter: &mut impl Layouter, + state: &[AssignedCell; 25], +) -> Result<[AssignedCell; 25], Error> { + let out_state = (0..5) + .cartesian_product(0..5) + .map(|(x, y)| { + let cells = vec![ + state[5 * x + y].clone(), + state[5 * ((x + 1) % 5) + y].clone(), + state[5 * ((x + 2) % 5) + y].clone(), + ]; + let vs = vec![F::from(A1), F::from(A2), F::from(A3)]; + generic.linear_combine_consts(layouter, cells, vs, None) + }) + .collect::, Error>>()?; + Ok(out_state.try_into().unwrap()) +} + +#[derive(Clone, Debug)] +pub struct IotaConstants { + pub round_constant_b13: F, + pub a4_times_round_constants_b9: [F; PERMUTATION], +} + +impl Default for IotaConstants { + fn default() -> Self { + let round_constant_b13 = + biguint_to_f::(&convert_b2_to_b13(ROUND_CONSTANTS[PERMUTATION - 1])); + + let a4_times_round_constants_b9: [F; 24] = ROUND_CONSTANTS + .iter() + .map(|&x| { + let constant = A4 * convert_b2_to_b9(x); + biguint_to_f::(&constant) + }) + .collect_vec() + .try_into() + .unwrap(); + + Self { + round_constant_b13, + a4_times_round_constants_b9, + } + } +} + +pub fn assign_next_input( + layouter: &mut impl Layouter, + next_input_col: &Column, + next_input: &Option<[F; NEXT_INPUTS_LANES]>, +) -> Result<[AssignedCell; NEXT_INPUTS_LANES], Error> { + let next_input_b9 = layouter.assign_region( + || "next input words", + |mut region| { + let next_input = next_input.map_or( + [None; NEXT_INPUTS_LANES], + |v| -> [Option; NEXT_INPUTS_LANES] { + v.map(|vv| Some(vv)) + .iter() + .cloned() + .collect_vec() + .try_into() + .unwrap() + }, + ); + next_input + .iter() + .enumerate() + .map(|(offset, input)| { + region.assign_advice( + || "next input words", + *next_input_col, + offset, + || Ok(input.unwrap_or_default()), + ) + }) + .collect::, Error>>() + }, + )?; + Ok(next_input_b9.try_into().unwrap()) +} + +pub fn convert_to_b9_mul_a4( + layouter: &mut impl Layouter, + from_b2_table: &FromBinaryTableConfig, + generic: &GenericConfig, + next_input: &[AssignedCell; NEXT_INPUTS_LANES], +) -> Result<[AssignedCell; NEXT_INPUTS_LANES], Error> { + let next_input = next_input + .iter() + .map(|input| { + let (base2s, base9s, _) = from_b2_table.assign_region(layouter, input)?; + let vs = (0..NUM_OF_BINARY_SLICES) + .map(|i| { + biguint_to_f( + &BigUint::from(B2).pow((NUM_OF_BINARY_CHUNKS_PER_SLICE * i) as u32), + ) + }) + .rev() + .collect_vec(); + generic.linear_combine_consts(layouter, base2s, vs, Some(input.clone()))?; + let vs = (0..NUM_OF_BINARY_SLICES) + .map(|i| { + biguint_to_f::( + &BigUint::from(B9).pow((NUM_OF_BINARY_CHUNKS_PER_SLICE * i) as u32), + ) * F::from(A4) + }) + .rev() + .collect_vec(); + let output = generic.linear_combine_consts(layouter, base9s, vs, None)?; + Ok(output) + }) + .collect::>, Error>>()?; + let next_input: [AssignedCell; NEXT_INPUTS_LANES] = next_input.try_into().unwrap(); + Ok(next_input) +} + +pub fn convert_from_b9_to_b13( + layouter: &mut impl Layouter, + from_b9_table: &FromBase9TableConfig, + generic: &GenericConfig, + state: [AssignedCell; 25], + output_b2: bool, +) -> Result<([AssignedCell; 25], Option<[AssignedCell; 25]>), Error> { + let (state_b13, state_b2): (Vec>, Vec>>) = state + .iter() + .map(|lane| { + let (base9s, base_13s, base_2s) = from_b9_table.assign_region(layouter, lane)?; + let vs = (0..NUM_OF_B9_SLICES) + .map(|i| { + biguint_to_f(&BigUint::from(B9).pow((NUM_OF_B9_CHUNKS_PER_SLICE * i) as u32)) + }) + .rev() + .collect_vec(); + generic.linear_combine_consts(layouter, base9s, vs, Some(lane.clone()))?; + let vs = (0..NUM_OF_B9_SLICES) + .map(|i| { + biguint_to_f(&BigUint::from(B13).pow((NUM_OF_B9_CHUNKS_PER_SLICE * i) as u32)) + }) + .rev() + .collect_vec(); + let lane_b13 = generic.linear_combine_consts(layouter, base_13s, vs, None)?; + let lane_b2 = if output_b2 { + let vs = (0..NUM_OF_B9_SLICES) + .map(|i| { + biguint_to_f( + &BigUint::from(B2).pow((NUM_OF_B9_CHUNKS_PER_SLICE * i) as u32), + ) + }) + .rev() + .collect_vec(); + let lane_b2 = generic.linear_combine_consts(layouter, base_2s, vs, None)?; + Some(lane_b2) + } else { + None + }; + Ok((lane_b13, lane_b2)) + }) + .collect::, Error>>()? + .iter() + .cloned() + .unzip(); + let state_b2: Option<[AssignedCell; 25]> = state_b2 + .into_iter() + .collect::>>() + .map(|v| v.try_into().unwrap()); + + Ok((state_b13.try_into().unwrap(), state_b2)) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::arith_helpers::StateBigInt; + use crate::common::*; + use crate::gate_helpers::biguint_to_f; + use crate::keccak_arith::*; + use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner}, + dev::MockProver, + pairing::bn256::Fr as Fp, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error, TableColumn}, + }; + use itertools::Itertools; + use std::convert::TryInto; + use std::marker::PhantomData; + + #[test] + fn test_theta_gates() { + #[derive(Clone, Debug)] + struct MyConfig { + lane: Column, + generic: GenericConfig, + } + + impl MyConfig { + pub fn configure(meta: &mut ConstraintSystem) -> Self { + let advices: [Column; 3] = (0..3) + .map(|_| { + let column = meta.advice_column(); + meta.enable_equality(column); + column + }) + .collect::>() + .try_into() + .unwrap(); + let fixed = meta.fixed_column(); + + let lane = advices[0]; + let generic = GenericConfig::configure(meta, advices, fixed); + Self { lane, generic } + } + } + #[derive(Default)] + struct MyCircuit { + in_state: [F; 25], + out_state: [F; 25], + _marker: PhantomData, + } + impl Circuit for MyCircuit { + type Config = MyConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + // this column is required by `constrain_constant` + let constant = meta.fixed_column(); + meta.enable_constant(constant); + Self::Config::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let in_state = layouter.assign_region( + || "Wittnes & assignation", + |mut region| { + // Witness `state` + let in_state: [AssignedCell; 25] = { + let mut state: Vec> = Vec::with_capacity(25); + for (offset, val) in self.in_state.iter().enumerate() { + let cell = region.assign_advice( + || "witness input state", + config.lane, + offset, + || Ok(*val), + )?; + state.push(cell) + } + state.try_into().unwrap() + }; + Ok(in_state) + }, + )?; + + let out_state = assign_theta(&config.generic, &mut layouter, &in_state)?; + + layouter.assign_region( + || "Check outstate", + |mut region| { + for (assigned, value) in out_state.iter().zip(self.out_state.iter()) { + region.constrain_constant(assigned.cell(), value)?; + } + Ok(()) + }, + )?; + Ok(()) + } + } + + let input1: State = [ + [1, 0, 0, 0, 0], + [0, 0, 0, 9223372036854775808, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + ]; + let mut in_biguint = StateBigInt::default(); + let mut in_state: [Fp; 25] = [Fp::zero(); 25]; + + for (x, y) in (0..5).cartesian_product(0..5) { + in_biguint[(x, y)] = convert_b2_to_b13(input1[x][y]); + in_state[5 * x + y] = biguint_to_f(&in_biguint[(x, y)]); + } + let s1_arith = KeccakFArith::theta(&in_biguint); + let mut out_state: [Fp; 25] = [Fp::zero(); 25]; + for (x, y) in (0..5).cartesian_product(0..5) { + out_state[5 * x + y] = biguint_to_f(&s1_arith[(x, y)]); + } + + let circuit = MyCircuit:: { + in_state, + out_state, + _marker: PhantomData, + }; + + // Test without public inputs + let prover = MockProver::::run(9, &circuit, vec![]).unwrap(); + + assert_eq!(prover.verify(), Ok(())); + + let mut out_state2 = out_state; + out_state2[0] = Fp::from(5566u64); + + let circuit2 = MyCircuit:: { + in_state, + out_state: out_state2, + _marker: PhantomData, + }; + + let prover = MockProver::::run(9, &circuit2, vec![]).unwrap(); + assert!(prover.verify().is_err()); + } + + #[test] + fn test_rho_gate() { + #[derive(Default)] + struct MyCircuit { + in_state: [F; 25], + out_state: [F; 25], + } + + #[derive(Clone)] + struct MyConfig { + advice: Column, + generic: GenericConfig, + stackable: StackableTable, + base13to9_config: Base13toBase9TableConfig, + } + impl Circuit for MyCircuit { + type Config = MyConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let advices: [Column; 3] = (0..3) + .map(|_| meta.advice_column()) + .collect::>() + .try_into() + .unwrap(); + + let fixed = meta.fixed_column(); + let stackable_cols: [TableColumn; 3] = (0..3) + .map(|_| meta.lookup_table_column()) + .collect_vec() + .try_into() + .unwrap(); + let base13to9_cols: [TableColumn; 3] = (0..3) + .map(|_| meta.lookup_table_column()) + .collect_vec() + .try_into() + .unwrap(); + let stackable = StackableTable::configure(meta, advices, stackable_cols); + let generic = GenericConfig::configure(meta, advices, fixed); + let base13to9_config = + Base13toBase9TableConfig::configure(meta, advices, base13to9_cols); + + Self::Config { + advice: advices[0], + generic, + stackable, + base13to9_config, + } + } + + fn synthesize( + &self, + mut config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + config.base13to9_config.load(&mut layouter)?; + config.stackable.load(&mut layouter)?; + let state = layouter.assign_region( + || "assign input state", + |mut region| { + let state = self + .in_state + .iter() + .enumerate() + .map(|(offset, &value)| { + region.assign_advice(|| "lane", config.advice, offset, || Ok(value)) + }) + .collect::>, Error>>()?; + + Ok(state.try_into().unwrap()) + }, + )?; + let out_state = assign_rho( + &mut layouter, + &config.base13to9_config, + &config.generic, + &config.stackable, + &state, + )?; + layouter.assign_region( + || "check final states", + |mut region| { + for (assigned, value) in out_state.iter().zip(self.out_state.iter()) { + region.constrain_constant(assigned.cell(), value)?; + } + Ok(()) + }, + )?; + + Ok(()) + } + } + + let input1: State = [ + [102, 111, 111, 98, 97], + [114, 0, 5, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 5, 0], + [0, 0, 0, 0, 0], + ]; + let mut in_biguint = StateBigInt::default(); + let mut in_state: [Fp; 25] = [Fp::zero(); 25]; + + for (x, y) in (0..5).cartesian_product(0..5) { + in_biguint[(x, y)] = convert_b2_to_b13(input1[x][y]); + } + let s0_arith = KeccakFArith::theta(&in_biguint); + for (x, y) in (0..5).cartesian_product(0..5) { + in_state[5 * x + y] = biguint_to_f(&s0_arith[(x, y)]); + } + let s1_arith = KeccakFArith::rho(&s0_arith); + let mut out_state: [Fp; 25] = [Fp::zero(); 25]; + for (x, y) in (0..5).cartesian_product(0..5) { + out_state[5 * x + y] = biguint_to_f(&s1_arith[(x, y)]); + } + let circuit = MyCircuit:: { + in_state, + out_state, + }; + let k = 15; + #[cfg(feature = "dev-graph")] + { + use plotters::prelude::*; + let root = + BitMapBackend::new("rho-test-circuit.png", (1024, 16384)).into_drawing_area(); + root.fill(&WHITE).unwrap(); + let root = root.titled("Rho", ("sans-serif", 60)).unwrap(); + halo2_proofs::dev::CircuitLayout::default() + .render(k, &circuit, &root) + .unwrap(); + } + // Test without public inputs + let prover = MockProver::::run(k, &circuit, vec![]).unwrap(); + + assert_eq!(prover.verify(), Ok(())); + } + + #[test] + fn test_xi_gate() { + #[derive(Clone, Debug)] + struct MyConfig { + lane: Column, + generic: GenericConfig, + } + + impl MyConfig { + pub fn configure(meta: &mut ConstraintSystem) -> Self { + let advices: [Column; 3] = (0..3) + .map(|_| { + let column = meta.advice_column(); + meta.enable_equality(column); + column + }) + .collect::>() + .try_into() + .unwrap(); + let fixed = meta.fixed_column(); + + let lane = advices[0]; + let generic = GenericConfig::configure(meta, advices, fixed); + Self { lane, generic } + } + } + #[derive(Default)] + struct MyCircuit { + in_state: [F; 25], + out_state: [F; 25], + _marker: PhantomData, + } + + impl Circuit for MyCircuit { + type Config = MyConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + // this column is required by `constrain_constant` + let constant = meta.fixed_column(); + meta.enable_constant(constant); + Self::Config::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let in_state = layouter.assign_region( + || "Wittnes & assignation", + |mut region| { + // Witness `state` + let in_state: [AssignedCell; 25] = { + let mut state: Vec> = Vec::with_capacity(25); + for (offset, val) in self.in_state.iter().enumerate() { + let cell = region.assign_advice( + || "witness input state", + config.lane, + offset, + || Ok(*val), + )?; + state.push(cell) + } + state.try_into().unwrap() + }; + Ok(in_state) + }, + )?; + + let out_state = assign_xi(&config.generic, &mut layouter, &in_state)?; + + layouter.assign_region( + || "Check outstate", + |mut region| { + for (assigned, value) in out_state.iter().zip(self.out_state.iter()) { + region.constrain_constant(assigned.cell(), value)?; + } + Ok(()) + }, + )?; + Ok(()) + } + } + + let input1: State = [ + [1, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + ]; + let mut in_biguint = StateBigInt::default(); + let mut in_state: [Fp; 25] = [Fp::zero(); 25]; + + for (x, y) in (0..5).cartesian_product(0..5) { + in_biguint[(x, y)] = convert_b2_to_b9(input1[x][y]); + in_state[5 * x + y] = biguint_to_f(&in_biguint[(x, y)]); + } + let s1_arith = KeccakFArith::xi(&in_biguint); + let mut out_state: [Fp; 25] = [Fp::zero(); 25]; + for (x, y) in (0..5).cartesian_product(0..5) { + out_state[5 * x + y] = biguint_to_f(&s1_arith[(x, y)]); + } + let circuit = MyCircuit:: { + in_state, + out_state, + _marker: PhantomData, + }; + + // Test without public inputs + let prover = MockProver::::run(9, &circuit, vec![]).unwrap(); + + assert_eq!(prover.verify(), Ok(())); + } +} diff --git a/keccak256/src/permutation/generic.rs b/keccak256/src/permutation/generic.rs index 192ee5b8bb..8a2d264b4b 100644 --- a/keccak256/src/permutation/generic.rs +++ b/keccak256/src/permutation/generic.rs @@ -53,60 +53,71 @@ impl GenericConfig { fn add_generic( &self, layouter: &mut impl Layouter, - input: AssignedCell, - left: Option>, - right: Option>, - value: Option, + input: Option<&AssignedCell>, + left: Option<&AssignedCell>, + right: Option<&AssignedCell>, + value: Option<&F>, ) -> Result, Error> { layouter.assign_region( || "add advice", |mut region| { let offset = 0; self.q_enable.enable(&mut region, offset)?; - input.copy_advice(|| "input", &mut region, self.io, offset)?; - let left = match &left { - Some(x) => { + let input = input + .as_ref() + .map(|input| input.copy_advice(|| "input", &mut region, self.io, offset)) + .unwrap_or_else(|| { + region.assign_advice_from_constant( + || "input is 0", + self.io, + offset, + F::zero(), + ) + })?; + + let left = left + .as_ref() + .map(|x| // copy x to use as a flag - (*x).copy_advice(|| "left adv", &mut region, self.left, offset)? - } - None => { + x.copy_advice(|| "left adv", &mut region, self.left, offset)) + .unwrap_or_else(|| { // constrain advice to 1 for a simple add. region.assign_advice_from_constant( || "left const", self.left, offset, F::one(), - )? - } - }; + ) + })?; - let right = match &right { - Some(right) => { + let right = right + .as_ref() + .map(|right| { if value.is_some() { panic!("right and value can't be both some"); } - right.copy_advice(|| "right adv", &mut region, self.right, offset)? - } - None => { - match value { - Some(value) => region.assign_advice_from_constant( - || "fixed value", - self.right, - offset, - value, - )?, - None => { + right.copy_advice(|| "right adv", &mut region, self.right, offset) + }) + .unwrap_or_else(|| { + value + .map(|&value| { + region.assign_advice_from_constant( + || "fixed value", + self.right, + offset, + value, + ) + }) + .unwrap_or_else(|| { // constrain fixed to 1 for a simple add. region.assign_advice_from_constant( || "fixed value", self.right, offset, F::one(), - )? - } - } - } - }; + ) + }) + })?; let offset = 1; region.assign_advice( @@ -114,9 +125,12 @@ impl GenericConfig { self.io, offset, || { - Ok(input.value().cloned().ok_or(Error::Synthesis)? - + left.value().cloned().ok_or(Error::Synthesis)? - * right.value().cloned().ok_or(Error::Synthesis)?) + input + .value() + .zip(left.value()) + .zip(right.value()) + .map(|((&input, &left), &right)| input + left * right) + .ok_or(Error::Synthesis) }, ) }, @@ -126,29 +140,38 @@ impl GenericConfig { pub fn add_advice_mul_const( &self, layouter: &mut impl Layouter, - input: AssignedCell, - x: AssignedCell, - v: F, + input: &AssignedCell, + x: &AssignedCell, + v: &F, ) -> Result, Error> { - self.add_generic(layouter, input, Some(x), None, Some(v)) + self.add_generic(layouter, Some(input), Some(x), None, Some(v)) } /// input -= x pub fn sub_advice( &self, layouter: &mut impl Layouter, - input: AssignedCell, - x: AssignedCell, + input: &AssignedCell, + x: &AssignedCell, ) -> Result, Error> { - self.add_generic(layouter, input, Some(x), None, Some(-F::one())) + self.add_generic(layouter, Some(input), Some(x), None, Some(&(-F::one()))) } /// input += v pub fn add_fixed( &self, layouter: &mut impl Layouter, - input: AssignedCell, - value: F, + input: &AssignedCell, + value: &F, ) -> Result, Error> { - self.add_generic(layouter, input, None, None, Some(value)) + self.add_generic(layouter, Some(input), None, None, Some(value)) + } + /// output = input * v + pub fn mul_fixed( + &self, + layouter: &mut impl Layouter, + input: &AssignedCell, + value: &F, + ) -> Result, Error> { + self.add_generic(layouter, None, Some(input), None, Some(value)) } /// input += flag * v /// No boolean check on the flag, we assume the flag is checked before @@ -156,11 +179,11 @@ impl GenericConfig { pub fn conditional_add_const( &self, layouter: &mut impl Layouter, - input: AssignedCell, - flag: AssignedCell, - value: F, + input: &AssignedCell, + flag: &AssignedCell, + value: &F, ) -> Result, Error> { - self.add_generic(layouter, input, Some(flag), None, Some(value)) + self.add_generic(layouter, Some(input), Some(flag), None, Some(value)) } /// input += flag * x /// No boolean check on the flag, we assume the flag is checked before @@ -168,11 +191,11 @@ impl GenericConfig { pub fn conditional_add_advice( &self, layouter: &mut impl Layouter, - input: AssignedCell, - flag: AssignedCell, - x: AssignedCell, + input: &AssignedCell, + flag: &AssignedCell, + x: &AssignedCell, ) -> Result, Error> { - self.add_generic(layouter, input, Some(flag), Some(x), None) + self.add_generic(layouter, Some(input), Some(flag), Some(x), None) } fn linear_combine_generic( &self, @@ -204,40 +227,40 @@ impl GenericConfig { // | ... | ... | ... | ... | // | N - 1 | | x_(N-1) | y_(N-1) | // | N | (sum) | | | - let mut acc = region.assign_advice(|| "input 0", self.io, 0, || Ok(F::zero()))?; - region.constrain_constant(acc.cell(), F::zero())?; + let mut acc = + region.assign_advice_from_constant(|| "input 0", self.io, 0, F::zero())?; + let mut sum = F::zero(); for (offset, x) in xs.iter().enumerate() { self.q_enable.enable(&mut region, offset)?; x.copy_advice(|| "x", &mut region, self.left, offset)?; - let right = { - match &vs { - Some(vs) => region.assign_advice_from_constant( + let right = vs + .as_ref() + .map(|vs| { + region.assign_advice_from_constant( || "v", self.right, offset, vs[offset], - )?, - None => match &ys { - Some(ys) => ys[offset].copy_advice( - || "y", - &mut region, - self.right, - offset, - )?, - None => { - unreachable!() - } - }, - } - }; + ) + }) + .unwrap_or_else(|| { + ys.as_ref() + .map(|ys| { + ys[offset].copy_advice(|| "y", &mut region, self.right, offset) + }) + .expect("ys should have something") + })?; acc = region.assign_advice( || "accumulation", self.io, offset + 1, || { - sum += x.value().cloned().ok_or(Error::Synthesis)? - * right.value().cloned().ok_or(Error::Synthesis)?; + sum += x + .value() + .zip(right.value()) + .map(|(&x, &right)| x * right) + .ok_or(Error::Synthesis)?; Ok(sum) }, )?; @@ -245,6 +268,11 @@ impl GenericConfig { if let Some(outcome) = &outcome { region.constrain_equal(outcome.cell(), acc.cell())?; } + if let Some((outcome, acc)) = + outcome.as_ref().and_then(|oc| oc.value().zip(acc.value())) + { + debug_assert_eq!(outcome, acc); + } Ok(acc) }, ) diff --git a/keccak256/src/permutation/iota.rs b/keccak256/src/permutation/iota.rs deleted file mode 100644 index 2cfb6f9bd5..0000000000 --- a/keccak256/src/permutation/iota.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::arith_helpers::{convert_b2_to_b13, convert_b2_to_b9, A4}; -use crate::common::{PERMUTATION, ROUND_CONSTANTS}; -use crate::gate_helpers::biguint_to_f; -use eth_types::Field; -use itertools::Itertools; - -#[derive(Clone, Debug)] -pub struct IotaConstants { - pub round_constant_b13: F, - pub a4_times_round_constants_b9: [F; PERMUTATION], -} - -impl Default for IotaConstants { - fn default() -> Self { - let round_constant_b13 = - biguint_to_f::(&convert_b2_to_b13(ROUND_CONSTANTS[PERMUTATION - 1])); - - let a4_times_round_constants_b9: [F; 24] = ROUND_CONSTANTS - .iter() - .map(|&x| { - let constant = A4 * convert_b2_to_b9(x); - biguint_to_f::(&constant) - }) - .collect_vec() - .try_into() - .unwrap(); - - Self { - round_constant_b13, - a4_times_round_constants_b9, - } - } -} diff --git a/keccak256/src/permutation/mixing.rs b/keccak256/src/permutation/mixing.rs deleted file mode 100644 index a96496b9a9..0000000000 --- a/keccak256/src/permutation/mixing.rs +++ /dev/null @@ -1,445 +0,0 @@ -use super::super::arith_helpers::*; -use super::generic::GenericConfig; -use super::tables::{FromBase9TableConfig, StackableTable}; -use super::{absorb::AbsorbConfig, base_conversion::BaseConversionConfig, iota::IotaConstants}; -use crate::common::*; -use crate::keccak_arith::KeccakFArith; -use eth_types::Field; -use halo2_proofs::{ - circuit::{AssignedCell, Layouter, Region}, - plonk::{Advice, Column, ConstraintSystem, Error, Selector}, - poly::Rotation, -}; - -#[derive(Clone, Debug)] -pub struct MixingConfig { - iota_constants: IotaConstants, - absorb_config: AbsorbConfig, - base_conv_config: BaseConversionConfig, - state: [Column; 25], - flag: Column, - q_out_copy: Selector, - generic: GenericConfig, - stackable: StackableTable, -} - -impl MixingConfig { - pub fn configure( - meta: &mut ConstraintSystem, - table: &FromBase9TableConfig, - state: [Column; 25], - generic: GenericConfig, - stackable: StackableTable, - ) -> MixingConfig { - let flag = meta.advice_column(); - meta.enable_equality(flag); - // We mix -> Flag = true - let absorb_config = AbsorbConfig::configure(meta, state); - - let base_info = table.get_base_info(false); - let base_conv_lane = meta.advice_column(); - let base_conv_config = BaseConversionConfig::configure( - meta, - base_info, - base_conv_lane, - flag, - state[0..5].try_into().unwrap(), - ); - - let q_out_copy = meta.selector(); - - meta.create_gate("Mixing result copies and constraints", |meta| { - let q_enable = meta.query_selector(q_out_copy); - // Add out mixing states together multiplied by the mixing_flag. - let negated_flag = meta.query_advice(flag, Rotation::next()); - let flag = meta.query_advice(flag, Rotation::cur()); - - // Multiply by flag and negated_flag the out mixing results. - let left_side = meta.query_advice(state[0], Rotation::cur()) * negated_flag; - let right_side = meta.query_advice(state[0], Rotation::next()) * flag; - let out_state = meta.query_advice(state[0], Rotation(2)); - - // We add the results of the mixing gate if/else branches multiplied - // by it's corresponding flags so that we always - // copy from the same place on the copy_constraints while enforcing - // the equality with the out_state of the permutation. - [q_enable * ((left_side + right_side) - out_state)] - }); - let iota_constants = IotaConstants::default(); - - MixingConfig { - iota_constants, - absorb_config, - base_conv_config, - state, - flag, - q_out_copy, - generic, - stackable, - } - } - - /// Enforce flag constraints - pub fn assign_out_mixing_states( - &self, - layouter: &mut impl Layouter, - flag_bool: bool, - negated_flag: AssignedCell, - out_mixing_circ: &[AssignedCell; 25], - out_non_mixing_circ: &[AssignedCell; 25], - out_state: [F; 25], - ) -> Result<[AssignedCell; 25], Error> { - layouter.assign_region( - || "Out Mixing states assignation", - |mut region| { - // Enable selector - self.q_out_copy.enable(&mut region, 0)?; - - // Copy constrain flags. - let _flag_cell = region.assign_advice( - || "witness is_mixing", - self.flag, - 0, - || Ok(F::from(flag_bool as u64)), - )?; - - negated_flag.copy_advice(|| "witness is_mixing", &mut region, self.flag, 1)?; - - // Copy-constrain both out states. - self.copy_state(&mut region, 0, self.state, out_non_mixing_circ)?; - - self.copy_state(&mut region, 1, self.state, out_mixing_circ)?; - - let out_state: [AssignedCell; 25] = { - let mut out_vec: Vec> = vec![]; - for (idx, lane) in out_state.iter().enumerate() { - let out_cell = region.assign_advice( - || format!("assign out_state [{}]", idx), - self.state[idx], - 2, - || Ok(*lane), - )?; - out_vec.push(out_cell); - } - out_vec.try_into().unwrap() - }; - - Ok(out_state) - }, - ) - } - - pub fn assign_state( - &self, - layouter: &mut impl Layouter, - in_state: &[AssignedCell; 25], - out_state: [F; 25], - flag_bool: bool, - next_mixing: Option<[F; NEXT_INPUTS_LANES]>, - ) -> Result<[AssignedCell; 25], Error> { - // Enforce flag constraints and witness them. - let (flag, negated_flag) = self - .stackable - .assign_boolean_flag(layouter, Some(flag_bool))?; - - // If we don't mix: - // IotaB9 - let non_mix_res = { - let mut state = in_state.clone(); - // If `no_mixing` is true: add `A4 * round_constant_b9` - // Otherwise, do nothing and return the orignal lane value in the - // next cell - state[0] = self.generic.conditional_add_const( - layouter, - state[0].clone(), - negated_flag.clone(), - self.iota_constants.a4_times_round_constants_b9[PERMUTATION - 1], - )?; - state - }; - - // If we mix: - // Absorb - let (out_state_absorb_cells, _) = self.absorb_config.copy_state_flag_next_inputs( - layouter, - in_state, - // Compute out_absorb state. - state_bigint_to_field(KeccakFArith::absorb( - &state_to_biguint(split_state_cells(in_state.clone())), - &state_to_state_bigint::(next_mixing.unwrap_or_default()), - )), - next_mixing.unwrap_or_default(), - flag.clone(), - )?; - - // Base conversion assign - let base_conv_cells = - self.base_conv_config - .assign_state(layouter, &out_state_absorb_cells, flag.clone())?; - - // IotaB13 - let mix_res = { - let mut base_conv_cells = base_conv_cells; - - // If `mixing` is true: add round constant in base 13. - // Otherwise, do nothing and return the orignal lane value in the - // next cell - base_conv_cells[0] = self.generic.conditional_add_const( - layouter, - base_conv_cells[0].clone(), - flag, - self.iota_constants.round_constant_b13, - )?; - base_conv_cells - }; - - self.assign_out_mixing_states( - layouter, - flag_bool, - negated_flag, - &mix_res, - &non_mix_res, - out_state, - ) - } - - /// Copies the `[(Cell,F);25]` to the passed [Column; 25]. - fn copy_state( - &self, - region: &mut Region<'_, F>, - offset: usize, - columns: [Column; 25], - state: &[AssignedCell; 25], - ) -> Result<(), Error> { - for (idx, state_cell) in state.iter().enumerate() { - state_cell.copy_advice( - || format!("Copy state {}", idx), - region, - columns[idx], - offset, - )?; - } - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::common::{State, ROUND_CONSTANTS}; - use halo2_proofs::{ - circuit::{Layouter, SimpleFloorPlanner}, - dev::MockProver, - pairing::bn256::Fr as Fp, - plonk::{Circuit, ConstraintSystem, Error, TableColumn}, - }; - use itertools::Itertools; - use pretty_assertions::assert_eq; - - #[test] - fn test_mixing_gate() { - #[derive(Default)] - struct MyCircuit { - in_state: [F; 25], - out_state: [F; 25], - next_mixing: Option<[F; NEXT_INPUTS_LANES]>, - // flag - is_mixing: bool, - } - - #[derive(Clone)] - struct MyConfig { - mixing_conf: MixingConfig, - table: FromBase9TableConfig, - stackable: StackableTable, - } - - impl Circuit for MyCircuit { - type Config = MyConfig; - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let table = FromBase9TableConfig::configure(meta); - - let state: [Column; 25] = (0..25) - .map(|_| { - let col = meta.advice_column(); - meta.enable_equality(col); - col - }) - .collect_vec() - .try_into() - .unwrap(); - 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); - let mixing_conf = - MixingConfig::configure(meta, &table, state, generic, stackable.clone()); - - MyConfig { - mixing_conf, - table, - stackable, - } - } - - fn synthesize( - &self, - mut config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - // Load the table - config.table.load(&mut layouter)?; - config.stackable.load(&mut layouter)?; - let offset: usize = 0; - - let in_state = layouter.assign_region( - || "Mixing Wittnes assignment", - |mut region| { - // Witness `in_state` - let in_state: [AssignedCell; 25] = { - let mut state: Vec> = Vec::with_capacity(25); - for (idx, val) in self.in_state.iter().enumerate() { - let cell = region.assign_advice( - || "witness input state", - config.mixing_conf.state[idx], - offset, - || Ok(*val), - )?; - state.push(cell) - } - state.try_into().unwrap() - }; - - Ok(in_state) - }, - )?; - - config.mixing_conf.assign_state( - &mut layouter, - &in_state, - self.out_state, - self.is_mixing, - self.next_mixing, - )?; - - Ok(()) - } - } - - let input1: State = [ - [1, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - ]; - - let input2: State = [ - [2, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - ]; - - // Convert the input to base9 as the gadget already expects it like this - // since it's always the output of IotaB9. - let mut in_state = StateBigInt::from(input1); - for (x, y) in (0..5).cartesian_product(0..5) { - in_state[(x, y)] = convert_b2_to_b9(input1[x][y]) - } - - // Convert the next_input_b9 to base9 as it needs to be added to the - // state in base9 too. - let next_input = StateBigInt::from(input2); - - // Compute out mixing state (when flag = 1) - let out_mixing_state = state_bigint_to_field(KeccakFArith::mixing( - &in_state, - Some(&input2), - *ROUND_CONSTANTS.last().unwrap(), - )); - - // Compute out non-mixing state (when flag = 0) - let out_non_mixing_state = state_bigint_to_field(KeccakFArith::mixing( - &in_state, - None, - *ROUND_CONSTANTS.last().unwrap(), - )); - - // Add inputs in the correct format. - let in_state = state_bigint_to_field(StateBigInt::from(input1)); - let next_mixing = Some(state_bigint_to_field(next_input)); - - // With flag set to false, we don't mix. And so we should obtain Absorb - // + base_conv + IotaB13 result - { - // With the correct input and output witnesses, the proof should - // pass. - let circuit = MyCircuit:: { - in_state, - out_state: out_mixing_state, - next_mixing, - is_mixing: true, - }; - - let prover = MockProver::::run(17, &circuit, vec![]).unwrap(); - - assert_eq!(prover.verify(), Ok(())); - - // With wrong input and/or output witnesses, the proof should fail - // to be verified. - let circuit = MyCircuit:: { - in_state, - out_state: out_non_mixing_state, - next_mixing, - is_mixing: true, - }; - - let prover = MockProver::::run(17, &circuit, vec![]).unwrap(); - - assert!(prover.verify().is_err()); - } - - // With flag set to `false`, we don't mix. And so we should obtain - // IotaB9 application as result. - { - let circuit = MyCircuit:: { - in_state, - out_state: out_non_mixing_state, - next_mixing: None, - is_mixing: false, - }; - - let prover = MockProver::::run(17, &circuit, vec![]).unwrap(); - - assert_eq!(prover.verify(), Ok(())); - - // With wrong input and/or output witnesses, the proof should fail - // to be verified. - let circuit = MyCircuit:: { - in_state, - out_state: in_state, - next_mixing, - is_mixing: false, - }; - - let prover = MockProver::::run(17, &circuit, vec![]).unwrap(); - - assert!(prover.verify().is_err()); - } - } -} diff --git a/keccak256/src/permutation/pi.rs b/keccak256/src/permutation/pi.rs deleted file mode 100644 index fae778e4d2..0000000000 --- a/keccak256/src/permutation/pi.rs +++ /dev/null @@ -1,18 +0,0 @@ -use eth_types::Field; -use halo2_proofs::circuit::AssignedCell; -use itertools::Itertools; - -/// The Keccak Pi step -/// -/// It has no gates. We just have to permute the previous state into the correct -/// order. The copy constrain in the next gate can then enforce the Pi step -/// permutation. -pub fn pi_gate_permutation(state: [AssignedCell; 25]) -> [AssignedCell; 25] { - let state: [AssignedCell; 25] = (0..5) - .cartesian_product(0..5) - .map(|(x, y)| state[5 * ((x + 3 * y) % 5) + x].clone()) - .collect::>() - .try_into() - .unwrap(); - state -} diff --git a/keccak256/src/permutation/rho.rs b/keccak256/src/permutation/rho.rs deleted file mode 100644 index 1e72dab6a0..0000000000 --- a/keccak256/src/permutation/rho.rs +++ /dev/null @@ -1,225 +0,0 @@ -use crate::common::ROTATION_CONSTANTS; -use crate::gate_helpers::{biguint_to_f, f_to_biguint}; -use crate::permutation::{ - generic::GenericConfig, - rho_helpers::{slice_lane, RhoLane}, - tables::{Base13toBase9TableConfig, StackableTable}, -}; -use eth_types::Field; -use halo2_proofs::{ - circuit::{AssignedCell, Layouter}, - plonk::Error, -}; -use itertools::Itertools; - -pub fn assign_rho( - layouter: &mut impl Layouter, - base13to9_config: &Base13toBase9TableConfig, - generic: &GenericConfig, - stackable: &StackableTable, - state: &[AssignedCell; 25], -) -> Result<[AssignedCell; 25], Error> { - let mut next_state = vec![]; - let mut step2_od_join = vec![]; - let mut step3_od_join = vec![]; - for (lane_idx, lane) in state.iter().enumerate() { - let rotation = { - let x = lane_idx / 5; - let y = lane_idx % 5; - ROTATION_CONSTANTS[x][y] - }; - let (conversions, special) = - RhoLane::new(f_to_biguint(*lane.value().unwrap_or(&F::zero())), rotation) - .get_full_witness(); - let slices = slice_lane(rotation); - - let (input_coefs, mut output_coefs, step2_od, step3_od) = - base13to9_config.assign_region(layouter, &slices, &conversions)?; - - let input_pobs = conversions - .iter() - .map(|c| biguint_to_f::(&c.input.power_of_base)) - .collect_vec(); - - let mut output_pobs = conversions - .iter() - .map(|c| biguint_to_f::(&c.output.power_of_base)) - .collect_vec(); - // Final output power of base - output_pobs.push(biguint_to_f::(&special.output_pob)); - - let input_from_chunks = - generic.linear_combine_consts(layouter, input_coefs, input_pobs, None)?; - let diff = generic.sub_advice(layouter, lane.clone(), input_from_chunks)?; - - let final_output_coef = stackable.lookup_special_chunks(layouter, &diff)?; - output_coefs.push(final_output_coef); - - let output_lane = - generic.linear_combine_consts(layouter, output_coefs, output_pobs, None)?; - next_state.push(output_lane); - step2_od_join.extend(step2_od); - step3_od_join.extend(step3_od); - } - let step2_sum = generic.running_sum(layouter, step2_od_join, None)?; - let step3_sum = generic.running_sum(layouter, step3_od_join, None)?; - stackable.lookup_range_12(layouter, &[step2_sum])?; - stackable.lookup_range_169(layouter, &[step3_sum])?; - Ok(next_state.try_into().unwrap()) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::arith_helpers::{convert_b2_to_b13, StateBigInt}; - use crate::common::*; - use crate::gate_helpers::biguint_to_f; - use crate::keccak_arith::*; - use halo2_proofs::{ - circuit::{Layouter, SimpleFloorPlanner}, - dev::MockProver, - pairing::bn256::Fr as Fp, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error, TableColumn}, - }; - use itertools::Itertools; - #[test] - fn test_rho_gate() { - #[derive(Default)] - struct MyCircuit { - in_state: [F; 25], - out_state: [F; 25], - } - - #[derive(Clone)] - struct MyConfig { - advice: Column, - generic: GenericConfig, - stackable: StackableTable, - base13to9_config: Base13toBase9TableConfig, - } - impl Circuit for MyCircuit { - type Config = MyConfig; - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let advices: [Column; 3] = (0..3) - .map(|_| meta.advice_column()) - .collect::>() - .try_into() - .unwrap(); - - let fixed = meta.fixed_column(); - let stackable_cols: [TableColumn; 3] = (0..3) - .map(|_| meta.lookup_table_column()) - .collect_vec() - .try_into() - .unwrap(); - let base13to9_cols: [TableColumn; 3] = (0..3) - .map(|_| meta.lookup_table_column()) - .collect_vec() - .try_into() - .unwrap(); - let stackable = StackableTable::configure(meta, advices, stackable_cols); - let generic = GenericConfig::configure(meta, advices, fixed); - let base13to9_config = - Base13toBase9TableConfig::configure(meta, advices, base13to9_cols); - - Self::Config { - advice: advices[0], - generic, - stackable, - base13to9_config, - } - } - - fn synthesize( - &self, - mut config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - config.base13to9_config.load(&mut layouter)?; - config.stackable.load(&mut layouter)?; - let state = layouter.assign_region( - || "assign input state", - |mut region| { - let state = self - .in_state - .iter() - .enumerate() - .map(|(offset, &value)| { - region.assign_advice(|| "lane", config.advice, offset, || Ok(value)) - }) - .collect::>, Error>>()?; - - Ok(state.try_into().unwrap()) - }, - )?; - let out_state = assign_rho( - &mut layouter, - &config.base13to9_config, - &config.generic, - &config.stackable, - &state, - )?; - layouter.assign_region( - || "check final states", - |mut region| { - for (assigned, value) in out_state.iter().zip(self.out_state.iter()) { - region.constrain_constant(assigned.cell(), value)?; - } - Ok(()) - }, - )?; - - Ok(()) - } - } - - let input1: State = [ - [102, 111, 111, 98, 97], - [114, 0, 5, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 5, 0], - [0, 0, 0, 0, 0], - ]; - let mut in_biguint = StateBigInt::default(); - let mut in_state: [Fp; 25] = [Fp::zero(); 25]; - - for (x, y) in (0..5).cartesian_product(0..5) { - in_biguint[(x, y)] = convert_b2_to_b13(input1[x][y]); - } - let s0_arith = KeccakFArith::theta(&in_biguint); - for (x, y) in (0..5).cartesian_product(0..5) { - in_state[5 * x + y] = biguint_to_f(&s0_arith[(x, y)]); - } - let s1_arith = KeccakFArith::rho(&s0_arith); - let mut out_state: [Fp; 25] = [Fp::zero(); 25]; - for (x, y) in (0..5).cartesian_product(0..5) { - out_state[5 * x + y] = biguint_to_f(&s1_arith[(x, y)]); - } - let circuit = MyCircuit:: { - in_state, - out_state, - }; - let k = 15; - #[cfg(feature = "dev-graph")] - { - use plotters::prelude::*; - let root = - BitMapBackend::new("rho-test-circuit.png", (1024, 16384)).into_drawing_area(); - root.fill(&WHITE).unwrap(); - let root = root.titled("Rho", ("sans-serif", 60)).unwrap(); - halo2_proofs::dev::CircuitLayout::default() - .render(k, &circuit, &root) - .unwrap(); - } - // Test without public inputs - let prover = MockProver::::run(k, &circuit, vec![]).unwrap(); - - assert_eq!(prover.verify(), Ok(())); - } -} diff --git a/keccak256/src/permutation/tables.rs b/keccak256/src/permutation/tables.rs index 4a041e6426..7fffe593e8 100644 --- a/keccak256/src/permutation/tables.rs +++ b/keccak256/src/permutation/tables.rs @@ -16,15 +16,16 @@ use strum_macros::{Display, EnumIter}; use super::rho_helpers::{Conversion, STEP2_RANGE, STEP3_RANGE}; const MAX_CHUNKS: usize = 64; -const NUM_OF_BINARY_CHUNKS: usize = 16; -const NUM_OF_B9_CHUNKS: usize = 5; +pub const NUM_OF_BINARY_CHUNKS_PER_SLICE: usize = 16; +pub const NUM_OF_BINARY_SLICES: usize = 4; +pub const NUM_OF_B9_CHUNKS_PER_SLICE: usize = 5; +/// is ceil(`MAX_CHUNKS`/ `NUM_OF_B9_CHUNKS_PER_SLICE`) = 13 +pub const NUM_OF_B9_SLICES: usize = 13; #[derive(Debug, Clone)] struct ThreeColumnsLookup { q_enable: Selector, - pub(crate) col0: (Column, TableColumn), - pub(crate) col1: (Column, TableColumn), - pub(crate) col2: (Column, TableColumn), + pub(crate) cols: [(Column, TableColumn); 3], _marker: PhantomData, } impl ThreeColumnsLookup { @@ -34,27 +35,29 @@ impl ThreeColumnsLookup { table_cols: [TableColumn; 3], name: &'static str, ) -> Self { - let col0 = (adv_cols[0], table_cols[0]); - let col1 = (adv_cols[1], table_cols[1]); - let col2 = (adv_cols[2], table_cols[2]); + let cols: [(Column, TableColumn); 3] = adv_cols + .iter() + .cloned() + .zip(table_cols.iter().cloned()) + .collect_vec() + .try_into() + .unwrap(); let q_enable = meta.complex_selector(); meta.lookup(name, |meta| { let q_enable = meta.query_selector(q_enable); - let col0_adv = meta.query_advice(col0.0, Rotation::cur()); - let col1_adv = meta.query_advice(col1.0, Rotation::cur()); - let col2_adv = meta.query_advice(col2.0, Rotation::cur()); + let col0_adv = meta.query_advice(cols[0].0, Rotation::cur()); + let col1_adv = meta.query_advice(cols[1].0, Rotation::cur()); + let col2_adv = meta.query_advice(cols[2].0, Rotation::cur()); vec![ - (q_enable.clone() * col0_adv, col0.1), - (q_enable.clone() * col1_adv, col1.1), - (q_enable * col2_adv, col2.1), + (q_enable.clone() * col0_adv, cols[0].1), + (q_enable.clone() * col1_adv, cols[1].1), + (q_enable * col2_adv, cols[2].1), ] }); Self { q_enable, - col0, - col1, - col2, + cols, _marker: PhantomData, } } @@ -102,19 +105,19 @@ impl StackableTable { for i in 0..=k { table.assign_cell( || format!("tag range{}", tag), - self.lookup_config.col0.1, + self.lookup_config.cols[0].1, offset, || Ok(F::from(tag as u64)), )?; table.assign_cell( || format!("range{}", tag), - self.lookup_config.col1.1, + self.lookup_config.cols[1].1, offset, || Ok(F::from(i)), )?; table.assign_cell( || format!("dummy col range{}", tag), - self.lookup_config.col2.1, + self.lookup_config.cols[2].1, offset, || Ok(F::zero()), )?; @@ -137,19 +140,19 @@ impl StackableTable { .insert(last_chunk.to_repr(), output_coef); table.assign_cell( || "tag special chunks", - self.lookup_config.col0.1, + self.lookup_config.cols[0].1, offset, || Ok(F::from(TableTags::SpecialChunk as u64)), )?; table.assign_cell( || "last chunk", - self.lookup_config.col1.1, + self.lookup_config.cols[1].1, offset, || Ok(last_chunk), )?; table.assign_cell( || "output coef", - self.lookup_config.col2.1, + self.lookup_config.cols[2].1, offset, || Ok(output_coef), )?; @@ -164,19 +167,19 @@ impl StackableTable { for (left, right) in [(true, false), (false, true)] { table.assign_cell( || "tag boolean flag", - self.lookup_config.col0.1, + self.lookup_config.cols[0].1, offset, || Ok(F::from(TableTags::BooleanFlag as u64)), )?; table.assign_cell( || "left", - self.lookup_config.col1.1, + self.lookup_config.cols[1].1, offset, || Ok(F::from(left)), )?; table.assign_cell( || "right", - self.lookup_config.col2.1, + self.lookup_config.cols[2].1, offset, || Ok(F::from(right)), )?; @@ -218,14 +221,19 @@ impl StackableTable { self.lookup_config.q_enable.enable(&mut region, offset)?; region.assign_advice_from_constant( || "tag", - self.lookup_config.col0.0, + self.lookup_config.cols[0].0, offset, tag, )?; - v.copy_advice(|| "value", &mut region, self.lookup_config.col1.0, offset)?; + v.copy_advice( + || "value", + &mut region, + self.lookup_config.cols[1].0, + offset, + )?; region.assign_advice_from_constant( || "dummy", - self.lookup_config.col2.0, + self.lookup_config.cols[2].0, offset, F::zero(), )?; @@ -262,19 +270,19 @@ impl StackableTable { self.lookup_config.q_enable.enable(&mut region, offset)?; region.assign_advice_from_constant( || "tag", - self.lookup_config.col0.0, + self.lookup_config.cols[0].0, offset, tag, )?; last_chunk.copy_advice( || "last chunk", &mut region, - self.lookup_config.col1.0, + self.lookup_config.cols[1].0, offset, )?; region.assign_advice( || "output coef", - self.lookup_config.col2.0, + self.lookup_config.cols[2].0, offset, || { last_chunk @@ -292,7 +300,7 @@ impl StackableTable { pub(crate) fn assign_boolean_flag( &self, layouter: &mut impl Layouter, - is_left: Option, + is_left: bool, ) -> Result<(AssignedCell, AssignedCell), Error> { layouter.assign_region( || "lookup for boolean flag", @@ -301,21 +309,21 @@ impl StackableTable { self.lookup_config.q_enable.enable(&mut region, offset)?; region.assign_advice_from_constant( || "tag", - self.lookup_config.col0.0, + self.lookup_config.cols[0].0, offset, F::from(TableTags::BooleanFlag as u64), )?; let left = region.assign_advice( || "left", - self.lookup_config.col1.0, + self.lookup_config.cols[1].0, offset, - || is_left.map(|flag| F::from(flag)).ok_or(Error::Synthesis), + || Ok(F::from(is_left)), )?; let right = region.assign_advice( || "right", - self.lookup_config.col2.0, + self.lookup_config.cols[2].0, offset, - || is_left.map(|flag| F::from(!flag)).ok_or(Error::Synthesis), + || Ok(F::from(!is_left)), )?; Ok((left, right)) }, @@ -386,20 +394,20 @@ impl Base13toBase9TableConfig { .insert(input_b13.to_repr(), (output_b9, overflow_detector)); table.assign_cell( || "base 13", - self.lookup_config.col0.1, + self.lookup_config.cols[0].1, i, || Ok(input_b13), )?; table.assign_cell( || "base 9", - self.lookup_config.col1.1, + self.lookup_config.cols[1].1, i, || Ok(output_b9), )?; table.assign_cell( || "overflow_detector", - self.lookup_config.col2.1, + self.lookup_config.cols[2].1, i, || Ok(overflow_detector), )?; @@ -451,7 +459,7 @@ impl Base13toBase9TableConfig { let outputs = self.map.get(&input.to_repr()); let input_coef = region.assign_advice( || "Input Coef", - self.lookup_config.col0.0, + self.lookup_config.cols[0].0, offset, || Ok(input), )?; @@ -459,7 +467,7 @@ impl Base13toBase9TableConfig { let output_coef = region.assign_advice( || "Output Coef", - self.lookup_config.col1.0, + self.lookup_config.cols[1].0, offset, || outputs.map(|o| o.0).ok_or(Error::Synthesis), )?; @@ -467,7 +475,7 @@ impl Base13toBase9TableConfig { let od = region.assign_advice( || "Overflow detector", - self.lookup_config.col2.0, + self.lookup_config.cols[2].0, offset, || outputs.map(|o| o.1).ok_or(Error::Synthesis), )?; @@ -486,110 +494,76 @@ impl Base13toBase9TableConfig { } } -#[derive(Clone, Debug)] -pub(crate) struct BaseInfo { - input_base: u8, - output_base: u8, - // How many chunks we perform in a lookup? +fn compute_input_coefs( + input: Option<&F>, + base: u8, num_chunks: usize, - // How many chunks in total - pub max_chunks: usize, - pub input_tc: TableColumn, - pub output_tc: TableColumn, - _marker: PhantomData, -} - -impl BaseInfo { - pub fn input_pob(&self) -> F { - F::from(self.input_base as u64).pow(&[self.num_chunks as u64, 0, 0, 0]) - } - pub fn output_pob(&self) -> F { - F::from(self.output_base as u64).pow(&[self.num_chunks as u64, 0, 0, 0]) - } - - pub fn compute_coefs(&self, input: F) -> Result<(Vec, Vec, F), Error> { +) -> [Option; SLICES] { + input.map_or([None; SLICES], |&input| { // big-endian let input_chunks: Vec = { let raw = f_to_biguint(input); - let mut v = raw.to_radix_le(self.input_base.into()); - debug_assert!(v.len() <= self.max_chunks); + let mut v = raw.to_radix_le(base.into()); + debug_assert!(v.len() <= MAX_CHUNKS); // fill 0 to max chunks - v.resize(self.max_chunks, 0); + v.resize(MAX_CHUNKS, 0); // v is big-endian now v.reverse(); v }; // Use rchunks + rev so that the remainder chunks stay at the big-endian // side - let input_coefs: Vec = input_chunks - .rchunks(self.num_chunks) + let input_coefs = input_chunks + .rchunks(num_chunks) .rev() - .map(|chunks| f_from_radix_be(chunks, self.input_base)) - .collect(); - let convert_chunk = match self.input_base { - B2 => |x| x, - B13 => convert_b13_coef, - B9 => convert_b9_coef, - _ => unreachable!(), - }; - let output: F = { - let converted_chunks: Vec = - input_chunks.iter().map(|&x| convert_chunk(x)).collect_vec(); - f_from_radix_be(&converted_chunks, self.output_base) - }; - - let output_coefs: Vec = input_chunks - .rchunks(self.num_chunks) - .rev() - .map(|chunks| { - let converted_chunks: Vec = - chunks.iter().map(|&x| convert_chunk(x)).collect_vec(); - f_from_radix_be(&converted_chunks, self.output_base) - }) - .collect(); - Ok((input_coefs, output_coefs, output)) - } + .map(|chunks| Some(f_from_radix_be(chunks, base))) + .collect_vec(); + input_coefs.try_into().unwrap() + }) } -#[allow(dead_code)] #[derive(Debug, Clone)] -pub struct FromBinaryTableConfig { - base2: TableColumn, - base9: TableColumn, - base13: TableColumn, - _marker: PhantomData, +pub struct FromBase9TableConfig { + lookup_config: ThreeColumnsLookup, + // mapping from base9 input to base13 and base2 output + map: HashMap<[u8; 32], (F, F)>, } -#[allow(dead_code)] -impl FromBinaryTableConfig { - pub(crate) fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { +impl FromBase9TableConfig { + pub fn load(&mut self, layouter: &mut impl Layouter) -> Result<(), Error> { layouter.assign_table( - || "2 -> (9 and 13)", + || "9 -> (2 and 13)", |mut table| { - // Iterate over all possible binary values of size 16 - for (i, b2_chunks) in (0..NUM_OF_BINARY_CHUNKS) - .map(|_| 0..B2) + // Iterate over all possible base 9 values of size 5 + for (i, b9_chunks) in (0..NUM_OF_B9_CHUNKS_PER_SLICE) + .map(|_| 0..B9) .multi_cartesian_product() .enumerate() { + let input_b9 = f_from_radix_be::(&b9_chunks, B9); + let converted_chunks: Vec = + b9_chunks.iter().map(|&x| convert_b9_coef(x)).collect_vec(); + let output_b13 = f_from_radix_be::(&converted_chunks, B13); + let output_b2 = f_from_radix_be::(&converted_chunks, B2); + self.map.insert(input_b9.to_repr(), (output_b13, output_b2)); table.assign_cell( - || "base 2", - self.base2, + || "base 9", + self.lookup_config.cols[0].1, i, - || Ok(f_from_radix_be::(&b2_chunks, B2)), + || Ok(input_b9), )?; table.assign_cell( - || "base 9", - self.base9, + || "base 13", + self.lookup_config.cols[1].1, i, - || Ok(f_from_radix_be::(&b2_chunks, B9)), + || Ok(output_b13), )?; table.assign_cell( - || "base 13", - self.base13, + || "base 2", + self.lookup_config.cols[2].1, i, - || Ok(f_from_radix_be::(&b2_chunks, B13)), + || Ok(output_b2), )?; } Ok(()) @@ -597,66 +571,112 @@ impl FromBinaryTableConfig { ) } - pub(crate) fn configure(meta: &mut ConstraintSystem) -> Self { - Self { - base2: meta.lookup_table_column(), - base9: meta.lookup_table_column(), - base13: meta.lookup_table_column(), - _marker: PhantomData, - } + pub fn configure( + meta: &mut ConstraintSystem, + adv_cols: [Column; 3], + table_cols: [TableColumn; 3], + ) -> Self { + let lookup_config = ThreeColumnsLookup::configure(meta, adv_cols, table_cols, "from base9"); + let map = HashMap::new(); + Self { lookup_config, map } } + pub fn assign_region( + &self, + layouter: &mut impl Layouter, + input: &AssignedCell, + ) -> Result< + ( + Vec>, + Vec>, + Vec>, + ), + Error, + > { + let input_coefs = compute_input_coefs::( + input.value(), + B9, + NUM_OF_B9_CHUNKS_PER_SLICE, + ); + layouter.assign_region( + || "base 9", + |mut region| { + let mut input_cells = vec![]; + let mut output_b13_cells = vec![]; + let mut output_b2_cells = vec![]; + for (offset, input_coef) in input_coefs.iter().enumerate() { + self.lookup_config.q_enable.enable(&mut region, offset)?; + let input = region.assign_advice( + || "base 9", + self.lookup_config.cols[0].0, + offset, + || input_coef.ok_or(Error::Synthesis), + )?; + input_cells.push(input.clone()); + let output = input_coef.and_then(|v| self.map.get(&v.to_repr())); - pub(crate) fn get_base_info(&self, output_b9: bool) -> BaseInfo { - BaseInfo { - input_base: B2, - output_base: if output_b9 { B9 } else { B13 }, - num_chunks: NUM_OF_BINARY_CHUNKS, - max_chunks: MAX_CHUNKS, - input_tc: self.base2, - output_tc: if output_b9 { self.base9 } else { self.base13 }, - _marker: PhantomData, - } + let output_b13 = region.assign_advice( + || "base 13", + self.lookup_config.cols[1].0, + offset, + || output.map(|v| v.0).ok_or(Error::Synthesis), + )?; + output_b13_cells.push(output_b13); + let output_b2 = region.assign_advice( + || "base 2", + self.lookup_config.cols[2].0, + offset, + || output.map(|v| v.1).ok_or(Error::Synthesis), + )?; + output_b2_cells.push(output_b2); + } + Ok((input_cells, output_b13_cells, output_b2_cells)) + }, + ) } } #[derive(Debug, Clone)] -pub struct FromBase9TableConfig { - base9: TableColumn, - base13: TableColumn, - base2: TableColumn, - _marker: PhantomData, +pub struct FromBinaryTableConfig { + lookup_config: ThreeColumnsLookup, + /// mapping from base2 input to base9 and base13 output + map: HashMap<[u8; 32], (F, F)>, } -impl FromBase9TableConfig { - pub fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { +impl FromBinaryTableConfig { + pub fn load(&mut self, layouter: &mut impl Layouter) -> Result<(), Error> { layouter.assign_table( - || "9 -> (2 and 13)", + || "2 -> (9 and 13)", |mut table| { - // Iterate over all possible base 9 values of size 5 - for (i, b9_chunks) in (0..NUM_OF_B9_CHUNKS) - .map(|_| 0..B9) + for (i, b2_chunks) in (0..NUM_OF_BINARY_CHUNKS_PER_SLICE) + .map(|_| 0..B2) .multi_cartesian_product() .enumerate() { + let input_b2 = f_from_radix_be::(&b2_chunks, B2); + let output_b9 = f_from_radix_be::(&b2_chunks, B9); + let output_b13 = f_from_radix_be::(&b2_chunks, B13); + + self.map.insert(input_b2.to_repr(), (output_b9, output_b13)); + // Iterate over all possible binary values of size 16 + table.assign_cell( - || "base 9", - self.base9, + || "base 2", + self.lookup_config.cols[0].1, i, - || Ok(f_from_radix_be::(&b9_chunks, B9)), + || Ok(input_b2), )?; - let converted_chunks: Vec = - b9_chunks.iter().map(|&x| convert_b9_coef(x)).collect_vec(); + table.assign_cell( - || "base 13", - self.base13, + || "base 9", + self.lookup_config.cols[1].1, i, - || Ok(f_from_radix_be::(&converted_chunks, B13)), + || Ok(output_b9), )?; table.assign_cell( - || "base 2", - self.base2, + || "base 13", + self.lookup_config.cols[2].1, i, - || Ok(f_from_radix_be::(&converted_chunks, B2)), + || Ok(output_b13), )?; } Ok(()) @@ -664,24 +684,67 @@ impl FromBase9TableConfig { ) } - pub fn configure(meta: &mut ConstraintSystem) -> Self { - Self { - base2: meta.lookup_table_column(), - base9: meta.lookup_table_column(), - base13: meta.lookup_table_column(), - _marker: PhantomData, - } + pub fn configure( + meta: &mut ConstraintSystem, + adv_cols: [Column; 3], + table_cols: [TableColumn; 3], + ) -> Self { + let lookup_config = ThreeColumnsLookup::configure(meta, adv_cols, table_cols, "from base9"); + let map = HashMap::new(); + Self { lookup_config, map } } + pub fn assign_region( + &self, + layouter: &mut impl Layouter, + input: &AssignedCell, + ) -> Result< + ( + Vec>, + Vec>, + Vec>, + ), + Error, + > { + let input_coefs = compute_input_coefs::( + input.value(), + B2, + NUM_OF_BINARY_CHUNKS_PER_SLICE, + ); + layouter.assign_region( + || "base 2", + |mut region| { + let mut input_cells = vec![]; + let mut output_b9_cells = vec![]; + let mut output_b13_cells = vec![]; + for (offset, input_coef) in input_coefs.iter().enumerate() { + self.lookup_config.q_enable.enable(&mut region, offset)?; + let input = region.assign_advice( + || "base 2", + self.lookup_config.cols[0].0, + offset, + || input_coef.ok_or(Error::Synthesis), + )?; + input_cells.push(input.clone()); - pub(crate) fn get_base_info(&self, output_b2: bool) -> BaseInfo { - BaseInfo { - input_base: B9, - output_base: if output_b2 { B2 } else { B13 }, - num_chunks: NUM_OF_B9_CHUNKS, - max_chunks: MAX_CHUNKS, - input_tc: self.base9, - output_tc: if output_b2 { self.base2 } else { self.base13 }, - _marker: PhantomData, - } + let output = input_coef.and_then(|v| self.map.get(&v.to_repr())); + + let output_b9 = region.assign_advice( + || "base 9", + self.lookup_config.cols[1].0, + offset, + || output.map(|v| v.0).ok_or(Error::Synthesis), + )?; + output_b9_cells.push(output_b9); + let output_b13 = region.assign_advice( + || "base 13", + self.lookup_config.cols[2].0, + offset, + || output.map(|v| v.1).ok_or(Error::Synthesis), + )?; + output_b13_cells.push(output_b13); + } + Ok((input_cells, output_b9_cells, output_b13_cells)) + }, + ) } } diff --git a/keccak256/src/permutation/theta.rs b/keccak256/src/permutation/theta.rs deleted file mode 100644 index d72faf52e1..0000000000 --- a/keccak256/src/permutation/theta.rs +++ /dev/null @@ -1,222 +0,0 @@ -use crate::arith_helpers::*; -use eth_types::Field; -use halo2_proofs::{ - circuit::{AssignedCell, Layouter}, - plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector}, - poly::Rotation, -}; -use itertools::Itertools; -use std::marker::PhantomData; - -#[derive(Clone, Debug)] -pub struct ThetaConfig { - q_enable: Selector, - pub(crate) state: [Column; 25], - _marker: PhantomData, -} - -impl ThetaConfig { - pub fn configure( - q_enable: Selector, - meta: &mut ConstraintSystem, - state: [Column; 25], - ) -> ThetaConfig { - meta.create_gate("theta", |meta| { - let q_enable = meta.query_selector(q_enable); - let column_sum: Vec> = (0..5) - .map(|x| { - let state_x0 = meta.query_advice(state[5 * x], Rotation::cur()); - let state_x1 = meta.query_advice(state[5 * x + 1], Rotation::cur()); - let state_x2 = meta.query_advice(state[5 * x + 2], Rotation::cur()); - let state_x3 = meta.query_advice(state[5 * x + 3], Rotation::cur()); - let state_x4 = meta.query_advice(state[5 * x + 4], Rotation::cur()); - state_x0 + state_x1 + state_x2 + state_x3 + state_x4 - }) - .collect::>(); - - (0..5) - .cartesian_product(0..5) - .map(|(x, y)| { - let new_state = meta.query_advice(state[5 * x + y], Rotation::next()); - let old_state = meta.query_advice(state[5 * x + y], Rotation::cur()); - let right = old_state - + column_sum[(x + 4) % 5].clone() - + Expression::Constant(F::from(B13 as u64)) - * column_sum[(x + 1) % 5].clone(); - q_enable.clone() * (new_state - right) - }) - .collect::>() - }); - - ThetaConfig { - q_enable, - state, - _marker: PhantomData, - } - } - - pub fn assign_state( - &self, - layouter: &mut impl Layouter, - state: &[AssignedCell; 25], - out_state: [F; 25], - ) -> Result<[AssignedCell; 25], Error> { - layouter.assign_region( - || "Theta gate", - |mut region| { - let offset = 0; - self.q_enable.enable(&mut region, offset)?; - - for (idx, state) in state.iter().enumerate() { - state.copy_advice( - || format!("assign state {}", idx), - &mut region, - self.state[idx], - offset, - )?; - } - - let mut out_vec: Vec> = vec![]; - let out_state: [AssignedCell; 25] = { - for (idx, lane) in out_state.iter().enumerate() { - let out_cell = region.assign_advice( - || format!("assign out_state {}", idx), - self.state[idx], - offset + 1, - || Ok(*lane), - )?; - out_vec.push(out_cell); - } - out_vec.try_into().unwrap() - }; - Ok(out_state) - }, - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::common::*; - use crate::gate_helpers::biguint_to_f; - use crate::keccak_arith::*; - use eth_types::Field; - use halo2_proofs::pairing::bn256::Fr as Fp; - use halo2_proofs::{ - circuit::{Layouter, SimpleFloorPlanner}, - dev::MockProver, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error}, - }; - use itertools::Itertools; - use std::marker::PhantomData; - - #[test] - fn test_theta_gates() { - #[derive(Default)] - struct MyCircuit { - in_state: [F; 25], - out_state: [F; 25], - _marker: PhantomData, - } - impl Circuit for MyCircuit { - type Config = ThetaConfig; - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let q_enable = meta.complex_selector(); - - let state: [Column; 25] = (0..25) - .map(|_| { - let column = meta.advice_column(); - meta.enable_equality(column); - column - }) - .collect::>() - .try_into() - .unwrap(); - - ThetaConfig::configure(q_enable, meta, state) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - let offset = 0; - let in_state = layouter.assign_region( - || "Wittnes & assignation", - |mut region| { - // Witness `state` - let in_state: [AssignedCell; 25] = { - let mut state: Vec> = Vec::with_capacity(25); - for (idx, val) in self.in_state.iter().enumerate() { - let cell = region.assign_advice( - || "witness input state", - config.state[idx], - offset, - || Ok(*val), - )?; - state.push(cell) - } - state.try_into().unwrap() - }; - Ok(in_state) - }, - )?; - - config.assign_state(&mut layouter, &in_state, self.out_state)?; - - Ok(()) - } - } - - let input1: State = [ - [1, 0, 0, 0, 0], - [0, 0, 0, 9223372036854775808, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - ]; - let mut in_biguint = StateBigInt::default(); - let mut in_state: [Fp; 25] = [Fp::zero(); 25]; - - for (x, y) in (0..5).cartesian_product(0..5) { - in_biguint[(x, y)] = convert_b2_to_b13(input1[x][y]); - in_state[5 * x + y] = biguint_to_f(&in_biguint[(x, y)]); - } - let s1_arith = KeccakFArith::theta(&in_biguint); - let mut out_state: [Fp; 25] = [Fp::zero(); 25]; - for (x, y) in (0..5).cartesian_product(0..5) { - out_state[5 * x + y] = biguint_to_f(&s1_arith[(x, y)]); - } - - let circuit = MyCircuit:: { - in_state, - out_state, - _marker: PhantomData, - }; - - // Test without public inputs - let prover = MockProver::::run(9, &circuit, vec![]).unwrap(); - - assert_eq!(prover.verify(), Ok(())); - - let mut out_state2 = out_state; - out_state2[0] = Fp::from(5566u64); - - let circuit2 = MyCircuit:: { - in_state, - out_state: out_state2, - _marker: PhantomData, - }; - - let prover = MockProver::::run(9, &circuit2, vec![]).unwrap(); - assert!(prover.verify().is_err()); - } -} diff --git a/keccak256/src/permutation/xi.rs b/keccak256/src/permutation/xi.rs deleted file mode 100644 index 1324f6c8a5..0000000000 --- a/keccak256/src/permutation/xi.rs +++ /dev/null @@ -1,210 +0,0 @@ -use eth_types::Field; -use halo2_proofs::{ - circuit::{AssignedCell, Layouter}, - plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector}, - poly::Rotation, -}; -use itertools::Itertools; -use std::marker::PhantomData; - -#[derive(Clone, Debug)] -pub struct XiConfig { - #[allow(dead_code)] - q_enable: Selector, - state: [Column; 25], - _marker: PhantomData, -} - -impl XiConfig { - // We assume state is recieved in base-9. - pub fn configure( - q_enable: Selector, - meta: &mut ConstraintSystem, - state: [Column; 25], - ) -> XiConfig { - meta.create_gate("xi", |meta| { - // state in base 9, coefficient in 0~1 - // def xi(state: List[List[int]]): - // new_state = [[0 for x in range(5)] for y in range(5)] - // # Xi step - // for x in range(5): - // for y in range(5): - // # a, b, c, d are base9, coefficient in 0~1 - // a = state[x][y] - // b = state[(x + 1) % 5][y] - // c = state[(x + 2) % 5][y] - // # coefficient in 0~6 - // new_state[x][y] = 2*a + b + 3*c - // return new_state - (0..5) - .cartesian_product(0..5usize) - .map(|(x, y)| { - let a = meta.query_advice(state[5 * x + y], Rotation::cur()); - let b = meta.query_advice(state[5 * ((x + 1) % 5) + y], Rotation::cur()); - let c = meta.query_advice(state[5 * ((x + 2) % 5) + y], Rotation::cur()); - let next_lane = meta.query_advice(state[5 * x + y], Rotation::next()); - meta.query_selector(q_enable) - * ((Expression::Constant(F::from(2)) * a - + b - + Expression::Constant(F::from(3)) * c) - - next_lane) - }) - .collect::>() - }); - - XiConfig { - q_enable, - state, - _marker: PhantomData, - } - } - - pub fn assign_state( - &self, - layouter: &mut impl Layouter, - state: &[AssignedCell; 25], - out_state: [F; 25], - ) -> Result<[AssignedCell; 25], Error> { - layouter.assign_region( - || "Xi assignation", - |mut region| { - let offset = 0; - self.q_enable.enable(&mut region, offset)?; - for (idx, state_item) in state.iter().enumerate() { - state_item.copy_advice( - || format!("assign state {}", idx), - &mut region, - self.state[idx], - offset, - )?; - } - - let mut out_vec: Vec> = vec![]; - let out_state: [AssignedCell; 25] = { - for (idx, lane) in out_state.iter().enumerate() { - let out_cell = region.assign_advice( - || format!("assign out_state {}", idx), - self.state[idx], - offset + 1, - || Ok(*lane), - )?; - out_vec.push(out_cell); - } - out_vec.try_into().unwrap() - }; - Ok(out_state) - }, - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::arith_helpers::*; - use crate::common::*; - use crate::gate_helpers::biguint_to_f; - use crate::keccak_arith::*; - use halo2_proofs::circuit::Layouter; - use halo2_proofs::pairing::bn256::Fr as Fp; - use halo2_proofs::plonk::{Advice, Column, ConstraintSystem, Error}; - use halo2_proofs::{circuit::SimpleFloorPlanner, dev::MockProver, plonk::Circuit}; - use itertools::Itertools; - use std::marker::PhantomData; - - #[test] - fn test_xi_gate() { - #[derive(Default)] - struct MyCircuit { - in_state: [F; 25], - out_state: [F; 25], - _marker: PhantomData, - } - - impl Circuit for MyCircuit { - type Config = XiConfig; - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let q_enable = meta.complex_selector(); - - let state: [Column; 25] = (0..25) - .map(|_| { - let column = meta.advice_column(); - meta.enable_equality(column); - column - }) - .collect::>() - .try_into() - .unwrap(); - - XiConfig::configure(q_enable, meta, state) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - let offset = 0; - let in_state = layouter.assign_region( - || "Wittnes & assignation", - |mut region| { - // Witness `state` - let in_state: [AssignedCell; 25] = { - let mut state: Vec> = Vec::with_capacity(25); - for (idx, val) in self.in_state.iter().enumerate() { - let cell = region.assign_advice( - || "witness input state", - config.state[idx], - offset, - || Ok(*val), - )?; - state.push(cell) - } - state.try_into().unwrap() - }; - Ok(in_state) - }, - )?; - - config.assign_state(&mut layouter, &in_state, self.out_state)?; - Ok(()) - } - } - - let input1: State = [ - [1, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - ]; - let mut in_biguint = StateBigInt::default(); - let mut in_state: [Fp; 25] = [Fp::zero(); 25]; - - for (x, y) in (0..5).cartesian_product(0..5) { - in_biguint[(x, y)] = convert_b2_to_b9(input1[x][y]); - in_state[5 * x + y] = biguint_to_f(&in_biguint[(x, y)]); - } - let s1_arith = KeccakFArith::xi(&in_biguint); - let mut out_state: [Fp; 25] = [Fp::zero(); 25]; - for (x, y) in (0..5).cartesian_product(0..5) { - out_state[5 * x + y] = biguint_to_f(&s1_arith[(x, y)]); - } - let circuit = MyCircuit:: { - in_state, - out_state, - _marker: PhantomData, - }; - - // Test without public inputs - let prover = MockProver::::run(9, &circuit, vec![]).unwrap(); - - assert_eq!(prover.verify(), Ok(())); - } -}