diff --git a/keccak256/Cargo.toml b/keccak256/Cargo.toml index 3a040f408b..13d7efa153 100644 --- a/keccak256/Cargo.toml +++ b/keccak256/Cargo.toml @@ -13,3 +13,6 @@ num-bigint = "0.4.2" num-traits = "0.2.14" pairing = { git = 'https://github.com/appliedzkp/pairing', package = "pairing_bn256" } plotters = { version = "0.3.0", optional = true } + +[dev-dependencies] +pretty_assertions = "1.0" diff --git a/keccak256/src/arith_helpers.rs b/keccak256/src/arith_helpers.rs index 9c4eee04b3..c1e894b02d 100644 --- a/keccak256/src/arith_helpers.rs +++ b/keccak256/src/arith_helpers.rs @@ -1,6 +1,9 @@ +use crate::common::State; use itertools::Itertools; use num_bigint::BigUint; use num_traits::{One, Zero}; +use pairing::arithmetic::FieldExt; +use pairing::bn256::Fr as Fp; use std::ops::{Index, IndexMut}; pub const B13: u64 = 13; @@ -21,7 +24,7 @@ pub type Lane13 = BigUint; pub type Lane9 = BigUint; pub struct StateBigInt { - xy: Vec, + pub(crate) xy: Vec, } impl Default for StateBigInt { fn default() -> Self { @@ -33,6 +36,17 @@ impl Default for StateBigInt { } } +impl From for StateBigInt { + fn from(state: State) -> Self { + let xy = state + .iter() + .flatten() + .map(|num| BigUint::from(*num)) + .collect(); + Self { xy } + } +} + impl StateBigInt { pub fn from_state_big_int(a: &StateBigInt, lane_transform: F) -> Self where @@ -183,6 +197,21 @@ pub fn convert_b9_lane_to_b2_normal(x: Lane9) -> u64 { .unwrap_or(0) } +pub fn big_uint_to_field(a: &BigUint) -> F { + let mut b: [u64; 4] = [0; 4]; + let mut iter = a.iter_u64_digits(); + + for i in &mut b { + *i = match &iter.next() { + Some(x) => *x, + None => 0u64, + }; + } + + // Workarround since `FieldExt` does not impl `from_raw`. + F::from_bytes(&Fp::from_raw(b).to_bytes()).unwrap() +} + /// This function allows us to inpect coefficients of big-numbers in different /// bases. pub fn inspect(x: BigUint, name: &str, base: u64) { @@ -198,3 +227,58 @@ pub fn inspect(x: BigUint, name: &str, base: u64) { } println!("inspect {} {} info {:?}", name, x, info); } + +pub fn state_to_biguint(state: [F; 25]) -> StateBigInt { + StateBigInt { + xy: state + .iter() + .map(|elem| elem.to_bytes()) + .map(|bytes| BigUint::from_bytes_le(&bytes)) + .collect(), + } +} + +pub fn state_to_state_bigint( + state: [F; N], +) -> State { + let mut matrix = [[0u64; 5]; 5]; + + let mut elems: Vec = state + .iter() + .map(|elem| elem.to_bytes()) + // This is horrible. But FieldExt does not give much better alternatives + // and refactoring `State` will be done once the + // keccak_all_togheter is done. + .map(|bytes| { + assert!(bytes[8..32] == vec![0u8; 24]); + let mut arr = [0u8; 8]; + arr.copy_from_slice(&bytes[0..8]); + u64::from_le_bytes(arr) + }) + .collect(); + elems.extend(vec![0u64; 25 - N]); + (0..5).into_iter().for_each(|idx| { + matrix[idx].copy_from_slice(&elems[5 * idx..(5 * idx + 5)]) + }); + + matrix +} + +pub fn state_bigint_to_field( + state: StateBigInt, +) -> [F; N] { + let mut arr = [F::zero(); N]; + let vector: Vec = state + .xy + .iter() + .map(|elem| { + let mut array = [0u8; 32]; + let bytes = elem.to_bytes_le(); + array[0..bytes.len()].copy_from_slice(&bytes[0..bytes.len()]); + array + }) + .map(|bytes| F::from_bytes(&bytes).unwrap()) + .collect(); + arr[0..N].copy_from_slice(&vector[0..N]); + arr +} diff --git a/keccak256/src/circuit.rs b/keccak256/src/circuit.rs new file mode 100644 index 0000000000..97c2777e7c --- /dev/null +++ b/keccak256/src/circuit.rs @@ -0,0 +1,253 @@ +use super::gates::{ + absorb::{AbsorbConfig, ABSORB_NEXT_INPUTS}, + iota_b13::IotaB13Config, + iota_b9::IotaB9Config, + pi::PiConfig, + rho::RhoConfig, + theta::ThetaConfig, + xi::XiConfig, +}; +use crate::{ + arith_helpers::*, common::{ROUND_CONSTANTS, PERMUTATION, ROTATION_CONSTANTS}, gates::rho_checks::RhoAdvices, +}; +use crate::{gates::mixing::MixingConfig, keccak_arith::*}; +use halo2::{ + circuit::Region, + plonk::{Advice, Column, ConstraintSystem, Error, Selector}, +}; +use itertools::Itertools; +use num_bigint::BigUint; +use pasta_curves::arithmetic::FieldExt; +use std::{convert::TryInto, marker::PhantomData}; + +#[derive(Clone, Debug)] +pub struct KeccakFConfig { + theta_config: ThetaConfig, + rho_config: RhoConfig, + pi_config: PiConfig, + xi_config: XiConfig, + iota_b9_config: IotaB9Config, + mixing_config: MixingConfig, + state: [Column; 25], + _marker: PhantomData, +} + +impl KeccakFConfig { + const B9_ROW: usize = 0; + const B13_ROW: usize = 1; + + // We assume state is recieved in base-9. + pub fn configure(meta: &mut ConstraintSystem) -> KeccakFConfig { + let state = (0..25) + .map(|_| { + let column = meta.advice_column(); + meta.enable_equality(column.into()); + column + }) + .collect_vec() + .try_into() + .unwrap(); + + // theta + let theta_config = ThetaConfig::configure(meta.selector(), meta, state); + // rho + let rho_config = { + let cols: [Column; 7] = state[0..7].try_into().unwrap(); + let adv = RhoAdvices::from(cols); + let axiliary = [state[8], state[9]]; + + let base13_to_9 = [ + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + ]; + let special = [meta.fixed_column(), meta.fixed_column()]; + RhoConfig::configure( + meta, + state, + &adv, + axiliary, + base13_to_9, + special, + ) + }; + // Pi + let pi_config = PiConfig::configure(meta.selector(), meta, state); + // xi + let xi_config = XiConfig::configure(meta.selector(), meta, state); + + // Iotab9 + // Generate advice and instance column for Round constants in base9 + let round_ctant_b9 = meta.advice_column(); + let round_constants_b9 = meta.instance_column(); + let iota_b9_config = IotaB9Config::configure( + meta, + state, + round_ctant_b9, + round_constants_b9, + ); + let not_mixing_b9_to_13 = StateConversion::configure(meta); + + + + let mixing_config = MixingConfig::configure(meta, state); + // in side mixing let b9_to_13 = StateConversion::configure(meta); + + + KeccakFConfig { + theta_config, + rho_config, + pi_config, + xi_config, + iota_b9_config, + mixing_config, + state, + _marker: PhantomData, + } + } + + pub fn assign_all( + &self, + region: &mut Region<'_, F>, + mut offset: usize, + state: [F; 25], + out_state: [F;25], + flag: bool, + next_mixing: Option<[F; ABSORB_NEXT_INPUTS]>, + absolute_row_b9: usize, + absolute_row_b13: usize, + ) -> Result<[F; 25], Error> { + // In case is needed + let mut state = state; + + // First 23 rounds + for round in 0..PERMUTATION { + // State in base-13 + // theta + state = { + // Apply theta outside circuit + let out_state = KeccakFArith::theta(&state_to_biguint(state)); + let out_state = state_bigint_to_pallas(out_state); + // assignment + self.theta_config + .assign_state(region, offset, state, out_state)? + }; + + offset += ThetaConfig::OFFSET; + + // rho + state = { + // Apply rho outside circuit + let out_state = KeccakFArith::rho(&state_to_biguint(state)); + let out_state = state_bigint_to_pallas(out_state); + // assignment + self.rho_config + .assign_region(region, offset, out_state)?; + out_state + }; + // Outputs in base-9 which is what Pi requires. + offset += RhoConfig::OFFSET; + + // pi + state = { + // Apply pi outside circuit + let out_state = KeccakFArith::pi(&state_to_biguint(state)); + let out_state = state_bigint_to_pallas(out_state); + // assignment + self.pi_config + .assign_state(region, offset, state, out_state)? + }; + + offset += PiConfig::OFFSET; + + // xi + state = { + // Apply xi outside circuit + let out_state = KeccakFArith::xi(&state_to_biguint(state)); + let out_state = state_bigint_to_pallas(out_state); + // assignment + self.xi_config + .assign_state(region, offset, state, out_state)? + }; + + offset += XiConfig::OFFSET; + + // iota_b9 + state = { + let out_state = KeccakFArith::iota_b9(&state_to_biguint(state), ROUND_CONSTANTS[round]); + let out_state = state_bigint_to_pallas(out_state); + self + .iota_b9_config + .not_last_round(region, offset, state, out_state, round)?; + out_state + }; + offset += IotaB9Config::OFFSET; + // The resulting state is in Base-13 now. Which is what Theta + // requires again at the start of the loop. + + self.not_mixing_b9_to_13.not_last_round(); + } + + // Final round. + let round = PERMUTATION; + // PERMUTATION'th round + // State in base-13 + // theta + state = { + // Apply theta outside circuit + let out_state = KeccakFArith::theta(&state_to_biguint(state)); + let out_state = state_bigint_to_pallas(out_state); + // assignment + self.theta_config + .assign_state(region, offset, state, out_state)? + }; + + offset += 1; + + // rho + state = { + // Apply rho outside circuit + let out_state = KeccakFArith::rho(&state_to_biguint(state)); + let out_state = state_bigint_to_pallas(out_state); + // assignment + self.rho_config + .assign_state(region, offset, state, out_state)? + }; + // Outputs in base-9 which is what Pi requires. + offset += 1; + + // pi + state = { + // Apply pi outside circuit + let out_state = KeccakFArith::pi(&state_to_biguint(state)); + let out_state = state_bigint_to_pallas(out_state); + // assignment + self.pi_config + .assign_state(region, offset, state, out_state)? + }; + + offset += PiConfig::OFFSET; + + // xi + state = { + // Apply xi outside circuit + let out_state = KeccakFArith::xi(&state_to_biguint(state)); + let out_state = state_bigint_to_pallas(out_state); + // assignment + self.xi_config + .assign_state(region, offset, state, out_state)? + }; + + offset += XiConfig::OFFSET; + + // Mixing step + state = { + let out_state = KeccakFArith::mixing(&state_to_biguint(state), next_mixing, ROUND_CONSTANTS[round]); + let out_state = state_bigint_to_pallas(out_state); + self.mixing_config.assign_state(region, offset, state, out_state, flag, next_mixing, round, round)?; + out_state + }; + + Ok(state) + } +} diff --git a/keccak256/src/gates.rs b/keccak256/src/gates.rs index e6eed7d6bd..456a44546d 100644 --- a/keccak256/src/gates.rs +++ b/keccak256/src/gates.rs @@ -1,7 +1,11 @@ +#![allow(dead_code)] +#![allow(clippy::type_complexity)] +#![allow(clippy::too_many_arguments)] pub mod absorb; pub mod gate_helpers; pub mod iota_b13; pub mod iota_b9; +pub mod mixing; pub mod pi; pub mod rho; pub mod rho_checks; diff --git a/keccak256/src/gates/absorb.rs b/keccak256/src/gates/absorb.rs index 23a7cdbe36..1a6f056964 100644 --- a/keccak256/src/gates/absorb.rs +++ b/keccak256/src/gates/absorb.rs @@ -1,30 +1,37 @@ +use crate::arith_helpers::*; +use crate::common::*; +use crate::keccak_arith::*; +use halo2::circuit::Cell; use halo2::{ circuit::Region, plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector}, poly::Rotation, }; -use pairing::arithmetic::FieldExt; -use std::marker::PhantomData; +use itertools::Itertools; +use pairing::{arithmetic::FieldExt, bn256::Fr as Fp}; +use std::{convert::TryInto, marker::PhantomData}; /// The number of next_inputs that are used inside the `absorb` circuit. pub(crate) const ABSORB_NEXT_INPUTS: usize = 17; #[derive(Clone, Debug)] pub struct AbsorbConfig { - #[allow(dead_code)] - q_enable: Selector, + q_mixing: Selector, state: [Column; 25], - next_input: [Column; ABSORB_NEXT_INPUTS], _marker: PhantomData, } impl AbsorbConfig { + pub const OFFSET: usize = 2; // 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( - q_enable: Selector, meta: &mut ConstraintSystem, state: [Column; 25], - next_input: [Column; ABSORB_NEXT_INPUTS], ) -> AbsorbConfig { // def absorb(state: List[List[int], next_input: List[List[int]): // for x in range(5): @@ -33,80 +40,168 @@ impl AbsorbConfig { // 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).into())); + 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 = `ABSORB_NEXT_INPUTS + 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. + let flag = Expression::Constant(F::one()) + - meta.query_advice( + state[ABSORB_NEXT_INPUTS], + Rotation::cur(), + ); + // Note also that we want to enable the gate when `is_mixing` is + // false. (flag = 0). Therefore, we are doing + // `1-flag` in order to enforce this. (See the flag computation + // above). + meta.query_selector(q_mixing) * flag + }; + (0..ABSORB_NEXT_INPUTS) .map(|idx| { - let val = meta.query_advice(state[idx], Rotation::cur()) + let val = meta.query_advice(state[idx], Rotation::prev()) + (Expression::Constant(F::from(2)) - * meta.query_advice( - next_input[idx], - Rotation::cur(), - )); + * meta.query_advice(state[idx], Rotation::cur())); let next_lane = meta.query_advice(state[idx], Rotation::next()); - meta.query_selector(q_enable) * (val - next_lane) + q_enable.clone() * (val - next_lane) }) .collect::>() }); AbsorbConfig { - q_enable, + q_mixing, state, - next_input, _marker: PhantomData, } } - pub fn assign_state( + /// Doc this + pub fn copy_state_flag_next_inputs( &self, region: &mut Region<'_, F>, - offset: usize, - state: [F; 25], - ) -> Result<[F; 25], Error> { - for (idx, lane) in state.iter().enumerate() { - region.assign_advice( + mut offset: usize, + in_state: [(Cell, F); 25], + out_state: [F; 25], + next_input: [F; ABSORB_NEXT_INPUTS], + flag: (Cell, F), + ) -> Result<([(Cell, F); 25], (Cell, F)), Error> { + let mut state_array = [F::zero(); 25]; + + // State at offset + 0 + for (idx, (cell, value)) in in_state.iter().enumerate() { + // Copy value into state_array + state_array[idx] = *value; + let new_cell = region.assign_advice( || format!("assign state {}", idx), self.state[idx], offset, - || Ok(*lane), + || Ok(*value), )?; + + region.constrain_equal(*cell, new_cell)?; } - Ok(state) - } - pub fn assign_next_input( - &self, - region: &mut Region<'_, F>, - offset: usize, - next_input: [F; ABSORB_NEXT_INPUTS], - ) -> Result<[F; ABSORB_NEXT_INPUTS], Error> { + offset += 1; + // Enable `q_mixing` at `offset + 1` + self.q_mixing.enable(region, offset)?; + // Assign next_mixing at offset + 1 for (idx, lane) in next_input.iter().enumerate() { region.assign_advice( || format!("assign next_input {}", idx), - self.next_input[idx], + self.state[idx], + offset, + || Ok(*lane), + )?; + } + // Assign flag at last column(17th) of the offset + 1 row. + let obtained_cell = region.assign_advice( + || format!("assign next_input {}", ABSORB_NEXT_INPUTS), + self.state[ABSORB_NEXT_INPUTS], + offset, + || Ok(flag.1), + )?; + region.constrain_equal(flag.0, obtained_cell)?; + + offset += 1; + // Assign out_state at offset + 2 + let mut state: Vec<(Cell, F)> = Vec::with_capacity(25); + for (idx, lane) in out_state.iter().enumerate() { + let cell = region.assign_advice( + || format!("assign state {}", idx), + self.state[idx], offset, || Ok(*lane), )?; + state.push((cell, *lane)); + } + let out_state: [(Cell, F); 25] = state + .try_into() + .expect("Unexpected into_slice conversion err"); + + Ok((out_state, (obtained_cell, flag.1))) + } + + /// Given a [`State`] and the `next_inputs` returns the `init_state` and + /// `out_state` ready to be added as circuit witnesses applying `Absorb` + /// to the input to get the output. + /// + /// It also returns `next_inputs` ready to be used in the circuit. + pub(crate) fn compute_circ_states( + state: StateBigInt, + next_input: State, + ) -> ([F; 25], [F; 25], [F; ABSORB_NEXT_INPUTS]) { + let mut in_biguint = StateBigInt::default(); + let mut next_biguint = StateBigInt::default(); + + let mut in_state: [Fp; 25] = [Fp::zero(); 25]; + let mut in_next_input_25: [Fp; 25] = [Fp::zero(); 25]; + + for (x, y) in (0..5).cartesian_product(0..5) { + in_biguint[(x, y)] = convert_b2_to_b9( + state[(x, y)].clone().try_into().expect("Conversion err"), + ); + next_biguint[(x, y)] = convert_b2_to_b9(next_input[x][y]); + in_state[5 * x + y] = big_uint_to_field(&in_biguint[(x, y)]); + in_next_input_25[5 * x + y] = + big_uint_to_field(&next_biguint[(x, y)]); } - Ok(next_input) + + let mut in_next_input_17 = [Fp::zero(); ABSORB_NEXT_INPUTS]; + in_next_input_17 + .copy_from_slice(&in_next_input_25[0..ABSORB_NEXT_INPUTS]); + let s1_arith = KeccakFArith::absorb(&in_biguint, &next_input); + let next_input = state_to_biguint(in_next_input_25); + ( + state_bigint_to_field::(in_biguint), + state_bigint_to_field::(s1_arith), + state_bigint_to_field::(next_input), + ) } } #[cfg(test)] mod tests { use super::*; - use crate::arith_helpers::*; - use crate::common::*; - use crate::keccak_arith::*; + use crate::common::State; use halo2::circuit::Layouter; use halo2::plonk::{Advice, Column, ConstraintSystem, Error}; use halo2::{circuit::SimpleFloorPlanner, dev::MockProver, plonk::Circuit}; - use itertools::Itertools; - use num_bigint::BigUint; - use pairing::arithmetic::FieldExt; - use pairing::bn256::Fr as Fp; + use pretty_assertions::assert_eq; use std::convert::TryInto; use std::marker::PhantomData; @@ -115,8 +210,9 @@ mod tests { #[derive(Default)] struct MyCircuit { in_state: [F; 25], - next_input: [F; ABSORB_NEXT_INPUTS], out_state: [F; 25], + next_input: [F; ABSORB_NEXT_INPUTS], + is_mixing: bool, _marker: PhantomData, } impl Circuit for MyCircuit { @@ -128,22 +224,17 @@ mod tests { } fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let q_enable = meta.complex_selector(); - let state: [Column; 25] = (0..25) - .map(|_| meta.advice_column()) - .collect::>() - .try_into() - .unwrap(); - - let next_input: [Column; ABSORB_NEXT_INPUTS] = (0 - ..ABSORB_NEXT_INPUTS) - .map(|_| meta.advice_column()) + .map(|_| { + let column = meta.advice_column(); + meta.enable_equality(column.into()); + column + }) .collect::>() .try_into() .unwrap(); - AbsorbConfig::configure(q_enable, meta, state, next_input) + AbsorbConfig::configure(meta, state) } fn synthesize( @@ -151,42 +242,58 @@ mod tests { config: Self::Config, mut layouter: impl Layouter, ) -> Result<(), Error> { + let val: F = (self.is_mixing as u64).into(); + let flag: (Cell, F) = layouter.assign_region( + || "witness_is_mixing_flag", + |mut region| { + let offset = 1; + let cell = region.assign_advice( + || "assign is_mising", + config.state[ABSORB_NEXT_INPUTS + 1], + offset, + || Ok(val), + )?; + Ok((cell, val)) + }, + )?; + + // Witness `in_state`. + let in_state: [(Cell, F); 25] = layouter.assign_region( + || "Witness input state", + |mut region| { + let mut state: Vec<(Cell, F)> = 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, *val)) + } + + Ok(state.try_into().unwrap()) + }, + )?; + layouter.assign_region( || "assign input state", |mut region| { let offset = 0; - config.q_enable.enable(&mut region, offset)?; - config.assign_state( - &mut region, - offset, - self.in_state, - )?; - config.assign_next_input( + config.copy_state_flag_next_inputs( &mut region, offset, + in_state, + self.out_state, self.next_input, - )?; - let offset = 1; - config.assign_state(&mut region, offset, self.out_state) + flag, + ) }, )?; Ok(()) } } - fn big_uint_to_pallas(a: &BigUint) -> Fp { - let mut b: [u64; 4] = [0; 4]; - let mut iter = a.iter_u64_digits(); - - for i in &mut b { - *i = match &iter.next() { - Some(x) => *x, - None => 0u64, - }; - } - - Fp::from_raw(b) - } let input1: State = [ [1, 0, 0, 0, 0], @@ -204,38 +311,54 @@ mod tests { [0, 0, 0, 0, 0], ]; - let mut in_biguint = StateBigInt::default(); - let mut next_biguint = StateBigInt::default(); + let (in_state, out_state, next_input) = + AbsorbConfig::compute_circ_states(input1.into(), next_input); - let mut in_state: [Fp; 25] = [Fp::zero(); 25]; - let mut in_next_input_25: [Fp; 25] = [Fp::zero(); 25]; + // With flag set to false, 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: false, + _marker: PhantomData, + }; - for (x, y) in (0..5).cartesian_product(0..5) { - in_biguint[(x, y)] = convert_b2_to_b9(input1[x][y]); - next_biguint[(x, y)] = convert_b2_to_b9(next_input[x][y]); - in_state[5 * x + y] = big_uint_to_pallas(&in_biguint[(x, y)]); - in_next_input_25[5 * x + y] = - big_uint_to_pallas(&next_biguint[(x, y)]); - } + let prover = MockProver::::run(9, &circuit, vec![]).unwrap(); - let mut in_next_input_17 = [Fp::zero(); ABSORB_NEXT_INPUTS]; - in_next_input_17 - .copy_from_slice(&in_next_input_25[0..ABSORB_NEXT_INPUTS]); - let s1_arith = KeccakFArith::absorb(&in_biguint, &next_input); - let mut out_state: [Fp; 25] = [Fp::zero(); 25]; - for (x, y) in (0..5).cartesian_product(0..5) { - out_state[5 * x + y] = big_uint_to_pallas(&s1_arith[(x, y)]); + 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: false, + _marker: PhantomData, + }; + + let prover = MockProver::::run(9, &circuit, vec![]).unwrap(); + + assert!(prover.verify().is_err()); } - let circuit = MyCircuit:: { - in_state, - next_input: in_next_input_17, - out_state, - _marker: PhantomData, - }; - // Test without public inputs - let prover = MockProver::::run(9, &circuit, vec![]).unwrap(); + // With flag set to `true`, 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: true, + _marker: PhantomData, + }; - assert_eq!(prover.verify(), Ok(())); + let prover = MockProver::::run(9, &circuit, vec![]).unwrap(); + + assert_eq!(prover.verify(), Ok(())); + } } } diff --git a/keccak256/src/gates/iota_b13.rs b/keccak256/src/gates/iota_b13.rs index b629451b13..6eceac85ad 100644 --- a/keccak256/src/gates/iota_b13.rs +++ b/keccak256/src/gates/iota_b13.rs @@ -1,17 +1,24 @@ +use crate::arith_helpers::*; +use crate::common::*; +use crate::keccak_arith::*; +use halo2::circuit::Cell; use halo2::plonk::Instance; use halo2::{ circuit::Region, - plonk::{Advice, Column, ConstraintSystem, Error, Selector}, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector}, poly::Rotation, }; +use itertools::Itertools; use pairing::arithmetic::FieldExt; +use std::convert::TryInto; use std::marker::PhantomData; #[derive(Clone, Debug)] pub struct IotaB13Config { - #[allow(dead_code)] - q_enable: Selector, + q_mixing: Selector, state: [Column; 25], + // Contains `is_mixing` flag at Rotation::next() and ROUND_CTANT_B13 at + // Rotation::cur() round_ctant_b13: Column, round_constants: Column, _marker: PhantomData, @@ -20,23 +27,49 @@ pub struct IotaB13Config { impl IotaB13Config { // We assume state is recieved in base-9. pub fn configure( - q_enable: Selector, meta: &mut ConstraintSystem, state: [Column; 25], round_ctant_b13: Column, round_constants: Column, ) -> IotaB13Config { - meta.create_gate("iota_b13", |meta| { - // def iota_b13(state: List[List[int], round_constant_base13: int): - // state[0][0] += round_constant_base13 - // return state + // def iota_b13(state: List[List[int], round_constant_base13: int): + // state[0][0] += round_constant_base13 + // return state + + // Declare the q_mixing. + let q_mixing = meta.selector(); + // Enable copy constraints over PI and the Advices. + meta.enable_equality(round_ctant_b13.into()); + meta.enable_equality(round_constants.into()); + + meta.create_gate("iota_b13 gate", |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`round_ctant_b13` `Advice` + // column at rotation next 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. + let flag = Expression::Constant(F::one()) + - meta.query_advice(round_ctant_b13, Rotation::next()); + + // Note also that we want to enable the gate when `is_mixing` is + // false. (flag = 0). Therefore, we are doing + // `1-flag` in order to enforce this. (See the flag computation + // above). + meta.query_selector(q_mixing) * flag + }; + let state_00 = meta.query_advice(state[0], Rotation::cur()) + meta.query_advice(round_ctant_b13, Rotation::cur()); let next_lane = meta.query_advice(state[0], Rotation::next()); - vec![meta.query_selector(q_enable) * (state_00 - next_lane)] + vec![q_enable * (state_00 - next_lane)] }); + IotaB13Config { - q_enable, + q_mixing, state, round_ctant_b13, round_constants, @@ -44,12 +77,76 @@ impl IotaB13Config { } } - pub fn assign_state( + /// Doc this + pub fn copy_state_flag_and_assing_rc( + &self, + region: &mut Region<'_, F>, + mut offset: usize, + state: [(Cell, F); 25], + out_state: [F; 25], + absolute_row: usize, + flag: (Cell, F), + ) -> Result<(), Error> { + // Enable `q_mixing`. + self.q_mixing.enable(region, offset)?; + // Copy state at offset + 0 + self.copy_state(region, offset, state)?; + // Assign round_ctant at offset + 0. + self.assign_round_ctant_b13(region, offset, absolute_row)?; + + offset += 1; + // Copy flag at `round_ctant_b9` at offset + 1 + self.copy_flag(region, offset, flag)?; + // Assign out state at offset + 1 + self.assign_state(region, offset, out_state) + } + + /// Copies the `[(Cell,F);25]` to the `state` Advice column. + fn copy_state( + &self, + region: &mut Region<'_, F>, + offset: usize, + in_state: [(Cell, F); 25], + ) -> Result<(), Error> { + for (idx, (cell, value)) in in_state.iter().enumerate() { + let new_cell = region.assign_advice( + || format!("copy in_state {}", idx), + self.state[idx], + offset, + || Ok(*value), + )?; + + region.constrain_equal(*cell, new_cell)?; + } + + Ok(()) + } + + /// Copies the `is_mixing` flag to the `round_ctant_b13` Advice column. + fn copy_flag( + &self, + region: &mut Region<'_, F>, + offset: usize, + flag: (Cell, F), + ) -> Result<(), Error> { + let obtained_cell = region.assign_advice( + || format!("assign is_mixing flag {:?}", flag.1), + self.round_ctant_b13, + offset, + || Ok(flag.1), + )?; + region.constrain_equal(flag.0, obtained_cell)?; + + Ok(()) + } + + // Assign `[F;25]` at `state` `Advice` column at the provided offset. + fn assign_state( &self, region: &mut Region<'_, F>, offset: usize, state: [F; 25], - ) -> Result<[F; 25], Error> { + ) -> Result<(), Error> { for (idx, lane) in state.iter().enumerate() { region.assign_advice( || format!("assign state {}", idx), @@ -58,40 +155,61 @@ impl IotaB13Config { || Ok(*lane), )?; } - Ok(state) + Ok(()) } + /// Assigns the ROUND_CONSTANTS_BASE_13 to the `absolute_row` passed as an + /// absolute instance column. Returns the new offset after the + /// assigment. pub fn assign_round_ctant_b13( &self, region: &mut Region<'_, F>, offset: usize, - round_ctant: usize, + absolute_row: usize, ) -> Result<(), Error> { region.assign_advice_from_instance( - || format!("assign round_ctant_b13 {}", 0), + || format!("assign round_ctant_b13 {}", absolute_row), self.round_constants, - round_ctant, + absolute_row, self.round_ctant_b13, offset, )?; Ok(()) } + + /// Given a [`State`] returns the `init_state` and `out_state` ready to be + /// added as circuit witnesses applying `IotaB13` to the input to get + /// the output. + pub(crate) fn compute_circ_states( + state: StateBigInt, + ) -> ([F; 25], [F; 25]) { + let mut in_biguint = StateBigInt::default(); + let mut in_state: [F; 25] = [F::zero(); 25]; + + for (x, y) in (0..5).cartesian_product(0..5) { + in_biguint[(x, y)] = convert_b2_to_b13( + state[(x, y)].clone().try_into().expect("Conversion err"), + ); + in_state[5 * x + y] = big_uint_to_field(&in_biguint[(x, y)]); + } + + // Compute out state + let round_ctant = ROUND_CONSTANTS[PERMUTATION - 1]; + let s1_arith = KeccakFArith::iota_b13(&in_biguint, round_ctant); + (in_state, state_bigint_to_field::(s1_arith)) + } } #[cfg(test)] mod tests { use super::*; - use crate::arith_helpers::*; - use crate::common::*; - use crate::keccak_arith::*; + use crate::common::{PERMUTATION, ROUND_CONSTANTS}; use halo2::circuit::Layouter; use halo2::plonk::{Advice, Column, ConstraintSystem, Error}; use halo2::{circuit::SimpleFloorPlanner, dev::MockProver, plonk::Circuit}; - use itertools::Itertools; - use num_bigint::BigUint; - use pairing::arithmetic::FieldExt; use pairing::bn256::Fr as Fp; + use pretty_assertions::assert_eq; use std::convert::TryInto; use std::marker::PhantomData; @@ -103,9 +221,12 @@ mod tests { out_state: [F; 25], // This usize is indeed pointing the exact row of the // ROUND_CTANTS_B13 we want to use. - round_ctant_b13: usize, + round_ctant: usize, + // The flag acts like a selector that turns ON/OFF the gate + flag: bool, _marker: PhantomData, } + impl Circuit for MyCircuit { type Config = IotaB13Config; type FloorPlanner = SimpleFloorPlanner; @@ -115,22 +236,23 @@ mod tests { } fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let q_enable = meta.complex_selector(); - let state: [Column; 25] = (0..25) - .map(|_| meta.advice_column()) + .map(|_| { + let column = meta.advice_column(); + meta.enable_equality(column.into()); + column + }) .collect::>() .try_into() .unwrap(); - let round_ctant_b13 = meta.advice_column(); + let round_ctant_b9 = meta.advice_column(); + // Allocate space for the round constants in base-13 which is an + // instance column let round_ctants = meta.instance_column(); - meta.enable_equality(round_ctant_b13.into()); - meta.enable_equality(round_ctants.into()); IotaB13Config::configure( - q_enable, meta, state, - round_ctant_b13, + round_ctant_b9, round_ctants, ) } @@ -140,46 +262,50 @@ mod tests { config: Self::Config, mut layouter: impl Layouter, ) -> Result<(), Error> { + let offset: usize = 0; + + let val: F = (self.flag as u64).into(); layouter.assign_region( - || "assign input & output state + constant in same region", + || "Wittnes & assignation", |mut region| { - let offset = 0; - config.q_enable.enable(&mut region, offset)?; - config.assign_state( - &mut region, - offset, - self.in_state, + // Witness `is_missing` flag + let cell = region.assign_advice( + || "witness is_missing", + config.round_ctant_b13, + offset + 1, + || Ok(val), )?; - let offset = 1; - config.assign_state( + let flag = (cell, val); + + // Witness `state` + let in_state: [(Cell, F); 25] = { + let mut state: Vec<(Cell, F)> = + 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, *val)) + } + state.try_into().unwrap() + }; + + // Assign `in_state`, `out_state`, round and flag + config.copy_state_flag_and_assing_rc( &mut region, offset, + in_state, self.out_state, + self.round_ctant, + flag, )?; - let offset = 0; - config.assign_round_ctant_b13( - &mut region, - offset, - self.round_ctant_b13, - ) + Ok(()) }, - )?; - - Ok(()) - } - } - fn big_uint_to_pallas(a: &BigUint) -> Fp { - let mut b: [u64; 4] = [0; 4]; - let mut iter = a.iter_u64_digits(); - - for i in &mut b { - *i = match &iter.next() { - Some(x) => *x, - None => 0u64, - }; + ) } - - Fp::from_raw(b) } let input1: State = [ @@ -189,33 +315,64 @@ mod tests { [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] = big_uint_to_pallas(&in_biguint[(x, y)]); - } - let s1_arith = KeccakFArith::iota_b13(&in_biguint, ROUND_CONSTANTS[0]); - let mut out_state: [Fp; 25] = [Fp::zero(); 25]; - for (x, y) in (0..5).cartesian_product(0..5) { - out_state[5 * x + y] = big_uint_to_pallas(&s1_arith[(x, y)]); - } - let circuit = MyCircuit:: { - in_state, - out_state, - round_ctant_b13: 0, - _marker: PhantomData, - }; + let (in_state, out_state) = + IotaB13Config::compute_circ_states(input1.into()); - let constants = ROUND_CONSTANTS + let constants: Vec = ROUND_CONSTANTS .iter() - .map(|num| big_uint_to_pallas(&convert_b2_to_b13(*num))) + .map(|num| big_uint_to_field(&convert_b2_to_b13(*num))) .collect(); - // Test without public inputs - let prover = - MockProver::::run(9, &circuit, vec![constants]).unwrap(); - assert_eq!(prover.verify(), Ok(())); + // With flag set to false, the gate should trigger. + { + // With the correct input and output witnesses, the proof should + // pass. + let circuit = MyCircuit:: { + in_state, + out_state, + round_ctant: PERMUTATION - 1, + flag: false, + _marker: PhantomData, + }; + + let prover = + MockProver::::run(9, &circuit, vec![constants.clone()]) + .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, + round_ctant: PERMUTATION - 1, + flag: false, + _marker: PhantomData, + }; + + let prover = + MockProver::::run(9, &circuit, vec![constants.clone()]) + .unwrap(); + + assert!(prover.verify().is_err()); + } + + // With flag set to `true`, 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, + round_ctant: PERMUTATION - 1, + flag: true, + _marker: PhantomData, + }; + + let prover = + MockProver::::run(9, &circuit, vec![constants]).unwrap(); + + assert_eq!(prover.verify(), Ok(())); + } } } diff --git a/keccak256/src/gates/iota_b9.rs b/keccak256/src/gates/iota_b9.rs index 19ba37c4a5..5c30d56083 100644 --- a/keccak256/src/gates/iota_b9.rs +++ b/keccak256/src/gates/iota_b9.rs @@ -1,46 +1,75 @@ +use crate::arith_helpers::*; +use crate::common::*; +use crate::keccak_arith::*; +use halo2::circuit::Cell; use halo2::plonk::Instance; use halo2::{ circuit::Region, plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector}, poly::Rotation, }; +use itertools::Itertools; use pairing::arithmetic::FieldExt; +use std::convert::TryInto; use std::marker::PhantomData; #[derive(Clone, Debug)] pub struct IotaB9Config { - #[allow(dead_code)] - q_enable: Selector, + q_not_last: Selector, + q_last: Selector, state: [Column; 25], - round_ctant_b9: Column, - round_constants: Column, + pub(crate) round_ctant_b9: Column, + pub(crate) round_constants: Column, _marker: PhantomData, } impl IotaB9Config { + pub const OFFSET: usize = 2; // We assume state is recieved in base-9. pub fn configure( - q_enable: Selector, meta: &mut ConstraintSystem, state: [Column; 25], round_ctant_b9: Column, round_constants: Column, ) -> IotaB9Config { - meta.create_gate("iota_b9", |meta| { - // def iota_b9(state: List[List[int], round_constant_base9: int): - // d = round_constant_base9 - // # state[0][0] 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[0][0] += 2*d - // return state + let q_not_last = meta.selector(); + let q_last = meta.selector(); + + // Enable copy constraints over PI and the Advices. + meta.enable_equality(round_ctant_b9.into()); + meta.enable_equality(round_constants.into()); + + // def iota_b9(state: List[List[int], round_constant_base9: int): + // d = round_constant_base9 + // # state[0][0] 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[0][0] += 2*d + // return state + meta.create_gate("iota_b9 in steady state", |meta| { + let q_enable = meta.query_selector(q_not_last); + let state_00 = meta.query_advice(state[0], Rotation::cur()) + + (Expression::Constant(F::from(2)) + * meta.query_advice(round_ctant_b9, Rotation::cur())); + let next_lane = meta.query_advice(state[0], Rotation::next()); + vec![q_enable * (state_00 - next_lane)] + }); + + meta.create_gate("iota_b9 in final round", |meta| { + let q_enable = { + let flag = meta.query_advice(round_ctant_b9, Rotation::next()); + meta.query_selector(q_last) * flag + }; + let state_00 = meta.query_advice(state[0], Rotation::cur()) + (Expression::Constant(F::from(2)) * meta.query_advice(round_ctant_b9, Rotation::cur())); let next_lane = meta.query_advice(state[0], Rotation::next()); - vec![meta.query_selector(q_enable) * (state_00 - next_lane)] + vec![q_enable * (state_00 - next_lane)] }); + IotaB9Config { - q_enable, + q_not_last, + q_last, state, round_ctant_b9, round_constants, @@ -48,12 +77,98 @@ impl IotaB9Config { } } - pub fn assign_state( + /// Doc this + pub fn not_last_round( + &self, + region: &mut Region<'_, F>, + mut offset: usize, + in_state: [F; 25], + out_state: [F; 25], + absolute_row: usize, + ) -> Result<(), Error> { + // Enable `q_not_last`. + self.q_not_last.enable(region, offset)?; + + // Assign state + self.assign_state(region, offset, in_state)?; + + // Assign round_constant at offset + 0 + self.assign_round_ctant_b9(region, offset, absolute_row)?; + + offset += 1; + // Assign out_state at offset + 1 + self.assign_state(region, offset, out_state) + } + + /// Assignment for iota_b9 in the context of the final round, where + /// the prover must witness an `is_mixing` flag in the round_ctant_b9 + /// advice column. + pub fn last_round( + &self, + region: &mut Region<'_, F>, + mut offset: usize, + state: [(Cell, F); 25], + out_state: [F; 25], + absolute_row: usize, + flag: (Cell, F), + ) -> Result<(), Error> { + // Copies the `[(Cell,F);25]` to the `state` Advice column. + let copy_state = |region: &mut Region<'_, F>, + offset: usize, + in_state: [(Cell, F); 25]| + -> Result<(), Error> { + for (idx, (cell, value)) in in_state.iter().enumerate() { + let new_cell = region.assign_advice( + || format!("copy in_state {}", idx), + self.state[idx], + offset, + || Ok(*value), + )?; + + region.constrain_equal(*cell, new_cell)?; + } + + Ok(()) + }; + + // Copies the `is_mixing` flag to the `round_ctant_b9` Advice column. + let copy_flag = |region: &mut Region<'_, F>, + offset: usize, + flag: (Cell, F)| + -> Result<(), Error> { + let obtained_cell = region.assign_advice( + || format!("assign is_mixing flag {:?}", flag.1), + self.round_ctant_b9, + offset, + || Ok(flag.1), + )?; + region.constrain_equal(flag.0, obtained_cell)?; + + Ok(()) + }; + + // Enable `q_last`. + self.q_last.enable(region, offset)?; + + // Copy state at offset + 0 + copy_state(region, offset, state)?; + // Assign round_ctant at offset + 0. + self.assign_round_ctant_b9(region, offset, absolute_row)?; + + offset += 1; + // Copy flag at `round_ctant_b9` at offset + 1 + copy_flag(region, offset, flag)?; + // Assign out state at offset + 1 + self.assign_state(region, offset, out_state) + } + + // Assign `[F;25]` at `state` `Advice` column at the provided offset. + fn assign_state( &self, region: &mut Region<'_, F>, offset: usize, state: [F; 25], - ) -> Result<[F; 25], Error> { + ) -> Result<(), Error> { for (idx, lane) in state.iter().enumerate() { region.assign_advice( || format!("assign state {}", idx), @@ -62,61 +177,80 @@ impl IotaB9Config { || Ok(*lane), )?; } - Ok(state) + Ok(()) } - /// Here we have reserved on an absolute perspective of the circuit the - /// second column for the round constants in base-9. - /// What we do is assign an advide column to them using copy constraints and - /// that requires to enable `meta.enable_equality()` when actually - /// defining the chip. - pub fn assign_round_ctant_b9( + /// Assigns the ROUND_CONSTANTS_BASE_9 to the `absolute_row` passed as an + /// absolute instance column. Returns the new offset after the + /// assigment. + fn assign_round_ctant_b9( &self, region: &mut Region<'_, F>, offset: usize, - round_ctant: usize, + absolute_row: usize, ) -> Result<(), Error> { region.assign_advice_from_instance( - // 1 is the absolute offset in the overall Region where the Column - // is laying. - || format!("assign round_ctant_b9 {}", 1), + // `absolute_row` is the absolute offset in the overall Region + // where the Column is laying. + || format!("assign round_ctant_b9 {}", absolute_row), self.round_constants, - round_ctant, + absolute_row, self.round_ctant_b9, offset, )?; Ok(()) } + + /// Given a [`StateBigInt`] returns the `init_state` and `out_state` ready + /// to be added as circuit witnesses applying `IotaB9` to the input to + /// get the output. + pub(crate) fn compute_circ_states( + state: StateBigInt, + ) -> ([F; 25], [F; 25]) { + let mut in_biguint = StateBigInt::default(); + let mut in_state: [F; 25] = [F::zero(); 25]; + + for (x, y) in (0..5).cartesian_product(0..5) { + in_biguint[(x, y)] = convert_b2_to_b9( + state[(x, y)].clone().try_into().expect("Conversion err"), + ); + in_state[5 * x + y] = big_uint_to_field(&in_biguint[(x, y)]); + } + + // Compute out state + let round_ctant = ROUND_CONSTANTS[PERMUTATION - 1]; + let s1_arith = KeccakFArith::iota_b9(&in_biguint, round_ctant); + (in_state, state_bigint_to_field::(s1_arith)) + } } #[cfg(test)] mod tests { use super::*; - use crate::arith_helpers::*; - use crate::common::*; - use crate::keccak_arith::*; + use crate::common::{PERMUTATION, ROUND_CONSTANTS}; use halo2::circuit::Layouter; use halo2::plonk::{Advice, Column, ConstraintSystem, Error}; use halo2::{circuit::SimpleFloorPlanner, dev::MockProver, plonk::Circuit}; - use itertools::Itertools; - use num_bigint::BigUint; - use pairing::arithmetic::FieldExt; use pairing::bn256::Fr as Fp; + use pretty_assertions::assert_eq; use std::convert::TryInto; use std::marker::PhantomData; #[test] - fn test_iota_b9_gate() { + fn test_iota_b9_gate_last_round() { #[derive(Default)] struct MyCircuit { in_state: [F; 25], out_state: [F; 25], // This usize is indeed pointing the exact row of the // ROUND_CTANTS_B9 we want to use. - round_ctant_b9: usize, + round_ctant: usize, + // The flag acts like a selector that turns ON/OFF the gate + flag: bool, _marker: PhantomData, } + impl Circuit for MyCircuit { type Config = IotaB9Config; type FloorPlanner = SimpleFloorPlanner; @@ -126,22 +260,25 @@ mod tests { } fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let q_enable = meta.complex_selector(); - let state: [Column; 25] = (0..25) - .map(|_| meta.advice_column()) + .map(|_| { + let column = meta.advice_column(); + meta.enable_equality(column.into()); + column + }) .collect::>() .try_into() .unwrap(); + let round_ctant_b9 = meta.advice_column(); // Allocate space for the round constants in base-9 which is an // instance column let round_ctants = meta.instance_column(); - // Enable copy constraints over PI and the Advices. - meta.enable_equality(round_ctant_b9.into()); - meta.enable_equality(round_ctants.into()); + + // Since we're not using a selector and want to test IotaB9 with + // the Mixing step, we make q_enable query + // the round_ctant_b9 at `Rotation::next`. IotaB9Config::configure( - q_enable, meta, state, round_ctant_b9, @@ -154,29 +291,175 @@ mod tests { config: Self::Config, mut layouter: impl Layouter, ) -> Result<(), Error> { + let offset: usize = 0; + + let val: F = (self.flag as u64).into(); layouter.assign_region( - || "assign input & output state + constant in same region", + || "Wittnes & assignation", |mut region| { - let offset = 0; - config.q_enable.enable(&mut region, offset)?; - config.assign_state( - &mut region, - offset, - self.in_state, + // Witness `is_mixing` flag + let cell = region.assign_advice( + || "witness is_mixing", + config.round_ctant_b9, + offset + 1, + || Ok(val), )?; - let offset = 1; - config.assign_state( + let flag = (cell, val); + + // Witness `state` + let in_state: [(Cell, F); 25] = { + let mut state: Vec<(Cell, F)> = + 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, *val)) + } + state.try_into().unwrap() + }; + + // Assign `in_state`, `out_state`, round and flag + config.last_round( &mut region, offset, + in_state, self.out_state, + self.round_ctant, + flag, )?; - let offset = 0; - // Within the Region itself, we use the constant in the - // same offset so at position - // (Rotation::curr()). Therefore we use `0` here. - config.assign_round_ctant_b9( + 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 (in_state, out_state) = + IotaB9Config::compute_circ_states(input1.into()); + + let constants: Vec = ROUND_CONSTANTS + .iter() + .map(|num| big_uint_to_field(&convert_b2_to_b9(*num))) + .collect(); + + // (flag = 1) -> Out state is checked as constraints are applied. + // Providing the correct `out_state` should pass the verification. + { + let circuit = MyCircuit:: { + in_state, + out_state, + round_ctant: PERMUTATION - 1, + flag: true, + _marker: PhantomData, + }; + + let prover = + MockProver::::run(9, &circuit, vec![constants.clone()]) + .unwrap(); + + assert_eq!(prover.verify(), Ok(())); + } + + // (flag = 1) -> Out state is checked as constraints are applied. + // Providing the wrong `out_state` should make the verification fail. + { + let circuit = MyCircuit:: { + in_state, + // Add wrong out_state that should cause the verification to + // fail. + out_state: in_state, + round_ctant: PERMUTATION - 1, + flag: true, + _marker: PhantomData, + }; + + let prover = + MockProver::::run(9, &circuit, vec![constants.clone()]) + .unwrap(); + + let _ = prover.verify().is_err(); + } + + // (flag = 0) + let circuit = MyCircuit:: { + in_state, + // Use a nonsensical out_state to verify that the gate is not + // checked. + out_state: in_state, + round_ctant: PERMUTATION - 1, + flag: false, + _marker: PhantomData, + }; + + let prover = + MockProver::::run(9, &circuit, vec![constants]).unwrap(); + + assert_eq!(prover.verify(), Ok(())); + } + + #[test] + fn test_iota_b9_gate_not_last_round() { + #[derive(Default)] + struct MyCircuit { + in_state: [F; 25], + out_state: [F; 25], + round_ctant_b9: usize, + _marker: PhantomData, + } + + impl Circuit for MyCircuit { + type Config = IotaB9Config; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let state: [Column; 25] = (0..25) + .map(|_| meta.advice_column()) + .collect::>() + .try_into() + .unwrap(); + let round_ctant_b9 = meta.advice_column(); + // Allocate space for the round constants in base-9 which is an + // instance column + let round_ctants = meta.instance_column(); + + IotaB9Config::configure( + meta, + state, + round_ctant_b9, + round_ctants, + ) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let offset: usize = 0; + // Assign input state at offset + 0 + layouter.assign_region( + || "assign input state", + |mut region| { + // Start IotaB9 config without copy at offset = 0 + config.not_last_round( &mut region, offset, + self.in_state, + self.out_state, self.round_ctant_b9, ) }, @@ -185,19 +468,6 @@ mod tests { Ok(()) } } - fn big_uint_to_pallas(a: &BigUint) -> Fp { - let mut b: [u64; 4] = [0; 4]; - let mut iter = a.iter_u64_digits(); - - for i in &mut b { - *i = match &iter.next() { - Some(x) => *x, - None => 0u64, - }; - } - - Fp::from_raw(b) - } let input1: State = [ [1, 0, 0, 0, 0], @@ -211,28 +481,33 @@ mod tests { 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] = big_uint_to_pallas(&in_biguint[(x, y)]); - } - let s1_arith = KeccakFArith::iota_b9(&in_biguint, ROUND_CONSTANTS[0]); - let mut out_state: [Fp; 25] = [Fp::zero(); 25]; - for (x, y) in (0..5).cartesian_product(0..5) { - out_state[5 * x + y] = big_uint_to_pallas(&s1_arith[(x, y)]); + in_state[5 * x + y] = big_uint_to_field(&in_biguint[(x, y)]); } - let circuit = MyCircuit:: { - in_state, - out_state, - round_ctant_b9: 0, - _marker: PhantomData, - }; - let constants = ROUND_CONSTANTS - .iter() - .map(|num| big_uint_to_pallas(&convert_b2_to_b9(*num))) - .collect(); - // Test without public inputs - let prover = - MockProver::::run(9, &circuit, vec![constants]).unwrap(); + // Test for the 25 rounds + for (round_idx, round_val) in + ROUND_CONSTANTS.iter().enumerate().take(PERMUTATION) + { + // Compute out state + let s1_arith = KeccakFArith::iota_b9(&in_biguint, *round_val); + let out_state = state_bigint_to_field::(s1_arith); - assert_eq!(prover.verify(), Ok(())); + let circuit = MyCircuit:: { + in_state, + out_state, + round_ctant_b9: round_idx, + _marker: PhantomData, + }; + + let constants: Vec = ROUND_CONSTANTS + .iter() + .map(|num| big_uint_to_field(&convert_b2_to_b9(*num))) + .collect(); + + let prover = + MockProver::::run(9, &circuit, vec![constants]).unwrap(); + + assert_eq!(prover.verify(), Ok(())); + } } } diff --git a/keccak256/src/gates/mixing.rs b/keccak256/src/gates/mixing.rs new file mode 100644 index 0000000000..6bad9a7270 --- /dev/null +++ b/keccak256/src/gates/mixing.rs @@ -0,0 +1,359 @@ +use super::{ + absorb::{AbsorbConfig, ABSORB_NEXT_INPUTS}, + iota_b13::IotaB13Config, + iota_b9::IotaB9Config, +}; +use crate::arith_helpers::*; +use crate::common::*; +use halo2::{ + circuit::{Cell, Region}, + plonk::{Advice, Column, ConstraintSystem, Error}, +}; +use pairing::arithmetic::FieldExt; +use std::convert::TryInto; + +#[derive(Clone, Debug)] +pub struct MixingConfig { + iota_b9_config: IotaB9Config, + iota_b13_config: IotaB13Config, + absorb_config: AbsorbConfig, + state: [Column; 25], + flag: Column, +} + +impl MixingConfig { + pub fn configure(meta: &mut ConstraintSystem) -> 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.into()); + + // Allocate state columns and enable copy constraints for them. + let state: [Column; 25] = (0..25) + .map(|_| { + let column = meta.advice_column(); + meta.enable_equality(column.into()); + column + }) + .collect::>() + .try_into() + .unwrap(); + + // Allocate space for the round constants in base-9 which is an + // instance column + let round_ctant_b9 = meta.advice_column(); + let round_constants_b9 = meta.instance_column(); + + // Allocate space for the round constants in base-13 which is an + // instance column + let round_ctant_b13 = meta.advice_column(); + let round_constants_b13 = meta.instance_column(); + + // We mix -> Flag = true + let iota_b9_config = IotaB9Config::configure( + meta, + state, + round_ctant_b9, + round_constants_b9, + ); + // We don't mix -> Flag = false + let absorb_config = AbsorbConfig::configure(meta, state); + let iota_b13_config = IotaB13Config::configure( + meta, + state, + round_ctant_b13, + round_constants_b13, + ); + + MixingConfig { + iota_b9_config, + iota_b13_config, + absorb_config, + state, + flag, + } + } + + pub fn assign_state( + &self, + region: &mut Region<'_, F>, + offset: usize, + in_state: [(Cell, F); 25], + out_state: [F; 25], + flag: bool, + next_mixing: Option<[F; ABSORB_NEXT_INPUTS]>, + absolute_row_b9: usize, + absolute_row_b13: usize, + ) -> Result<(), Error> { + // Witness the mixing flag. + let val: F = (flag as u64).into(); + // Witness `is_mixing` flag. + let cell = region.assign_advice( + || "witness is_mixing", + self.flag, + offset, + || Ok(val), + )?; + let flag = (cell, val); + + // If we mix, + self.iota_b9_config.last_round( + region, + offset, + in_state, + out_state, + absolute_row_b9, + flag, + )?; + + let (out_state_absorb_cells, flag) = + self.absorb_config.copy_state_flag_next_inputs( + region, + offset + IotaB9Config::::OFFSET, + in_state, + out_state, + next_mixing.unwrap_or_default(), + flag, + )?; + + // Base conversion assign + + self.iota_b13_config.copy_state_flag_and_assing_rc( + region, + offset + AbsorbConfig::::OFFSET, + out_state_absorb_cells, + out_state, + absolute_row_b13, + flag, + ) + } + + /// Given an `in_state` as [`State`] and `next_inputs` as [`Option`], + /// compute the result of the mixing step depending on the mixing flag + /// returning the `in_state`, `out_state` and `next_inputs` (if any) on a + /// way on which can be directly witnessed in the circuit. + pub(crate) fn compute_circ_states( + in_state: State, + next_inputs: Option, + ) -> ([F; 25], [F; 25], Option<[F; ABSORB_NEXT_INPUTS]>) { + if let Some(next_inputs) = next_inputs { + // We mix, therefore we apply Absorb + IotaB13 + let (in_state, out, next_inputs) = + AbsorbConfig::compute_circ_states(in_state.into(), next_inputs); + let (_, out_state) = IotaB13Config::compute_circ_states( + state_to_state_bigint::(out).into(), + ); + (in_state, out_state, Some(next_inputs)) + } else { + // We don't mix, therefore we run IotaB9 + let (in_state, out_state) = + IotaB9Config::compute_circ_states(in_state.into()); + (in_state, out_state, None) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::common::{State, PERMUTATION, ROUND_CONSTANTS}; + use halo2::circuit::Layouter; + use halo2::plonk::{ConstraintSystem, Error}; + use halo2::{circuit::SimpleFloorPlanner, dev::MockProver, plonk::Circuit}; + use pairing::bn256::Fr as Fp; + use pretty_assertions::assert_eq; + use std::convert::TryInto; + + #[ignore] + #[test] + fn test_mixing_gate() { + #[derive(Default)] + struct MyCircuit { + in_state: [F; 25], + out_state: [F; 25], + next_mixing: Option<[F; ABSORB_NEXT_INPUTS]>, + // This usize is indeed pointing the exact row of the + // ROUND_CTANTS_B9 we want to use. + round_ctant_b9: usize, + // This usize is indeed pointing the exact row of the + // ROUND_CTANTS_B13 we want to use. + round_ctant_b13: usize, + // flag + is_mixing: bool, + } + + impl Circuit for MyCircuit { + type Config = MixingConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + MixingConfig::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let offset: usize = 0; + + layouter.assign_region( + || "Wittnes & assignation", + |mut region| { + // Witness `state` + let in_state: [(Cell, F); 25] = { + let mut state: Vec<(Cell, F)> = + 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, *val)) + } + state.try_into().unwrap() + }; + + config.assign_state( + &mut region, + offset, + in_state, + self.out_state, + self.is_mixing, + self.next_mixing, + self.round_ctant_b9, + self.round_ctant_b13, + )?; + 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 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 (in_state, out_mixing_state, next_mixing) = + MixingConfig::compute_circ_states(input1, Some(next_input)); + + let (_, out_non_mixing_state, _) = + MixingConfig::compute_circ_states(input1, None); + + let constants_b13: Vec = ROUND_CONSTANTS + .iter() + .map(|num| big_uint_to_field(&convert_b2_to_b13(*num))) + .collect(); + + let constants_b9: Vec = ROUND_CONSTANTS + .iter() + .map(|num| big_uint_to_field(&convert_b2_to_b9(*num))) + .collect(); + + // With flag set to false, we mix. And so we should obtain Absorb + + // IotaB13 result + { + // FIXME: This should be passing + // 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: false, + round_ctant_b13: PERMUTATION - 1, + round_ctant_b9: PERMUTATION - 1, + }; + + let prover = MockProver::::run( + 9, + &circuit, + vec![constants_b9.clone(), constants_b13.clone()], + ) + .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: false, + round_ctant_b13: PERMUTATION - 1, + round_ctant_b9: PERMUTATION - 1, + }; + + let prover = MockProver::::run( + 9, + &circuit, + vec![constants_b9.clone(), constants_b13.clone()], + ) + .unwrap(); + + assert!(prover.verify().is_err()); + } + + // With flag set to `true`, we don't mix. And so we should obtain IotaB9 + // application as result. + { + let circuit = MyCircuit:: { + in_state, + out_state: out_mixing_state, + next_mixing, + is_mixing: true, + round_ctant_b13: PERMUTATION - 1, + round_ctant_b9: PERMUTATION - 1, + }; + + let prover = MockProver::::run( + 9, + &circuit, + vec![constants_b9.clone(), constants_b13.clone()], + ) + .unwrap(); + + assert_eq!(prover.verify(), Ok(())); + + // FIXME: This should be failing. + // 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, + round_ctant_b13: PERMUTATION - 1, + round_ctant_b9: PERMUTATION - 1, + }; + + let prover = MockProver::::run( + 9, + &circuit, + vec![constants_b9, constants_b13], + ) + .unwrap(); + + assert!(prover.verify().is_err()); + } + } +} diff --git a/keccak256/src/gates/pi.rs b/keccak256/src/gates/pi.rs index 3e8278e39d..1d6c37c18a 100644 --- a/keccak256/src/gates/pi.rs +++ b/keccak256/src/gates/pi.rs @@ -10,6 +10,7 @@ use pairing::arithmetic::FieldExt; use std::convert::TryInto; use std::marker::PhantomData; +#[derive(Clone, Debug)] pub struct PiConfig { q_enable: Selector, state: [Column; 25], @@ -17,6 +18,7 @@ pub struct PiConfig { } impl PiConfig { + pub const OFFSET: usize = 2; pub fn configure( q_enable: Selector, meta: &mut ConstraintSystem, diff --git a/keccak256/src/gates/rho.rs b/keccak256/src/gates/rho.rs index 14e8773ff0..0bac2a821a 100644 --- a/keccak256/src/gates/rho.rs +++ b/keccak256/src/gates/rho.rs @@ -13,7 +13,7 @@ use itertools::Itertools; use pairing::arithmetic::FieldExt; use std::convert::TryInto; -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct RhoConfig { state: [Column; 25], state_rotate_convert_configs: [LaneRotateConversionConfig; 25], @@ -21,6 +21,7 @@ pub struct RhoConfig { } impl RhoConfig { + pub const OFFSET: usize = 2; pub fn configure( meta: &mut ConstraintSystem, state: [Column; 25], diff --git a/keccak256/src/gates/rho_checks.rs b/keccak256/src/gates/rho_checks.rs index f1f66c7968..c59e536ebe 100644 --- a/keccak256/src/gates/rho_checks.rs +++ b/keccak256/src/gates/rho_checks.rs @@ -780,7 +780,7 @@ impl BlockCountAccConfig { } } -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct BlockCountFinalConfig { q_enable: Selector, block_count_cols: [Column; 2], diff --git a/keccak256/src/gates/theta.rs b/keccak256/src/gates/theta.rs index 01f410d5aa..456139bfb3 100644 --- a/keccak256/src/gates/theta.rs +++ b/keccak256/src/gates/theta.rs @@ -10,13 +10,13 @@ use std::marker::PhantomData; #[derive(Clone, Debug)] pub struct ThetaConfig { - #[allow(dead_code)] q_enable: Selector, - state: [Column; 25], + pub(crate) state: [Column; 25], _marker: PhantomData, } impl ThetaConfig { + pub const OFFSET: usize = 2; pub fn configure( q_enable: Selector, meta: &mut ConstraintSystem, @@ -68,7 +68,9 @@ impl ThetaConfig { region: &mut Region<'_, F>, offset: usize, state: [F; 25], + out_state: [F; 25], ) -> Result<[F; 25], Error> { + self.q_enable.enable(region, offset)?; for (idx, lane) in state.iter().enumerate() { region.assign_advice( || format!("assign state {}", idx), @@ -77,7 +79,16 @@ impl ThetaConfig { || Ok(*lane), )?; } - Ok(state) + + for (idx, lane) in out_state.iter().enumerate() { + region.assign_advice( + || format!("assign out_state {}", idx), + self.state[idx], + offset + 1, + || Ok(*lane), + )?; + } + Ok(out_state) } } @@ -92,7 +103,6 @@ mod tests { plonk::{Advice, Circuit, Column, ConstraintSystem, Error}, }; use itertools::Itertools; - use num_bigint::BigUint; use pairing::{arithmetic::FieldExt, bn256::Fr as Fp}; use std::convert::TryInto; use std::marker::PhantomData; @@ -134,33 +144,18 @@ mod tests { || "assign input state", |mut region| { let offset = 0; - config.q_enable.enable(&mut region, offset)?; config.assign_state( &mut region, offset, self.in_state, - )?; - let offset = 1; - config.assign_state(&mut region, offset, self.out_state) + self.out_state, + ) }, )?; Ok(()) } } - fn big_uint_to_pallas(a: &BigUint) -> Fp { - let mut b: [u64; 4] = [0; 4]; - let mut iter = a.iter_u64_digits(); - - for i in &mut b { - *i = match &iter.next() { - Some(x) => *x, - None => 0u64, - }; - } - - Fp::from_raw(b) - } let input1: State = [ [1, 0, 0, 0, 0], @@ -174,12 +169,12 @@ mod tests { 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] = big_uint_to_pallas(&in_biguint[(x, y)]); + in_state[5 * x + y] = big_uint_to_field(&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] = big_uint_to_pallas(&s1_arith[(x, y)]); + out_state[5 * x + y] = big_uint_to_field(&s1_arith[(x, y)]); } let circuit = MyCircuit:: { diff --git a/keccak256/src/gates/xi.rs b/keccak256/src/gates/xi.rs index c9d381e388..5d2d601c4c 100644 --- a/keccak256/src/gates/xi.rs +++ b/keccak256/src/gates/xi.rs @@ -1,11 +1,11 @@ use halo2::{ - circuit::Region, + circuit::{Cell, Region}, plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector}, poly::Rotation, }; use itertools::Itertools; use pairing::arithmetic::FieldExt; -use std::marker::PhantomData; +use std::{convert::TryInto, marker::PhantomData}; #[derive(Clone, Debug)] pub struct XiConfig { @@ -16,6 +16,7 @@ pub struct XiConfig { } impl XiConfig { + pub const OFFSET: usize = 2; // We assume state is recieved in base-9. pub fn configure( q_enable: Selector, @@ -71,17 +72,34 @@ impl XiConfig { &self, region: &mut Region<'_, F>, offset: usize, - state: [F; 25], - ) -> Result<[F; 25], Error> { + state: [(Cell, F); 25], + out_state: [F; 25], + ) -> Result<[(Cell, F); 25], Error> { + self.q_enable.enable(region, offset)?; for (idx, lane) in state.iter().enumerate() { - region.assign_advice( + let obtained_cell = region.assign_advice( || format!("assign state {}", idx), self.state[idx], offset, - || Ok(*lane), + || Ok(lane.1), )?; + region.constrain_equal(lane.0, obtained_cell)?; } - Ok(state) + + let mut out_vec: Vec<(Cell, F)> = vec![]; + let out_state: [(Cell, F); 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, *lane)); + } + out_vec.try_into().unwrap() + }; + Ok(out_state) } } @@ -95,7 +113,6 @@ mod tests { use halo2::plonk::{Advice, Column, ConstraintSystem, Error}; use halo2::{circuit::SimpleFloorPlanner, dev::MockProver, plonk::Circuit}; use itertools::Itertools; - use num_bigint::BigUint; use pairing::arithmetic::FieldExt; use pairing::bn256::Fr as Fp; use std::convert::TryInto; @@ -109,6 +126,7 @@ mod tests { out_state: [F; 25], _marker: PhantomData, } + impl Circuit for MyCircuit { type Config = XiConfig; type FloorPlanner = SimpleFloorPlanner; @@ -121,7 +139,11 @@ mod tests { let q_enable = meta.complex_selector(); let state: [Column; 25] = (0..25) - .map(|_| meta.advice_column()) + .map(|_| { + let column = meta.advice_column(); + meta.enable_equality(column.into()); + column + }) .collect::>() .try_into() .unwrap(); @@ -134,37 +156,45 @@ 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: [(Cell, F); 25] = { + let mut state: Vec<(Cell, F)> = + 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, *val)) + } + state.try_into().unwrap() + }; + Ok(in_state) + }, + )?; + layouter.assign_region( || "assign input state", |mut region| { - let offset = 0; config.q_enable.enable(&mut region, offset)?; config.assign_state( &mut region, offset, - self.in_state, - )?; - let offset = 1; - config.assign_state(&mut region, offset, self.out_state) + in_state, + self.out_state, + ) }, )?; Ok(()) } } - fn big_uint_to_pallas(a: &BigUint) -> Fp { - let mut b: [u64; 4] = [0; 4]; - let mut iter = a.iter_u64_digits(); - - for i in &mut b { - *i = match &iter.next() { - Some(x) => *x, - None => 0u64, - }; - } - - Fp::from_raw(b) - } let input1: State = [ [1, 0, 0, 0, 0], @@ -178,12 +208,12 @@ mod tests { 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] = big_uint_to_pallas(&in_biguint[(x, y)]); + in_state[5 * x + y] = big_uint_to_field(&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] = big_uint_to_pallas(&s1_arith[(x, y)]); + out_state[5 * x + y] = big_uint_to_field(&s1_arith[(x, y)]); } let circuit = MyCircuit:: { in_state, diff --git a/keccak256/src/keccak_arith.rs b/keccak256/src/keccak_arith.rs index 43166bb6b1..eba9daabdf 100644 --- a/keccak256/src/keccak_arith.rs +++ b/keccak256/src/keccak_arith.rs @@ -60,7 +60,7 @@ impl KeccakFArith { out } - fn pi(a: &StateBigInt) -> StateBigInt { + pub fn pi(a: &StateBigInt) -> StateBigInt { let mut out = StateBigInt::default(); for (x, y) in (0..5).cartesian_product(0..5) { out[(y, (2 * x + 3 * y) % 5)] = a[(x, y)].clone(); @@ -98,6 +98,23 @@ impl KeccakFArith { out[(0, 0)] += convert_b2_to_b13(rc); out } + + pub fn mixing( + a: &StateBigInt, + next_input: Option<&State>, + rc: u64, + ) -> StateBigInt { + if let Some(next_input) = next_input { + let out_1 = KeccakFArith::absorb(a, next_input); + KeccakFArith::iota_b13(&out_1, rc) + } else { + let mut state = KeccakFArith::iota_b9(a, rc); + for (x, y) in (0..5).cartesian_product(0..5) { + state[(x, y)] = convert_b9_lane_to_b13(state[(x, y)].clone()); + } + state + } + } } pub struct Keccak { diff --git a/keccak256/src/lib.rs b/keccak256/src/lib.rs index 48ab016217..64262edcd6 100644 --- a/keccak256/src/lib.rs +++ b/keccak256/src/lib.rs @@ -2,6 +2,7 @@ // just used in tests pub mod arith_helpers; +//pub mod circuit; pub mod common; pub mod gates; // We build arith module to get test cases for the circuit