diff --git a/keccak256/src/circuit/word_builder.rs b/keccak256/src/circuit/word_builder.rs index 6ea1814a3f..ea9293c315 100644 --- a/keccak256/src/circuit/word_builder.rs +++ b/keccak256/src/circuit/word_builder.rs @@ -1,18 +1,47 @@ // Added until this is used by another component #![allow(dead_code)] use super::BYTES_PER_WORD; -use crate::permutation::tables::RangeCheckConfig; use eth_types::Field; use halo2_proofs::{ circuit::{AssignedCell, Layouter}, - plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector}, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector, TableColumn}, poly::Rotation, }; +use std::marker::PhantomData; pub type Byte = u8; pub type AssignedByte = AssignedCell; pub type AssignedWord = AssignedCell; +#[derive(Debug, Clone)] +pub struct RangeCheckConfig { + pub range: TableColumn, + _marker: PhantomData, +} + +impl RangeCheckConfig { + pub(crate) fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { + layouter.assign_table( + || "range", + |mut table| { + for i in 0..=K { + table.assign_cell(|| "range", self.range, i as usize, || Ok(F::from(i)))?; + } + Ok(()) + }, + ) + } + // dead_code reason: WordBuilderConfig is using it. We defer the decision to + // remove this after WordBuilderConfig is complete + #[allow(dead_code)] + pub(crate) fn configure(meta: &mut ConstraintSystem) -> Self { + Self { + range: meta.lookup_table_column(), + _marker: PhantomData, + } + } +} + #[derive(Debug, Clone)] /// Gets 8 Advice columns with the 8bytes to form the word + the final word /// value of the composed bytes at the end. diff --git a/keccak256/src/permutation/circuit.rs b/keccak256/src/permutation/circuit.rs index e36320e418..b60cc42cf5 100644 --- a/keccak256/src/permutation/circuit.rs +++ b/keccak256/src/permutation/circuit.rs @@ -1,9 +1,6 @@ use crate::{ common::{NEXT_INPUTS_LANES, PERMUTATION}, - permutation::{ - generic::GenericConfig, - tables::{Base13toBase9TableConfig, FromBase9TableConfig, StackableTable}, - }, + permutation::{generic::GenericConfig, tables::LookupConfig}, }; use eth_types::Field; use halo2_proofs::{ @@ -12,28 +9,22 @@ use halo2_proofs::{ }; use itertools::Itertools; -use super::{ - components::{ - assign_next_input, assign_rho, assign_theta, assign_xi, convert_from_b9_to_b13, - convert_to_b9_mul_a4, pi_gate_permutation, IotaConstants, - }, - tables::FromBinaryTableConfig, +use super::components::{ + assign_next_input, assign_rho, assign_theta, assign_xi, convert_from_b9_to_b13, + convert_to_b9_mul_a4, pi_gate_permutation, IotaConstants, }; #[derive(Clone, Debug)] pub struct KeccakFConfig { generic: GenericConfig, - stackable: StackableTable, - base13to9_config: Base13toBase9TableConfig, - from_b9_table: FromBase9TableConfig, - from_b2_table: FromBinaryTableConfig, + lookup_config: LookupConfig, pub advice: Column, } impl KeccakFConfig { // We assume state is received in base-9. pub fn configure(meta: &mut ConstraintSystem) -> Self { - let advices: [Column; 3] = (0..3) + let advices: [Column; 4] = (0..4) .map(|_| { let column = meta.advice_column(); meta.enable_equality(column); @@ -44,47 +35,23 @@ impl KeccakFConfig { .unwrap(); let fixed = meta.fixed_column(); - let generic = GenericConfig::configure(meta, advices, fixed); - let stackable_cols: [TableColumn; 3] = (0..3) + let generic = GenericConfig::configure(meta, advices[0..3].try_into().unwrap(), fixed); + let lookup_cols: [TableColumn; 4] = (0..4) .map(|_| meta.lookup_table_column()) .collect_vec() .try_into() .unwrap(); - let base13to9_cols: [TableColumn; 3] = (0..3) - .map(|_| meta.lookup_table_column()) - .collect_vec() - .try_into() - .unwrap(); - let from_base9_cols: [TableColumn; 3] = (0..3) - .map(|_| meta.lookup_table_column()) - .collect_vec() - .try_into() - .unwrap(); - let from_base2_cols: [TableColumn; 3] = (0..3) - .map(|_| meta.lookup_table_column()) - .collect_vec() - .try_into() - .unwrap(); - let stackable = StackableTable::configure(meta, advices, stackable_cols); - let base13to9_config = Base13toBase9TableConfig::configure(meta, advices, base13to9_cols); - let from_b9_table = FromBase9TableConfig::configure(meta, advices, from_base9_cols); - let from_b2_table = FromBinaryTableConfig::configure(meta, advices, from_base2_cols); + let lookup_config = LookupConfig::configure(meta, advices, lookup_cols); Self { generic, - stackable, - base13to9_config, - from_b9_table, - from_b2_table, + lookup_config, advice: advices[0], } } pub fn load(&mut self, layouter: &mut impl Layouter) -> Result<(), Error> { - self.stackable.load(layouter)?; - self.base13to9_config.load(layouter)?; - self.from_b9_table.load(layouter)?; - self.from_b2_table.load(layouter) + self.lookup_config.load(layouter) } // Result b13 state for next round, b2 state for end result @@ -101,13 +68,7 @@ impl KeccakFConfig { for round_idx in 0..PERMUTATION { // State in base-13 state = assign_theta(&self.generic, layouter, &state)?; - state = assign_rho( - layouter, - &self.base13to9_config, - &self.generic, - &self.stackable, - &state, - )?; + state = assign_rho(layouter, &self.lookup_config, &self.generic, &state)?; // Outputs in base-9 which is what Pi requires state = pi_gate_permutation(&state); state = assign_xi(&self.generic, layouter, &state)?; @@ -127,11 +88,11 @@ impl KeccakFConfig { // base_13 which is what Theta requires again at the // start of the loop. state = - convert_from_b9_to_b13(layouter, &self.from_b9_table, &self.generic, state, false)? + convert_from_b9_to_b13(layouter, &self.lookup_config, &self.generic, state, false)? .0; } let (f_mix, f_no_mix) = self - .stackable + .lookup_config .assign_boolean_flag(layouter, next_mixing.is_some())?; state[0] = self.generic.conditional_add_const( layouter, @@ -143,7 +104,7 @@ impl KeccakFConfig { // Convert to base 9 and multiply by A4 let next_input = - convert_to_b9_mul_a4(layouter, &self.from_b2_table, &self.generic, &next_input)?; + convert_to_b9_mul_a4(layouter, &self.lookup_config, &self.generic, &next_input)?; for (i, input) in next_input.iter().enumerate() { state[i] = self @@ -151,7 +112,7 @@ impl KeccakFConfig { .conditional_add_advice(layouter, &state[i], &f_mix, input)?; } let (mut state_b13, state_b2) = - convert_from_b9_to_b13(layouter, &self.from_b9_table, &self.generic, state, true)?; + convert_from_b9_to_b13(layouter, &self.lookup_config, &self.generic, state, true)?; let state_b2 = state_b2.unwrap(); state_b13[0] = self.generic.conditional_add_const( layouter, @@ -304,6 +265,7 @@ mod tests { // Generate next_input as `[Fp;NEXT_INPUTS_LANES]` let next_input_fp: [Fp; NEXT_INPUTS_LANES] = state_bigint_to_field(StateBigInt::from(next_input)); + let k = 18; // When we pass no `mixing_inputs`, we perform the full keccak round // ending with Mixing executing IotaB9 @@ -316,7 +278,7 @@ mod tests { next_mixing: None, }; - let prover = MockProver::::run(17, &circuit, vec![]).unwrap(); + let prover = MockProver::::run(k, &circuit, vec![]).unwrap(); assert_eq!(prover.verify(), Ok(()), "is_mixing: false"); @@ -327,7 +289,7 @@ mod tests { out_state: out_state_non_mix, next_mixing: None, }; - let k = 17; + let prover = MockProver::::run(k, &circuit, vec![]).unwrap(); #[cfg(feature = "dev-graph")] @@ -354,7 +316,7 @@ mod tests { next_mixing: Some(next_input_fp), }; - let prover = MockProver::::run(17, &circuit, vec![]).unwrap(); + let prover = MockProver::::run(k, &circuit, vec![]).unwrap(); assert_eq!(prover.verify(), Ok(()), "is_mixing: true"); @@ -366,7 +328,7 @@ mod tests { next_mixing: Some(next_input_fp), }; - let prover = MockProver::::run(17, &circuit, vec![]).unwrap(); + let prover = MockProver::::run(k, &circuit, vec![]).unwrap(); assert!(prover.verify().is_err()); } diff --git a/keccak256/src/permutation/components.rs b/keccak256/src/permutation/components.rs index 7529681974..c6b945184d 100644 --- a/keccak256/src/permutation/components.rs +++ b/keccak256/src/permutation/components.rs @@ -7,7 +7,7 @@ use itertools::Itertools; use std::convert::TryInto; use std::vec; -use super::tables::{FromBinaryTableConfig, NUM_OF_BINARY_CHUNKS_PER_SLICE, NUM_OF_BINARY_SLICES}; +use super::tables::{LookupConfig, NUM_OF_BINARY_CHUNKS_PER_SLICE, NUM_OF_BINARY_SLICES}; use crate::{ arith_helpers::{convert_b2_to_b13, convert_b2_to_b9, A1, A2, A3, A4, B13, B2, B9}, @@ -16,10 +16,7 @@ use crate::{ permutation::{ generic::GenericConfig, rho_helpers::{slice_lane, RhoLane}, - tables::{ - Base13toBase9TableConfig, FromBase9TableConfig, StackableTable, - NUM_OF_B9_CHUNKS_PER_SLICE, NUM_OF_B9_SLICES, - }, + tables::{NUM_OF_B9_CHUNKS_PER_SLICE, NUM_OF_B9_SLICES}, }, }; @@ -58,9 +55,8 @@ pub fn assign_theta( pub fn assign_rho( layouter: &mut impl Layouter, - base13to9_config: &Base13toBase9TableConfig, + lookup_config: &LookupConfig, generic: &GenericConfig, - stackable: &StackableTable, state: &[AssignedCell; 25], ) -> Result<[AssignedCell; 25], Error> { let mut next_state = vec![]; @@ -78,7 +74,7 @@ pub fn assign_rho( let slices = slice_lane(rotation); let (input_coefs, mut output_coefs, step2_od, step3_od) = - base13to9_config.assign_region(layouter, &slices, &conversions)?; + lookup_config.assign_base13(layouter, &slices, &conversions)?; let input_pobs = conversions .iter() @@ -96,7 +92,7 @@ pub fn assign_rho( generic.linear_combine_consts(layouter, input_coefs, input_pobs, None)?; let last_chunk = generic.sub_advice(layouter, lane, &input_from_chunks)?; - let final_output_coef = stackable.lookup_special_chunks(layouter, &last_chunk)?; + let final_output_coef = lookup_config.lookup_special_chunks(layouter, &last_chunk)?; output_coefs.push(final_output_coef); let output_lane = @@ -107,8 +103,8 @@ pub fn assign_rho( } let step2_sum = generic.running_sum(layouter, step2_od_join, None)?; let step3_sum = generic.running_sum(layouter, step3_od_join, None)?; - stackable.lookup_range_12(layouter, &[step2_sum])?; - stackable.lookup_range_169(layouter, &[step3_sum])?; + lookup_config.lookup_range_12(layouter, &[step2_sum])?; + lookup_config.lookup_range_169(layouter, &[step3_sum])?; Ok(next_state.try_into().unwrap()) } @@ -212,14 +208,14 @@ pub fn assign_next_input( pub fn convert_to_b9_mul_a4( layouter: &mut impl Layouter, - from_b2_table: &FromBinaryTableConfig, + lookup_config: &LookupConfig, generic: &GenericConfig, next_input: &[AssignedCell; NEXT_INPUTS_LANES], ) -> Result<[AssignedCell; NEXT_INPUTS_LANES], Error> { let next_input = next_input .iter() .map(|input| { - let (base2s, base9s, _) = from_b2_table.assign_region(layouter, input)?; + let (base2s, base9s, _) = lookup_config.assign_base2(layouter, input)?; let vs = (0..NUM_OF_BINARY_SLICES) .map(|i| { biguint_to_f( @@ -247,7 +243,7 @@ pub fn convert_to_b9_mul_a4( pub fn convert_from_b9_to_b13( layouter: &mut impl Layouter, - from_b9_table: &FromBase9TableConfig, + lookup_config: &LookupConfig, generic: &GenericConfig, state: [AssignedCell; 25], output_b2: bool, @@ -255,7 +251,7 @@ pub fn convert_from_b9_to_b13( let (state_b13, state_b2): (Vec>, Vec>>) = state .iter() .map(|lane| { - let (base9s, base_13s, base_2s) = from_b9_table.assign_region(layouter, lane)?; + let (base9s, base_13s, base_2s) = lookup_config.assign_base9(layouter, lane)?; let vs = (0..NUM_OF_B9_SLICES) .map(|i| { biguint_to_f(&BigUint::from(B9).pow((NUM_OF_B9_CHUNKS_PER_SLICE * i) as u32)) @@ -297,414 +293,3 @@ pub fn convert_from_b9_to_b13( Ok((state_b13.try_into().unwrap(), state_b2)) } - -#[cfg(test)] -mod tests { - use super::*; - use crate::arith_helpers::StateBigInt; - use crate::common::*; - use crate::gate_helpers::biguint_to_f; - use crate::keccak_arith::*; - use halo2_proofs::{ - circuit::{Layouter, SimpleFloorPlanner}, - dev::MockProver, - pairing::bn256::Fr as Fp, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error, TableColumn}, - }; - use itertools::Itertools; - use std::convert::TryInto; - use std::marker::PhantomData; - - #[test] - fn test_theta_gates() { - #[derive(Clone, Debug)] - struct MyConfig { - lane: Column, - generic: GenericConfig, - } - - impl MyConfig { - pub fn configure(meta: &mut ConstraintSystem) -> Self { - let advices: [Column; 3] = (0..3) - .map(|_| { - let column = meta.advice_column(); - meta.enable_equality(column); - column - }) - .collect::>() - .try_into() - .unwrap(); - let fixed = meta.fixed_column(); - - let lane = advices[0]; - let generic = GenericConfig::configure(meta, advices, fixed); - Self { lane, generic } - } - } - #[derive(Default)] - struct MyCircuit { - in_state: [F; 25], - out_state: [F; 25], - _marker: PhantomData, - } - impl Circuit for MyCircuit { - type Config = MyConfig; - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - // this column is required by `constrain_constant` - let constant = meta.fixed_column(); - meta.enable_constant(constant); - Self::Config::configure(meta) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - let in_state = layouter.assign_region( - || "Wittnes & assignation", - |mut region| { - // Witness `state` - let in_state: [AssignedCell; 25] = { - let mut state: Vec> = Vec::with_capacity(25); - for (offset, val) in self.in_state.iter().enumerate() { - let cell = region.assign_advice( - || "witness input state", - config.lane, - offset, - || Ok(*val), - )?; - state.push(cell) - } - state.try_into().unwrap() - }; - Ok(in_state) - }, - )?; - - let out_state = assign_theta(&config.generic, &mut layouter, &in_state)?; - - layouter.assign_region( - || "Check outstate", - |mut region| { - for (assigned, value) in out_state.iter().zip(self.out_state.iter()) { - region.constrain_constant(assigned.cell(), value)?; - } - Ok(()) - }, - )?; - Ok(()) - } - } - - let input1: State = [ - [1, 0, 0, 0, 0], - [0, 0, 0, 9223372036854775808, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - ]; - let mut in_biguint = StateBigInt::default(); - let mut in_state: [Fp; 25] = [Fp::zero(); 25]; - - for (x, y) in (0..5).cartesian_product(0..5) { - in_biguint[(x, y)] = convert_b2_to_b13(input1[x][y]); - in_state[5 * x + y] = biguint_to_f(&in_biguint[(x, y)]); - } - let s1_arith = KeccakFArith::theta(&in_biguint); - let mut out_state: [Fp; 25] = [Fp::zero(); 25]; - for (x, y) in (0..5).cartesian_product(0..5) { - out_state[5 * x + y] = biguint_to_f(&s1_arith[(x, y)]); - } - - let circuit = MyCircuit:: { - in_state, - out_state, - _marker: PhantomData, - }; - - // Test without public inputs - let prover = MockProver::::run(9, &circuit, vec![]).unwrap(); - - assert_eq!(prover.verify(), Ok(())); - - let mut out_state2 = out_state; - out_state2[0] = Fp::from(5566u64); - - let circuit2 = MyCircuit:: { - in_state, - out_state: out_state2, - _marker: PhantomData, - }; - - let prover = MockProver::::run(9, &circuit2, vec![]).unwrap(); - assert!(prover.verify().is_err()); - } - - #[test] - fn test_rho_gate() { - #[derive(Default)] - struct MyCircuit { - in_state: [F; 25], - out_state: [F; 25], - } - - #[derive(Clone)] - struct MyConfig { - advice: Column, - generic: GenericConfig, - stackable: StackableTable, - base13to9_config: Base13toBase9TableConfig, - } - impl Circuit for MyCircuit { - type Config = MyConfig; - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let advices: [Column; 3] = (0..3) - .map(|_| meta.advice_column()) - .collect::>() - .try_into() - .unwrap(); - - let fixed = meta.fixed_column(); - let stackable_cols: [TableColumn; 3] = (0..3) - .map(|_| meta.lookup_table_column()) - .collect_vec() - .try_into() - .unwrap(); - let base13to9_cols: [TableColumn; 3] = (0..3) - .map(|_| meta.lookup_table_column()) - .collect_vec() - .try_into() - .unwrap(); - let stackable = StackableTable::configure(meta, advices, stackable_cols); - let generic = GenericConfig::configure(meta, advices, fixed); - let base13to9_config = - Base13toBase9TableConfig::configure(meta, advices, base13to9_cols); - - Self::Config { - advice: advices[0], - generic, - stackable, - base13to9_config, - } - } - - fn synthesize( - &self, - mut config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - config.base13to9_config.load(&mut layouter)?; - config.stackable.load(&mut layouter)?; - let state = layouter.assign_region( - || "assign input state", - |mut region| { - let state = self - .in_state - .iter() - .enumerate() - .map(|(offset, &value)| { - region.assign_advice(|| "lane", config.advice, offset, || Ok(value)) - }) - .collect::>, Error>>()?; - - Ok(state.try_into().unwrap()) - }, - )?; - let out_state = assign_rho( - &mut layouter, - &config.base13to9_config, - &config.generic, - &config.stackable, - &state, - )?; - layouter.assign_region( - || "check final states", - |mut region| { - for (assigned, value) in out_state.iter().zip(self.out_state.iter()) { - region.constrain_constant(assigned.cell(), value)?; - } - Ok(()) - }, - )?; - - Ok(()) - } - } - - let input1: State = [ - [102, 111, 111, 98, 97], - [114, 0, 5, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 5, 0], - [0, 0, 0, 0, 0], - ]; - let mut in_biguint = StateBigInt::default(); - let mut in_state: [Fp; 25] = [Fp::zero(); 25]; - - for (x, y) in (0..5).cartesian_product(0..5) { - in_biguint[(x, y)] = convert_b2_to_b13(input1[x][y]); - } - let s0_arith = KeccakFArith::theta(&in_biguint); - for (x, y) in (0..5).cartesian_product(0..5) { - in_state[5 * x + y] = biguint_to_f(&s0_arith[(x, y)]); - } - let s1_arith = KeccakFArith::rho(&s0_arith); - let mut out_state: [Fp; 25] = [Fp::zero(); 25]; - for (x, y) in (0..5).cartesian_product(0..5) { - out_state[5 * x + y] = biguint_to_f(&s1_arith[(x, y)]); - } - let circuit = MyCircuit:: { - in_state, - out_state, - }; - let k = 15; - #[cfg(feature = "dev-graph")] - { - use plotters::prelude::*; - let root = - BitMapBackend::new("rho-test-circuit.png", (1024, 16384)).into_drawing_area(); - root.fill(&WHITE).unwrap(); - let root = root.titled("Rho", ("sans-serif", 60)).unwrap(); - halo2_proofs::dev::CircuitLayout::default() - .render(k, &circuit, &root) - .unwrap(); - } - // Test without public inputs - let prover = MockProver::::run(k, &circuit, vec![]).unwrap(); - - assert_eq!(prover.verify(), Ok(())); - } - - #[test] - fn test_xi_gate() { - #[derive(Clone, Debug)] - struct MyConfig { - lane: Column, - generic: GenericConfig, - } - - impl MyConfig { - pub fn configure(meta: &mut ConstraintSystem) -> Self { - let advices: [Column; 3] = (0..3) - .map(|_| { - let column = meta.advice_column(); - meta.enable_equality(column); - column - }) - .collect::>() - .try_into() - .unwrap(); - let fixed = meta.fixed_column(); - - let lane = advices[0]; - let generic = GenericConfig::configure(meta, advices, fixed); - Self { lane, generic } - } - } - #[derive(Default)] - struct MyCircuit { - in_state: [F; 25], - out_state: [F; 25], - _marker: PhantomData, - } - - impl Circuit for MyCircuit { - type Config = MyConfig; - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - // this column is required by `constrain_constant` - let constant = meta.fixed_column(); - meta.enable_constant(constant); - Self::Config::configure(meta) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - let in_state = layouter.assign_region( - || "Wittnes & assignation", - |mut region| { - // Witness `state` - let in_state: [AssignedCell; 25] = { - let mut state: Vec> = Vec::with_capacity(25); - for (offset, val) in self.in_state.iter().enumerate() { - let cell = region.assign_advice( - || "witness input state", - config.lane, - offset, - || Ok(*val), - )?; - state.push(cell) - } - state.try_into().unwrap() - }; - Ok(in_state) - }, - )?; - - let out_state = assign_xi(&config.generic, &mut layouter, &in_state)?; - - layouter.assign_region( - || "Check outstate", - |mut region| { - for (assigned, value) in out_state.iter().zip(self.out_state.iter()) { - region.constrain_constant(assigned.cell(), value)?; - } - Ok(()) - }, - )?; - Ok(()) - } - } - - let input1: State = [ - [1, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - ]; - let mut in_biguint = StateBigInt::default(); - let mut in_state: [Fp; 25] = [Fp::zero(); 25]; - - for (x, y) in (0..5).cartesian_product(0..5) { - in_biguint[(x, y)] = convert_b2_to_b9(input1[x][y]); - in_state[5 * x + y] = biguint_to_f(&in_biguint[(x, y)]); - } - let s1_arith = KeccakFArith::xi(&in_biguint); - let mut out_state: [Fp; 25] = [Fp::zero(); 25]; - for (x, y) in (0..5).cartesian_product(0..5) { - out_state[5 * x + y] = biguint_to_f(&s1_arith[(x, y)]); - } - let circuit = MyCircuit:: { - in_state, - out_state, - _marker: PhantomData, - }; - - // Test without public inputs - let prover = MockProver::::run(9, &circuit, vec![]).unwrap(); - - assert_eq!(prover.verify(), Ok(())); - } -} diff --git a/keccak256/src/permutation/tables.rs b/keccak256/src/permutation/tables.rs index 7fffe593e8..deb137b24a 100644 --- a/keccak256/src/permutation/tables.rs +++ b/keccak256/src/permutation/tables.rs @@ -10,7 +10,6 @@ use halo2_proofs::{ }; use itertools::Itertools; use std::collections::HashMap; -use std::marker::PhantomData; use strum_macros::{Display, EnumIter}; use super::rho_helpers::{Conversion, STEP2_RANGE, STEP3_RANGE}; @@ -22,20 +21,36 @@ pub const NUM_OF_B9_CHUNKS_PER_SLICE: usize = 5; /// is ceil(`MAX_CHUNKS`/ `NUM_OF_B9_CHUNKS_PER_SLICE`) = 13 pub const NUM_OF_B9_SLICES: usize = 13; +#[derive(EnumIter, Display, Clone, Copy)] +enum TableTags { + Range12 = 0, + Range169, + SpecialChunk, + BooleanFlag, + FromBase13, + FromBase9, + FromBase2, +} + #[derive(Debug, Clone)] -struct ThreeColumnsLookup { +pub struct LookupConfig { q_enable: Selector, - pub(crate) cols: [(Column, TableColumn); 3], - _marker: PhantomData, + pub(crate) cols: [(Column, TableColumn); 4], + special_chunks_map: HashMap<[u8; 32], F>, + /// mapping from base13 input to base9 output and overflow detector + base13_map: HashMap<[u8; 32], (F, F)>, + /// mapping from base9 input to base13 and base2 output + base9_map: HashMap<[u8; 32], (F, F)>, + /// mapping from base2 input to base9 and base13 output + base2_map: HashMap<[u8; 32], (F, F)>, } -impl ThreeColumnsLookup { +impl LookupConfig { pub(crate) fn configure( meta: &mut ConstraintSystem, - adv_cols: [Column; 3], - table_cols: [TableColumn; 3], - name: &'static str, + adv_cols: [Column; 4], + table_cols: [TableColumn; 4], ) -> Self { - let cols: [(Column, TableColumn); 3] = adv_cols + let cols: [(Column, TableColumn); 4] = adv_cols .iter() .cloned() .zip(table_cols.iter().cloned()) @@ -43,55 +58,46 @@ impl ThreeColumnsLookup { .try_into() .unwrap(); let q_enable = meta.complex_selector(); - meta.lookup(name, |meta| { + meta.lookup("keccak", |meta| { let q_enable = meta.query_selector(q_enable); let col0_adv = meta.query_advice(cols[0].0, Rotation::cur()); let col1_adv = meta.query_advice(cols[1].0, Rotation::cur()); let col2_adv = meta.query_advice(cols[2].0, Rotation::cur()); + let col3_adv = meta.query_advice(cols[3].0, Rotation::cur()); vec![ (q_enable.clone() * col0_adv, cols[0].1), (q_enable.clone() * col1_adv, cols[1].1), - (q_enable * col2_adv, cols[2].1), + (q_enable.clone() * col2_adv, cols[2].1), + (q_enable * col3_adv, cols[3].1), ] }); + let special_chunks_map = HashMap::new(); + let base13_map = HashMap::new(); + let base9_map = HashMap::new(); + let base2_map = HashMap::new(); Self { q_enable, cols, - _marker: PhantomData, + special_chunks_map, + base13_map, + base9_map, + base2_map, } } -} -#[derive(EnumIter, Display, Clone, Copy)] -enum TableTags { - Range12 = 0, - Range169, - SpecialChunk, - BooleanFlag, -} - -#[derive(Debug, Clone)] -pub struct StackableTable { - lookup_config: ThreeColumnsLookup, - special_chunks_map: HashMap<[u8; 32], F>, -} - -impl StackableTable { - /// We use col0 for tag that restricts the lookup into certain rows - /// we use col1 and col2 for different purposes depend on the tag - pub(crate) fn configure( - meta: &mut ConstraintSystem, - adv_cols: [Column; 3], - table_cols: [TableColumn; 3], - ) -> Self { - let lookup_config = - ThreeColumnsLookup::configure(meta, adv_cols, table_cols, "stackable lookup"); - let special_chunks_map = HashMap::new(); - Self { - lookup_config, - special_chunks_map, + fn assign_table_row( + &self, + table: &mut Table, + offset: usize, + tag: TableTags, + values: [F; 3], + ) -> Result<(), Error> { + table.assign_cell(|| "tag", self.cols[0].1, offset, || Ok(F::from(tag as u64)))?; + for (col, value) in self.cols[1..4].iter().zip(values.iter()) { + table.assign_cell(|| "cell", col.1, offset, || Ok(value))?; } + Ok(()) } fn load_range( @@ -103,24 +109,7 @@ impl StackableTable { ) -> Result { let mut offset = offset; for i in 0..=k { - table.assign_cell( - || format!("tag range{}", tag), - self.lookup_config.cols[0].1, - offset, - || Ok(F::from(tag as u64)), - )?; - table.assign_cell( - || format!("range{}", tag), - self.lookup_config.cols[1].1, - offset, - || Ok(F::from(i)), - )?; - table.assign_cell( - || format!("dummy col range{}", tag), - self.lookup_config.cols[2].1, - offset, - || Ok(F::zero()), - )?; + self.assign_table_row(table, offset, tag, [F::from(i), F::zero(), F::zero()])?; offset += 1; } Ok(offset) @@ -138,23 +127,11 @@ impl StackableTable { let output_coef = F::from(convert_b13_coef(low + high) as u64); self.special_chunks_map .insert(last_chunk.to_repr(), output_coef); - table.assign_cell( - || "tag special chunks", - self.lookup_config.cols[0].1, + self.assign_table_row( + table, offset, - || Ok(F::from(TableTags::SpecialChunk as u64)), - )?; - table.assign_cell( - || "last chunk", - self.lookup_config.cols[1].1, - offset, - || Ok(last_chunk), - )?; - table.assign_cell( - || "output coef", - self.lookup_config.cols[2].1, - offset, - || Ok(output_coef), + TableTags::SpecialChunk, + [last_chunk, output_coef, F::zero()], )?; offset += 1; } @@ -165,23 +142,11 @@ impl StackableTable { fn load_boolean_flag(&self, table: &mut Table, offset: usize) -> Result { let mut offset = offset; for (left, right) in [(true, false), (false, true)] { - table.assign_cell( - || "tag boolean flag", - self.lookup_config.cols[0].1, - offset, - || Ok(F::from(TableTags::BooleanFlag as u64)), - )?; - table.assign_cell( - || "left", - self.lookup_config.cols[1].1, - offset, - || Ok(F::from(left)), - )?; - table.assign_cell( - || "right", - self.lookup_config.cols[2].1, + self.assign_table_row( + table, offset, - || Ok(F::from(right)), + TableTags::BooleanFlag, + [F::from(left), F::from(right), F::zero()], )?; offset += 1; } @@ -201,7 +166,10 @@ impl StackableTable { offset = self.load_range(&mut table, offset, tag, k)?; } offset = self.load_special_chunks(&mut table, offset)?; - self.load_boolean_flag(&mut table, offset)?; + offset = self.load_boolean_flag(&mut table, offset)?; + offset = self.load_base13(&mut table, offset)?; + offset = self.load_base9(&mut table, offset)?; + self.load_base2(&mut table, offset)?; Ok(()) }, ) @@ -216,24 +184,24 @@ impl StackableTable { layouter.assign_region( || format!("lookup for {}", tag), |mut region| { - let tag = F::from(tag as u64); for (offset, v) in values.iter().enumerate() { - self.lookup_config.q_enable.enable(&mut region, offset)?; + self.q_enable.enable(&mut region, offset)?; region.assign_advice_from_constant( || "tag", - self.lookup_config.cols[0].0, + self.cols[0].0, offset, - tag, + F::from(tag as u64), )?; - v.copy_advice( - || "value", - &mut region, - self.lookup_config.cols[1].0, + v.copy_advice(|| "v", &mut region, self.cols[1].0, offset)?; + region.assign_advice_from_constant( + || "0", + self.cols[2].0, offset, + F::zero(), )?; region.assign_advice_from_constant( - || "dummy", - self.lookup_config.cols[2].0, + || "0", + self.cols[3].0, offset, F::zero(), )?; @@ -267,22 +235,12 @@ impl StackableTable { |mut region| { let offset = 0; let tag = F::from(TableTags::SpecialChunk as u64); - self.lookup_config.q_enable.enable(&mut region, offset)?; - region.assign_advice_from_constant( - || "tag", - self.lookup_config.cols[0].0, - offset, - tag, - )?; - last_chunk.copy_advice( - || "last chunk", - &mut region, - self.lookup_config.cols[1].0, - offset, - )?; - region.assign_advice( + self.q_enable.enable(&mut region, offset)?; + region.assign_advice_from_constant(|| "tag", self.cols[0].0, offset, tag)?; + last_chunk.copy_advice(|| "last chunk", &mut region, self.cols[1].0, offset)?; + let output_coef = region.assign_advice( || "output coef", - self.lookup_config.cols[2].0, + self.cols[2].0, offset, || { last_chunk @@ -291,7 +249,9 @@ impl StackableTable { .map(|v| v.to_owned()) .ok_or(Error::Synthesis) }, - ) + )?; + region.assign_advice_from_constant(|| "0", self.cols[3].0, offset, F::zero())?; + Ok(output_coef) }, ) } @@ -306,131 +266,67 @@ impl StackableTable { || "lookup for boolean flag", |mut region| { let offset = 0; - self.lookup_config.q_enable.enable(&mut region, offset)?; + self.q_enable.enable(&mut region, offset)?; region.assign_advice_from_constant( || "tag", - self.lookup_config.cols[0].0, + self.cols[0].0, offset, F::from(TableTags::BooleanFlag as u64), )?; let left = region.assign_advice( || "left", - self.lookup_config.cols[1].0, + self.cols[1].0, offset, || Ok(F::from(is_left)), )?; let right = region.assign_advice( || "right", - self.lookup_config.cols[2].0, + self.cols[2].0, offset, || Ok(F::from(!is_left)), )?; + region.assign_advice_from_constant(|| "0", self.cols[3].0, offset, F::zero())?; Ok((left, right)) }, ) } -} -#[derive(Debug, Clone)] -pub struct RangeCheckConfig { - pub range: TableColumn, - _marker: PhantomData, -} - -impl RangeCheckConfig { - pub(crate) fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { - layouter.assign_table( - || "range", - |mut table| { - for i in 0..=K { - table.assign_cell(|| "range", self.range, i as usize, || Ok(F::from(i)))?; - } - Ok(()) - }, - ) - } - // dead_code reason: WordBuilderConfig is using it. We defer the decision to - // remove this after WordBuilderConfig is complete - #[allow(dead_code)] - pub(crate) fn configure(meta: &mut ConstraintSystem) -> Self { - Self { - range: meta.lookup_table_column(), - _marker: PhantomData, + pub(crate) fn load_base13( + &mut self, + table: &mut Table, + offset: usize, + ) -> Result { + let mut offset = offset; + // Iterate over all possible 13-ary values of size 4 + for b13_chunks in (0..BASE_NUM_OF_CHUNKS) + .map(|_| 0..B13) + .multi_cartesian_product() + { + let input_b13 = f_from_radix_be::(&b13_chunks, B13); + let output_b9 = f_from_radix_be::( + &b13_chunks + .iter() + .map(|&x| convert_b13_coef(x)) + .collect_vec(), + B9, + ); + let overflow_detector = + F::from(get_overflow_detector(b13_chunks.clone().try_into().unwrap()) as u64); + + self.base13_map + .insert(input_b13.to_repr(), (output_b9, overflow_detector)); + + self.assign_table_row( + table, + offset, + TableTags::FromBase13, + [input_b13, output_b9, overflow_detector], + )?; + offset += 1; } + Ok(offset) } -} - -#[derive(Debug, Clone)] -pub struct Base13toBase9TableConfig { - lookup_config: ThreeColumnsLookup, - // mapping from base13 input to base9 output and overflow detector - map: HashMap<[u8; 32], (F, F)>, -} - -impl Base13toBase9TableConfig { - pub(crate) fn load(&mut self, layouter: &mut impl Layouter) -> Result<(), Error> { - layouter.assign_table( - || "13 -> 9", - |mut table| { - // Iterate over all possible 13-ary values of size 4 - for (i, b13_chunks) in (0..BASE_NUM_OF_CHUNKS) - .map(|_| 0..B13) - .multi_cartesian_product() - .enumerate() - { - let input_b13 = f_from_radix_be::(&b13_chunks, B13); - let output_b9 = f_from_radix_be::( - &b13_chunks - .iter() - .map(|&x| convert_b13_coef(x)) - .collect_vec(), - B9, - ); - let overflow_detector = F::from(get_overflow_detector( - b13_chunks.clone().try_into().unwrap(), - ) as u64); - - self.map - .insert(input_b13.to_repr(), (output_b9, overflow_detector)); - table.assign_cell( - || "base 13", - self.lookup_config.cols[0].1, - i, - || Ok(input_b13), - )?; - - table.assign_cell( - || "base 9", - self.lookup_config.cols[1].1, - i, - || Ok(output_b9), - )?; - table.assign_cell( - || "overflow_detector", - self.lookup_config.cols[2].1, - i, - || Ok(overflow_detector), - )?; - } - Ok(()) - }, - ) - } - - /// We use col0 for base 13 input - /// we use col1 for base 9 output - /// we use col2 for overflow detector - pub(crate) fn configure( - meta: &mut ConstraintSystem, - adv_cols: [Column; 3], - table_cols: [TableColumn; 3], - ) -> Self { - let lookup_config = - ThreeColumnsLookup::configure(meta, adv_cols, table_cols, "from base 13"); - let map = HashMap::new(); - Self { lookup_config, map } - } - pub(crate) fn assign_region( + pub(crate) fn assign_base13( &self, layouter: &mut impl Layouter, slices: &[(u32, u32)], @@ -454,12 +350,18 @@ impl Base13toBase9TableConfig { for (offset, (&(_, step), conv)) in slices.iter().zip(conversions.iter()).enumerate() { - self.lookup_config.q_enable.enable(&mut region, offset)?; + self.q_enable.enable(&mut region, offset)?; let input = biguint_to_f::(&conv.input.coef); - let outputs = self.map.get(&input.to_repr()); + let outputs = self.base13_map.get(&input.to_repr()); + region.assign_advice_from_constant( + || "tag", + self.cols[0].0, + offset, + F::from(TableTags::FromBase13 as u64), + )?; let input_coef = region.assign_advice( || "Input Coef", - self.lookup_config.cols[0].0, + self.cols[1].0, offset, || Ok(input), )?; @@ -467,7 +369,7 @@ impl Base13toBase9TableConfig { let output_coef = region.assign_advice( || "Output Coef", - self.lookup_config.cols[1].0, + self.cols[2].0, offset, || outputs.map(|o| o.0).ok_or(Error::Synthesis), )?; @@ -475,7 +377,7 @@ impl Base13toBase9TableConfig { let od = region.assign_advice( || "Overflow detector", - self.lookup_config.cols[2].0, + self.cols[3].0, offset, || outputs.map(|o| o.1).ok_or(Error::Synthesis), )?; @@ -492,95 +394,61 @@ impl Base13toBase9TableConfig { }, ) } -} -fn compute_input_coefs( - input: Option<&F>, - base: u8, - num_chunks: usize, -) -> [Option; SLICES] { - input.map_or([None; SLICES], |&input| { - // big-endian - let input_chunks: Vec = { - let raw = f_to_biguint(input); - let mut v = raw.to_radix_le(base.into()); - debug_assert!(v.len() <= MAX_CHUNKS); - // fill 0 to max chunks - v.resize(MAX_CHUNKS, 0); - // v is big-endian now - v.reverse(); - v - }; - // Use rchunks + rev so that the remainder chunks stay at the big-endian - // side - let input_coefs = input_chunks - .rchunks(num_chunks) - .rev() - .map(|chunks| Some(f_from_radix_be(chunks, base))) - .collect_vec(); - input_coefs.try_into().unwrap() - }) -} - -#[derive(Debug, Clone)] -pub struct FromBase9TableConfig { - lookup_config: ThreeColumnsLookup, - // mapping from base9 input to base13 and base2 output - map: HashMap<[u8; 32], (F, F)>, -} - -impl FromBase9TableConfig { - pub fn load(&mut self, layouter: &mut impl Layouter) -> Result<(), Error> { - layouter.assign_table( - || "9 -> (2 and 13)", - |mut table| { - // Iterate over all possible base 9 values of size 5 - for (i, b9_chunks) in (0..NUM_OF_B9_CHUNKS_PER_SLICE) - .map(|_| 0..B9) - .multi_cartesian_product() - .enumerate() - { - let input_b9 = f_from_radix_be::(&b9_chunks, B9); - let converted_chunks: Vec = - b9_chunks.iter().map(|&x| convert_b9_coef(x)).collect_vec(); - let output_b13 = f_from_radix_be::(&converted_chunks, B13); - let output_b2 = f_from_radix_be::(&converted_chunks, B2); - self.map.insert(input_b9.to_repr(), (output_b13, output_b2)); - table.assign_cell( - || "base 9", - self.lookup_config.cols[0].1, - i, - || Ok(input_b9), - )?; - - table.assign_cell( - || "base 13", - self.lookup_config.cols[1].1, - i, - || Ok(output_b13), - )?; - table.assign_cell( - || "base 2", - self.lookup_config.cols[2].1, - i, - || Ok(output_b2), - )?; - } - Ok(()) - }, - ) + fn compute_input_coefs( + input: Option<&F>, + base: u8, + num_chunks: usize, + ) -> [Option; SLICES] { + input.map_or([None; SLICES], |&input| { + // big-endian + let input_chunks: Vec = { + let raw = f_to_biguint(input); + let mut v = raw.to_radix_le(base.into()); + debug_assert!(v.len() <= MAX_CHUNKS); + // fill 0 to max chunks + v.resize(MAX_CHUNKS, 0); + // v is big-endian now + v.reverse(); + v + }; + // Use rchunks + rev so that the remainder chunks stay at the big-endian + // side + let input_coefs = input_chunks + .rchunks(num_chunks) + .rev() + .map(|chunks| Some(f_from_radix_be(chunks, base))) + .collect_vec(); + input_coefs.try_into().unwrap() + }) } - pub fn configure( - meta: &mut ConstraintSystem, - adv_cols: [Column; 3], - table_cols: [TableColumn; 3], - ) -> Self { - let lookup_config = ThreeColumnsLookup::configure(meta, adv_cols, table_cols, "from base9"); - let map = HashMap::new(); - Self { lookup_config, map } + pub fn load_base9(&mut self, table: &mut Table, offset: usize) -> Result { + let mut offset = offset; + // Iterate over all possible base 9 values of size 5 + for b9_chunks in (0..NUM_OF_B9_CHUNKS_PER_SLICE) + .map(|_| 0..B9) + .multi_cartesian_product() + { + let input_b9 = f_from_radix_be::(&b9_chunks, B9); + let converted_chunks: Vec = + b9_chunks.iter().map(|&x| convert_b9_coef(x)).collect_vec(); + let output_b13 = f_from_radix_be::(&converted_chunks, B13); + let output_b2 = f_from_radix_be::(&converted_chunks, B2); + self.base9_map + .insert(input_b9.to_repr(), (output_b13, output_b2)); + self.assign_table_row( + table, + offset, + TableTags::FromBase9, + [input_b9, output_b13, output_b2], + )?; + offset += 1; + } + Ok(offset) } - pub fn assign_region( + + pub fn assign_base9( &self, layouter: &mut impl Layouter, input: &AssignedCell, @@ -592,7 +460,7 @@ impl FromBase9TableConfig { ), Error, > { - let input_coefs = compute_input_coefs::( + let input_coefs = Self::compute_input_coefs::( input.value(), B9, NUM_OF_B9_CHUNKS_PER_SLICE, @@ -604,26 +472,32 @@ impl FromBase9TableConfig { let mut output_b13_cells = vec![]; let mut output_b2_cells = vec![]; for (offset, input_coef) in input_coefs.iter().enumerate() { - self.lookup_config.q_enable.enable(&mut region, offset)?; + self.q_enable.enable(&mut region, offset)?; + region.assign_advice_from_constant( + || "tag", + self.cols[0].0, + offset, + F::from(TableTags::FromBase9 as u64), + )?; let input = region.assign_advice( || "base 9", - self.lookup_config.cols[0].0, + self.cols[1].0, offset, || input_coef.ok_or(Error::Synthesis), )?; input_cells.push(input.clone()); - let output = input_coef.and_then(|v| self.map.get(&v.to_repr())); + let output = input_coef.and_then(|v| self.base9_map.get(&v.to_repr())); let output_b13 = region.assign_advice( || "base 13", - self.lookup_config.cols[1].0, + self.cols[2].0, offset, || output.map(|v| v.0).ok_or(Error::Synthesis), )?; output_b13_cells.push(output_b13); let output_b2 = region.assign_advice( || "base 2", - self.lookup_config.cols[2].0, + self.cols[3].0, offset, || output.map(|v| v.1).ok_or(Error::Synthesis), )?; @@ -633,67 +507,31 @@ impl FromBase9TableConfig { }, ) } -} - -#[derive(Debug, Clone)] -pub struct FromBinaryTableConfig { - lookup_config: ThreeColumnsLookup, - /// mapping from base2 input to base9 and base13 output - map: HashMap<[u8; 32], (F, F)>, -} - -impl FromBinaryTableConfig { - pub fn load(&mut self, layouter: &mut impl Layouter) -> Result<(), Error> { - layouter.assign_table( - || "2 -> (9 and 13)", - |mut table| { - for (i, b2_chunks) in (0..NUM_OF_BINARY_CHUNKS_PER_SLICE) - .map(|_| 0..B2) - .multi_cartesian_product() - .enumerate() - { - let input_b2 = f_from_radix_be::(&b2_chunks, B2); - let output_b9 = f_from_radix_be::(&b2_chunks, B9); - let output_b13 = f_from_radix_be::(&b2_chunks, B13); - - self.map.insert(input_b2.to_repr(), (output_b9, output_b13)); - // Iterate over all possible binary values of size 16 - - table.assign_cell( - || "base 2", - self.lookup_config.cols[0].1, - i, - || Ok(input_b2), - )?; - table.assign_cell( - || "base 9", - self.lookup_config.cols[1].1, - i, - || Ok(output_b9), - )?; - table.assign_cell( - || "base 13", - self.lookup_config.cols[2].1, - i, - || Ok(output_b13), - )?; - } - Ok(()) - }, - ) + pub fn load_base2(&mut self, table: &mut Table, offset: usize) -> Result { + let mut offset = offset; + for b2_chunks in (0..NUM_OF_BINARY_CHUNKS_PER_SLICE) + .map(|_| 0..B2) + .multi_cartesian_product() + { + let input_b2 = f_from_radix_be::(&b2_chunks, B2); + let output_b9 = f_from_radix_be::(&b2_chunks, B9); + let output_b13 = f_from_radix_be::(&b2_chunks, B13); + + self.base2_map + .insert(input_b2.to_repr(), (output_b9, output_b13)); + self.assign_table_row( + table, + offset, + TableTags::FromBase2, + [input_b2, output_b9, output_b13], + )?; + offset += 1; + } + Ok(offset) } - pub fn configure( - meta: &mut ConstraintSystem, - adv_cols: [Column; 3], - table_cols: [TableColumn; 3], - ) -> Self { - let lookup_config = ThreeColumnsLookup::configure(meta, adv_cols, table_cols, "from base9"); - let map = HashMap::new(); - Self { lookup_config, map } - } - pub fn assign_region( + pub fn assign_base2( &self, layouter: &mut impl Layouter, input: &AssignedCell, @@ -705,7 +543,7 @@ impl FromBinaryTableConfig { ), Error, > { - let input_coefs = compute_input_coefs::( + let input_coefs = Self::compute_input_coefs::( input.value(), B2, NUM_OF_BINARY_CHUNKS_PER_SLICE, @@ -717,27 +555,33 @@ impl FromBinaryTableConfig { let mut output_b9_cells = vec![]; let mut output_b13_cells = vec![]; for (offset, input_coef) in input_coefs.iter().enumerate() { - self.lookup_config.q_enable.enable(&mut region, offset)?; + self.q_enable.enable(&mut region, offset)?; + region.assign_advice_from_constant( + || "tag", + self.cols[0].0, + offset, + F::from(TableTags::FromBase2 as u64), + )?; let input = region.assign_advice( || "base 2", - self.lookup_config.cols[0].0, + self.cols[1].0, offset, || input_coef.ok_or(Error::Synthesis), )?; input_cells.push(input.clone()); - let output = input_coef.and_then(|v| self.map.get(&v.to_repr())); + let output = input_coef.and_then(|v| self.base2_map.get(&v.to_repr())); let output_b9 = region.assign_advice( || "base 9", - self.lookup_config.cols[1].0, + self.cols[2].0, offset, || output.map(|v| v.0).ok_or(Error::Synthesis), )?; output_b9_cells.push(output_b9); let output_b13 = region.assign_advice( || "base 13", - self.lookup_config.cols[2].0, + self.cols[3].0, offset, || output.map(|v| v.1).ok_or(Error::Synthesis), )?;