diff --git a/keccak256/src/permutation.rs b/keccak256/src/permutation.rs index 312658b48f..c1b95177b2 100644 --- a/keccak256/src/permutation.rs +++ b/keccak256/src/permutation.rs @@ -1,8 +1,10 @@ #![allow(clippy::type_complexity)] #![allow(clippy::too_many_arguments)] pub(crate) mod absorb; +pub(crate) mod add; pub(crate) mod base_conversion; pub mod circuit; +pub(crate) mod flag; pub(crate) mod iota; pub(crate) mod mixing; pub(crate) mod pi; diff --git a/keccak256/src/permutation/absorb.rs b/keccak256/src/permutation/absorb.rs index 2da8fcae6b..c85b849bb9 100644 --- a/keccak256/src/permutation/absorb.rs +++ b/keccak256/src/permutation/absorb.rs @@ -1,352 +1,55 @@ use crate::arith_helpers::*; use crate::common::*; +use crate::gate_helpers::*; +use crate::permutation::add::AddConfig; 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::{convert::TryInto, 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::common::State; - use crate::keccak_arith::KeccakFArith; - use halo2_proofs::circuit::Layouter; - use halo2_proofs::pairing; - use halo2_proofs::plonk::{Advice, Column, ConstraintSystem, Error}; - use halo2_proofs::{circuit::SimpleFloorPlanner, dev::MockProver, plonk::Circuit}; - use itertools::Itertools; - use pairing::bn256::Fr as Fp; - use pairing::group::ff::PrimeField; - use pretty_assertions::assert_eq; - use std::convert::TryInto; - use std::marker::PhantomData; - - #[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), - ) +use halo2_proofs::circuit::{AssignedCell, Layouter}; +use halo2_proofs::plonk::{Advice, Column, Error}; +use std::convert::TryInto; + +// TODO: should do proper base conv here +pub(crate) fn apply_absorb( + add: &AddConfig, + layouter: &mut impl Layouter, + next_input_col: Column, + state: &[AssignedCell; 25], + next_input: &[Option; NEXT_INPUTS_LANES], +) -> Result<[AssignedCell; 25], Error> { + let next_input_b9 = layouter.assign_region( + || "next input words", + |mut region| { + let mut next_input_b9: Vec> = vec![]; + for (offset, input) in next_input.iter().enumerate() { + let cell = region.assign_advice( + || "next input words", + next_input_col, + offset, + || { + Ok(input + .map(|input| { + let input = f_to_biguint(input); + let input = convert_b2_to_b9( + *input.to_u64_digits().first().unwrap_or(&0u64), + ); + biguint_to_f(&input) + }) + .unwrap_or(F::zero())) }, )?; - - // 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(()) + next_input_b9.push(cell); } - } - - 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(())); - } + Ok(next_input_b9) + }, + )?; + + let mut out_state: Vec> = vec![]; + for (i, input) in next_input_b9.iter().enumerate() { + let out_lane = + add.add_advice_mul_const(layouter, state[i].clone(), input.clone(), F::from(A4))?; + out_state.push(out_lane); + } + for i in NEXT_INPUTS_LANES..25 { + out_state.push(state[i].clone()); } + Ok(out_state.try_into().unwrap()) } diff --git a/keccak256/src/permutation/add.rs b/keccak256/src/permutation/add.rs new file mode 100644 index 0000000000..8fa2fb3daf --- /dev/null +++ b/keccak256/src/permutation/add.rs @@ -0,0 +1,285 @@ +use eth_types::Field; +use halo2_proofs::circuit::AssignedCell; +use halo2_proofs::circuit::Layouter; +use halo2_proofs::{ + plonk::{Advice, Column, ConstraintSystem, Error, Fixed, Selector}, + poly::Rotation, +}; +use itertools::Itertools; +use std::marker::PhantomData; + +#[derive(Clone, Debug)] +pub struct AddConfig { + q_enable: Selector, + io: Column, + left: Column, + right: Column, + _marker: PhantomData, +} + +impl AddConfig { + pub fn configure( + meta: &mut ConstraintSystem, + advices: [Column; 3], + fixed: Column, + ) -> Self { + let q_enable = meta.selector(); + let [io, left, right] = advices; + meta.enable_equality(io); + meta.enable_equality(left); + meta.enable_equality(right); + meta.enable_constant(fixed); + + meta.create_gate("add", |meta| { + let q_enable = meta.query_selector(q_enable); + let input = meta.query_advice(io, Rotation::cur()); + let output = meta.query_advice(io, Rotation::next()); + let left = meta.query_advice(left, Rotation::cur()); + let right = meta.query_advice(right, Rotation::cur()); + vec![q_enable * (output - input - left * right)] + }); + + Self { + q_enable, + io, + left, + right, + _marker: PhantomData, + } + } + + fn add_generic( + &self, + layouter: &mut impl Layouter, + input: AssignedCell, + left: Option>, + right: Option>, + value: Option, + ) -> 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) => { + // copy x to use as a flag + (*x).copy_advice(|| "left adv", &mut region, self.left, offset)? + } + None => { + // 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) => { + 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 => { + // 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( + || "input + x", + 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 += v * x + pub fn add_advice_mul_const( + &self, + layouter: &mut impl Layouter, + input: AssignedCell, + x: AssignedCell, + v: F, + ) -> Result, Error> { + self.add_generic(layouter, input, Some(x), None, Some(v)) + } + /// input -= x + pub fn sub_advice( + &self, + layouter: &mut impl Layouter, + input: AssignedCell, + x: AssignedCell, + ) -> Result, Error> { + self.add_generic(layouter, input, Some(x), None, Some(-F::one())) + } + /// input += v + pub fn add_fixed( + &self, + layouter: &mut impl Layouter, + input: AssignedCell, + value: F, + ) -> Result, Error> { + self.add_generic(layouter, input, None, None, Some(value)) + } + /// input += flag * v + /// No boolean check on the flag, we assume the flag is checked before + /// copied to here + pub fn conditional_add_const( + &self, + layouter: &mut impl Layouter, + input: AssignedCell, + flag: AssignedCell, + value: F, + ) -> Result, Error> { + self.add_generic(layouter, input, Some(flag), None, Some(value)) + } + /// input += flag * x + /// No boolean check on the flag, we assume the flag is checked before + /// copied to here + pub fn conditional_add_advice( + &self, + layouter: &mut impl Layouter, + input: AssignedCell, + flag: AssignedCell, + x: AssignedCell, + ) -> Result, Error> { + self.add_generic(layouter, input, Some(flag), Some(x), None) + } + fn linear_combine_generic( + &self, + layouter: &mut impl Layouter, + xs: Vec>, + ys: Option>>, + vs: Option>, + outcome: Option>, + ) -> Result, Error> { + debug_assert_eq!( + ys.is_some(), + vs.is_none(), + "They can't both some or both none" + ); + if let Some(ref vs) = vs { + debug_assert_eq!(xs.len(), vs.len()); + } + if let Some(ref ys) = ys { + debug_assert_eq!(xs.len(), ys.len()); + } + layouter.assign_region( + || "linear combine", + |mut region| { + // | offset | input | x | y | + // | ------ | -----------: | -------: | ------: | + // | 0 | 0 | x0 | y0 | + // | 1 | x0y0 | x1 | y1 | + // | 2 | x0y0 + x1y1 | x2 | y2 | + // | ... | ... | ... | ... | + // | 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 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( + || "v", + self.right, + offset, + vs[offset], + )?, + None => match &ys { + Some(ys) => ys[offset].copy_advice( + || "y", + &mut region, + self.right, + offset, + )?, + None => { + unreachable!() + } + }, + } + }; + acc = region.assign_advice( + || "accumulation", + self.io, + offset + 1, + || { + sum += x.value().cloned().ok_or(Error::Synthesis)? + * right.value().cloned().ok_or(Error::Synthesis)?; + Ok(sum) + }, + )?; + } + if let Some(outcome) = &outcome { + region.constrain_equal(outcome.cell(), acc.cell())?; + } + Ok(acc) + }, + ) + } + + pub fn linear_combine_consts( + &self, + layouter: &mut impl Layouter, + xs: Vec>, + vs: Vec, + outcome: Option>, + ) -> Result, Error> { + self.linear_combine_generic(layouter, xs, None, Some(vs), outcome) + } + + pub fn linear_combine_advices( + &self, + layouter: &mut impl Layouter, + xs: Vec>, + ys: Vec>, + outcome: Option>, + ) -> Result, Error> { + self.linear_combine_generic(layouter, xs, Some(ys), None, outcome) + } + + pub fn running_sum( + &self, + layouter: &mut impl Layouter, + xs: Vec>, + outcome: Option>, + ) -> Result, Error> { + let len = xs.len(); + self.linear_combine_consts( + layouter, + xs, + (0..len).map(|_| F::one()).collect_vec(), + outcome, + ) + } +} diff --git a/keccak256/src/permutation/base_conversion.rs b/keccak256/src/permutation/base_conversion.rs index 7e177ce23c..9c0319e832 100644 --- a/keccak256/src/permutation/base_conversion.rs +++ b/keccak256/src/permutation/base_conversion.rs @@ -4,22 +4,19 @@ use halo2_proofs::{ poly::Rotation, }; +use crate::permutation::add::AddConfig; + use super::tables::BaseInfo; use eth_types::Field; use std::convert::TryInto; #[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, + add: AddConfig, } impl BaseConversionConfig { @@ -27,63 +24,31 @@ impl BaseConversionConfig { pub(crate) fn configure( meta: &mut ConstraintSystem, base_info: BaseInfo, - input_lane: Column, - parent_flag: Column, - advices: [Column; 5], + advices: [Column; 2], + add: &AddConfig, ) -> Self { - let q_running_sum = meta.selector(); let q_lookup = meta.complex_selector(); - let [flag, input_coef, input_acc, output_coef, output_acc] = advices; + let [input_coef, output_coef] = 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), + (q_enable.clone() * input_slices, base_info.input_tc), + (q_enable * output_slices, base_info.output_tc), ] }); Self { - q_running_sum, q_lookup, base_info, - flag, input_coef, - input_acc, output_coef, - output_acc, + add: add.clone(), } } @@ -91,27 +56,22 @@ impl BaseConversionConfig { &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())?; + let input_pobs = self.base_info.input_pobs(); + let output_pobs = self.base_info.output_pobs(); - layouter.assign_region( + let (input_coef_cells, output_coef_cells) = 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(); + let mut input_coef_cells = vec![]; + let mut output_coef_cells = vec![]; 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", @@ -119,51 +79,36 @@ impl BaseConversionConfig { 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), - )?; + input_coef_cells.push(input_coef_cell); 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); - } + output_coef_cells.push(output_coef_cell); } - unreachable!(); + Ok((input_coef_cells, output_coef_cells)) }, - ) + )?; + self.add + .linear_combine_consts(layouter, input_coef_cells, input_pobs, Some(input))?; + let output_lane = + self.add + .linear_combine_consts(layouter, output_coef_cells, output_pobs, None)?; + + Ok(output_lane) } 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())?; + let output = self.assign_lane(layouter, lane.clone())?; Ok(output) }) .into_iter() @@ -179,7 +124,10 @@ 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 crate::permutation::{ + add::AddConfig, + tables::{FromBase9TableConfig, FromBinaryTableConfig}, + }; use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner}, dev::MockProver, @@ -196,7 +144,6 @@ mod tests { #[derive(Debug, Clone)] struct MyConfig { lane: Column, - flag: Column, table: FromBinaryTableConfig, conversion: BaseConversionConfig, } @@ -204,18 +151,28 @@ mod tests { 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()) + meta.enable_equality(lane); + let advices: [Column; 3] = (0..3) + .map(|_| { + let col = meta.advice_column(); + meta.enable_equality(col); + col + }) .collect_vec() .try_into() .unwrap(); let base_info = table.get_base_info(false); - let conversion = - BaseConversionConfig::configure(meta, base_info, lane, flag, advices); + let fixed = meta.fixed_column(); + meta.enable_constant(fixed); + let add = AddConfig::configure(meta, advices, fixed); + let conversion = BaseConversionConfig::configure( + meta, + base_info, + advices[0..2].try_into().unwrap(), + &add, + ); Self { lane, - flag, table, conversion, } @@ -230,23 +187,11 @@ mod tests { layouter: &mut impl Layouter, input: F, ) -> Result, Error> { - // The main flag is enabled - let flag_value = F::one(); - let (lane, flag) = layouter.assign_region( + let lane = 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)) - }, + |mut region| region.assign_advice(|| "Input lane", self.lane, 0, || Ok(input)), )?; - let output = self.conversion.assign_lane(layouter, lane, flag)?; + let output = self.conversion.assign_lane(layouter, lane)?; layouter.assign_region( || "Input lane", |mut region| output.copy_advice(|| "Output lane", &mut region, self.lane, 0), @@ -312,7 +257,6 @@ mod tests { #[derive(Debug, Clone)] struct MyConfig { lane: Column, - flag: Column, table: FromBase9TableConfig, conversion: BaseConversionConfig, } @@ -320,18 +264,28 @@ mod tests { 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()) + meta.enable_equality(lane); + let advices: [Column; 3] = (0..3) + .map(|_| { + let col = meta.advice_column(); + meta.enable_equality(col); + col + }) .collect_vec() .try_into() .unwrap(); let base_info = table.get_base_info(false); - let conversion = - BaseConversionConfig::configure(meta, base_info, lane, flag, advices); + let fixed = meta.fixed_column(); + meta.enable_constant(fixed); + let add = AddConfig::configure(meta, advices, fixed); + let conversion = BaseConversionConfig::configure( + meta, + base_info, + advices[0..2].try_into().unwrap(), + &add, + ); Self { lane, - flag, table, conversion, } @@ -346,24 +300,12 @@ mod tests { layouter: &mut impl Layouter, input: F, ) -> Result, Error> { - // The main flag is enabled - let flag_value = F::one(); - let (lane, flag) = layouter.assign_region( + let lane = 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)) - }, + |mut region| region.assign_advice(|| "Input lane", self.lane, 0, || Ok(input)), )?; - let output = self.conversion.assign_lane(layouter, lane, flag)?; + let output = self.conversion.assign_lane(layouter, lane)?; layouter.assign_region( || "Input lane", |mut region| output.copy_advice(|| "Output lane", &mut region, self.lane, 0), @@ -419,7 +361,6 @@ mod tests { // We need to load the table #[derive(Debug, Clone)] struct MyConfig { - flag: Column, state: [Column; 25], table: FromBinaryTableConfig, conversion: BaseConversionConfig, @@ -428,21 +369,35 @@ mod tests { pub fn configure(meta: &mut ConstraintSystem) -> Self { let table = FromBinaryTableConfig::configure(meta); let state: [Column; 25] = (0..25) - .map(|_| meta.advice_column()) + .map(|_| { + let col = meta.advice_column(); + meta.enable_equality(col); + col + }) .collect::>() .try_into() .unwrap(); - let flag = meta.advice_column(); - let lane = meta.advice_column(); - let advices = (0..5) - .map(|_| meta.advice_column()) + let advices: [Column; 3] = (0..3) + .map(|_| { + let col = meta.advice_column(); + meta.enable_equality(col); + col + }) .collect_vec() .try_into() .unwrap(); let bi = table.get_base_info(false); - let conversion = BaseConversionConfig::configure(meta, bi, lane, flag, advices); + let fixed = meta.fixed_column(); + meta.enable_equality(fixed); + meta.enable_constant(fixed); + let add = AddConfig::configure(meta, advices, fixed); + let conversion = BaseConversionConfig::configure( + meta, + bi, + advices[0..2].try_into().unwrap(), + &add, + ); Self { - flag, state, table, conversion, @@ -458,8 +413,7 @@ mod tests { layouter: &mut impl Layouter, input: [F; 25], ) -> Result<[F; 25], Error> { - let flag_value = F::one(); - let (state, flag) = layouter.assign_region( + let state = layouter.assign_region( || "Input state", |mut region| { let state: [AssignedCell; 25] = input @@ -478,12 +432,10 @@ mod tests { .collect::>() .try_into() .unwrap(); - let flag = - region.assign_advice(|| "Flag", self.flag, 0, || Ok(flag_value))?; - Ok((state, flag)) + Ok(state) }, )?; - let output_state = self.conversion.assign_state(layouter, &state, flag)?; + let output_state = self.conversion.assign_state(layouter, &state)?; let output_state: [F; 25] = output_state .iter() .map(|cell| cell.value().copied().unwrap_or_default()) diff --git a/keccak256/src/permutation/circuit.rs b/keccak256/src/permutation/circuit.rs index 14fc29d38b..50bba2060c 100644 --- a/keccak256/src/permutation/circuit.rs +++ b/keccak256/src/permutation/circuit.rs @@ -1,40 +1,34 @@ use crate::{ - arith_helpers::*, - common::{NEXT_INPUTS_LANES, PERMUTATION, ROUND_CONSTANTS}, - keccak_arith::*, + common::{NEXT_INPUTS_LANES, PERMUTATION}, permutation::{ - base_conversion::BaseConversionConfig, iota::IotaConfig, mixing::MixingConfig, - pi::pi_gate_permutation, rho::RhoConfig, tables::FromBase9TableConfig, theta::ThetaConfig, - xi::XiConfig, + add::AddConfig, base_conversion::BaseConversionConfig, flag::FlagConfig, iota::IotaConfig, + mixing::MixingConfig, pi::pi_gate_permutation, rho::RhoConfig, + tables::FromBase9TableConfig, theta::assign_theta, xi::assign_xi, }, }; use eth_types::Field; use halo2_proofs::{ - circuit::{AssignedCell, Layouter, Region}, - plonk::{Advice, Column, ConstraintSystem, Error, Selector}, - poly::Rotation, + circuit::{AssignedCell, Layouter}, + plonk::{Advice, Column, ConstraintSystem, Error}, }; use itertools::Itertools; use std::convert::TryInto; #[derive(Clone, Debug)] pub struct KeccakFConfig { - theta_config: ThetaConfig, + add: AddConfig, rho_config: RhoConfig, - xi_config: XiConfig, iota_config: IotaConfig, from_b9_table: FromBase9TableConfig, - base_conversion_config: BaseConversionConfig, + from9_config: BaseConversionConfig, mixing_config: MixingConfig, - pub state: [Column; 25], - q_out: Selector, - base_conv_activator: Column, + pub advices: [Column; 3], } impl KeccakFConfig { // We assume state is received in base-9. pub fn configure(meta: &mut ConstraintSystem) -> Self { - let state = (0..25) + let advices: [Column; 3] = (0..3) .map(|_| { let column = meta.advice_column(); meta.enable_equality(column); @@ -44,68 +38,43 @@ impl KeccakFConfig { .try_into() .unwrap(); - let flag = meta.advice_column(); - let fixed = [ - meta.fixed_column(), - meta.fixed_column(), - meta.fixed_column(), - ]; + let fixed = meta.fixed_column(); + + let add = AddConfig::configure(meta, advices, fixed); + let flag = FlagConfig::configure(meta, advices[0]); - // theta - let theta_config = ThetaConfig::configure(meta.selector(), meta, state); // rho - let rho_config = RhoConfig::configure(meta, state, fixed); - // xi - let xi_config = XiConfig::configure(meta.selector(), meta, state); - let iota_config = IotaConfig::configure(meta, state[0], flag, fixed[0]); - - // Allocate space for the activation flag of the base_conversion. - let base_conv_activator = meta.advice_column(); - meta.enable_equality(base_conv_activator); + let rho_config = RhoConfig::configure(meta, advices, fixed, add.clone()); + let iota_config = IotaConfig::configure(add.clone()); + // 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( + let from9_config = BaseConversionConfig::configure( meta, base_info, - base_conv_lane, - base_conv_activator, - state[0..5].try_into().unwrap(), + advices[0..2].try_into().unwrap(), + &add, ); // 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, iota_config.clone(), state); - - // 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() - }); + let mixing_config = MixingConfig::configure( + from9_config.clone(), + iota_config.clone(), + &add, + flag, + advices[0], + ); - KeccakFConfig { - theta_config, + Self { + add, rho_config, - xi_config, iota_config, from_b9_table, - base_conversion_config, + from9_config, mixing_config, - state, - q_out, - base_conv_activator, + advices, } } @@ -118,9 +87,8 @@ impl KeccakFConfig { &self, layouter: &mut impl Layouter, in_state: [AssignedCell; 25], - out_state: [F; 25], - flag: bool, - next_mixing: Option<[F; NEXT_INPUTS_LANES]>, + flag: Option, + next_mixing: [Option; NEXT_INPUTS_LANES], ) -> Result<[AssignedCell; 25], Error> { let mut state = in_state; @@ -128,32 +96,17 @@ impl KeccakFConfig { 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)? - }; + state = assign_theta(&self.add, layouter, &state)?; // rho state = self.rho_config.assign_rotation_checks(layouter, &state)?; // Outputs in base-9 which is what Pi requires // Apply Pi permutation - state = pi_gate_permutation(state.clone()); + state = pi_gate_permutation(&state); // 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 = assign_xi(&self.add, layouter, &state)?; // Last round before Mixing does not run IotaB9 nor BaseConversion if round_idx == PERMUTATION - 1 { @@ -168,105 +121,23 @@ impl KeccakFConfig { // 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 = self.from9_config.assign_state(layouter, &state)?; } - // 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( - layouter, - &state, - state_bigint_to_field(mix_res), - flag, - next_mixing, - )?; - - self.constrain_out_state(layouter, &mix_res, out_state) - } - - 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, - )?; - } - - Ok(()) + let mix_res = self + .mixing_config + .assign_state(layouter, &state, flag, next_mixing)?; + Ok(mix_res) } } #[cfg(test)] mod tests { use super::*; + use crate::arith_helpers::*; use crate::common::{State, NEXT_INPUTS_LANES}; 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::{ConstraintSystem, Error}; @@ -282,7 +153,7 @@ mod tests { struct MyCircuit { in_state: [F; 25], out_state: [F; 25], - next_mixing: Option<[F; NEXT_INPUTS_LANES]>, + next_mixing: [Option; NEXT_INPUTS_LANES], // flag is_mixing: bool, } @@ -306,7 +177,6 @@ mod tests { ) -> Result<(), Error> { // Load the table config.load(&mut layouter)?; - let offset: usize = 0; let in_state = layouter.assign_region( || "Keccak round Wittnes & flag assignation", @@ -314,10 +184,10 @@ mod tests { // 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 (offset, val) in self.in_state.iter().enumerate() { let cell = region.assign_advice( || "witness input state", - config.state[idx], + config.advices[0], offset, || Ok(*val), )?; @@ -330,13 +200,21 @@ mod tests { }, )?; - config.assign_all( + let out_state = config.assign_all( &mut layouter, in_state, - self.out_state, - self.is_mixing, + Some(self.is_mixing), self.next_mixing, )?; + layouter.assign_region( + || "State check", + |mut region| { + for (lane, value) in out_state.iter().zip(self.out_state.iter()) { + region.constrain_constant(lane.cell(), value)?; + } + Ok(()) + }, + )?; Ok(()) } } @@ -380,8 +258,13 @@ mod tests { // Generate next_input (tho one that is not None) in the form `[F;17]` // Generate next_input as `[Fp;NEXT_INPUTS_LANES]` - let next_input_fp: [Fp; NEXT_INPUTS_LANES] = - state_bigint_to_field(StateBigInt::from(next_input)); + let next_input_fp: [Option; NEXT_INPUTS_LANES] = + state_bigint_to_field::<_, NEXT_INPUTS_LANES>(StateBigInt::from(next_input)) + .iter() + .map(|&x| Some(x)) + .collect_vec() + .try_into() + .unwrap(); // When we pass no `mixing_inputs`, we perform the full keccak round // ending with Mixing executing IotaB9 @@ -391,7 +274,7 @@ mod tests { let circuit = MyCircuit:: { in_state: in_state_fp, out_state: out_state_non_mix, - next_mixing: None, + next_mixing: [None; NEXT_INPUTS_LANES], is_mixing: false, }; @@ -404,7 +287,7 @@ mod tests { let circuit = MyCircuit:: { in_state: out_state_non_mix, out_state: out_state_non_mix, - next_mixing: None, + next_mixing: [None; NEXT_INPUTS_LANES], is_mixing: true, }; let k = 17; @@ -431,7 +314,7 @@ mod tests { let circuit = MyCircuit:: { in_state: in_state_fp, out_state: out_state_mix, - next_mixing: Some(next_input_fp), + next_mixing: next_input_fp, is_mixing: true, }; @@ -444,7 +327,7 @@ mod tests { let circuit = MyCircuit:: { in_state: out_state_non_mix, out_state: out_state_non_mix, - next_mixing: Some(next_input_fp), + next_mixing: next_input_fp, is_mixing: true, }; diff --git a/keccak256/src/permutation/flag.rs b/keccak256/src/permutation/flag.rs new file mode 100644 index 0000000000..c391aaea47 --- /dev/null +++ b/keccak256/src/permutation/flag.rs @@ -0,0 +1,80 @@ +use eth_types::Field; +use halo2_proofs::{ + circuit::{AssignedCell, Layouter}, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector}, + poly::Rotation, +}; +use std::marker::PhantomData; + +#[derive(Clone, Debug)] +pub struct FlagConfig { + flag: Column, + q_enable: Selector, + _marker: PhantomData, +} + +impl FlagConfig { + pub fn configure(meta: &mut ConstraintSystem, flag: Column) -> Self { + meta.enable_equality(flag); + + let q_enable = meta.selector(); + + meta.create_gate("Ensure flag consistency", |meta| { + let q_enable = meta.query_selector(q_enable); + + let f_positive = meta.query_advice(flag, Rotation::cur()); + let f_negative = meta.query_advice(flag, Rotation::next()); + let one = Expression::Constant(F::one()); + + [ + ( + "f_pos must be 1 or 0", + q_enable.clone() * (one.clone() - f_positive.clone()) * f_positive.clone(), + ), + ( + "f_neg must be 1 - f_pos", + q_enable * (f_positive + f_negative - one), + ), + ] + }); + + Self { + flag, + q_enable, + _marker: PhantomData, + } + } + + pub fn assign_flag( + &self, + layouter: &mut impl Layouter, + flag: Option, + ) -> Result<(AssignedCell, AssignedCell), Error> { + layouter.assign_region( + || "Flag and Negated flag assignation", + |mut region| { + self.q_enable.enable(&mut region, 0)?; + let positive = region.assign_advice( + || "flag positive", + self.flag, + 0, + || { + flag.map(|flag| F::from(flag as u64)) + .ok_or(Error::Synthesis) + }, + )?; + let negative = region.assign_advice( + || "flag negative", + self.flag, + 1, + || { + flag.map(|flag| F::from(!flag as u64)) + .ok_or(Error::Synthesis) + }, + )?; + + Ok((positive, negative)) + }, + ) + } +} diff --git a/keccak256/src/permutation/iota.rs b/keccak256/src/permutation/iota.rs index 76e7aa2122..0ce4bb792f 100644 --- a/keccak256/src/permutation/iota.rs +++ b/keccak256/src/permutation/iota.rs @@ -1,22 +1,17 @@ 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 crate::permutation::add::AddConfig; use eth_types::Field; use halo2_proofs::circuit::AssignedCell; use halo2_proofs::circuit::Layouter; -use halo2_proofs::{ - plonk::{Advice, Column, ConstraintSystem, Error, Fixed, Selector}, - poly::Rotation, -}; +use halo2_proofs::plonk::Error; use itertools::Itertools; use std::convert::TryInto; #[derive(Clone, Debug)] pub struct IotaConfig { - q_enable: Selector, - lane00: Column, - flag: Column, - round_constant: Column, + add: AddConfig, round_constant_b13: F, a4_times_round_constants_b9: [F; PERMUTATION], } @@ -39,25 +34,7 @@ impl IotaConfig { /// /// Otherwise, apply the round constant in base 13 to the state, which has /// been mixed with new input and converted form base 9 to base 13. - pub fn configure( - meta: &mut ConstraintSystem, - lane00: Column, - flag: Column, - round_constant: Column, - ) -> Self { - let q_enable = meta.selector(); - meta.enable_equality(lane00); - meta.enable_equality(flag); - - meta.create_gate("iota", |meta| { - let q_enable = meta.query_selector(q_enable); - let flag = meta.query_advice(flag, Rotation::cur()); - let lane00_next = meta.query_advice(lane00, Rotation::next()); - let lane00 = meta.query_advice(lane00, Rotation::cur()); - let round_constant = meta.query_fixed(round_constant, Rotation::cur()); - vec![q_enable * (lane00_next - lane00 - flag * round_constant)] - }); - + pub fn configure(add: AddConfig) -> Self { let round_constant_b13 = biguint_to_f::(&convert_b2_to_b13(ROUND_CONSTANTS[PERMUTATION - 1])); @@ -72,10 +49,7 @@ impl IotaConfig { .unwrap(); Self { - q_enable, - lane00, - flag, - round_constant, + add, round_constant_b13, a4_times_round_constants_b9, } @@ -91,33 +65,8 @@ impl IotaConfig { lane00: AssignedCell, round: usize, ) -> Result, Error> { - layouter.assign_region( - || "IotaB9", - |mut region| { - let offset = 0; - self.q_enable.enable(&mut region, offset)?; - lane00.copy_advice(|| "lane 00", &mut region, self.lane00, offset)?; - // In the normal round, we must add round constant. constrain flag to 1. - let flag = region.assign_advice(|| "flag", self.flag, offset, || Ok(F::one()))?; - region.constrain_constant(flag.cell(), F::one())?; - - let constant = self.a4_times_round_constants_b9[round]; - region.assign_fixed( - || "A4 * round_constant_b9", - self.round_constant, - offset, - || Ok(constant), - )?; - - let offset = 1; - region.assign_advice( - || "lane 00 + A4 * round_constant_b9", - self.lane00, - offset, - || Ok(lane00.value().cloned().unwrap_or_default() + constant), - ) - }, - ) + self.add + .add_fixed(layouter, lane00, self.a4_times_round_constants_b9[round]) } /// The 24-th round. Copy the flag `no_mixing` here. @@ -130,34 +79,11 @@ impl IotaConfig { lane00: AssignedCell, flag: AssignedCell, ) -> Result, Error> { - layouter.assign_region( - || "IotaB9", - |mut region| { - let offset = 0; - self.q_enable.enable(&mut region, offset)?; - lane00.copy_advice(|| "lane 00", &mut region, self.lane00, offset)?; - flag.copy_advice(|| "flag", &mut region, self.flag, offset)?; - - let constant = self.a4_times_round_constants_b9[PERMUTATION - 1]; - region.assign_fixed( - || "A4 * round_constant_b9", - self.round_constant, - offset, - || Ok(constant), - )?; - - let offset = 1; - region.assign_advice( - || "lane 00 + A4 * round_constant_b9", - self.lane00, - offset, - || { - let flag = flag.value().cloned().unwrap_or_default(); - let lane00 = lane00.value().cloned().unwrap_or_default(); - Ok(lane00 + flag * constant) - }, - ) - }, + self.add.conditional_add_const( + layouter, + lane00, + flag, + self.a4_times_round_constants_b9[PERMUTATION - 1], ) } @@ -171,33 +97,7 @@ impl IotaConfig { lane00: AssignedCell, flag: AssignedCell, ) -> Result, Error> { - layouter.assign_region( - || "IotaB9", - |mut region| { - let offset = 0; - self.q_enable.enable(&mut region, offset)?; - lane00.copy_advice(|| "lane 00", &mut region, self.lane00, offset)?; - flag.copy_advice(|| "flag", &mut region, self.flag, offset)?; - - region.assign_fixed( - || "round_constant_b13", - self.round_constant, - offset, - || Ok(self.round_constant_b13), - )?; - - let offset = 1; - region.assign_advice( - || "lane 00 + round_constant_b13", - self.lane00, - offset, - || { - let lane00 = lane00.value().cloned().unwrap_or_default(); - let flag = flag.value().cloned().unwrap_or_default(); - Ok(lane00 + flag * self.round_constant_b13) - }, - ) - }, - ) + self.add + .conditional_add_const(layouter, lane00, flag, self.round_constant_b13) } } diff --git a/keccak256/src/permutation/mixing.rs b/keccak256/src/permutation/mixing.rs index 84be22b959..9dd016ebf0 100644 --- a/keccak256/src/permutation/mixing.rs +++ b/keccak256/src/permutation/mixing.rs @@ -1,291 +1,98 @@ -use super::super::arith_helpers::*; -use super::tables::FromBase9TableConfig; -use super::{absorb::AbsorbConfig, base_conversion::BaseConversionConfig, iota::IotaConfig}; +use super::{ + absorb::apply_absorb, add::AddConfig, base_conversion::BaseConversionConfig, flag::FlagConfig, + iota::IotaConfig, +}; use crate::common::*; -use crate::keccak_arith::KeccakFArith; use eth_types::Field; -use halo2_proofs::circuit::{AssignedCell, Region}; -use halo2_proofs::plonk::{Expression, Selector}; -use halo2_proofs::poly::Rotation; use halo2_proofs::{ - circuit::Layouter, - plonk::{Advice, Column, ConstraintSystem, Error}, + circuit::{AssignedCell, Layouter}, + plonk::{Advice, Column, Error}, }; +use itertools::izip; use std::convert::TryInto; #[derive(Clone, Debug)] -pub struct MixingConfig { +pub(crate) struct MixingConfig { iota_config: IotaConfig, - absorb_config: AbsorbConfig, - base_conv_config: BaseConversionConfig, - state: [Column; 25], - flag: Column, - q_flag: Selector, - q_out_copy: Selector, + from9_config: BaseConversionConfig, + add: AddConfig, + flag: FlagConfig, + advice: Column, } impl MixingConfig { pub fn configure( - meta: &mut ConstraintSystem, - table: &FromBase9TableConfig, + from9_config: BaseConversionConfig, iota_config: IotaConfig, - state: [Column; 25], - ) -> MixingConfig { - // Allocate space for the flag column from which we will copy to all of - // the sub-configs. - let flag = meta.advice_column(); - meta.enable_equality(flag); - - let q_flag = meta.selector(); - - meta.create_gate("Ensure flag consistency", |meta| { - let q_flag = meta.query_selector(q_flag); - - let negated_flag = meta.query_advice(flag, Rotation::next()); - let flag = meta.query_advice(flag, Rotation::cur()); - // We do a trick which consists on multiplying an internal selector - // which is always active by the actual `negated_flag` - // which will then enable or disable the gate. - // - // Force that `flag + negated_flag = 1`. - // This ensures that flag = !negated_flag. - let flag_consistency = - (flag.clone() + negated_flag.clone()) - Expression::Constant(F::one()); - - // Define bool constraint for flags. - // Based on: `(1-flag) * flag = 0` only if `flag` is boolean. - let bool_constraint = |flag: Expression| -> Expression { - (Expression::Constant(F::one()) - flag.clone()) * flag - }; - - // Add a constraint that sums up the results of the two branches - // constraining it to be equal to `out_state`. - [ - q_flag.clone() * flag_consistency, - q_flag.clone() * bool_constraint(flag), - q_flag * bool_constraint(negated_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)] - }); - - MixingConfig { + add: &AddConfig, + flag: FlagConfig, + advice: Column, + ) -> Self { + Self { + from9_config, iota_config, - absorb_config, - base_conv_config, - state, + add: add.clone(), flag, - q_flag, - q_out_copy, + advice, } } - /// Enforce flag constraints - pub fn enforce_flag_consistency( - &self, - layouter: &mut impl Layouter, - flag_bool: bool, - ) -> Result<(AssignedCell, AssignedCell), Error> { - layouter.assign_region( - || "Flag and Negated flag assignation", - |mut region| { - self.q_flag.enable(&mut region, 0)?; - // Witness `is_mixing` flag - let flag = region.assign_advice( - || "witness is_mixing", - self.flag, - 0, - || Ok(F::from(flag_bool as u64)), - )?; - - // Witness negated `is_mixing` flag - let negated_flag = region.assign_advice( - || "witness negated is_mixing", - self.flag, - 1, - || Ok(F::from(!flag_bool as u64)), - )?; - - Ok((flag, negated_flag)) - }, - ) - } - - /// 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]>, + flag: Option, + next_mixing: [Option; NEXT_INPUTS_LANES], ) -> Result<[AssignedCell; 25], Error> { - // Enforce flag constraints and witness them. - let (flag, negated_flag) = self.enforce_flag_consistency(layouter, flag_bool)?; + let (f_pos, f_neg) = self.flag.assign_flag(layouter, flag)?; // If we don't mix: // IotaB9 - let non_mix_res = { + let state_non_mix = { let mut state = in_state.clone(); - state[0] = self.iota_config.assign_b9_last_round( - layouter, - state[0].clone(), - negated_flag.clone(), - )?; + state[0] = + self.iota_config + .assign_b9_last_round(layouter, state[0].clone(), f_neg.clone())?; 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(), - )?; + let state_mix = apply_absorb(&self.add, layouter, self.advice, in_state, &next_mixing)?; // Base conversion assign - let base_conv_cells = - self.base_conv_config - .assign_state(layouter, &out_state_absorb_cells, flag.clone())?; + let state_mix = self.from9_config.assign_state(layouter, &state_mix)?; // IotaB13 - let mix_res = { - let mut base_conv_cells = base_conv_cells; + let state_mix = { + let mut state = state_mix; - base_conv_cells[0] = + state[0] = self.iota_config - .assign_round_b13(layouter, base_conv_cells[0].clone(), flag)?; - base_conv_cells + .assign_round_b13(layouter, state[0].clone(), f_pos.clone())?; + state }; - - let mixing_res = self.assign_out_mixing_states( - layouter, - flag_bool, - negated_flag, - &mix_res, - &non_mix_res, - out_state, - ); - - if !flag_bool { - Ok(non_mix_res) - } else { - mixing_res - } - } - - /// 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, + let mut out_state: Vec> = vec![]; + for (mix, non_mix) in izip!(state_mix, state_non_mix) { + let out_lane = self.add.linear_combine_advices( + layouter, + vec![mix, non_mix], + vec![f_pos.clone(), f_neg.clone()], + None, )?; + out_state.push(out_lane) } - - Ok(()) + Ok(out_state.try_into().unwrap()) } } #[cfg(test)] mod tests { use super::*; + use crate::arith_helpers::*; use crate::common::{State, ROUND_CONSTANTS}; - use crate::permutation::iota::IotaConfig; + use crate::keccak_arith::KeccakFArith; + use crate::permutation::{add::AddConfig, iota::IotaConfig}; use halo2_proofs::circuit::Layouter; use halo2_proofs::pairing::bn256::Fr as Fp; use halo2_proofs::plonk::{ConstraintSystem, Error}; @@ -296,7 +103,7 @@ mod tests { #[test] fn test_mixing_gate() { - #[derive(Default)] + #[derive(Default, Debug)] struct MyCircuit { in_state: [F; 25], out_state: [F; 25], @@ -309,6 +116,7 @@ mod tests { struct MyConfig { mixing_conf: MixingConfig, table: FromBase9TableConfig, + advices: [Column; 3], } impl Circuit for MyCircuit { @@ -321,8 +129,8 @@ mod tests { fn configure(meta: &mut ConstraintSystem) -> Self::Config { let table = FromBase9TableConfig::configure(meta); - - let state: [Column; 25] = (0..25) + let base_info = table.get_base_info(false); + let advices: [Column; 3] = (0..3) .map(|_| { let col = meta.advice_column(); meta.enable_equality(col); @@ -332,11 +140,26 @@ mod tests { .try_into() .unwrap(); let fixed = meta.fixed_column(); - let iota_config = IotaConfig::configure(meta, state[0], state[1], fixed); + let add = AddConfig::configure(meta, advices, fixed); + let iota_config = IotaConfig::configure(add.clone()); + let flag = FlagConfig::configure(meta, advices[0]); + let from9_config = BaseConversionConfig::configure( + meta, + base_info, + advices[0..2].try_into().unwrap(), + &add, + ); MyConfig { - mixing_conf: MixingConfig::configure(meta, &table, iota_config, state), + mixing_conf: MixingConfig::configure( + from9_config, + iota_config, + &add, + flag, + advices[0], + ), table, + advices, } } @@ -347,7 +170,6 @@ mod tests { ) -> Result<(), Error> { // Load the table config.table.load(&mut layouter)?; - let offset: usize = 0; let in_state = layouter.assign_region( || "Mixing Wittnes assignment", @@ -355,10 +177,10 @@ mod tests { // 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() { + for (offset, val) in self.in_state.iter().enumerate() { let cell = region.assign_advice( || "witness input state", - config.mixing_conf.state[idx], + config.advices[0], offset, || Ok(*val), )?; @@ -371,14 +193,23 @@ mod tests { }, )?; - config.mixing_conf.assign_state( + let out_state = config.mixing_conf.assign_state( &mut layouter, &in_state, - self.out_state, - self.is_mixing, - self.next_mixing, + Some(self.is_mixing), + self.next_mixing.map_or([None; NEXT_INPUTS_LANES], |x| { + x.iter().map(|&x| Some(x)).collect_vec().try_into().unwrap() + }), + )?; + layouter.assign_region( + || "State check", + |mut region| { + for (lane, value) in out_state.iter().zip(self.out_state.iter()) { + region.constrain_constant(lane.cell(), value)?; + } + Ok(()) + }, )?; - Ok(()) } } diff --git a/keccak256/src/permutation/pi.rs b/keccak256/src/permutation/pi.rs index 9bfc1d71c4..281d5c2207 100644 --- a/keccak256/src/permutation/pi.rs +++ b/keccak256/src/permutation/pi.rs @@ -8,7 +8,7 @@ use std::convert::TryInto; /// 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] { +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()) diff --git a/keccak256/src/permutation/rho.rs b/keccak256/src/permutation/rho.rs index a347099e4c..cf3f554bdd 100644 --- a/keccak256/src/permutation/rho.rs +++ b/keccak256/src/permutation/rho.rs @@ -1,4 +1,5 @@ use crate::permutation::{ + add::AddConfig, rho_checks::{LaneRotateConversionConfig, OverflowCheckConfig}, rho_helpers::{STEP2_RANGE, STEP3_RANGE}, tables::{Base13toBase9TableConfig, RangeCheckConfig, SpecialChunkTableConfig}, @@ -24,10 +25,10 @@ pub struct RhoConfig { impl RhoConfig { pub fn configure( meta: &mut ConstraintSystem, - state: [Column; 25], - fixed: [Column; 3], + advices: [Column; 3], + fixed: Column, + add: AddConfig, ) -> Self { - state.iter().for_each(|col| meta.enable_equality(*col)); let base13_to_9_table = Base13toBase9TableConfig::configure(meta); let special_chunk_table = SpecialChunkTableConfig::configure(meta); let step2_range_table = RangeCheckConfig::::configure(meta); @@ -37,15 +38,17 @@ impl RhoConfig { meta, &base13_to_9_table, &special_chunk_table, - state[0..5].try_into().unwrap(), + advices, fixed, + add.clone(), ); let overflow_check_config = OverflowCheckConfig::configure( meta, &step2_range_table, &step3_range_table, - state[5..7].try_into().unwrap(), + add.clone(), + advices[0], ); Self { lane_config, @@ -117,7 +120,6 @@ mod tests { use halo2_proofs::pairing::bn256::Fr as Fp; use halo2_proofs::plonk::Selector; use halo2_proofs::plonk::{Advice, Column, ConstraintSystem, Error}; - use halo2_proofs::poly::Rotation; use halo2_proofs::{circuit::SimpleFloorPlanner, dev::MockProver, plonk::Circuit}; use itertools::Itertools; use std::convert::TryInto; @@ -134,7 +136,7 @@ mod tests { struct MyConfig { q_enable: Selector, rho_config: RhoConfig, - state: [Column; 25], + advices: [Column; 3], } impl Circuit for MyCircuit { type Config = MyConfig; @@ -145,37 +147,23 @@ mod tests { } fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let state: [Column; 25] = (0..25) + let advices: [Column; 3] = (0..3) .map(|_| meta.advice_column()) .collect::>() .try_into() .unwrap(); - let fixed = [ - meta.fixed_column(), - meta.fixed_column(), - meta.fixed_column(), - ]; + let fixed = meta.fixed_column(); + let add = AddConfig::configure(meta, advices, fixed); - let rho_config = RhoConfig::configure(meta, state, fixed); + let rho_config = RhoConfig::configure(meta, advices, fixed, add); let q_enable = meta.selector(); - meta.create_gate("Check states", |meta| { - let q_enable = meta.query_selector(q_enable); - state - .iter() - .map(|col| { - let final_state = meta.query_advice(*col, Rotation::cur()); - let expected_final_state = meta.query_advice(*col, Rotation::next()); - q_enable.clone() * (final_state - expected_final_state) - }) - .collect::>() - }); MyConfig { q_enable, rho_config, - state, + advices, } } @@ -188,16 +176,15 @@ mod tests { let state = layouter.assign_region( || "assign input state", |mut region| { - let offset = 0; let state: [AssignedCell; 25] = self .in_state .iter() .enumerate() - .map(|(idx, &value)| { + .map(|(offset, &value)| { region .assign_advice( - || format!("lane {}", idx), - config.state[idx], + || "lane", + config.advices[0], offset, || Ok(value), ) @@ -216,26 +203,9 @@ mod tests { || "check final states", |mut region| { config.q_enable.enable(&mut region, 0)?; - out_state.iter().enumerate().for_each(|(idx, cell)| { - cell.copy_advice( - || "out_state obtained", - &mut region, - config.state[idx], - 0, - ) - .unwrap(); - }); - - self.out_state.iter().enumerate().for_each(|(idx, &value)| { - region - .assign_advice( - || format!("lane {}", idx), - config.state[idx], - 1, - || Ok(value), - ) - .unwrap(); - }); + for (lane, value) in out_state.iter().zip(self.out_state.iter()) { + region.constrain_constant(lane.cell(), value)?; + } Ok(()) }, diff --git a/keccak256/src/permutation/rho_checks.rs b/keccak256/src/permutation/rho_checks.rs index 790ea7b0e1..4a44e0cd19 100644 --- a/keccak256/src/permutation/rho_checks.rs +++ b/keccak256/src/permutation/rho_checks.rs @@ -108,6 +108,7 @@ use crate::arith_helpers::*; use crate::common::ROTATION_CONSTANTS; use crate::gate_helpers::{biguint_to_f, f_to_biguint}; use crate::permutation::{ + add::AddConfig, rho_helpers::*, tables::{Base13toBase9TableConfig, RangeCheckConfig, SpecialChunkTableConfig}, }; @@ -117,20 +118,15 @@ use halo2_proofs::{ plonk::{Advice, Column, ConstraintSystem, Error, Fixed, Selector}, poly::Rotation, }; -use std::marker::PhantomData; #[derive(Debug, Clone)] pub struct LaneRotateConversionConfig { q_normal: Selector, q_special: Selector, input_coef: Column, - input_pob: Column, - input_acc: Column, output_coef: Column, - output_pob: Column, - output_acc: Column, pub overflow_detector: Column, - _marker: PhantomData, + add: AddConfig, } impl LaneRotateConversionConfig { @@ -138,53 +134,17 @@ impl LaneRotateConversionConfig { meta: &mut ConstraintSystem, base13_to_9_table: &Base13toBase9TableConfig, special_chunk_table: &SpecialChunkTableConfig, - advices: [Column; 5], - fixed: [Column; 3], + advices: [Column; 3], + constant: Column, + add: AddConfig, ) -> Self { let q_normal = meta.complex_selector(); let q_special = meta.complex_selector(); - let [input_coef, input_acc, output_coef, output_acc, overflow_detector] = advices; - let [input_pob, output_pob, constant] = fixed; + let [input_coef, output_coef, overflow_detector] = advices; - meta.enable_equality(input_acc); - meta.enable_equality(output_acc); meta.enable_equality(overflow_detector); meta.enable_constant(constant); - // | coef | 13**x | acc | - // |------|-------|-----------| - // | a | b | c | - // | ... | ... | c - a * b | - meta.create_gate("Running down input", |meta| { - let q_normal = meta.query_selector(q_normal); - let coef = meta.query_advice(input_coef, Rotation::cur()); - let pob = meta.query_fixed(input_pob, Rotation::cur()); - let acc = meta.query_advice(input_acc, Rotation::cur()); - let acc_next = meta.query_advice(input_acc, Rotation::next()); - vec![( - "delta_acc === - coef * power_of_base", - q_normal * (acc_next - acc + coef * pob), - )] - }); - // | coef | 9**x | acc | - // |------|-------|--------| - // | a | b | 0 | - // | ... | ... | a * b | - meta.create_gate("Running up for output", |meta| { - let q_normal = meta.query_selector(q_normal); - let q_special = meta.query_selector(q_special); - let coef = meta.query_advice(output_coef, Rotation::cur()); - let pob = meta.query_fixed(output_pob, Rotation::cur()); - let acc = meta.query_advice(output_acc, Rotation::cur()); - let acc_next = meta.query_advice(output_acc, Rotation::next()); - // delta_acc === coef * power_of_base - let poly = acc_next - acc - coef * pob; - vec![ - ("check for q_normal", q_normal * poly.clone()), - ("check for q_special", q_special * poly), - ] - }); - meta.lookup("b13 -> b9 table", |meta| { let q_normal = meta.query_selector(q_normal); let base13_coef = meta.query_advice(input_coef, Rotation::cur()); @@ -200,12 +160,12 @@ impl LaneRotateConversionConfig { meta.lookup("special chunk", |meta| { let q_special = meta.query_selector(q_special); - let input_acc = meta.query_advice(input_acc, Rotation::cur()); + let input_coef = meta.query_advice(input_coef, Rotation::cur()); let output_coef = meta.query_advice(output_coef, Rotation::cur()); vec![ ( - q_special.clone() * input_acc, + q_special.clone() * input_coef, special_chunk_table.last_chunk, ), (q_special * output_coef, special_chunk_table.output_coef), @@ -215,13 +175,9 @@ impl LaneRotateConversionConfig { q_normal, q_special, input_coef, - input_pob, - input_acc, output_coef, - output_pob, - output_acc, overflow_detector, - _marker: PhantomData, + add, } } @@ -248,185 +204,95 @@ impl LaneRotateConversionConfig { rotation, ) .get_full_witness(); - layouter.assign_region( - || "lane rotate conversion", - |mut region| { - let slices = slice_lane(rotation); - let (step2_od, step3_od) = { + let slices = slice_lane(rotation); + + let (input_coefs, input_pobs, output_coefs, output_pobs, step2_od, step3_od) = layouter + .assign_region( + || "lane rotate conversion", + |mut region| { + let mut input_coefs: Vec> = vec![]; + let mut output_coefs: Vec> = vec![]; + let mut input_pobs: Vec = vec![]; + let mut output_pobs: Vec = vec![]; let mut step2_od: Vec> = vec![]; let mut step3_od: Vec> = vec![]; for (offset, (&(chunk_idx, step), conv)) in slices.iter().zip(conversions.iter()).enumerate() { self.q_normal.enable(&mut region, offset)?; - region.assign_advice( + let input_coef = region.assign_advice( || format!("Input Coef {}", chunk_idx), self.input_coef, offset, || Ok(biguint_to_f::(&conv.input.coef)), )?; - region.assign_fixed( - || "Input power of base", - self.input_pob, - offset, - || Ok(biguint_to_f::(&conv.input.power_of_base)), - )?; - { - let cell = region - .assign_advice( - || "Input accumulator", - self.input_acc, - offset, - || Ok(biguint_to_f::(&conv.input.pre_acc)), - )? - .cell(); - if offset == 0 { - region.constrain_equal(lane_base_13.cell(), cell)?; - } - } - region.assign_advice( + input_coefs.push(input_coef); + input_pobs.push(biguint_to_f::(&conv.input.power_of_base)); + let output_coef = region.assign_advice( || "Output Coef", self.output_coef, offset, || Ok(biguint_to_f::(&conv.output.coef)), )?; - region.assign_fixed( - || "Output power of base", - self.output_pob, + output_coefs.push(output_coef); + output_pobs.push(biguint_to_f::(&conv.output.power_of_base)); + + let od = region.assign_advice( + || "Overflow detector", + self.overflow_detector, offset, - || Ok(biguint_to_f::(&conv.output.power_of_base)), + || Ok(F::from(conv.overflow_detector.value as u64)), )?; - { - let cell = region - .assign_advice( - || "Output accumulator", - self.output_acc, - offset, - || Ok(biguint_to_f::(&conv.output.pre_acc)), - )? - .cell(); - if offset == 0 { - region.constrain_constant(cell, F::zero())?; - } - } - let od = { - let value = F::from(conv.overflow_detector.value as u64); - let od = region.assign_advice( - || "Overflow detector", - self.overflow_detector, - offset, - || Ok(value), - )?; - if step == 1 { - region.constrain_constant(od.cell(), F::zero())?; - } - od - }; match step { + 1 => region.constrain_constant(od.cell(), F::zero())?, 2 => step2_od.push(od), 3 => step3_od.push(od), - _ => {} + 4 => { // Do nothing + } + _ => unreachable!(), } } - (step2_od, step3_od) - }; - // special chunks - let output_lane = { - let offset = slices.len(); - self.q_special.enable(&mut region, offset)?; - region.assign_advice( - || "Special Input acc", - self.input_acc, - offset, - || Ok(biguint_to_f::(&special.input)), - )?; - region.assign_advice( - || "Special output coef", - self.output_coef, - offset, - || Ok(F::from(special.output_coef as u64)), - )?; - region.assign_fixed( - || "Special output power of base", - self.output_pob, - offset, - || Ok(F::from(B9 as u64).pow(&[rotation.into(), 0, 0, 0])), - )?; - region.assign_advice( - || "Special output acc pre", - self.output_acc, - offset, - || Ok(biguint_to_f::(&special.output_acc_pre)), - )?; - { - let value = biguint_to_f::(&special.output_acc_post); - region.assign_advice( - || "Special output acc post", - self.output_acc, - offset + 1, - || Ok(value), - )? - } - }; - Ok((output_lane, step2_od, step3_od)) - }, - ) - } -} -#[derive(Debug, Clone)] -pub struct SumConfig { - q_enable: Selector, - x: Column, - sum: Column, - _marker: PhantomData, -} -impl SumConfig { - // We assume the input columns are all copiable - pub fn configure(meta: &mut ConstraintSystem, advices: [Column; 2]) -> Self { - let q_enable = meta.selector(); - let [x, sum] = advices; - - meta.enable_equality(x); - meta.enable_equality(sum); + Ok(( + input_coefs, + input_pobs, + output_coefs, + output_pobs, + step2_od, + step3_od, + )) + }, + )?; + let input_from_chunks = self.add.linear_combine_consts(layouter, input_coefs, input_pobs, None)?; + let diff = self + .add + .sub_advice(layouter, lane_base_13, input_from_chunks)?; - meta.create_gate("sum", |meta| { - let q_enable = meta.query_selector(q_enable); - let x = meta.query_advice(x, Rotation::cur()); - let sum_next = meta.query_advice(sum, Rotation::next()); - let sum = meta.query_advice(sum, Rotation::cur()); - vec![q_enable * (sum_next - sum - x)] - }); - Self { - q_enable, - x, - sum, - _marker: PhantomData, - } - } - pub fn assign_region( - &self, - layouter: &mut impl Layouter, - xs: Vec>, - ) -> Result, Error> { - debug_assert!(xs.len() > 1); - layouter.assign_region( - || "running sum", + let (final_output_coef, final_output_pob) = layouter.assign_region( + || "special chunks", |mut region| { - let mut sum = F::zero(); - let mut offset = 0; - for xs_item in xs.iter() { - self.q_enable.enable(&mut region, offset)?; - xs_item.copy_advice(|| "x", &mut region, self.x, offset)?; - region.assign_advice(|| "sum", self.sum, offset, || Ok(sum))?; - sum += xs_item.value().copied().unwrap_or_default(); - offset += 1; - } - let sum = region.assign_advice(|| "last sum", self.sum, offset, || Ok(sum))?; - - Ok(sum) + let offset = 0; + self.q_special.enable(&mut region, offset)?; + diff.copy_advice(|| "input lane diff", &mut region, self.input_coef, offset)?; + let output_coef = region.assign_advice( + || "Special output coef", + self.output_coef, + offset, + || Ok(F::from(special.output_coef as u64)), + )?; + let final_output_pob = F::from(B9 as u64).pow(&[rotation.into(), 0, 0, 0]); + Ok((output_coef, final_output_pob)) }, - ) + )?; + let mut output_coefs = output_coefs.clone(); + output_coefs.push(final_output_coef); + let mut output_pobs = output_pobs.clone(); + output_pobs.push(final_output_pob); + + let output_lane = self + .add + .linear_combine_consts(layouter, output_coefs, output_pobs, None)?; + Ok((output_lane, step2_od, step3_od)) } } @@ -434,7 +300,7 @@ impl SumConfig { pub struct OverflowCheckConfig { q_step2: Selector, q_step3: Selector, - sum_config: SumConfig, + add: AddConfig, acc: Column, } impl OverflowCheckConfig { @@ -442,13 +308,11 @@ impl OverflowCheckConfig { meta: &mut ConstraintSystem, step2_range_table: &RangeCheckConfig, step3_range_table: &RangeCheckConfig, - advices: [Column; 2], + add: AddConfig, + acc: Column, ) -> Self { - let sum_config = SumConfig::configure(meta, advices); - let q_step2 = meta.complex_selector(); let q_step3 = meta.complex_selector(); - let acc = advices[0]; meta.enable_equality(acc); meta.lookup("Overflow check step2", |meta| { @@ -465,7 +329,7 @@ impl OverflowCheckConfig { Self { q_step2, q_step3, - sum_config, + add, acc, } } @@ -475,8 +339,8 @@ impl OverflowCheckConfig { step2_cells: Vec>, step3_cells: Vec>, ) -> Result<(), Error> { - let step2_sum = self.sum_config.assign_region(layouter, step2_cells)?; - let step3_sum = self.sum_config.assign_region(layouter, step3_cells)?; + let step2_sum = self.add.running_sum(layouter, step2_cells, None)?; + let step3_sum = self.add.running_sum(layouter, step3_cells, None)?; layouter.assign_region( || "Overflow range check", |mut region| { diff --git a/keccak256/src/permutation/tables.rs b/keccak256/src/permutation/tables.rs index 49b825cd9f..17a455a1ce 100644 --- a/keccak256/src/permutation/tables.rs +++ b/keccak256/src/permutation/tables.rs @@ -181,6 +181,21 @@ impl BaseInfo { pub fn output_pob(&self) -> F { F::from(self.output_base as u64).pow(&[self.num_chunks as u64, 0, 0, 0]) } + fn num_slices(&self) -> usize { + ((self.max_chunks as f32) / (self.num_chunks as f32)).ceil() as usize + } + pub fn input_pobs(&self) -> Vec { + (0..self.num_slices()) + .map(|i| self.input_pob().pow(&[i as u64, 0, 0, 0])) + .rev() + .collect_vec() + } + pub fn output_pobs(&self) -> Vec { + (0..self.num_slices()) + .map(|i| self.output_pob().pow(&[i as u64, 0, 0, 0])) + .rev() + .collect_vec() + } pub fn compute_coefs(&self, input: F) -> Result<(Vec, Vec, F), Error> { // big-endian diff --git a/keccak256/src/permutation/theta.rs b/keccak256/src/permutation/theta.rs index 38e54d4e33..ca078fbdbe 100644 --- a/keccak256/src/permutation/theta.rs +++ b/keccak256/src/permutation/theta.rs @@ -1,99 +1,50 @@ use crate::arith_helpers::*; +use crate::permutation::add::AddConfig; use eth_types::Field; use halo2_proofs::{ circuit::{AssignedCell, Layouter}, - plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector}, - poly::Rotation, + plonk::Error, }; use itertools::Itertools; use std::convert::TryInto; -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) - }, - ) - } +pub fn assign_theta( + add: &AddConfig, + layouter: &mut impl Layouter, + state: &[AssignedCell; 25], +) -> Result<[AssignedCell; 25], Error> { + let theta_col_sums: Result>, Error> = (0..5) + .map(|x| { + let col_sum = add.running_sum( + layouter, + (0..5).map(|y| state[5 * x + y].clone()).collect(), + None, + )?; + Ok(col_sum) + }) + .into_iter() + .collect(); + let theta_col_sums = theta_col_sums?; + let theta_col_sums: [AssignedCell; 5] = theta_col_sums.try_into().unwrap(); + + let out_state: Result>, Error> = (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)]; + let new_lane = add.linear_combine_consts(layouter, cells, vs, None)?; + Ok(new_lane) + }) + .into_iter() + .collect(); + let out_state = out_state?; + let out_state: [AssignedCell; 25] = out_state.try_into().unwrap(); + + Ok(out_state) } #[cfg(test)] @@ -115,6 +66,30 @@ mod tests { #[test] fn test_theta_gates() { + #[derive(Clone, Debug)] + struct MyConfig { + lane: Column, + add: AddConfig, + } + + 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 add = AddConfig::configure(meta, advices, fixed); + Self { lane, add } + } + } #[derive(Default)] struct MyCircuit { in_state: [F; 25], @@ -122,7 +97,7 @@ mod tests { _marker: PhantomData, } impl Circuit for MyCircuit { - type Config = ThetaConfig; + type Config = MyConfig; type FloorPlanner = SimpleFloorPlanner; fn without_witnesses(&self) -> Self { @@ -130,19 +105,10 @@ mod tests { } 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) + // this column is required by `constrain_constant` + let constant = meta.fixed_column(); + meta.enable_constant(constant); + Self::Config::configure(meta) } fn synthesize( @@ -150,17 +116,16 @@ mod tests { 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() { + for (offset, val) in self.in_state.iter().enumerate() { let cell = region.assign_advice( || "witness input state", - config.state[idx], + config.lane, offset, || Ok(*val), )?; @@ -172,8 +137,17 @@ mod tests { }, )?; - config.assign_state(&mut layouter, &in_state, self.out_state)?; + let out_state = assign_theta(&config.add, &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(()) } } diff --git a/keccak256/src/permutation/xi.rs b/keccak256/src/permutation/xi.rs index 5db50ac991..18a6af4a7a 100644 --- a/keccak256/src/permutation/xi.rs +++ b/keccak256/src/permutation/xi.rs @@ -1,101 +1,35 @@ +use crate::arith_helpers::{A1, A2, A3}; +use crate::permutation::add::AddConfig; use eth_types::Field; use halo2_proofs::{ circuit::{AssignedCell, Layouter}, - plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector}, - poly::Rotation, + plonk::Error, }; use itertools::Itertools; -use std::{convert::TryInto, 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) - }, - ) - } +use std::convert::TryInto; + +pub fn assign_xi( + add: &AddConfig, + layouter: &mut impl Layouter, + state: &[AssignedCell; 25], +) -> Result<[AssignedCell; 25], Error> { + let out_state: Result>, Error> = (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)]; + let new_lane = add.linear_combine_consts(layouter, cells, vs, None)?; + Ok(new_lane) + }) + .into_iter() + .collect(); + let out_state = out_state?; + let out_state: [AssignedCell; 25] = out_state.try_into().unwrap(); + Ok(out_state) } #[cfg(test)] @@ -115,6 +49,30 @@ mod tests { #[test] fn test_xi_gate() { + #[derive(Clone, Debug)] + struct MyConfig { + lane: Column, + add: AddConfig, + } + + 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 add = AddConfig::configure(meta, advices, fixed); + Self { lane, add } + } + } #[derive(Default)] struct MyCircuit { in_state: [F; 25], @@ -123,7 +81,7 @@ mod tests { } impl Circuit for MyCircuit { - type Config = XiConfig; + type Config = MyConfig; type FloorPlanner = SimpleFloorPlanner; fn without_witnesses(&self) -> Self { @@ -131,19 +89,10 @@ mod tests { } 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) + // this column is required by `constrain_constant` + let constant = meta.fixed_column(); + meta.enable_constant(constant); + Self::Config::configure(meta) } fn synthesize( @@ -151,17 +100,16 @@ mod tests { 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() { + for (offset, val) in self.in_state.iter().enumerate() { let cell = region.assign_advice( || "witness input state", - config.state[idx], + config.lane, offset, || Ok(*val), )?; @@ -173,7 +121,17 @@ mod tests { }, )?; - config.assign_state(&mut layouter, &in_state, self.out_state)?; + let out_state = assign_xi(&config.add, &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(()) } }