diff --git a/circuit-benchmarks/src/keccak_permutation.rs b/circuit-benchmarks/src/keccak_permutation.rs index cc4097dae8..7b8c74b8b1 100644 --- a/circuit-benchmarks/src/keccak_permutation.rs +++ b/circuit-benchmarks/src/keccak_permutation.rs @@ -1,28 +1,23 @@ //! Evm circuit benchmarks - use eth_types::Field; use halo2_proofs::{ - circuit::{AssignedCell, Layouter, SimpleFloorPlanner}, + circuit::{floor_planner::V1, AssignedCell, Layouter}, plonk::{Circuit, ConstraintSystem, Error}, }; -use keccak256::{ - common::NEXT_INPUTS_LANES, keccak_arith::KeccakFArith, permutation::circuit::KeccakFConfig, -}; +use keccak256::{circuit::KeccakConfig, common::NEXT_INPUTS_LANES, keccak_arith::KeccakFArith}; #[derive(Default, Clone)] -struct KeccakRoundTestCircuit { - in_state: [F; 25], - out_state: [F; 25], - next_mixing: Option<[F; NEXT_INPUTS_LANES]>, - is_mixing: bool, +struct KeccakTestCircuit { + input: Vec>, + output: [u8; 32], } -impl Circuit for KeccakRoundTestCircuit { - type Config = KeccakFConfig; - type FloorPlanner = SimpleFloorPlanner; +impl Circuit for KeccakTestCircuit { + type Config = KeccakConfig; + type FloorPlanner = V1; fn without_witnesses(&self) -> Self { - Self::default() + self.clone() } fn configure(meta: &mut ConstraintSystem) -> Self::Config { @@ -36,36 +31,11 @@ impl Circuit for KeccakRoundTestCircuit { ) -> Result<(), Error> { // Load the table config.load(&mut layouter)?; - let offset: usize = 0; - - let in_state = layouter.assign_region( - || "Keccak round witnes & flag assignment", - |mut region| { - // Witness `state` - let in_state: [AssignedCell; 25] = { - let mut state: Vec> = Vec::with_capacity(25); - for (idx, val) in self.in_state.iter().enumerate() { - let cell = region.assign_advice( - || "witness input state", - config.state[idx], - offset, - || Ok(*val), - )?; - state.push(cell) - } - state.try_into().unwrap() - }; - Ok(in_state) - }, - )?; - - config.assign_all( - &mut layouter, - in_state, - self.out_state, - self.is_mixing, - self.next_mixing, - )?; + let mut config = config.clone(); + + for input in self.input.iter() { + config.assign_hash(&mut layouter, input.as_slice(), self.output)?; + } Ok(()) } } @@ -93,42 +63,34 @@ mod tests { #[test] fn bench_keccak_round() { - let in_state: 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 input = vec![ + vec![ + 65u8, 108, 105, 99, 101, 32, 119, 97, 115, 32, 98, 101, 103, 105, 110, 110, 105, + 110, 103, 32, 116, 111, 32, 103, 101, 116, 32, 118, 101, 114, 121, 32, 116, 105, + 114, 101, 100, 32, 111, 102, 32, 115, 105, 116, 116, 105, 110, 103, 32, 98, 121, + 32, 104, 101, 114, 32, 115, 105, 115, 116, 101, 114, 32, 111, 110, 32, 116, 104, + 101, 32, 98, 97, 110, 107, 44, 32, 97, 110, 100, 32, 111, 102, 32, 104, 97, 118, + 105, 110, 103, 32, 110, 111, 116, 104, 105, 110, 103, 32, 116, 111, 32, 100, 111, + 58, 32, 111, 110, 99, 101, 32, 111, 114, 32, 116, 119, 105, 99, 101, 32, 115, 104, + 101, 32, 104, 97, 100, 32, 112, 101, 101, 112, 101, 100, 32, 105, 110, 116, 111, + 32, 116, 104, 101, 32, 98, 111, 111, 107, 32, 104, 101, 114, 32, 115, 105, 115, + 116, 101, 114, 32, 119, 97, 115, 32, 114, 101, 97, 100, 105, 110, 103, 44, 32, 98, + 117, 116, 32, 105, 116, 32, 104, 97, 100, 32, 110, 111, 32, 112, 105, 99, 116, 117, + 114, 101, 115, 32, 111, 114, 32, 99, 111, 110, 118, 101, 114, 115, 97, 116, 105, + 111, 110, 115, 32, 105, 110, 32, 105, 116, 44, 32, 97, 110, 100, 32, 119, 104, 97, + 116, 32, 105, 115, 32, 116, 104, 101, 32, 117, 115, 101, 32, 111, 102, 32, 97, 32, + 98, 111, 111, 107, 44, 32, 116, 104, 111, 117, 103, 104, 116, 32, 65, 108, 105, 99, + 101, 32, 119, 105, 116, 104, 111, 117, 116, 32, 112, 105, 99, 116, 117, 114, 101, + 115, 32, 111, 114, 32, 99, 111, 110, 118, 101, 114, 115, 97, 116, 105, 111, 110, + 115, 63, + ]; + 3000 ]; - - 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 output = [ + 60u8, 227, 142, 8, 143, 135, 108, 85, 13, 254, 190, 58, 30, 106, 153, 194, 188, 6, 208, + 49, 16, 102, 150, 120, 100, 130, 224, 177, 64, 98, 53, 252, ]; - let mut in_state_biguint = StateBigInt::default(); - - // Generate in_state as `[Fr;25]` - let mut in_state_fp: [Fr; 25] = [Fr::zero(); 25]; - for (x, y) in (0..5).cartesian_product(0..5) { - in_state_fp[5 * x + y] = biguint_to_f(&convert_b2_to_b13(in_state[x][y])); - in_state_biguint[(x, y)] = convert_b2_to_b13(in_state[x][y]); - } - - // Compute out_state_mix - let mut out_state_mix = in_state_biguint.clone(); - KeccakFArith::permute_and_absorb(&mut out_state_mix, Some(&next_input)); - - // Compute out_state_non_mix - let mut out_state_non_mix = in_state_biguint.clone(); - KeccakFArith::permute_and_absorb(&mut out_state_non_mix, None); - - // Generate out_state as `[Fr;25]` - let out_state_non_mix: [Fr; 25] = state_bigint_to_field(out_state_non_mix); - let constants_b13: Vec = ROUND_CONSTANTS .iter() .map(|num| biguint_to_f(&convert_b2_to_b13(*num))) @@ -140,12 +102,7 @@ mod tests { .collect(); // Build the circuit - let circuit = KeccakRoundTestCircuit:: { - in_state: in_state_fp, - out_state: out_state_non_mix, - next_mixing: None, - is_mixing: false, - }; + let circuit = KeccakTestCircuit { input, output }; let degree: u32 = var("DEGREE") .expect("No DEGREE env var was provided") @@ -196,7 +153,7 @@ mod tests { &verifier_params, pk.get_vk(), strategy, - &[&[constants_b9.as_slice(), constants_b13.as_slice()]], + &[], &mut verifier_transcript, ) .unwrap(); diff --git a/circuit-benchmarks/src/keccak_permutation_old.rs b/circuit-benchmarks/src/keccak_permutation_old.rs new file mode 100644 index 0000000000..d88e7709ec --- /dev/null +++ b/circuit-benchmarks/src/keccak_permutation_old.rs @@ -0,0 +1,199 @@ +//! Evm circuit benchmarks + +use eth_types::Field; +use halo2_proofs::{ + circuit::{AssignedCell, Layouter, SimpleFloorPlanner}, + plonk::{Circuit, ConstraintSystem, Error}, +}; +use keccak256::{ + common::NEXT_INPUTS_LANES, keccak_arith::KeccakFArith, permutation::circuit::KeccakFConfig, +}; + +#[derive(Default, Clone)] +struct KeccakRoundTestCircuit { + in_state: [F; 25], + out_state: [F; 25], + next_mixing: Option<[F; NEXT_INPUTS_LANES]>, + is_mixing: bool, +} + +impl Circuit for KeccakRoundTestCircuit { + type Config = KeccakFConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + Self::Config::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + // Load the table + config.load(&mut layouter)?; + let offset: usize = 0; + + let in_state = layouter.assign_region( + || "Keccak round witnes & flag assignment", + |mut region| { + // Witness `state` + let in_state: [AssignedCell; 25] = { + let mut state: Vec> = Vec::with_capacity(25); + for (idx, val) in self.in_state.iter().enumerate() { + let cell = region.assign_advice( + || "witness input state", + config.state[idx], + offset, + || Ok(*val), + )?; + state.push(cell) + } + state.try_into().unwrap() + }; + Ok(in_state) + }, + )?; + + config.assign_permutation(&mut layouter, in_state, self.is_mixing, self.next_mixing)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ark_std::{end_timer, start_timer}; + use halo2_proofs::plonk::{create_proof, keygen_pk, keygen_vk, verify_proof, SingleVerifier}; + use halo2_proofs::{ + pairing::bn256::{Bn256, Fr, G1Affine}, + poly::commitment::{Params, ParamsVerifier}, + transcript::{Blake2bRead, Blake2bWrite, Challenge255}, + }; + use itertools::Itertools; + use keccak256::common::PERMUTATION; + use keccak256::{ + arith_helpers::*, + common::{State, ROUND_CONSTANTS}, + gate_helpers::biguint_to_f, + }; + use rand::SeedableRng; + use rand_xorshift::XorShiftRng; + use std::env::var; + + #[test] + fn bench_keccak_round() { + let in_state: 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 mut in_state_biguint = StateBigInt::default(); + + // Generate in_state as `[Fr;25]` + let mut in_state_fp: [Fr; 25] = [Fr::zero(); 25]; + for (x, y) in (0..5).cartesian_product(0..5) { + in_state_fp[5 * x + y] = biguint_to_f(&convert_b2_to_b13(in_state[x][y])); + in_state_biguint[(x, y)] = convert_b2_to_b13(in_state[x][y]); + } + + // Compute out_state_mix + let mut out_state_mix = in_state_biguint.clone(); + KeccakFArith::permute_and_absorb(&mut out_state_mix, Some(next_input)); + + // Compute out_state_non_mix + let mut out_state_non_mix = in_state_biguint.clone(); + KeccakFArith::permute_and_absorb(&mut out_state_non_mix, None); + + // Generate out_state as `[Fr;25]` + let out_state_non_mix: [Fr; 25] = state_bigint_to_field(out_state_non_mix); + + let constants_b13: Vec = ROUND_CONSTANTS + .iter() + .map(|num| biguint_to_f(&convert_b2_to_b13(*num))) + .collect(); + + let constants_b9: Vec = ROUND_CONSTANTS + .iter() + .map(|num| biguint_to_f(&convert_b2_to_b9(*num))) + .collect(); + + // Build the circuit + let circuit = KeccakRoundTestCircuit:: { + in_state: in_state_fp, + out_state: out_state_non_mix, + next_mixing: None, + is_mixing: false, + }; + + let degree: u32 = var("DEGREE") + .expect("No DEGREE env var was provided") + .parse() + .expect("Cannot parse DEGREE env var as u32"); + + let rng = XorShiftRng::from_seed([ + 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, + 0xbc, 0xe5, + ]); + + // Bench setup generation + let setup_message = format!("Setup generation with degree = {}", degree); + let start1 = start_timer!(|| setup_message); + let general_params: Params = Params::::unsafe_setup::(degree); + end_timer!(start1); + + let vk = keygen_vk(&general_params, &circuit).unwrap(); + let pk = keygen_pk(&general_params, vk, &circuit).unwrap(); + + // Prove + let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); + + // Bench proof generation time + let proof_message = format!("EVM Proof generation with {} rows", degree); + let start2 = start_timer!(|| proof_message); + create_proof( + &general_params, + &pk, + &[circuit], + &[&[constants_b9.as_slice(), constants_b13.as_slice()]], + rng, + &mut transcript, + ) + .unwrap(); + let proof = transcript.finalize(); + end_timer!(start2); + + // Verify + let verifier_params: ParamsVerifier = + general_params.verifier(PERMUTATION * 2).unwrap(); + let mut verifier_transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); + let strategy = SingleVerifier::new(&verifier_params); + + // Bench verification time + let start3 = start_timer!(|| "EVM Proof verification"); + verify_proof( + &verifier_params, + pk.get_vk(), + strategy, + &[&[constants_b9.as_slice(), constants_b13.as_slice()]], + &mut verifier_transcript, + ) + .unwrap(); + end_timer!(start3); + } +} diff --git a/circuit-benchmarks/src/lib.rs b/circuit-benchmarks/src/lib.rs index bcc8272fbd..f6302485cd 100644 --- a/circuit-benchmarks/src/lib.rs +++ b/circuit-benchmarks/src/lib.rs @@ -8,6 +8,10 @@ pub mod state_circuit; #[cfg(feature = "benches")] pub mod bench_params; +// #[cfg(test)] +// #[cfg(feature = "benches")] +// pub mod keccak_permutation; + #[cfg(test)] #[cfg(feature = "benches")] pub mod keccak_permutation; diff --git a/gadgets/src/expression.rs b/gadgets/src/expression.rs new file mode 100644 index 0000000000..8e2c75ca50 --- /dev/null +++ b/gadgets/src/expression.rs @@ -0,0 +1,117 @@ +//! This module contains a collection of utility functions that help to +//! construct expressions to be used inside gate constraints. + +use digest::generic_array::typenum::Exp; +use eth_types::Field; +use halo2_proofs::plonk::Expression; + +/// This function generates a Lagrange polynomial in the range [start, end) +/// which will be evaluated to 1 when `exp == value`, otherwise 0 +pub fn generate_lagrange_base_polynomial>( + exp: Expression, + val: usize, + range: R, +) -> Expression { + let mut numerator = Expression::Constant(F::one()); + let mut denominator = F::one(); + for x in range { + if x != val { + numerator = numerator * (exp.clone() - Expression::Constant(F::from(x as u64))); + denominator *= F::from(val as u64) - F::from(x as u64); + } + } + numerator * denominator.invert().unwrap() +} + +/// Generate an expression which constraints an expression to be boolean. +/// Returns 0 if it is. +/// Based on: `(1-expr) * expr = 0` only if `expr` is boolean. +pub fn bool_constraint_expr(exp: Expression) -> Expression { + (Expression::Constant(F::one()) - exp.clone()) * exp +} + +/// Generates an expression which equals 0 if the expr is within the range +/// provided by the user. It does so by: `(range[0] - expr) * ... * (range[N-1] +/// - expr)`. +pub fn is_within_range>( + expr: Expression, + range: R, +) -> Expression { + range + .into_iter() + .fold(Expression::Constant(F::one()), |acc, x| { + acc * (Expression::Constant(F::from(x as u64)) - expr.clone()) + }) +} + +/// Performs `base.pow(exp)` with log(N) complexity by squaring the base and +/// multiplying the coefficients until the exp is achieved. +pub fn square_and_multiply(base: Expression, exp: usize) -> Expression { + let mut powers = vec![]; + let mut acc = base.clone(); + let mut exp_acc = 2usize; + let final_exp = if exp.is_power_of_two() { + exp + } else { + exp.next_power_of_two() + }; + // Fill the buckets with [base, base^2, base^4.... base^(log2(exp))] + while exp_acc <= final_exp { + acc = acc.clone() * base.clone(); + powers.push((acc.clone(), exp)); + exp_acc *= 2; + } + + // If the exp was a power of two, we can return the last expr computed. + if exp.is_power_of_two() { + return powers.last().unwrap().0.clone(); + } + + let mut remainder = exp - powers.last().unwrap().1; + for (base_power, exp) in powers.iter().rev() { + if *exp <= remainder { + remainder = remainder - exp; + acc = acc * base_power.clone(); + } + } + + acc +} + +/// Utilities to generate expressions related to Random Linear Combination +/// procedures. +pub mod rlc { + use super::*; + + /// Generate an Expression which computes the RLC for the given expressions + /// and randomness. + /// It also computes the powers of the randomness needed to compute the RLC + /// for the entire expr set. + pub fn compose( + expressions: &[Expression], + mut randomness: Expression, + ) -> Expression { + let og_rand = randomness.clone(); + let mut rlc = expressions[0].clone(); + for expression in expressions[1..].iter() { + rlc = rlc + expression.clone() * randomness.clone(); + randomness = randomness * og_rand.clone(); + } + rlc + } + + /// Generate an Expression which computes the RLC for the given expressions + /// and randomness. + pub fn compose_with_powers( + expressions: &[Expression], + powers_of_randomness: &[Expression], + ) -> Expression { + debug_assert!(expressions.len() <= powers_of_randomness.len() + 1); + + let mut rlc = expressions[0].clone(); + for (expression, randomness) in expressions[1..].iter().zip(powers_of_randomness.iter()) { + rlc = rlc + expression.clone() * randomness.clone(); + } + rlc + } +} diff --git a/gadgets/src/lib.rs b/gadgets/src/lib.rs index 4d92a065ad..e4fac9a08a 100644 --- a/gadgets/src/lib.rs +++ b/gadgets/src/lib.rs @@ -12,6 +12,7 @@ #![deny(clippy::debug_assert_with_mut_call)] pub mod evm_word; +pub mod expression; pub mod is_zero; pub mod monotone; diff --git a/keccak256/src/circuit.rs b/keccak256/src/circuit.rs index 1172fa79d2..336c098065 100644 --- a/keccak256/src/circuit.rs +++ b/keccak256/src/circuit.rs @@ -1,3 +1,30 @@ +use self::padding::PaddingConfig; +use crate::common::State; +use crate::rlc::RlcConfig; +use crate::{ + keccak_arith::Keccak, + permutation::{ + base_conversion::BaseConversionConfig, + circuit::KeccakFConfig, + generic::GenericConfig, + mixing::MixingConfig, + tables::{FromBase9TableConfig, FromBinaryTableConfig, RangeCheckConfig, StackableTable}, + NextInput, PermutationInputs, + }, +}; +use eth_types::Field; +use gadgets::expression::*; +use halo2_proofs::circuit::{layouter, Region}; +use halo2_proofs::plonk::{Assignment, TableColumn}; +use halo2_proofs::{ + circuit::{AssignedCell, Layouter}, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Instance, Selector}, + poly::Rotation, +}; +use itertools::Itertools; +use std::convert::TryFrom; +use std::convert::TryInto; + pub mod padding; pub mod word_builder; @@ -5,4 +32,953 @@ pub const MAX_INPUT_BYTES: usize = MAX_INPUT_WORDS * BYTES_PER_WORD; pub const MAX_INPUT_WORDS: usize = MAX_PERM_ROUNDS * NEXT_INPUTS_WORDS; pub const BYTES_PER_WORD: usize = 8; pub const NEXT_INPUTS_WORDS: usize = 17; +pub const NEXT_INPUTS_BYTES: usize = NEXT_INPUTS_WORDS * 8; +pub const STATE_WORDS: usize = 25; pub const MAX_PERM_ROUNDS: usize = 10; + +pub type AssignedState = [AssignedCell; STATE_WORDS]; +pub type AssignedNextInput = [AssignedCell; NEXT_INPUTS_BYTES]; + +#[derive(Debug, Clone, Copy)] +/// Represents the State tag that represents which State is the permutation +/// representing. +pub enum StateTag { + Start, + Continue, + Finalize, + End, +} + +impl StateTag { + pub fn into_f(&self) -> F { + match self { + StateTag::Start => F::zero(), + StateTag::Continue => F::one(), + StateTag::Finalize => F::from(2u64), + StateTag::End => F::from(3u64), + } + } + + pub fn is_continue(&self) -> bool { + match self { + StateTag::Continue => true, + _ => false, + } + } + + pub fn is_finalize(&self) -> bool { + match self { + StateTag::Finalize => true, + _ => false, + } + } + + pub fn is_end(&self) -> bool { + match self { + StateTag::End => true, + _ => false, + } + } +} + +#[derive(Debug, Clone)] +pub struct AssignedPermInput { + state_tag: AssignedCell, + input_len: AssignedCell, + input: AssignedCell, + perm_count: AssignedCell, + acc_input: AssignedCell, + output: AssignedCell, +} + +impl AssignedPermInput { + fn new( + state_tag: AssignedCell, + input_len: AssignedCell, + input: AssignedCell, + perm_count: AssignedCell, + acc_input: AssignedCell, + output: AssignedCell, + ) -> Self { + Self { + state_tag, + input_len, + input, + perm_count, + acc_input, + output, + } + } +} + +#[derive(Debug, Clone)] +pub struct KeccakConfig { + base_conv_b9_b13: BaseConversionConfig, + base_conv_b2_b9: BaseConversionConfig, + pub(crate) keccak_f_config: KeccakFConfig, + pub(crate) padding_config: PaddingConfig, + q_enable: Selector, + state: [Column; STATE_WORDS], + next_inputs: [Column; NEXT_INPUTS_BYTES + 1], + state_tag: Column, + input_len: Column, + input: Column, + perm_count: Column, + acc_input: Column, + output_rlc: Column, + range_check_config: RangeCheckConfig, + state_rlc_config: RlcConfig, + absorb_inputs_rlc_config: RlcConfig, + acc_input_rlc_config: RlcConfig, + randomness: Column, + last_state: Option>, + last_perm: Option>, + latest_acc_randomness: Option>, +} + +impl KeccakConfig { + pub fn configure(meta: &mut ConstraintSystem) -> Self { + let state = [(); 25].map(|_| meta.advice_column()).map(|col| { + meta.enable_equality(col); + col + }); + + // The first position always stores the latest acc_input. + let next_inputs = [(); NEXT_INPUTS_BYTES + 1] + .map(|_| meta.advice_column()) + .map(|col| { + meta.enable_equality(col); + col + }); + + let fixed = meta.fixed_column(); + let generic = GenericConfig::configure(meta, state[0..3].try_into().unwrap(), fixed); + let table_cols: [TableColumn; 3] = (0..3) + .map(|_| meta.lookup_table_column()) + .collect_vec() + .try_into() + .unwrap(); + let stackable = + StackableTable::configure(meta, state[0..3].try_into().unwrap(), table_cols); + + let base_conv_lane = meta.advice_column(); + meta.enable_equality(base_conv_lane); + + let flag = meta.advice_column(); + meta.enable_equality(flag); + + let randomness = meta.advice_column(); + meta.enable_equality(randomness); + + let padding_config = PaddingConfig::configure(meta); + + let table_b9 = FromBase9TableConfig::configure(meta); + let table_b2 = FromBinaryTableConfig::configure(meta); + let base_conv_b9_b13 = BaseConversionConfig::configure( + meta, + table_b9.get_base_info(false), + base_conv_lane, + flag, + state[0..5].try_into().unwrap(), + ); + let base_conv_b2_b9 = BaseConversionConfig::configure( + meta, + table_b2.get_base_info(true), + base_conv_lane, + flag, + state[0..5].try_into().unwrap(), + ); + + let q_enable = meta.complex_selector(); + let state_tag = meta.advice_column(); + meta.enable_equality(state_tag); + let input_len = meta.advice_column(); + meta.enable_equality(input_len); + let input = meta.advice_column(); + meta.enable_equality(input); + let perm_count = meta.advice_column(); + meta.enable_equality(perm_count); + let acc_input = meta.advice_column(); + meta.enable_equality(acc_input); + let output_rlc = meta.advice_column(); + meta.enable_equality(output_rlc); + let range_check_136 = RangeCheckConfig::::configure(meta); + + let state_rlc_config = RlcConfig::configure(meta, state, randomness, output_rlc); + let absorb_inputs_rlc_config = RlcConfig::configure( + meta, + next_inputs[1..].try_into().unwrap(), + randomness, + output_rlc, + ); + let acc_input_rlc_config = RlcConfig::configure(meta, next_inputs, randomness, acc_input); + + let keccak_f_config = KeccakFConfig::configure( + meta, + state, + base_conv_b9_b13.clone(), + base_conv_b2_b9.clone(), + base_conv_lane, + flag, + generic, + stackable, + ); + + // Lookup to activate the valid Finalize place check + // `(curr.perm_count * 136 - input_len) in 1~136` + meta.lookup("Valid Finalize place", |meta| { + let q_enable = meta.query_selector(q_enable); + let state_tag = meta.query_advice(state_tag, Rotation::cur()); + let input_len = meta.query_advice(input_len, Rotation::cur()); + let perm_count = meta.query_advice(perm_count, Rotation::cur()); + let state_finalize = generate_lagrange_base_polynomial(state_tag, 2, 0..=3); + let perm_lookup_val = perm_count * Expression::Constant(F::from(136u64)) - input_len; + vec![( + q_enable * state_finalize * perm_lookup_val, + range_check_136.range, + )] + }); + + meta.create_gate("State gate", |meta| { + let q_enable = meta.query_selector(q_enable); + let randomness = meta.query_advice(randomness, Rotation::cur()); + let next_state_tag = meta.query_advice(state_tag, Rotation::next()); + let state_tag = meta.query_advice(state_tag, Rotation::cur()); + let input_len = meta.query_advice(input_len, Rotation::cur()); + let next_input = meta.query_advice(input, Rotation::next()); + let input = meta.query_advice(input, Rotation::cur()); + let next_perm_count = meta.query_advice(perm_count, Rotation::next()); + let perm_count = meta.query_advice(perm_count, Rotation::cur()); + let next_acc_input = meta.query_advice(acc_input, Rotation::next()); + let acc_input = meta.query_advice(acc_input, Rotation::cur()); + let expected_output_rlc = meta.query_advice(output_rlc, Rotation::next()); + let output_rlc = meta.query_advice(output_rlc, Rotation::cur()); + + let state_start = generate_lagrange_base_polynomial(state_tag.clone(), 0, 0..=3); + let state_continue = generate_lagrange_base_polynomial(state_tag.clone(), 1, 0..=3); + let state_finalize = generate_lagrange_base_polynomial(state_tag.clone(), 2, 0..=3); + let state_end = generate_lagrange_base_polynomial(state_tag, 3, 0..=3); + + let next_state_start = Expression::Constant(F::one()) + - generate_lagrange_base_polynomial(next_state_tag.clone(), 0, 0..=3); + let next_state_continue = Expression::Constant(F::one()) + - generate_lagrange_base_polynomial(next_state_tag.clone(), 1, 0..=3); + let next_state_finalize = Expression::Constant(F::one()) + - generate_lagrange_base_polynomial(next_state_tag.clone(), 2, 0..=3); + let next_state_end = Expression::Constant(F::one()) + - generate_lagrange_base_polynomial(next_state_tag.clone(), 3, 0..=3); + + // We need to make sure that the lagrange interpolation results are boolean. + // This expressions will be zero if the values are boolean. + let is_bool_cur_state_start = bool_constraint_expr(state_start.clone()); + let is_bool_cur_state_continue = bool_constraint_expr(state_continue.clone()); + let is_bool_cur_state_finalize = bool_constraint_expr(state_finalize.clone()); + let is_bool_cur_state_end = bool_constraint_expr(state_end.clone()); + + let is_bool_next_state_start = bool_constraint_expr(next_state_start.clone()); + let is_bool_next_state_continue = bool_constraint_expr(next_state_continue.clone()); + let is_bool_next_state_finalize = bool_constraint_expr(next_state_finalize.clone()); + let is_bool_next_state_end = bool_constraint_expr(next_state_end.clone()); + + let bool_state_checks = q_enable.clone() + * (is_bool_cur_state_start + + is_bool_cur_state_continue + + is_bool_cur_state_finalize + + is_bool_cur_state_end + + is_bool_next_state_start + + is_bool_next_state_continue + + is_bool_next_state_finalize + + is_bool_next_state_end); + // ------------------------------------------------------- // + // --------------------- Start State --------------------- // + // ------------------------------------------------------- // + + // Constrain `current_state_tag = Start` && `next_state_tag in (Continue, + // Finalize, End)` Note that the second condition is constrained via + // negation. If it's not within `(Continue, Finalize, End)` it has to be + // `Start`. + // + // Check `input_len === input === perm_count === acc_input === output === 0` + // We do also need to make sure that we can't add non-binary numbers and get a + // 0. + let zero_assumptions = bool_constraint_expr(input_len.clone()) + + input_len.clone() + + bool_constraint_expr(input.clone()) + + input + + bool_constraint_expr(perm_count.clone()) + + perm_count.clone() + + bool_constraint_expr(acc_input.clone()) + + acc_input.clone() + + bool_constraint_expr(output_rlc.clone()) + + output_rlc; + + let start_constraint = + (q_enable.clone() * state_start) * (next_state_start + zero_assumptions); + + // ------------------------------------------------------- // + // -------------------- Continue State ------------------- // + // ------------------------------------------------------- // + + // TODO: Figure out if the constraint for each permutation is needed. + let absortion_check = { + // `next.state_tag === Finalize` + let next_input_is_zero = + next_input.clone() + bool_constraint_expr(next_input.clone()); + let have_to_pad_and_absorb = input_len + - (Expression::Constant(F::from(136u64)) + * (perm_count.clone() + Expression::Constant(F::one()))); + + // Absortion check + state_continue.clone() + * have_to_pad_and_absorb + * (next_state_finalize.clone() + next_input_is_zero) + //* (expected_output_rlc - output_rlc) + // Not sure this is needed. We will end up with the final + // out_rlc which is the one we will use and it's linked to all + // the previous ones. + }; + + // `next.acc_input === curr.acc_input * r**136 + next.input` + let next_row_validity_input = next_acc_input.clone() + - (acc_input.clone() * square_and_multiply(randomness, 136)) + + next_input; + // `next.perm_count === curr.perm_count + 1` + let next_row_validity_perm_count = + next_perm_count.clone() - perm_count + Expression::Constant(F::one()); + // `next.state_tag in (Continue, Finalize)` + let next_state_continue_or_finalize = + next_state_finalize.clone() * next_state_continue.clone(); + + // Next Row validity + State transition Continue + let continue_constraint = (q_enable.clone() * state_continue) + * (next_row_validity_input + + next_row_validity_perm_count + + next_state_continue_or_finalize); + + // ------------------------------------------------------- // + // -------------------- Finalize State ------------------- // + // ------------------------------------------------------- // + + // Note that `(curr.perm_count * 136 - input_len) in 1~136` is checked in the + // lookup table above. + + let next_row_validity_perm_count = next_perm_count - Expression::Constant(F::one()); + let next_row_validity_input = next_acc_input - acc_input; + + // State transition: (Continue, Finalize, End) all 3 states allowed + let state_transition_finalize = + next_state_continue * next_state_finalize * next_state_end.clone(); + + let finalize_constraint = (q_enable * state_finalize) + * (state_transition_finalize + + next_row_validity_input + + next_row_validity_perm_count); + + // ------------------------------------------------------- // + // ---------------------- End State ---------------------- // + // ------------------------------------------------------- // + + // State transition: next.state_tag === End + let end_constraint = state_end * next_state_end; + + vec![ + bool_state_checks, + start_constraint, + continue_constraint, + finalize_constraint, + end_constraint, + ] + }); + + Self { + base_conv_b9_b13, + base_conv_b2_b9, + keccak_f_config, + padding_config, + q_enable, + state, + next_inputs, + state_tag, + input_len, + input, + perm_count, + acc_input, + output_rlc, + range_check_config: range_check_136, + state_rlc_config, + absorb_inputs_rlc_config, + randomness, + acc_input_rlc_config, + last_state: None, + last_perm: None, + latest_acc_randomness: None, + } + } + + pub fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { + self.keccak_f_config.load(layouter)?; + self.range_check_config.load(layouter) + } + + pub fn assign_hash( + &mut self, + layouter: &mut impl Layouter, + hash_data: &[u8], + output: [u8; 32], + ) -> Result<(), Error> { + //panic!("Start"); + // If this is the first hash, we need to add an `Start` state first. + if self.last_state.is_none() { + //panic!("Assign start state"); + let (start_state_perm, init_state, _init_absorb) = self.assign_start_state(layouter)?; + //panic!("Start state assigned"); + self.last_state = Some(init_state); + self.last_perm = Some(start_state_perm); + self.latest_acc_randomness = Some(layouter.assign_region( + || "Assign init_state", + |mut region| { + // Dummy randomness + region.assign_advice(|| "randomness", self.randomness, 0, || Ok(F::one())) + }, + )?); + } + + // Fetch all the permutations we will need to assign. + let perm_inputs = PermutationInputs::::from_bytes(hash_data); + //panic!("We have the perm inputs!"); + + // Init dummy randomness + let randomness = layouter.assign_region( + || "Assign init_state", + |mut region| { + // Dummy randomness + region.assign_advice(|| "randomness", self.randomness, 0, || Ok(F::one())) + }, + )?; + + // Init acc_len for this hash input. + let mut acc_len = 0; + + // Assign the first permutation of the hash. + acc_len += perm_inputs.0.first().unwrap().og_len; + let (first_perm, first_state_out) = self.assign_permutation( + layouter, + self.last_state.clone().unwrap(), + self.last_perm.clone().unwrap(), + perm_inputs.0.first().cloned().unwrap(), + acc_len, + randomness.clone(), + )?; + println!("Assigned permutation first instance"); + // Store the actual latest state and permutation inside the config. + self.last_state = Some(first_state_out); + self.last_perm = Some(first_perm); + + for next_perm in perm_inputs.0.iter().skip(1) { + //panic!("inside perm"); + acc_len += perm_inputs.0.first().unwrap().og_len; + let (perm, state) = self.assign_permutation( + layouter, + self.last_state.clone().unwrap(), + self.last_perm.clone().unwrap(), + next_perm.clone(), + acc_len, + randomness.clone(), + )?; + println!("Assigned permutation"); + // Store the actual latest state and permutation inside the config. + self.last_state = Some(state); + self.last_perm = Some(perm); + } + + // TODO: Include the input_RLC and output_RLC into the lookup table as an entry. + + Ok(()) + } + + pub fn assign_start_state( + &self, + layouter: &mut impl Layouter, + ) -> Result<(AssignedPermInput, AssignedState, AssignedNextInput), Error> { + let (state_tag, input_len, input, perm_count, acc_input, output, in_state, absorb_inputs) = + layouter.assign_region( + || "Start state", + |mut region| { + let state_tag = region.assign_advice( + || "State_tag ", + self.state_tag, + 0, + || Ok(F::zero()), + )?; + let input_len = region.assign_advice( + || "Input len", + self.input_len, + 0, + || Ok(F::zero()), + )?; + let input = + region.assign_advice(|| "Input rlc", self.input, 0, || Ok(F::zero()))?; + let perm_count = region.assign_advice( + || "Perm count", + self.perm_count, + 0, + || Ok(F::zero()), + )?; + let acc_input = region.assign_advice( + || "Acc Input", + self.acc_input, + 0, + || Ok(F::zero()), + )?; + + let output = region.assign_advice( + || "Acc Input", + self.output_rlc, + 0, + || Ok(F::zero()), + )?; + + let state: [AssignedCell; STATE_WORDS] = (0..STATE_WORDS) + .map(|idx| -> Result, Error> { + region.assign_advice( + || "input_state init", + self.state[idx], + 0, + || Ok(F::zero()), + ) + }) + .collect::>, Error>>()? + .try_into() + .unwrap(); + + // The first position is reserved to the + let absorb_inputs: [AssignedCell; NEXT_INPUTS_BYTES] = (1 + ..=NEXT_INPUTS_BYTES) + .map(|idx| -> Result, Error> { + region.assign_advice( + || "input_state init", + self.next_inputs[idx], + 0, + || Ok(F::zero()), + ) + }) + .collect::>, Error>>()? + .try_into() + .unwrap(); + Ok(( + state_tag, + input_len, + input, + perm_count, + acc_input, + output, + state, + absorb_inputs, + )) + }, + )?; + Ok(( + AssignedPermInput { + state_tag, + input_len, + input, + perm_count, + acc_input, + output, + }, + in_state, + absorb_inputs, + )) + } + + pub fn assign_end_state( + &mut self, + layouter: &mut impl Layouter, + ) -> Result<(AssignedPermInput, AssignedState, AssignedNextInput), Error> { + let (state_tag, input_len, input, perm_count, acc_input, output, in_state, absorb_inputs) = + layouter.assign_region( + || "End state", + |mut region| { + let state_tag = region.assign_advice( + || "State_tag ", + self.state_tag, + 0, + || Ok(StateTag::End.into_f()), + )?; + let input_len = region.assign_advice( + || "Input len", + self.input_len, + 0, + || Ok(F::zero()), + )?; + let input = + region.assign_advice(|| "Input rlc", self.input, 0, || Ok(F::zero()))?; + let perm_count = region.assign_advice( + || "Perm count", + self.perm_count, + 0, + || Ok(F::zero()), + )?; + let acc_input = region.assign_advice( + || "Acc Input", + self.acc_input, + 0, + || Ok(F::zero()), + )?; + + let output = region.assign_advice( + || "Acc Input", + self.output_rlc, + 0, + || Ok(F::zero()), + )?; + + let state: [AssignedCell; STATE_WORDS] = (0..STATE_WORDS) + .map(|idx| -> Result, Error> { + region.assign_advice( + || "input_state init", + self.state[idx], + 0, + || Ok(F::zero()), + ) + }) + .collect::>, Error>>()? + .try_into() + .unwrap(); + let absorb_inputs: [AssignedCell; NEXT_INPUTS_BYTES] = (1 + ..NEXT_INPUTS_BYTES + 1) + .map(|idx| -> Result, Error> { + region.assign_advice( + || "input_state init", + self.next_inputs[idx], + 0, + || Ok(F::zero()), + ) + }) + .collect::>, Error>>()? + .try_into() + .unwrap(); + Ok(( + state_tag, + input_len, + input, + perm_count, + acc_input, + output, + state, + absorb_inputs, + )) + }, + )?; + Ok(( + AssignedPermInput { + state_tag, + input_len, + input, + perm_count, + acc_input, + output, + }, + in_state, + absorb_inputs, + )) + } + + /// Assigns a permutation + fn assign_permutation( + &mut self, + layouter: &mut impl Layouter, + in_state: AssignedState, + prev_perm: AssignedPermInput, + perm: NextInput, + acc_len: usize, + randomness: AssignedCell, + ) -> Result<(AssignedPermInput, AssignedState), Error> { + let absorb_inputs = layouter.assign_region( + || "Assign perm_absorb inputs", + |mut region| { + let absorb_inputs: [AssignedCell; NEXT_INPUTS_BYTES] = (1..NEXT_INPUTS_BYTES + + 1) + .map(|idx| -> Result, Error> { + region.assign_advice( + || "input_state init", + self.next_inputs[idx], + 0, + || Ok(perm.unpadded_bytes[idx]), + ) + }) + .collect::>, Error>>()? + .try_into() + .unwrap(); + Ok(absorb_inputs) + }, + )?; + + let out_state = self.assign_permutation_and_padding( + layouter, + in_state, + perm.unpadded_bytes, + perm.state_tag.is_continue() || perm.state_tag.is_end(), + acc_len, + perm.og_len, + )?; + + let out_state_rlc = + self.state_rlc_config + .assign_rlc(layouter, out_state.clone(), randomness.clone())?; + + let input_rlc = self.absorb_inputs_rlc_config.assign_rlc( + layouter, + absorb_inputs.clone(), + randomness, + )?; + + let (acc_input, last_randomness) = self + .acc_input_rlc_config + .assign_rlc_retunring_last_randomness( + layouter, + [prev_perm.acc_input.clone()] + .iter() + .chain(absorb_inputs.iter()) + .cloned() + .collect_vec() + .try_into() + .unwrap(), + self.latest_acc_randomness.as_ref().cloned().unwrap(), + )?; + self.latest_acc_randomness = Some(last_randomness); + + let assigned_perm_input = layouter.assign_region( + || "Permutation assignation", + |mut region| { + // Enable selector for the current state "row". + self.q_enable.enable(&mut region, 0)?; + let state_tag = region.assign_advice( + || "State_tag ", + self.state_tag, + 1, + || Ok(perm.state_tag.into_f()), + )?; + prev_perm.state_tag.copy_advice( + || "next_state_tag", + &mut region, + self.state_tag, + 0, + )?; + let input_len = region.assign_advice( + || "Input len", + self.input_len, + 1, + || Ok(F::from(perm.og_len as u64)), + )?; + prev_perm.input_len.copy_advice( + || "Next input len", + &mut region, + self.input_len, + 0, + )?; + + prev_perm + .input + .copy_advice(|| "Input rlc", &mut region, self.input, 0)?; + let input_rlc = + input_rlc.copy_advice(|| "Next Input rlc", &mut region, self.input, 1)?; + + prev_perm.perm_count.copy_advice( + || "Perm count", + &mut region, + self.perm_count, + 0, + )?; + let perm_count = region.assign_advice( + || "Next Perm count", + self.perm_count, + 1, + || Ok(F::from(perm.perm_count as u64)), + )?; + + let output = region.assign_advice( + || "Output rlc", + self.output_rlc, + 1, + || Ok(out_state_rlc.value().copied().ok_or(Error::Synthesis)?), + )?; + prev_perm.output.copy_advice( + || "Next output rlc", + &mut region, + self.output_rlc, + 0, + )?; + + prev_perm + .acc_input + .copy_advice(|| "Acc input", &mut region, self.acc_input, 0)?; + let acc_input = + acc_input.copy_advice(|| "Next acc_input", &mut region, self.acc_input, 1)?; + + // TODO: Assign expected_out_rlc so that we can constrain it. + // Pending to see if the constraint applies or not. + + Ok(AssignedPermInput { + state_tag, + input: input_rlc, + input_len, + perm_count, + acc_input, + output, + }) + }, + )?; + + Ok((assigned_perm_input, out_state)) + } + + pub(crate) fn assign_permutation_and_padding( + &self, + layouter: &mut impl Layouter, + in_state: [AssignedCell; STATE_WORDS], + unpadded: [F; NEXT_INPUTS_BYTES], + is_finalize: bool, + acc_len: usize, + input_len: usize, + ) -> Result<[AssignedCell; 25], Error> { + // TODO: Copy the input_len and acc_len instead of assign each time (See padding + // config). + // + // TODO: Handle byteRLC. + + // Assign padding for each one of the cells + let padded_input = self.padding_config.assign_region( + layouter, + is_finalize, + input_len, + acc_len, + unpadded, + )?; + + let out_state = self.keccak_f_config.assign_permutation( + layouter, + in_state, + !is_finalize, + padded_input, + )?; + + Ok(out_state) + } +} + +pub(crate) fn compute_rlc_cells( + witness: &[AssignedCell; N], + randomness: AssignedCell, +) -> Result { + let og_randomness = randomness.value().cloned().unwrap_or_default(); + let mut randomness = og_randomness.clone(); + let witness = witness + .iter() + .map(|w| w.value().cloned().unwrap_or_default()) + .collect::>(); + let mut rlc = witness[0].clone(); + + // Compute rlc + for value in witness[1..].iter() { + rlc = rlc + value.clone() * randomness.clone(); + randomness = randomness * og_randomness.clone(); + } + + Ok(rlc) +} + +pub(crate) fn compute_rlc_field(witness: &[F; N], randomness: F) -> F { + let og_randomness = randomness; + let mut randomness = og_randomness.clone(); + let mut rlc = witness[0].clone(); + + // Compute rlc + for value in witness[1..].iter() { + rlc = rlc + value.clone() * randomness.clone(); + randomness = randomness * og_randomness.clone(); + } + + rlc +} + +#[cfg(test)] +mod tests { + use super::*; + use halo2_proofs::circuit::floor_planner::V1; + use halo2_proofs::circuit::Layouter; + use halo2_proofs::pairing::bn256::Fr as Fp; + use halo2_proofs::plonk::{ConstraintSystem, Error, Instance}; + use halo2_proofs::{circuit::SimpleFloorPlanner, dev::MockProver, plonk::Circuit}; + use pretty_assertions::assert_eq; + use std::convert::TryInto; + #[derive(Default, Clone)] + struct KeccakTestCircuit { + input: Vec>, + output: [u8; 32], + } + + impl Circuit for KeccakTestCircuit { + type Config = KeccakConfig; + type FloorPlanner = V1; + + fn without_witnesses(&self) -> Self { + self.clone() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + Self::Config::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + // Load the table + config.load(&mut layouter)?; + let mut config = config.clone(); + + for input in self.input.iter() { + config.assign_hash(&mut layouter, input.as_slice(), self.output)?; + } + Ok(()) + } + } + + #[test] + fn get_keccak_info() { + let input = vec![ + vec![ + 65u8, 108, 105, 99, 101, 32, 119, 97, 115, 32, 98, 101, 103, 105, 110, 110, 105, + 110, 103, 32, 116, 111, 32, 103, 101, 116, 32, 118, 101, 114, 121, 32, 116, 105, + 114, 101, 100, 32, 111, 102, 32, 115, 105, 116, 116, 105, 110, 103, 32, 98, 121, + 32, 104, 101, 114, 32, 115, 105, 115, 116, 101, 114, 32, 111, 110, 32, 116, 104, + 101, 32, 98, 97, 110, 107, 44, 32, 97, 110, 100, 32, 111, 102, 32, 104, 97, 118, + 105, 110, 103, 32, 110, 111, 116, 104, 105, 110, 103, 32, 116, 111, 32, 100, 111, + 58, 32, 111, 110, 99, 101, 32, 111, 114, 32, 116, 119, 105, 99, 101, 32, 115, 104, + 101, 32, 104, 97, 100, 32, 112, 101, 101, 112, 101, 100, 32, 105, 110, 116, 111, + 32, 116, 104, 101, 32, 98, 111, 111, 107, 32, 104, 101, 114, 32, 115, 105, 115, + 116, 101, 114, 32, 119, 97, 115, 32, 114, 101, 97, 100, 105, 110, 103, 44, 32, 98, + 117, 116, 32, 105, 116, 32, 104, 97, 100, 32, 110, 111, 32, 112, 105, 99, 116, 117, + 114, 101, 115, 32, 111, 114, 32, 99, 111, 110, 118, 101, 114, 115, 97, 116, 105, + 111, 110, 115, 32, 105, 110, 32, 105, 116, 44, 32, 97, 110, 100, 32, 119, 104, 97, + 116, 32, 105, 115, 32, 116, 104, 101, 32, 117, 115, 101, 32, 111, 102, 32, 97, 32, + 98, 111, 111, 107, 44, 32, 116, 104, 111, 117, 103, 104, 116, 32, 65, 108, 105, 99, + 101, 32, 119, 105, 116, 104, 111, 117, 116, 32, 112, 105, 99, 116, 117, 114, 101, + 115, 32, 111, 114, 32, 99, 111, 110, 118, 101, 114, 115, 97, 116, 105, 111, 110, + 115, 63, + ]; + 1000 + ]; + let output = [ + 60u8, 227, 142, 8, 143, 135, 108, 85, 13, 254, 190, 58, 30, 106, 153, 194, 188, 6, 208, + 49, 16, 102, 150, 120, 100, 130, 224, 177, 64, 98, 53, 252, + ]; + + // Build the circuit + let circuit = KeccakTestCircuit { input, output }; + let prover = MockProver::::run(17, &circuit, vec![]).unwrap(); + assert!(prover.verify().is_err()); + } +} diff --git a/keccak256/src/circuit/padding.rs b/keccak256/src/circuit/padding.rs index 793a9afc4e..5d0e6f1872 100644 --- a/keccak256/src/circuit/padding.rs +++ b/keccak256/src/circuit/padding.rs @@ -181,10 +181,10 @@ impl PaddingConfig { pub fn assign_region( &self, layouter: &mut impl Layouter, - is_finalize: AssignedCell, - input_len_cell: AssignedCell, - acc_len_cell: AssignedCell, - bytes: [u8; BYTES_LEN_17_WORDS], + is_finalize: bool, + input_len: usize, + acc_len: usize, + bytes: [F; BYTES_LEN_17_WORDS], ) -> Result<[AssignedCell; 17], Error> { let diff_is_zero_chip = IsZeroChip::construct(self.diff_is_zero.clone()); layouter.assign_region( @@ -193,7 +193,7 @@ impl PaddingConfig { const LAST: usize = BYTES_LEN_17_WORDS - 1; self.q_last.enable(&mut region, LAST)?; let mut is_pad_zone = F::zero(); - let mut padded_bytes = [0u8; BYTES_LEN_17_WORDS]; + let mut padded_bytes = [F::zero(); BYTES_LEN_17_WORDS]; for (offset, &byte) in bytes.iter().enumerate().take(BYTES_LEN_17_WORDS) { self.q_all.enable(&mut region, offset)?; if offset != 0 { @@ -202,35 +202,32 @@ impl PaddingConfig { if offset != LAST { self.q_without_last.enable(&mut region, offset)?; } - is_finalize.clone().copy_advice( + region.assign_advice( || "flag enable", - &mut region, self.is_finalize, offset, + || Ok(F::from(is_finalize as u64)), )?; - input_len_cell.copy_advice( + region.assign_advice( || "input len", - &mut region, self.input_len, offset, + || Ok(F::from(input_len as u64)), )?; - let acc_len = - acc_len_cell.value().cloned().unwrap_or_default() + F::from(offset as u64); + let acc_len = F::from(acc_len as u64) + F::from(offset as u64); region.assign_advice( || "acc_len_rest", self.acc_len, offset, || Ok(acc_len), )?; - let diff_value = - Some(input_len_cell.value().cloned().unwrap_or_default() - acc_len); + let diff_value = Some(F::from(input_len as u64) - acc_len); let is_zero = diff_value .map(|diff_value| F::from(diff_value == F::zero())) .unwrap_or_default(); diff_is_zero_chip.assign(&mut region, offset, diff_value)?; - let byte_f = F::from(byte as u64); - region.assign_advice(|| "byte", self.byte, offset, || Ok(byte_f))?; + region.assign_advice(|| "byte", self.byte, offset, || Ok(byte))?; is_pad_zone += is_zero; region.assign_advice( || "is pad zone", @@ -238,14 +235,13 @@ impl PaddingConfig { offset, || Ok(is_pad_zone), )?; - let is_finalize_bit = - is_finalize.value().cloned().unwrap_or_default() == F::one(); + padded_bytes[offset] = byte + diff_value - .map(|diff_value| (diff_value == F::zero()) as u8) + .map(|diff_value| F::from((diff_value == F::zero()) as u64)) .unwrap_or_default() - * 0x80u8 - + (((offset == LAST) && is_finalize_bit) as u8); + * F::from(0x80u64) + + F::from(((offset == LAST) && is_finalize) as u64); } let padded_byte_cells: Result, _> = padded_bytes .iter() @@ -256,7 +252,7 @@ impl PaddingConfig { || "padded byte", self.padded_byte, offset, - || Ok(F::from(padded_byte as u64)), + || Ok(padded_byte), ) }) .collect(); @@ -296,8 +292,8 @@ mod tests { struct MyCircuit { bytes: [u8; BYTES_LEN_17_WORDS], is_finalize: bool, - input_len: u64, - acc_len: u64, + input_len: usize, + acc_len: usize, _marker: PhantomData, } impl Default for MyCircuit { @@ -349,38 +345,12 @@ mod tests { config: Self::Config, mut layouter: impl Layouter, ) -> Result<(), Error> { - let (is_finalize, input_len, acc_len) = layouter.assign_region( - || "external values", - |mut region| { - let offset = 0; - let is_finalize = region.assign_advice( - || "parent flag", - config.is_finalize, - offset, - || Ok(F::from(self.is_finalize)), - )?; - let input_len = region.assign_advice( - || "input len", - config.input_len, - offset, - || Ok(F::from(self.input_len)), - )?; - let acc_len = region.assign_advice( - || "acc len", - config.acc_len, - offset, - || Ok(F::from(self.acc_len)), - )?; - Ok((is_finalize, input_len, acc_len)) - }, - )?; - config.padding_conf.assign_region( &mut layouter, - is_finalize, - input_len, - acc_len, - self.bytes, + self.is_finalize, + self.input_len, + self.acc_len, + self.bytes.map(|byte| F::from(byte as u64)), )?; Ok(()) diff --git a/keccak256/src/keccak_arith.rs b/keccak256/src/keccak_arith.rs index 4fe228350a..64652b0f52 100644 --- a/keccak256/src/keccak_arith.rs +++ b/keccak256/src/keccak_arith.rs @@ -8,7 +8,7 @@ pub struct KeccakFArith {} impl KeccakFArith { pub fn permute_and_absorb( a: &mut StateBigInt, - next_inputs: Option<&State>, + next_inputs: Option, ) -> Option { for rc in ROUND_CONSTANTS.iter().take(PERMUTATION - 1) { let s1 = KeccakFArith::theta(a); @@ -91,9 +91,9 @@ impl KeccakFArith { out } - pub fn mixing(a: &StateBigInt, next_input: Option<&State>, rc: u64) -> StateBigInt { + pub fn mixing(a: &StateBigInt, next_input: Option, rc: u64) -> StateBigInt { if let Some(next_input) = next_input { - let out_1 = KeccakFArith::absorb(a, next_input); + let out_1 = KeccakFArith::absorb(a, &next_input); // Base conversion from 9 to 13 let mut out_2 = StateBigInt::default(); @@ -188,7 +188,7 @@ impl Sponge { } continue; } - KeccakFArith::permute_and_absorb(&mut state_bit_int, Some(&next_inputs)); + KeccakFArith::permute_and_absorb(&mut state_bit_int, Some(next_inputs)); } KeccakFArith::permute_and_absorb(&mut state_bit_int, None); for (x, y) in (0..5).cartesian_product(0..5) { diff --git a/keccak256/src/lib.rs b/keccak256/src/lib.rs index 0191d9df48..8a011995c7 100644 --- a/keccak256/src/lib.rs +++ b/keccak256/src/lib.rs @@ -6,6 +6,7 @@ pub mod circuit; pub mod common; pub mod gate_helpers; pub mod permutation; +pub mod rlc; // We build arith module to get test cases for the circuit pub mod keccak_arith; // We build plain module for the purpose of reviewing the circuit diff --git a/keccak256/src/permutation.rs b/keccak256/src/permutation.rs index f4ce5d35fd..cabf1148a4 100644 --- a/keccak256/src/permutation.rs +++ b/keccak256/src/permutation.rs @@ -1,5 +1,16 @@ #![allow(clippy::type_complexity)] #![allow(clippy::too_many_arguments)] + +use crate::circuit::StateTag; +use crate::{circuit::NEXT_INPUTS_BYTES, common::State}; +use eth_types::Field; +use halo2_proofs::{ + circuit::{layouter, AssignedCell, Layouter}, + plonk::Error, +}; +use itertools::Itertools; +use std::{convert::TryInto, ops::Div}; + pub(crate) mod absorb; pub(crate) mod base_conversion; pub mod circuit; @@ -13,3 +24,173 @@ pub(crate) mod rho_helpers; pub(crate) mod tables; pub(crate) mod theta; pub(crate) mod xi; + +#[repr(transparent)] +#[derive(Debug, Clone)] +pub(crate) struct PermutationInputs(pub(crate) Vec>); + +impl PermutationInputs { + pub fn new() -> Self { + Self(vec![]) + } + + pub fn from_bytes(mut bytes: &[u8]) -> Self { + let permutations = (bytes.len() as f64).div(NEXT_INPUTS_BYTES as f64).ceil() as usize; + let mut idx = 1; + let mut perm_inputs = Self::new(); + let bytes = &mut bytes; + let state_tag = |idx| match permutations - idx > 0 { + true => StateTag::Continue, + false => StateTag::Finalize, + _ => unreachable!(), + }; + + // Add the Continue and Finalize permutations + while bytes.len() > 0 { + perm_inputs + .0 + .push(NextInput::from_bytes(bytes, idx, state_tag(idx))); + idx += 1; + } + + perm_inputs + } +} + +#[derive(Debug, Clone)] +pub(crate) struct NextInput { + pub(crate) unpadded_bytes: [F; NEXT_INPUTS_BYTES], + padded_bytes: [F; NEXT_INPUTS_BYTES], + pub(crate) og_len: usize, + pub(crate) state_tag: StateTag, + pub(crate) perm_count: usize, +} + +impl NextInput { + pub fn new() -> Self { + Self { + unpadded_bytes: [F::zero(); NEXT_INPUTS_BYTES], + padded_bytes: [F::zero(); NEXT_INPUTS_BYTES], + og_len: 0, + state_tag: StateTag::Start, + perm_count: 0, + } + } + + pub fn with_og_len(bytes: &[u8], len: usize) -> Self { + let bytes: [F; NEXT_INPUTS_BYTES] = bytes + .iter() + .map(|&byte| F::from(byte as u64)) + .chain(vec![F::zero(); NEXT_INPUTS_BYTES - len]) + .collect_vec() + .try_into() + .unwrap(); + Self { + unpadded_bytes: bytes.clone(), + padded_bytes: bytes, + og_len: len, + state_tag: StateTag::Start, + perm_count: 0, + } + } + + fn pad(&mut self) { + match ( + self.og_len == NEXT_INPUTS_BYTES, + self.og_len == NEXT_INPUTS_BYTES - 1, + ) { + (true, false) => (), + (false, true) => { + if let Some(last) = self.padded_bytes.last_mut() { + *last = F::from(0x81u64); + } + } + (false, false) => { + self.padded_bytes[self.og_len] = F::from(0x80u64); + self.padded_bytes[NEXT_INPUTS_BYTES - 1] = F::one(); + } + _ => unreachable!(), + } + } + + pub fn from_bytes(byte_slice: &mut &[u8], perm_count: usize, state_tag: StateTag) -> Self { + let len = if byte_slice.len() < NEXT_INPUTS_BYTES { + byte_slice.len() + } else { + NEXT_INPUTS_BYTES + }; + + let mut bytes = vec![0u8; len]; + bytes[0..len].copy_from_slice(&byte_slice[0..len]); + *byte_slice = &byte_slice[len..]; + + let mut next_inp = Self::with_og_len(&bytes, len); + next_inp.pad(); + next_inp.perm_count = perm_count; + next_inp.state_tag = state_tag; + next_inp + } +} + +#[cfg(test)] +mod next_inputs { + use super::*; + use halo2_proofs::pairing::bn256::Fr as Fp; + use pretty_assertions::assert_eq; + + #[test] + fn correct_padding() { + let input = [ + 65, 108, 105, 99, 101, 32, 119, 97, 115, 32, 98, 101, 103, 105, 110, 110, 105, 110, + 103, 32, 116, 111, 32, 103, 101, 116, 32, 118, 101, 114, 121, 32, 116, 105, 114, 101, + 100, 32, 111, 102, 32, 115, 105, 116, 116, 105, 110, 103, 32, 98, 121, 32, 104, 101, + 114, 32, 115, 105, 115, 116, 101, 114, 32, 111, 110, 32, 116, 104, 101, 32, 98, 97, + 110, 107, 44, 32, 97, 110, 100, 32, 111, 102, 32, 104, 97, 118, 105, 110, 103, 32, 110, + 111, 116, 104, 105, 110, 103, 32, 116, 111, 32, 100, 111, 58, 32, 111, 110, 99, 101, + 32, 111, 114, 32, 116, 119, 105, 99, 101, 32, 115, 104, 101, 32, 104, 97, 100, 32, 112, + 101, 101, 112, 101, 100, 32, 105, 110, 116, 111, 32, 116, 104, 101, 32, 98, 111, 111, + 107, 32, 104, 101, 114, 32, 115, 105, 115, 116, 101, 114, 32, 119, 97, 115, 32, 114, + 101, 97, 100, 105, 110, 103, 44, 32, 98, 117, 116, 32, 105, 116, 32, 104, 97, 100, 32, + 110, 111, 32, 112, 105, 99, 116, 117, 114, 101, 115, 32, 111, 114, 32, 99, 111, 110, + 118, 101, 114, 115, 97, 116, 105, 111, 110, 115, 32, 105, 110, 32, 105, 116, 44, 32, + 97, 110, 100, 32, 119, 104, 97, 116, 32, 105, 115, 32, 116, 104, 101, 32, 117, 115, + 101, 32, 111, 102, 32, 97, 32, 98, 111, 111, 107, 44, 32, 116, 104, 111, 117, 103, 104, + 116, 32, 65, 108, 105, 99, 101, 32, 119, 105, 116, 104, 111, 117, 116, 32, 112, 105, + 99, 116, 117, 114, 101, 115, 32, 111, 114, 32, 99, 111, 110, 118, 101, 114, 115, 97, + 116, 105, 111, 110, 115, 63, + ]; + + let perm_inputs = PermutationInputs::::from_bytes(&input); + + let first_perm = input[0..NEXT_INPUTS_BYTES] + .iter() + .map(|&byte| Fp::from(byte as u64)) + .collect_vec(); + + assert_eq!(perm_inputs.0[0].padded_bytes, first_perm[..]); + assert!(perm_inputs.0[0].state_tag.is_continue()); + + let second_perm = input[NEXT_INPUTS_BYTES..2 * NEXT_INPUTS_BYTES] + .iter() + .map(|&byte| Fp::from(byte as u64)) + .collect_vec(); + + assert_eq!(perm_inputs.0[1].padded_bytes, second_perm[..]); + assert!(perm_inputs.0[1].state_tag.is_continue()); + + let mut last_perm_expected = input[NEXT_INPUTS_BYTES * 2..] + .iter() + .map(|&byte| Fp::from(byte as u64)) + .collect_vec(); + + last_perm_expected.extend_from_slice(&[Fp::from(0x80u64)]); + last_perm_expected.extend_from_slice(&vec![Fp::zero(); 136 - 28]); + last_perm_expected.extend_from_slice(&[Fp::one()]); + + assert_eq!( + perm_inputs.0.last().unwrap().padded_bytes, + last_perm_expected[..] + ); + assert!(perm_inputs.0.last().unwrap().state_tag.is_finalize()); + } +} diff --git a/keccak256/src/permutation/absorb.rs b/keccak256/src/permutation/absorb.rs index 2da8fcae6b..60cf872c5f 100644 --- a/keccak256/src/permutation/absorb.rs +++ b/keccak256/src/permutation/absorb.rs @@ -1,3 +1,4 @@ +use super::base_conversion::BaseConversionConfig; use crate::arith_helpers::*; use crate::common::*; use eth_types::Field; @@ -6,14 +7,13 @@ use halo2_proofs::{ plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector}, poly::Rotation, }; -use itertools::Itertools; -use std::{convert::TryInto, marker::PhantomData}; +use std::convert::TryInto; #[derive(Clone, Debug)] pub struct AbsorbConfig { q_mixing: Selector, state: [Column; 25], - _marker: PhantomData, + base_conversion_config: BaseConversionConfig, } impl AbsorbConfig { @@ -26,6 +26,7 @@ impl AbsorbConfig { pub fn configure( meta: &mut ConstraintSystem, state: [Column; 25], + base_conversion_config: BaseConversionConfig, ) -> AbsorbConfig { // def absorb(state: List[List[int], next_input: List[List[int]): // for x in range(5): @@ -75,7 +76,7 @@ impl AbsorbConfig { AbsorbConfig { q_mixing, state, - _marker: PhantomData, + base_conversion_config, } } @@ -84,28 +85,28 @@ impl AbsorbConfig { region: &mut Region, offset: usize, flag: AssignedCell, - next_input: [F; NEXT_INPUTS_LANES], + // Passed in base-9 + next_input: [AssignedCell; 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), - )?; - } + // // 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); + + // Copy next_inputs in base9 + next_input + .iter() + .enumerate() + .map(|(idx, cell)| { + cell.copy_advice(|| "Assign next_inputs", region, self.state[idx], offset) + }) + .collect::>, Error>>()?; // Assign flag at last column(17th). let flag_assig_cell = flag.copy_advice( @@ -118,16 +119,21 @@ impl AbsorbConfig { Ok(flag_assig_cell) } - /// Doc this $ + /// TODO: 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], + next_input: [AssignedCell; NEXT_INPUTS_LANES], flag: AssignedCell, ) -> Result<([AssignedCell; 25], AssignedCell), Error> { + // Convert base of `next_inputs` from 2 to 9 + let next_inputs_b9 = + self.base_conversion_config + .assign_state(layouter, &next_input, flag.clone())?; + layouter.assign_region( || "Absorb state assignations", |mut region| { @@ -147,8 +153,12 @@ impl AbsorbConfig { 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)?; + let flag = self.assign_next_inp_and_flag( + &mut region, + offset, + flag.clone(), + next_inputs_b9.clone(), + )?; offset += 1; // Assign out_state at offset + 2 @@ -177,6 +187,7 @@ mod tests { use super::*; use crate::common::State; use crate::keccak_arith::KeccakFArith; + use crate::permutation::tables::FromBinaryTableConfig; use halo2_proofs::circuit::Layouter; use halo2_proofs::pairing; use halo2_proofs::plonk::{Advice, Column, ConstraintSystem, Error}; @@ -194,15 +205,22 @@ mod tests { struct MyCircuit { in_state: [F; 25], out_state: [F; 25], - next_input: [F; NEXT_INPUTS_LANES], + next_inputs: [F; NEXT_INPUTS_LANES], is_mixing: bool, _marker: PhantomData, } + + #[derive(Clone)] + struct MyConfig { + absorb_conf: AbsorbConfig, + table: FromBinaryTableConfig, + } + impl Circuit for MyCircuit where F: PrimeField, { - type Config = AbsorbConfig; + type Config = MyConfig; type FloorPlanner = SimpleFloorPlanner; fn without_witnesses(&self) -> Self { @@ -219,8 +237,25 @@ mod tests { .collect::>() .try_into() .unwrap(); + let table = FromBinaryTableConfig::configure(meta); + let base_info = table.get_base_info(true); + let base_conv_lane = meta.advice_column(); + let advices = (0..5) + .map(|_| meta.advice_column()) + .collect::>>() + .try_into() + .unwrap(); + let base_conversion_config = BaseConversionConfig::configure( + meta, + base_info, + base_conv_lane, + state[NEXT_INPUTS_LANES], + advices, + ); + + let absorb_conf = AbsorbConfig::configure(meta, state, base_conversion_config); - AbsorbConfig::configure(meta, state) + MyConfig { absorb_conf, table } } fn synthesize( @@ -228,6 +263,9 @@ mod tests { config: Self::Config, mut layouter: impl Layouter, ) -> Result<(), Error> { + // Load the table + config.table.load(&mut layouter)?; + let val: F = (self.is_mixing as u64).into(); let flag: AssignedCell = layouter.assign_region( || "witness_is_mixing_flag", @@ -235,7 +273,7 @@ mod tests { let offset = 1; region.assign_advice( || "assign is_mixing", - config.state[NEXT_INPUTS_LANES + 1], + config.absorb_conf.state[NEXT_INPUTS_LANES + 1], offset, || Ok(val), ) @@ -250,7 +288,7 @@ mod tests { for (idx, val) in self.in_state.iter().enumerate() { let cell = region.assign_advice( || "witness input state", - config.state[idx], + config.absorb_conf.state[idx], 0, || Ok(*val), )?; @@ -261,11 +299,31 @@ mod tests { }, )?; - config.copy_state_flag_next_inputs( + // Witness `next_inputs`. + let next_inputs: [AssignedCell; NEXT_INPUTS_LANES] = layouter.assign_region( + || "Witness input state", + |mut region| { + let mut state: Vec> = + Vec::with_capacity(NEXT_INPUTS_LANES); + for (idx, val) in self.next_inputs.iter().enumerate() { + let cell = region.assign_advice( + || "witness next_inputs", + config.absorb_conf.state[idx], + 1, + || Ok(*val), + )?; + state.push(cell) + } + + Ok(state.try_into().unwrap()) + }, + )?; + + config.absorb_conf.copy_state_flag_next_inputs( &mut layouter, &in_state, self.out_state, - self.next_input, + next_inputs, flag, )?; @@ -292,15 +350,21 @@ mod tests { // 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]) + in_state[(x, y)] = convert_b2_to_b9(input1[x][y]); + //next_inputs[(x, y)] = convert_b2_to_b9(input2[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)); + // // Get next_inputs in b9 + // for (x, y) in (0..5).cartesian_product(0..5) { + // next_inputs[(x, y)] = convert_b2_to_b9(input2[x][y]); + // } + let next_inputs = state_bigint_to_field(StateBigInt::from(input2)); // With flag set to true, the gate should trigger. { @@ -309,12 +373,12 @@ mod tests { let circuit = MyCircuit:: { in_state, out_state, - next_input, + next_inputs, is_mixing: true, _marker: PhantomData, }; - let prover = MockProver::::run(9, &circuit, vec![]).unwrap(); + let prover = MockProver::::run(17, &circuit, vec![]).unwrap(); assert_eq!(prover.verify(), Ok(())); @@ -323,12 +387,12 @@ mod tests { let circuit = MyCircuit:: { in_state, out_state: in_state, - next_input, + next_inputs, is_mixing: true, _marker: PhantomData, }; - let prover = MockProver::::run(9, &circuit, vec![]).unwrap(); + let prover = MockProver::::run(17, &circuit, vec![]).unwrap(); assert!(prover.verify().is_err()); } @@ -339,12 +403,12 @@ mod tests { let circuit = MyCircuit:: { in_state, out_state: in_state, - next_input, + next_inputs, is_mixing: false, _marker: PhantomData, }; - let prover = MockProver::::run(9, &circuit, vec![]).unwrap(); + let prover = MockProver::::run(17, &circuit, vec![]).unwrap(); assert_eq!(prover.verify(), Ok(())); } diff --git a/keccak256/src/permutation/base_conversion.rs b/keccak256/src/permutation/base_conversion.rs index 7e177ce23c..ef0af3a036 100644 --- a/keccak256/src/permutation/base_conversion.rs +++ b/keccak256/src/permutation/base_conversion.rs @@ -9,7 +9,7 @@ use eth_types::Field; use std::convert::TryInto; #[derive(Clone, Debug)] -pub(crate) struct BaseConversionConfig { +pub struct BaseConversionConfig { q_running_sum: Selector, q_lookup: Selector, base_info: BaseInfo, @@ -24,7 +24,7 @@ pub(crate) struct BaseConversionConfig { impl BaseConversionConfig { /// Side effect: lane and parent_flag is equality enabled - pub(crate) fn configure( + pub fn configure( meta: &mut ConstraintSystem, base_info: BaseInfo, input_lane: Column, @@ -87,7 +87,7 @@ impl BaseConversionConfig { } } - pub(crate) fn assign_lane( + pub fn assign_lane( &self, layouter: &mut impl Layouter, input: AssignedCell, @@ -154,12 +154,12 @@ impl BaseConversionConfig { ) } - pub(crate) fn assign_state( + pub fn assign_state( &self, layouter: &mut impl Layouter, - state: &[AssignedCell; 25], + state: &[AssignedCell; N], flag: AssignedCell, - ) -> Result<[AssignedCell; 25], Error> { + ) -> Result<[AssignedCell; N], Error> { let state: Result>, Error> = state .iter() .map(|lane| { @@ -169,7 +169,7 @@ impl BaseConversionConfig { .into_iter() .collect(); let state = state?; - let state: [AssignedCell; 25] = state.try_into().unwrap(); + let state: [AssignedCell; N] = state.try_into().unwrap(); Ok(state) } } diff --git a/keccak256/src/permutation/circuit.rs b/keccak256/src/permutation/circuit.rs index 4a12953305..3f80a5b9ec 100644 --- a/keccak256/src/permutation/circuit.rs +++ b/keccak256/src/permutation/circuit.rs @@ -29,68 +29,44 @@ pub struct KeccakFConfig { theta_config: ThetaConfig, rho_config: RhoConfig, xi_config: XiConfig, - from_b9_table: FromBase9TableConfig, - base_conversion_config: BaseConversionConfig, + base_conv_config_b9: BaseConversionConfig, mixing_config: MixingConfig, - pub state: [Column; 25], + state: [Column; 25], q_out: Selector, base_conv_activator: Column, } impl KeccakFConfig { // We assume state is received in base-9. - pub fn configure(meta: &mut ConstraintSystem) -> Self { - let state: [Column; 25] = (0..25) - .map(|_| { - let column = meta.advice_column(); - meta.enable_equality(column); - column - }) - .collect_vec() - .try_into() - .unwrap(); - - let fixed = meta.fixed_column(); - let generic = GenericConfig::configure(meta, state[0..3].try_into().unwrap(), fixed); - let table_cols: [TableColumn; 3] = (0..3) - .map(|_| meta.lookup_table_column()) - .collect_vec() - .try_into() - .unwrap(); - let stackable = - StackableTable::configure(meta, state[0..3].try_into().unwrap(), table_cols); - + pub fn configure( + meta: &mut ConstraintSystem, + state: [Column; 25], + base_conv_config_b9: BaseConversionConfig, + base_conv_config_b2: BaseConversionConfig, + base_conv_activator: Column, + flag: Column, + generic: GenericConfig, + stackable: StackableTable, + ) -> Self { // theta let theta_config = ThetaConfig::configure(meta.selector(), meta, state); // rho + let fixed = meta.fixed_column(); let rho_config = RhoConfig::configure(meta, state, fixed, generic.clone(), stackable.clone()); // xi let xi_config = XiConfig::configure(meta.selector(), meta, state); - // Allocate space for the activation flag of the base_conversion. - let base_conv_activator = meta.advice_column(); - meta.enable_equality(base_conv_activator); - // Base conversion config. - let from_b9_table = FromBase9TableConfig::configure(meta); - let base_info = from_b9_table.get_base_info(false); - let base_conv_lane = meta.advice_column(); - let base_conversion_config = BaseConversionConfig::configure( - meta, - base_info, - base_conv_lane, - base_conv_activator, - state[0..5].try_into().unwrap(), - ); - // Mixing will make sure that the flag is binary constrained and that // the out state matches the expected result. let mixing_config = MixingConfig::configure( meta, - &from_b9_table, + base_conv_config_b9.clone(), + base_conv_config_b2, state, generic.clone(), stackable.clone(), + flag, ); // Allocate the `out state correctness` gate selector @@ -115,9 +91,8 @@ impl KeccakFConfig { theta_config, rho_config, xi_config, - from_b9_table, - base_conversion_config, mixing_config, + base_conv_config_b9, state, q_out, base_conv_activator, @@ -126,17 +101,15 @@ impl KeccakFConfig { pub fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { self.stackable.load(layouter)?; - self.rho_config.load(layouter)?; - self.from_b9_table.load(layouter) + self.rho_config.load(layouter) } - pub fn assign_all( + pub fn assign_permutation( &self, layouter: &mut impl Layouter, in_state: [AssignedCell; 25], - out_state: [F; 25], flag: bool, - next_mixing: Option<[F; NEXT_INPUTS_LANES]>, + next_mixing: [AssignedCell; NEXT_INPUTS_LANES], ) -> Result<[AssignedCell; 25], Error> { let mut state = in_state; @@ -200,7 +173,7 @@ impl KeccakFConfig { }, )?; - self.base_conversion_config + self.base_conv_config_b9 .assign_state(layouter, &state, activation_flag)? } } @@ -208,21 +181,23 @@ impl KeccakFConfig { // 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(), + if !flag { + None + } else { + Some(state_to_state_bigint::( + split_state_cells(next_mixing.clone()), + )) + }, *ROUND_CONSTANTS.last().unwrap(), ); - let mix_res = self.mixing_config.assign_state( + 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( @@ -286,6 +261,7 @@ mod tests { use super::*; use crate::common::{State, NEXT_INPUTS_LANES}; use crate::gate_helpers::biguint_to_f; + use crate::permutation::tables::{FromBase9TableConfig, FromBinaryTableConfig}; use halo2_proofs::circuit::Layouter; use halo2_proofs::pairing::bn256::Fr as Fp; use halo2_proofs::plonk::{ConstraintSystem, Error}; @@ -293,10 +269,8 @@ mod tests { use pretty_assertions::assert_eq; use std::convert::TryInto; - // TODO: Remove ignore once this can run in the CI without hanging. - #[ignore] #[test] - fn test_keccak_round() { + fn test_keccak_f_round() { #[derive(Default)] struct MyCircuit { in_state: [F; 25], @@ -306,8 +280,17 @@ mod tests { is_mixing: bool, } + #[derive(Clone)] + struct MyConfig { + keccak_config: KeccakFConfig, + state: [Column; 25], + next_inputs: [Column; NEXT_INPUTS_LANES], + table_b9_b13: FromBase9TableConfig, + table_b2_b9: FromBinaryTableConfig, + } + impl Circuit for MyCircuit { - type Config = KeccakFConfig; + type Config = MyConfig; type FloorPlanner = SimpleFloorPlanner; fn without_witnesses(&self) -> Self { @@ -315,7 +298,75 @@ mod tests { } fn configure(meta: &mut ConstraintSystem) -> Self::Config { - Self::Config::configure(meta) + let state = [(); 25].map(|_| meta.advice_column()).map(|col| { + meta.enable_equality(col); + col + }); + + let next_inputs = + [(); NEXT_INPUTS_LANES] + .map(|_| meta.advice_column()) + .map(|col| { + meta.enable_equality(col); + col + }); + + let flag = meta.advice_column(); + meta.enable_equality(flag); + let table_b9 = FromBase9TableConfig::configure(meta); + let table_b2 = FromBinaryTableConfig::configure(meta); + + let base_conv_lane = meta.advice_column(); + meta.enable_equality(base_conv_lane); + + let base_conv_activator = meta.advice_column(); + meta.enable_equality(base_conv_activator); + + let fixed = meta.fixed_column(); + let generic = + GenericConfig::configure(meta, state[0..3].try_into().unwrap(), fixed); + let table_cols: [TableColumn; 3] = (0..3) + .map(|_| meta.lookup_table_column()) + .collect_vec() + .try_into() + .unwrap(); + let stackable = + StackableTable::configure(meta, state[0..3].try_into().unwrap(), table_cols); + + let base_conv_config_b2_b9 = BaseConversionConfig::configure( + meta, + table_b2.get_base_info(true), + base_conv_lane, + flag, + state[0..5].try_into().unwrap(), + ); + + let base_conv_config_b9_b13 = BaseConversionConfig::configure( + meta, + table_b9.get_base_info(false), + base_conv_lane, + flag, + state[0..5].try_into().unwrap(), + ); + + let keccak_config = KeccakFConfig::configure( + meta, + state, + base_conv_config_b9_b13, + base_conv_config_b2_b9, + base_conv_activator, + flag, + generic, + stackable, + ); + + MyConfig { + keccak_config, + state, + next_inputs, + table_b2_b9: table_b2, + table_b9_b13: table_b9, + } } fn synthesize( @@ -324,7 +375,9 @@ mod tests { mut layouter: impl Layouter, ) -> Result<(), Error> { // Load the table - config.load(&mut layouter)?; + config.table_b2_b9.load(&mut layouter)?; + config.table_b9_b13.load(&mut layouter)?; + config.keccak_config.load(&mut layouter)?; let offset: usize = 0; let in_state = layouter.assign_region( @@ -349,12 +402,41 @@ mod tests { }, )?; - config.assign_all( + let next_inputs = layouter.assign_region( + || "Witness next_inputs", + |mut region| { + // Witness `state` + let next_inputs: [AssignedCell; NEXT_INPUTS_LANES] = { + let mut state: Vec> = + Vec::with_capacity(NEXT_INPUTS_LANES); + for (idx, val) in + self.next_mixing.unwrap_or_default().iter().enumerate() + { + let cell = region.assign_advice( + || "witness input state", + config.next_inputs[idx], + offset, + || Ok(*val), + )?; + state.push(cell) + } + state.try_into().unwrap() + }; + + Ok(next_inputs) + }, + )?; + + let out_state_obtained = config.keccak_config.assign_permutation( &mut layouter, in_state, - self.out_state, self.is_mixing, - self.next_mixing, + next_inputs, + )?; + config.keccak_config.constrain_out_state( + &mut layouter, + &out_state_obtained, + self.out_state, )?; Ok(()) } @@ -387,7 +469,7 @@ mod tests { // Compute out_state_mix let mut out_state_mix = in_state_biguint.clone(); - KeccakFArith::permute_and_absorb(&mut out_state_mix, Some(&next_input)); + KeccakFArith::permute_and_absorb(&mut out_state_mix, Some(next_input)); // Compute out_state_non_mix let mut out_state_non_mix = in_state_biguint.clone(); diff --git a/keccak256/src/permutation/mixing.rs b/keccak256/src/permutation/mixing.rs index 37e9e3f268..eddd88a40c 100644 --- a/keccak256/src/permutation/mixing.rs +++ b/keccak256/src/permutation/mixing.rs @@ -16,7 +16,7 @@ use std::convert::TryInto; pub struct MixingConfig { iota_constants: IotaConstants, absorb_config: AbsorbConfig, - base_conv_config: BaseConversionConfig, + base_conv_config_b9_b13: BaseConversionConfig, state: [Column; 25], flag: Column, q_out_copy: Selector, @@ -27,25 +27,16 @@ pub struct MixingConfig { impl MixingConfig { pub fn configure( meta: &mut ConstraintSystem, - table: &FromBase9TableConfig, + base_conv_config_b9_b13: BaseConversionConfig, + base_conv_config_b2_b9: BaseConversionConfig, state: [Column; 25], generic: GenericConfig, stackable: StackableTable, + flag: Column, ) -> MixingConfig { - let flag = meta.advice_column(); meta.enable_equality(flag); // We mix -> Flag = true - let absorb_config = AbsorbConfig::configure(meta, state); - - let base_info = table.get_base_info(false); - let base_conv_lane = meta.advice_column(); - let base_conv_config = BaseConversionConfig::configure( - meta, - base_info, - base_conv_lane, - flag, - state[0..5].try_into().unwrap(), - ); + let absorb_config = AbsorbConfig::configure(meta, state, base_conv_config_b2_b9); let q_out_copy = meta.selector(); @@ -71,7 +62,7 @@ impl MixingConfig { MixingConfig { iota_constants, absorb_config, - base_conv_config, + base_conv_config_b9_b13, state, flag, q_out_copy, @@ -136,7 +127,7 @@ impl MixingConfig { in_state: &[AssignedCell; 25], out_state: [F; 25], flag_bool: bool, - next_mixing: Option<[F; NEXT_INPUTS_LANES]>, + next_mixing: [AssignedCell; NEXT_INPUTS_LANES], ) -> Result<[AssignedCell; 25], Error> { // Enforce flag constraints and witness them. let (flag, negated_flag) = self @@ -165,18 +156,26 @@ impl MixingConfig { 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(), + { + let out_absorb: StateBigInt = KeccakFArith::absorb( + &state_to_biguint(split_state_cells(in_state.clone())), + &state_to_state_bigint::(split_state_cells( + next_mixing.clone(), + )), + ); + + state_bigint_to_field::(out_absorb) + }, + next_mixing, flag.clone(), )?; // Base conversion assign - let base_conv_cells = - self.base_conv_config - .assign_state(layouter, &out_state_absorb_cells, flag.clone())?; + let base_conv_cells = self.base_conv_config_b9_b13.assign_state( + layouter, + &out_state_absorb_cells, + flag.clone(), + )?; // IotaB13 let mix_res = { @@ -228,7 +227,10 @@ impl MixingConfig { #[cfg(test)] mod tests { use super::*; - use crate::common::{State, ROUND_CONSTANTS}; + use crate::{ + common::{State, ROUND_CONSTANTS}, + permutation::tables::FromBinaryTableConfig, + }; use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner}, dev::MockProver, @@ -253,8 +255,8 @@ mod tests { #[derive(Clone)] struct MyConfig { mixing_conf: MixingConfig, - table: FromBase9TableConfig, - stackable: StackableTable, + table_b9_b13: FromBase9TableConfig, + table_b2_b9: FromBinaryTableConfig, } impl Circuit for MyCircuit { @@ -266,8 +268,6 @@ mod tests { } fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let table = FromBase9TableConfig::configure(meta); - let state: [Column; 25] = (0..25) .map(|_| { let col = meta.advice_column(); @@ -277,6 +277,32 @@ mod tests { .collect_vec() .try_into() .unwrap(); + + let flag = meta.advice_column(); + meta.enable_equality(flag); + + let table_b9_b13 = FromBase9TableConfig::configure(meta); + let table_b2_b9 = FromBinaryTableConfig::configure(meta); + + let base_conv_lane = meta.advice_column(); + meta.enable_equality(base_conv_lane); + + let base_conv_config_b2_b9 = BaseConversionConfig::configure( + meta, + table_b2_b9.get_base_info(true), + base_conv_lane, + flag, + state[0..5].try_into().unwrap(), + ); + + let base_conv_config_b9_b13 = BaseConversionConfig::configure( + meta, + table_b9_b13.get_base_info(false), + base_conv_lane, + flag, + state[0..5].try_into().unwrap(), + ); + let fixed = meta.fixed_column(); let generic = GenericConfig::configure(meta, state[0..3].try_into().unwrap(), fixed); @@ -287,13 +313,20 @@ mod tests { .unwrap(); let stackable = StackableTable::configure(meta, state[0..3].try_into().unwrap(), table_cols); - let mixing_conf = - MixingConfig::configure(meta, &table, state, generic, stackable.clone()); + let mixing_conf = MixingConfig::configure( + meta, + base_conv_config_b9_b13, + base_conv_config_b2_b9, + state, + generic, + stackable.clone(), + flag, + ); MyConfig { mixing_conf, - table, - stackable, + table_b9_b13, + table_b2_b9, } } @@ -302,9 +335,10 @@ mod tests { config: Self::Config, mut layouter: impl Layouter, ) -> Result<(), Error> { - // Load the table - config.table.load(&mut layouter)?; - config.stackable.load(&mut layouter)?; + // Load the tables + config.table_b9_b13.load(&mut layouter)?; + config.table_b2_b9.load(&mut layouter)?; + config.mixing_conf.stackable.load(&mut layouter)?; let offset: usize = 0; let in_state = layouter.assign_region( @@ -329,12 +363,37 @@ mod tests { }, )?; + let next_inputs = layouter.assign_region( + || "Mixing Wittnes assignment", + |mut region| { + // Witness `next_inputs` + let next_inputs: [AssignedCell; NEXT_INPUTS_LANES] = { + let mut state: Vec> = + Vec::with_capacity(NEXT_INPUTS_LANES); + for (idx, val) in + self.next_mixing.unwrap_or_default().iter().enumerate() + { + let cell = region.assign_advice( + || "witness next_inputs", + config.mixing_conf.state[idx], + offset, + || Ok(*val), + )?; + state.push(cell) + } + state.try_into().unwrap() + }; + + Ok(next_inputs) + }, + )?; + config.mixing_conf.assign_state( &mut layouter, &in_state, self.out_state, self.is_mixing, - self.next_mixing, + next_inputs, )?; Ok(()) @@ -364,14 +423,12 @@ mod tests { in_state[(x, y)] = convert_b2_to_b9(input1[x][y]) } - // Convert the next_input_b9 to base9 as it needs to be added to the - // state in base9 too. let next_input = StateBigInt::from(input2); // Compute out mixing state (when flag = 1) - let out_mixing_state = state_bigint_to_field(KeccakFArith::mixing( + let out_mixing_state: [Fp; 25] = state_bigint_to_field(KeccakFArith::mixing( &in_state, - Some(&input2), + Some(input2), *ROUND_CONSTANTS.last().unwrap(), )); @@ -430,6 +487,33 @@ mod tests { assert_eq!(prover.verify(), Ok(())); + // With wrong input and/or output witnesses, the proof should fail + // to be verified. + let circuit = MyCircuit:: { + in_state, + out_state: out_non_mixing_state, + next_mixing, + is_mixing: true, + }; + + let prover = MockProver::::run(17, &circuit, vec![]).unwrap(); + + assert!(prover.verify().is_err()); + } + + // With flag set to `true`, we mix. + { + let circuit = MyCircuit:: { + in_state, + out_state: out_mixing_state, + next_mixing, + is_mixing: true, + }; + + let prover = MockProver::::run(17, &circuit, vec![]).unwrap(); + + assert_eq!(prover.verify(), Ok(())); + // With wrong input and/or output witnesses, the proof should fail // to be verified. let circuit = MyCircuit:: { diff --git a/keccak256/src/permutation/tables.rs b/keccak256/src/permutation/tables.rs index e73f07468a..08ae143074 100644 --- a/keccak256/src/permutation/tables.rs +++ b/keccak256/src/permutation/tables.rs @@ -348,7 +348,7 @@ impl Base13toBase9TableConfig { } #[derive(Clone, Debug)] -pub(crate) struct BaseInfo { +pub struct BaseInfo { input_base: u8, output_base: u8, // How many chunks we perform in a lookup? diff --git a/keccak256/src/rlc.rs b/keccak256/src/rlc.rs new file mode 100644 index 0000000000..f740cb1a92 --- /dev/null +++ b/keccak256/src/rlc.rs @@ -0,0 +1,302 @@ +use std::marker::PhantomData; + +use eth_types::Field; +use gadgets::expression::*; +use halo2_proofs::{ + circuit::{AssignedCell, Layouter}, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector}, + poly::Rotation, +}; + +#[derive(Debug, Clone)] +pub struct RlcConfig { + q_enable: Selector, + witness: [Column; N], + rlc: Column, + randomness: Column, + _marker: PhantomData, +} + +impl RlcConfig { + pub fn configure( + meta: &mut ConstraintSystem, + witness: [Column; N], + randomness: Column, + rlc: Column, + ) -> RlcConfig { + let q_enable = meta.selector(); + meta.create_gate("RLC check", |meta| { + // Query witnesses to accumulate in the RLC + let witness: [Expression; N] = + witness.map(|w| meta.query_advice(w, Rotation::cur())); + let randomness = meta.query_advice(randomness, Rotation::cur()); + + // Query resulting RLC result + let result = meta.query_advice(rlc, Rotation::cur()); + + let obtained_result = rlc::compose::(&witness, randomness); + let q_enable = meta.query_selector(q_enable); + + vec![q_enable * (obtained_result - result)] + }); + + RlcConfig { + q_enable, + witness, + randomness, + rlc, + _marker: PhantomData, + } + } + + pub fn assign_rlc( + &self, + layouter: &mut impl Layouter, + witness: [AssignedCell; N], + randomness: AssignedCell, + ) -> Result, Error> { + layouter.assign_region( + || "RLC", + |mut region| { + self.q_enable.enable(&mut region, 0)?; + let rlc = crate::circuit::compute_rlc_cells(&witness, randomness.clone())?; + witness + .iter() + .enumerate() + .map(|(idx, cell)| -> Result<_, _> { + cell.copy_advice(|| "RLC witness data", &mut region, self.witness[idx], 0) + }) + .collect::, Error>>()?; + + randomness.copy_advice(|| "RLC randomness", &mut region, self.randomness, 0)?; + + // Assign RLC result + region.assign_advice(|| "RLC result", self.rlc, 0, || Ok(rlc)) + }, + ) + } + + pub fn assign_rlc_retunring_last_randomness( + &self, + layouter: &mut impl Layouter, + witness: [AssignedCell; N], + randomness: AssignedCell, + ) -> Result<(AssignedCell, AssignedCell), Error> { + layouter.assign_region( + || "RLC", + |mut region| { + self.q_enable.enable(&mut region, 0)?; + let rlc = crate::circuit::compute_rlc_cells(&witness, randomness.clone())?; + witness + .iter() + .enumerate() + .map(|(idx, cell)| -> Result<_, _> { + cell.copy_advice(|| "RLC witness data", &mut region, self.witness[idx], 0) + }) + .collect::, Error>>()?; + + randomness.copy_advice(|| "RLC randomness", &mut region, self.randomness, 0)?; + + // Assign RLC result + let rlc = region.assign_advice(|| "RLC result", self.rlc, 0, || Ok(rlc))?; + + let mut last_randomness = randomness.value().copied().unwrap_or_default(); + for _ in 0..N { + last_randomness *= last_randomness; + } + + let last_randomness = region.assign_advice( + || "Last randomness", + self.randomness, + 1, + || Ok(last_randomness), + )?; + + Ok((rlc, last_randomness)) + }, + ) + } +} + +#[cfg(test)] +mod rlc_tests { + use super::*; + use halo2_proofs::circuit::Layouter; + use halo2_proofs::pairing::bn256::Fr as Fp; + use halo2_proofs::plonk::{ConstraintSystem, Error, Instance}; + use halo2_proofs::{circuit::SimpleFloorPlanner, dev::MockProver, plonk::Circuit}; + use pretty_assertions::assert_eq; + use std::convert::TryInto; + + struct MyCircuit { + witness: [F; N], + randomness: F, + rlc: F, + } + + impl Default for MyCircuit { + fn default() -> Self { + MyCircuit { + witness: [F::zero(); N], + randomness: F::zero(), + rlc: F::zero(), + } + } + } + + #[derive(Clone)] + struct MyConfig { + rlc_config: RlcConfig, + q_enable: Selector, + randomness: Column, + randomness_adv: Column, + witness: [Column; N], + rlc: Column, + } + + impl Circuit for MyCircuit { + type Config = MyConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let q_enable = meta.selector(); + let witness = [(); N].map(|_| meta.advice_column()).map(|col| { + meta.enable_equality(col); + col + }); + + let randomness = meta.instance_column(); + meta.enable_equality(randomness); + + let rlc = meta.advice_column(); + meta.enable_equality(rlc); + + let randomness_adv = meta.advice_column(); + meta.enable_equality(randomness_adv); + + let rlc_config = RlcConfig::configure(meta, witness, randomness_adv, rlc); + meta.create_gate("Constrain output", |meta| { + let obtained_rlc = meta.query_advice(rlc, Rotation::cur()); + let result_rlc = meta.query_advice(rlc, Rotation::next()); + let q_enable = meta.query_selector(q_enable); + [q_enable * (obtained_rlc - result_rlc)] + }); + + MyConfig { + rlc_config, + q_enable, + randomness, + randomness_adv, + witness, + rlc, + } + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let offset: usize = 0; + + let witness = layouter.assign_region( + || "Keccak round Wittnes & flag assignation", + |mut region| { + // Witness `state` + let witness: [AssignedCell; N] = { + let mut state: Vec> = Vec::with_capacity(N); + for (idx, val) in self.witness.iter().enumerate() { + let cell = region.assign_advice( + || "RLC witness", + config.witness[idx], + offset, + || Ok(*val), + )?; + state.push(cell) + } + state.try_into().unwrap() + }; + + Ok(witness) + }, + )?; + + let randomness = layouter.assign_region( + || "RLC randomness", + |mut region| { + region.assign_advice_from_instance( + || "RLC randomness", + config.randomness, + 0usize, + config.randomness_adv, + 0usize, + ) + }, + )?; + + let obtained_rlc = config + .rlc_config + .assign_rlc(&mut layouter, witness, randomness)?; + + layouter.assign_region( + || "RLC result check", + |mut region| { + config.q_enable.enable(&mut region, 0)?; + region.assign_advice(|| "RLC result", config.rlc, 1, || Ok(self.rlc))?; + obtained_rlc.copy_advice( + || "Expected RLC result", + &mut region, + config.rlc, + 0, + )?; + + Ok(()) + }, + )?; + + Ok(()) + } + } + + #[test] + fn end_to_end() { + const N: usize = 25; + let witness = [Fp::one(); N]; + let og_randomness = Fp::from(2u64); + let mut randomness = og_randomness.clone(); + let mut rlc = witness[0].clone(); + + // Compute rlc + for value in witness[1..].iter() { + rlc = rlc + value.clone() * randomness.clone(); + randomness = randomness * og_randomness.clone(); + } + + let circuit = MyCircuit:: { + witness, + randomness, + rlc, + }; + + // Correct result should pass the tests. + let prover = MockProver::::run(9, &circuit, vec![vec![og_randomness]]).unwrap(); + assert_eq!(prover.verify(), Ok(())); + + // Wrong randomness PI should make the test fail. + let prover = MockProver::::run(9, &circuit, vec![vec![Fp::from(25519u64)]]).unwrap(); + assert!(prover.verify().is_err()); + + // Wrong RLC result should make the test fail. + let circuit = MyCircuit:: { + witness, + randomness, + rlc: Fp::from(999u64), + }; + let prover = MockProver::::run(9, &circuit, vec![vec![og_randomness]]).unwrap(); + assert!(prover.verify().is_err()); + } +}