pub mod keccak_packed_multi;
mod param;
mod table;
mod util;
#[cfg(any(test, feature = "test-circuits"))]
mod dev;
#[cfg(test)]
mod test;
#[cfg(feature = "test-circuits")]
pub use dev::KeccakCircuit as TestKeccakCircuit;
use std::marker::PhantomData;
pub use KeccakCircuitConfig as KeccakConfig;
use self::{
keccak_packed_multi::{keccak_unusable_rows, multi_keccak, KeccakRow},
param::*,
table::*,
util::*,
};
use crate::{
evm_circuit::util::constraint_builder::{BaseConstraintBuilder, ConstrainBuilderCommon},
keccak_circuit::keccak_packed_multi::{
decode, get_num_bits_per_absorb_lookup, get_num_bits_per_base_chi_lookup,
get_num_bits_per_rho_pi_lookup, get_num_bits_per_theta_c_lookup, get_num_rows_per_round,
split, split_uniform, transform, transform_to, Part,
},
table::{KeccakTable, LookupTable},
util::{
cell_manager::{CMFixedHeightStrategy, Cell, CellManager, CellType},
word::{Word32, WordExpr},
Challenges, SubCircuit, SubCircuitConfig,
},
witness::{self, Chunk},
};
use eth_types::Field;
use gadgets::util::{and, not, select, sum, Expr};
use halo2_proofs::{
circuit::{Layouter, Region, Value},
plonk::{Column, ConstraintSystem, Error, Expression, Fixed, TableColumn, VirtualCells},
poly::Rotation,
};
use log::debug;
pub(crate) static DEFAULT_CELL_TYPE: CellType = CellType::StoragePhase1;
#[derive(Clone, Debug)]
pub struct KeccakCircuitConfig<F> {
q_enable: Column<Fixed>,
q_first: Column<Fixed>,
q_round: Column<Fixed>,
q_absorb: Column<Fixed>,
q_round_last: Column<Fixed>,
q_padding: Column<Fixed>,
q_padding_last: Column<Fixed>,
pub keccak_table: KeccakTable,
cell_manager: CellManager<CMFixedHeightStrategy>,
round_cst: Column<Fixed>,
normalize_3: [TableColumn; 2],
normalize_4: [TableColumn; 2],
normalize_6: [TableColumn; 2],
chi_base_table: [TableColumn; 2],
pack_table: [TableColumn; 2],
_marker: PhantomData<F>,
}
pub struct KeccakCircuitConfigArgs<F: Field> {
pub keccak_table: KeccakTable,
pub challenges: Challenges<Expression<F>>,
}
impl<F: Field> SubCircuitConfig<F> for KeccakCircuitConfig<F> {
type ConfigArgs = KeccakCircuitConfigArgs<F>;
fn new(
meta: &mut ConstraintSystem<F>,
Self::ConfigArgs {
keccak_table,
challenges,
}: Self::ConfigArgs,
) -> Self {
assert!(
get_num_rows_per_round() > NUM_BYTES_PER_WORD,
"KeccakCircuit requires KECCAK_ROWS>=9"
);
let q_enable = meta.fixed_column();
let q_first = meta.fixed_column();
let q_round = meta.fixed_column();
let q_absorb = meta.fixed_column();
let q_round_last = meta.fixed_column();
let q_padding = meta.fixed_column();
let q_padding_last = meta.fixed_column();
let round_cst = meta.fixed_column();
let is_final = keccak_table.is_enabled;
let length = keccak_table.input_len;
let data_rlc = keccak_table.input_rlc;
let hash_word = keccak_table.output;
let normalize_3 = array_init::array_init(|_| meta.lookup_table_column());
let normalize_4 = array_init::array_init(|_| meta.lookup_table_column());
let normalize_6 = array_init::array_init(|_| meta.lookup_table_column());
let chi_base_table = array_init::array_init(|_| meta.lookup_table_column());
let pack_table = array_init::array_init(|_| meta.lookup_table_column());
let mut cell_manager = CellManager::new(CMFixedHeightStrategy::new(
get_num_rows_per_round(),
DEFAULT_CELL_TYPE,
));
let mut cb = BaseConstraintBuilder::new(MAX_DEGREE);
let mut total_lookup_counter = 0;
let start_new_hash = |meta: &mut VirtualCells<F>, rot| {
meta.query_fixed(q_first, rot) + meta.query_advice(is_final, rot)
};
let mut round_cst_expr = 0.expr();
meta.create_gate("Query round cst", |meta| {
round_cst_expr = meta.query_fixed(round_cst, Rotation::cur());
vec![0u64.expr()]
});
let mut s = vec![vec![0u64.expr(); 5]; 5];
let mut s_next = vec![vec![0u64.expr(); 5]; 5];
for i in 0..5 {
for j in 0..5 {
let cell = cell_manager.query_cell(meta, DEFAULT_CELL_TYPE);
s[i][j] = cell.expr();
s_next[i][j] = cell.at_offset(meta, get_num_rows_per_round() as i32).expr();
}
}
let absorb_from = cell_manager.query_cell(meta, DEFAULT_CELL_TYPE);
let absorb_data = cell_manager.query_cell(meta, DEFAULT_CELL_TYPE);
let absorb_result = cell_manager.query_cell(meta, DEFAULT_CELL_TYPE);
let mut absorb_from_next = vec![0u64.expr(); NUM_WORDS_TO_ABSORB];
let mut absorb_data_next = vec![0u64.expr(); NUM_WORDS_TO_ABSORB];
let mut absorb_result_next = vec![0u64.expr(); NUM_WORDS_TO_ABSORB];
for i in 0..NUM_WORDS_TO_ABSORB {
let rot = ((i + 1) * get_num_rows_per_round()) as i32;
absorb_from_next[i] = absorb_from.at_offset(meta, rot).expr();
absorb_data_next[i] = absorb_data.at_offset(meta, rot).expr();
absorb_result_next[i] = absorb_result.at_offset(meta, rot).expr();
}
let pre_s = s.clone();
cell_manager.get_strategy().start_region();
let mut lookup_counter = 0;
let part_size = get_num_bits_per_absorb_lookup();
let input = absorb_from.expr() + absorb_data.expr();
let absorb_fat = split::expr(meta, &mut cell_manager, &mut cb, input, 0, part_size);
cell_manager.get_strategy().start_region();
let absorb_res = transform::expr(
"absorb",
meta,
&mut cell_manager,
&mut lookup_counter,
absorb_fat,
normalize_3,
true,
);
cb.require_equal(
"absorb result",
decode::expr(absorb_res),
absorb_result.expr(),
);
debug!("- Post absorb:");
debug!("Lookups: {}", lookup_counter);
debug!("Columns: {}", cell_manager.get_width());
total_lookup_counter += lookup_counter;
cell_manager.get_strategy().start_region();
let mut lookup_counter = 0;
let packed_parts = split::expr(
meta,
&mut cell_manager,
&mut cb,
absorb_data.expr(),
0,
NUM_BYTES_PER_WORD,
);
cell_manager.get_strategy().start_region();
let input_bytes = transform::expr(
"input unpack",
meta,
&mut cell_manager,
&mut lookup_counter,
packed_parts,
pack_table
.into_iter()
.rev()
.collect::<Vec<_>>()
.try_into()
.unwrap(),
true,
);
cell_manager.get_strategy().start_region();
let mut is_paddings = Vec::new();
for _ in input_bytes.iter() {
is_paddings.push(cell_manager.query_cell(meta, DEFAULT_CELL_TYPE));
}
debug!("- Post padding:");
debug!("Lookups: {}", lookup_counter);
debug!("Columns: {}", cell_manager.get_width());
total_lookup_counter += lookup_counter;
cell_manager.get_strategy().start_region();
let mut lookup_counter = 0;
let part_size_c = get_num_bits_per_theta_c_lookup();
let mut c_parts = Vec::new();
for s in s.iter() {
let c = s[0].clone() + s[1].clone() + s[2].clone() + s[3].clone() + s[4].clone();
c_parts.push(split::expr(
meta,
&mut cell_manager,
&mut cb,
c,
1,
part_size_c,
));
}
cell_manager.get_strategy().start_region();
let mut bc = Vec::new();
for c in c_parts {
bc.push(transform::expr(
"theta c",
meta,
&mut cell_manager,
&mut lookup_counter,
c,
normalize_6,
true,
));
}
let mut os = vec![vec![0u64.expr(); 5]; 5];
for i in 0..5 {
let t = decode::expr(bc[(i + 4) % 5].clone())
+ decode::expr(rotate(bc[(i + 1) % 5].clone(), 1, part_size_c));
for j in 0..5 {
os[i][j] = s[i][j].clone() + t.clone();
}
}
s = os.clone();
debug!("- Post theta:");
debug!("Lookups: {}", lookup_counter);
debug!("Columns: {}", cell_manager.get_width());
total_lookup_counter += lookup_counter;
cell_manager.get_strategy().start_region();
let mut lookup_counter = 0;
let part_size = get_num_bits_per_base_chi_lookup();
let target_word_sizes = target_part_sizes(part_size);
let num_word_parts = target_word_sizes.len();
let mut rho_pi_chi_cells: [[[Vec<Cell<F>>; 5]; 5]; 3] = array_init::array_init(|_| {
array_init::array_init(|_| array_init::array_init(|_| Vec::new()))
});
let mut num_columns = 0;
let mut column_starts = [0usize; 3];
for p in 0..3 {
column_starts[p] = cell_manager.get_strategy().start_region();
let mut row_idx = 0;
num_columns = 0;
for j in 0..5 {
for _ in 0..num_word_parts {
for i in 0..5 {
rho_pi_chi_cells[p][i][j].push(cell_manager.query_cell_with_affinity(
meta,
DEFAULT_CELL_TYPE,
row_idx,
));
}
if row_idx == 0 {
num_columns += 1;
}
row_idx = (row_idx + 1) % get_num_rows_per_round();
}
}
}
let pi_region_start = cell_manager.get_strategy().start_region();
let mut os_parts = vec![vec![Vec::new(); 5]; 5];
for (j, os_part) in os_parts.iter_mut().enumerate() {
for i in 0..5 {
let s_parts = split_uniform::expr(
meta,
&rho_pi_chi_cells[0][j][(2 * i + 3 * j) % 5],
&mut cell_manager,
&mut cb,
s[i][j].clone(),
RHO_MATRIX[i][j],
part_size,
);
let s_parts = transform_to::expr(
"rho/pi",
meta,
&rho_pi_chi_cells[1][j][(2 * i + 3 * j) % 5],
&mut lookup_counter,
s_parts.clone(),
normalize_4,
true,
);
os_part[(2 * i + 3 * j) % 5] = s_parts.clone();
}
}
let pi_region_end = cell_manager.get_strategy().start_region();
for c in pi_region_start..pi_region_end {
meta.lookup("pi part range check", |meta| {
vec![(cell_manager.columns()[c].expr_vc(meta), normalize_4[0])]
});
lookup_counter += 1;
}
debug!("- Post rho/pi:");
debug!("Lookups: {}", lookup_counter);
debug!("Columns: {}", cell_manager.get_width());
total_lookup_counter += lookup_counter;
let mut lookup_counter = 0;
let part_size_base = get_num_bits_per_base_chi_lookup();
for idx in 0..num_columns {
let mut input: [Expression<F>; 5] = array_init::array_init(|_| 0.expr());
let mut output: [Expression<F>; 5] = array_init::array_init(|_| 0.expr());
for c in 0..5 {
input[c] = cell_manager.columns()[column_starts[1] + idx * 5 + c]
.expr(meta)
.clone();
output[c] = cell_manager.columns()[column_starts[2] + idx * 5 + c]
.expr(meta)
.clone();
}
for i in 0..5 {
let input = scatter::expr(3, part_size_base) - 2.expr() * input[i].clone()
+ input[(i + 1) % 5].clone()
- input[(i + 2) % 5].clone().clone();
let output = output[i].clone();
meta.lookup("chi base", |_| {
vec![
(input.clone(), chi_base_table[0]),
(output.clone(), chi_base_table[1]),
]
});
lookup_counter += 1;
}
}
let mut os = vec![vec![0u64.expr(); 5]; 5];
for (i, os) in os.iter_mut().enumerate() {
for (j, os) in os.iter_mut().enumerate() {
let mut parts = Vec::new();
for idx in 0..num_word_parts {
parts.push(Part {
num_bits: part_size_base,
cell: rho_pi_chi_cells[2][i][j][idx].clone(),
expr: rho_pi_chi_cells[2][i][j][idx].expr(),
});
}
*os = decode::expr(parts);
}
}
s = os.clone();
cell_manager.get_strategy().start_region();
let part_size = get_num_bits_per_absorb_lookup();
let input = s[0][0].clone() + round_cst_expr.clone();
let iota_parts = split::expr(meta, &mut cell_manager, &mut cb, input, 0, part_size);
cell_manager.get_strategy().start_region();
s[0][0] = decode::expr(transform::expr(
"iota",
meta,
&mut cell_manager,
&mut lookup_counter,
iota_parts,
normalize_3,
true,
));
for i in 0..5 {
for j in 0..5 {
cb.require_equal("next row check", s[i][j].clone(), s_next[i][j].clone());
}
}
debug!("- Post chi:");
debug!("Lookups: {}", lookup_counter);
debug!("Columns: {}", cell_manager.get_width());
total_lookup_counter += lookup_counter;
let mut lookup_counter = 0;
cell_manager.get_strategy().start_region();
let squeeze_from = cell_manager.query_cell(meta, DEFAULT_CELL_TYPE);
let mut squeeze_from_prev = vec![0u64.expr(); NUM_WORDS_TO_SQUEEZE];
for (idx, squeeze_from_prev) in squeeze_from_prev.iter_mut().enumerate() {
let rot = (-(idx as i32) - 1) * get_num_rows_per_round() as i32;
*squeeze_from_prev = squeeze_from.at_offset(meta, rot).expr();
}
cell_manager.get_strategy().start_region();
let squeeze_from_parts =
split::expr(meta, &mut cell_manager, &mut cb, squeeze_from.expr(), 0, 8);
cell_manager.get_strategy().start_region();
let squeeze_bytes = transform::expr(
"squeeze unpack",
meta,
&mut cell_manager,
&mut lookup_counter,
squeeze_from_parts,
pack_table
.into_iter()
.rev()
.collect::<Vec<_>>()
.try_into()
.unwrap(),
true,
);
debug!("- Post squeeze:");
debug!("Lookups: {}", lookup_counter);
debug!("Columns: {}", cell_manager.get_width());
total_lookup_counter += lookup_counter;
meta.create_gate("round", |meta| {
cb.gate(meta.query_fixed(q_round, Rotation::cur()))
});
meta.create_gate("absorb", |meta| {
let mut cb = BaseConstraintBuilder::new(MAX_DEGREE);
let continue_hash = not::expr(start_new_hash(meta, Rotation::cur()));
let absorb_positions = get_absorb_positions();
let mut a_slice = 0;
for j in 0..5 {
for i in 0..5 {
if absorb_positions.contains(&(i, j)) {
cb.condition(continue_hash.clone(), |cb| {
cb.require_equal(
"absorb verify input",
absorb_from_next[a_slice].clone(),
pre_s[i][j].clone(),
);
});
cb.require_equal(
"absorb result copy",
select::expr(
continue_hash.clone(),
absorb_result_next[a_slice].clone(),
absorb_data_next[a_slice].clone(),
),
s_next[i][j].clone(),
);
a_slice += 1;
} else {
cb.require_equal(
"absorb state copy",
pre_s[i][j].clone() * continue_hash.clone(),
s_next[i][j].clone(),
);
}
}
}
cb.gate(meta.query_fixed(q_absorb, Rotation::cur()))
});
let mut hash_bytes = Vec::new();
for i in 0..NUM_WORDS_TO_SQUEEZE {
for byte in squeeze_bytes.iter() {
let rot = (-(i as i32) - 1) * get_num_rows_per_round() as i32;
hash_bytes.push(byte.cell.at_offset(meta, rot).expr());
}
}
meta.create_gate("squeeze", |meta| {
let mut cb = BaseConstraintBuilder::new(MAX_DEGREE);
let start_new_hash = start_new_hash(meta, Rotation::cur());
let hash_words: Vec<_> = pre_s
.into_iter()
.take(4)
.map(|a| a[0].clone())
.take(4)
.collect();
for (idx, word) in hash_words.iter().enumerate() {
cb.condition(start_new_hash.clone(), |cb| {
cb.require_equal(
"squeeze verify packed",
word.clone(),
squeeze_from_prev[idx].clone(),
);
});
}
let hash_bytes_le = hash_bytes.into_iter().rev().collect::<Vec<_>>();
cb.condition(start_new_hash, |cb| {
cb.require_equal_word(
"output check",
Word32::new(hash_bytes_le.try_into().expect("32 limbs")).to_word(),
hash_word.map(|col| meta.query_advice(col, Rotation::cur())),
);
});
cb.gate(meta.query_fixed(q_round_last, Rotation::cur()))
});
meta.create_gate("input checks", |meta| {
let mut cb = BaseConstraintBuilder::new(MAX_DEGREE);
cb.require_boolean(
"boolean is_final",
meta.query_advice(is_final, Rotation::cur()),
);
cb.gate(meta.query_fixed(q_enable, Rotation::cur()))
});
meta.create_gate("first row", |meta| {
let mut cb = BaseConstraintBuilder::new(MAX_DEGREE);
cb.require_zero(
"is_final needs to be disabled on the first row",
meta.query_advice(is_final, Rotation::cur()),
);
cb.gate(meta.query_fixed(q_first, Rotation::cur()))
});
let last_is_padding_in_block = is_paddings.last().unwrap().at_offset(
meta,
-(((NUM_ROUNDS + 1 - NUM_WORDS_TO_ABSORB) * get_num_rows_per_round()) as i32),
);
meta.create_gate("is final", |meta| {
let mut cb = BaseConstraintBuilder::new(MAX_DEGREE);
cb.condition(
meta.query_fixed(q_absorb, Rotation::cur())
- meta.query_fixed(q_first, Rotation::cur()),
|cb| {
cb.require_equal(
"is_final needs to be the same as the last is_padding in the block",
meta.query_advice(is_final, Rotation::cur()),
last_is_padding_in_block.expr(),
);
},
);
cb.condition(
(1..get_num_rows_per_round() as i32)
.map(|i| meta.query_fixed(q_enable, Rotation(-i)))
.fold(0.expr(), |acc, elem| acc + elem),
|cb| {
cb.require_zero(
"is_final only when q_enable",
meta.query_advice(is_final, Rotation::cur()),
);
},
);
cb.gate(1.expr())
});
let prev_is_padding = is_paddings
.last()
.unwrap()
.at_offset(meta, -(get_num_rows_per_round() as i32));
meta.create_gate("padding", |meta| {
let mut cb = BaseConstraintBuilder::new(MAX_DEGREE);
let q_padding = meta.query_fixed(q_padding, Rotation::cur());
let q_padding_last = meta.query_fixed(q_padding_last, Rotation::cur());
for is_padding in is_paddings.iter() {
cb.condition(meta.query_fixed(q_enable, Rotation::cur()), |cb| {
cb.require_boolean("is_padding boolean", is_padding.expr());
});
}
cb.condition(meta.query_fixed(q_absorb, Rotation::cur()), |cb| {
cb.require_zero(
"last is_padding should be zero on absorb rows",
is_paddings.last().unwrap().expr(),
);
});
for idx in 0..is_paddings.len() {
let is_padding_prev = if idx == 0 {
prev_is_padding.expr()
} else {
is_paddings[idx - 1].expr()
};
let is_first_padding = is_paddings[idx].expr() - is_padding_prev.clone();
cb.condition(q_padding.expr(), |cb| {
cb.require_boolean("padding step boolean", is_first_padding.clone());
});
if idx == is_paddings.len() - 1 {
cb.condition(
and::expr([
q_padding.expr() - q_padding_last.expr(),
is_paddings[idx].expr(),
]),
|cb| {
cb.require_equal(
"padding start/intermediate byte last byte",
input_bytes[idx].expr.clone(),
is_first_padding.expr(),
);
},
);
cb.condition(
and::expr([q_padding_last.expr(), is_paddings[idx].expr()]),
|cb| {
cb.require_equal(
"padding start/end byte",
input_bytes[idx].expr.clone(),
is_first_padding.expr() + 128.expr(),
);
},
);
} else {
cb.condition(
and::expr([q_padding.expr(), is_paddings[idx].expr()]),
|cb| {
cb.require_equal(
"padding start/intermediate byte",
input_bytes[idx].expr.clone(),
is_first_padding.expr(),
);
},
);
}
}
cb.gate(1.expr())
});
meta.create_gate("length and data rlc", |meta| {
let mut cb = BaseConstraintBuilder::new(MAX_DEGREE);
let q_padding = meta.query_fixed(q_padding, Rotation::cur());
let start_new_hash_prev =
start_new_hash(meta, Rotation(-(get_num_rows_per_round() as i32)));
let length_prev =
meta.query_advice(length, Rotation(-(get_num_rows_per_round() as i32)));
let length = meta.query_advice(length, Rotation::cur());
let data_rlc_prev =
meta.query_advice(data_rlc, Rotation(-(get_num_rows_per_round() as i32)));
let data_rlcs: Vec<_> = (0..NUM_BYTES_PER_WORD + 1)
.map(|i| meta.query_advice(data_rlc, Rotation(i as i32)))
.collect();
assert_eq!(data_rlcs.len(), input_bytes.len() + 1);
cb.condition(q_padding.expr(), |cb| {
cb.require_equal(
"update length",
length.clone(),
length_prev.clone() * not::expr(start_new_hash_prev.expr())
+ sum::expr(
is_paddings
.iter()
.map(|is_padding| not::expr(is_padding.expr())),
),
);
let mut new_data_rlc = data_rlcs[NUM_BYTES_PER_WORD].expr();
let data_rlc_zero_or_prev =
data_rlc_prev.clone() * not::expr(start_new_hash_prev.expr());
cb.require_equal(
"initial data rlc",
data_rlc_zero_or_prev,
new_data_rlc.clone(),
);
for (idx, (byte, is_padding)) in
input_bytes.iter().zip(is_paddings.iter()).enumerate()
{
new_data_rlc = select::expr(
is_padding.expr(),
new_data_rlc.clone(),
new_data_rlc.clone() * challenges.keccak_input() + byte.expr.clone(),
);
let data_rlc_after_this_byte = data_rlcs[NUM_BYTES_PER_WORD - (idx + 1)].expr();
cb.require_equal(
"intermediate data rlc",
data_rlc_after_this_byte.clone(),
new_data_rlc,
);
new_data_rlc = data_rlc_after_this_byte;
}
});
cb.condition(
and::expr([
meta.query_fixed(q_enable, Rotation::cur())
- meta.query_fixed(q_first, Rotation::cur()),
not::expr(q_padding),
]),
|cb| {
cb.require_equal("length equality check", length.clone(), length_prev.clone());
cb.require_equal(
"data_rlc equality check",
data_rlcs[0].clone(),
data_rlc_prev.clone(),
);
},
);
cb.gate(1.expr())
});
keccak_table.annotate_columns(meta);
normalize_3.iter().enumerate().for_each(|(idx, &col)| {
meta.annotate_lookup_column(col, || format!("KECCAK_normalize_3_{}", idx))
});
normalize_4.iter().enumerate().for_each(|(idx, &col)| {
meta.annotate_lookup_column(col, || format!("KECCAK_normalize_4_{}", idx))
});
normalize_6.iter().enumerate().for_each(|(idx, &col)| {
meta.annotate_lookup_column(col, || format!("KECCAK_normalize_6_{}", idx))
});
chi_base_table.iter().enumerate().for_each(|(idx, &col)| {
meta.annotate_lookup_column(col, || format!("KECCAK_chi_base_{}", idx))
});
pack_table.iter().enumerate().for_each(|(idx, &col)| {
meta.annotate_lookup_column(col, || format!("KECCAK_pack_table_{}", idx))
});
debug!("Degree: {}", meta.degree());
debug!("Minimum rows: {}", meta.minimum_rows());
debug!("Total Lookups: {}", total_lookup_counter);
debug!("Total Columns: {}", cell_manager.get_width());
debug!(
"num unused cells: {}",
cell_manager.get_strategy().get_num_unused_cells()
);
debug!("part_size absorb: {}", get_num_bits_per_absorb_lookup());
debug!("part_size theta: {}", get_num_bits_per_theta_c_lookup());
debug!(
"part_size theta c: {}",
get_num_bits_per_lookup(THETA_C_LOOKUP_RANGE)
);
debug!("part_size theta t: {}", get_num_bits_per_lookup(4));
debug!("part_size rho/pi: {}", get_num_bits_per_rho_pi_lookup());
debug!("part_size chi base: {}", get_num_bits_per_base_chi_lookup());
debug!(
"uniform part sizes: {:?}",
target_part_sizes(get_num_bits_per_theta_c_lookup())
);
KeccakCircuitConfig {
q_enable,
q_first,
q_round,
q_absorb,
q_round_last,
q_padding,
q_padding_last,
keccak_table,
cell_manager,
round_cst,
normalize_3,
normalize_4,
normalize_6,
chi_base_table,
pack_table,
_marker: PhantomData,
}
}
}
impl<F: Field> KeccakCircuitConfig<F> {
pub(crate) fn assign(
&self,
layouter: &mut impl Layouter<F>,
witness: &[KeccakRow<F>],
) -> Result<(), Error> {
layouter.assign_region(
|| "assign keccak rows",
|mut region| {
for (offset, keccak_row) in witness.iter().enumerate() {
self.set_row(&mut region, offset, keccak_row)?;
}
self.keccak_table.annotate_columns_in_region(&mut region);
self.annotate_circuit(&mut region);
Ok(())
},
)
}
fn set_row(
&self,
region: &mut Region<'_, F>,
offset: usize,
row: &KeccakRow<F>,
) -> Result<(), Error> {
for (name, column, value) in &[
("q_enable", self.q_enable, F::from(row.q_enable as u64)),
("q_first", self.q_first, F::from((offset == 0) as u64)),
("q_round", self.q_round, F::from(row.q_round as u64)),
(
"q_round_last",
self.q_round_last,
F::from(row.q_round_last as u64),
),
("q_absorb", self.q_absorb, F::from(row.q_absorb as u64)),
("q_padding", self.q_padding, F::from(row.q_padding as u64)),
(
"q_padding_last",
self.q_padding_last,
F::from(row.q_padding_last as u64),
),
] {
region.assign_fixed(
|| format!("assign {} {}", name, offset),
*column,
offset,
|| Value::known(*value),
)?;
}
self.keccak_table.assign_row(
region,
offset,
[
Value::known(F::from(row.is_final as u64)),
row.data_rlc,
Value::known(F::from(row.length as u64)),
row.hash.lo(),
row.hash.hi(),
],
)?;
for (idx, (bit, column)) in row
.cell_values
.iter()
.zip(self.cell_manager.columns())
.enumerate()
{
region.assign_advice(
|| format!("assign lookup value {} {}", idx, offset),
column.advice,
offset,
|| Value::known(*bit),
)?;
}
region.assign_fixed(
|| format!("assign round cst {}", offset),
self.round_cst,
offset,
|| Value::known(row.round_cst),
)?;
Ok(())
}
pub(crate) fn load_aux_tables(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
load_normalize_table(layouter, "normalize_6", &self.normalize_6, 6u64)?;
load_normalize_table(layouter, "normalize_4", &self.normalize_4, 4u64)?;
load_normalize_table(layouter, "normalize_3", &self.normalize_3, 3u64)?;
load_lookup_table(
layouter,
"chi base",
&self.chi_base_table,
get_num_bits_per_base_chi_lookup(),
&CHI_BASE_LOOKUP_TABLE,
)?;
load_pack_table(layouter, &self.pack_table)
}
fn annotate_circuit(&self, region: &mut Region<F>) {
region.name_column(|| "KECCAK_q_enable", self.q_enable);
region.name_column(|| "KECCAK_q_first", self.q_first);
region.name_column(|| "KECCAK_q_round", self.q_round);
region.name_column(|| "KECCAK_q_absorb", self.q_absorb);
region.name_column(|| "KECCAK_q_round_last", self.q_round_last);
region.name_column(|| "KECCAK_q_padding_last", self.q_padding_last);
}
}
#[derive(Default, Clone, Debug)]
pub struct KeccakCircuit<F: Field> {
inputs: Vec<Vec<u8>>,
num_rows: usize,
_marker: PhantomData<F>,
}
impl<F: Field> SubCircuit<F> for KeccakCircuit<F> {
type Config = KeccakCircuitConfig<F>;
fn unusable_rows() -> usize {
keccak_unusable_rows()
}
fn new_from_block(block: &witness::Block<F>, chunk: &Chunk<F>) -> Self {
Self::new(
chunk.fixed_param.max_keccak_rows,
block.keccak_inputs.clone(),
)
}
fn min_num_rows_block(block: &witness::Block<F>, chunk: &Chunk<F>) -> (usize, usize) {
let rows_perchunk = (NUM_ROUNDS + 1) * get_num_rows_per_round();
(
block
.keccak_inputs
.iter()
.map(|bytes| (bytes.len() as f64 / 136.0).ceil() as usize * rows_perchunk)
.sum(),
chunk.fixed_param.max_keccak_rows,
)
}
fn synthesize_sub(
&self,
config: &Self::Config,
challenges: &Challenges<Value<F>>,
layouter: &mut impl Layouter<F>,
) -> Result<(), Error> {
config.load_aux_tables(layouter)?;
let witness = self.generate_witness(*challenges);
config.assign(layouter, witness.as_slice())
}
}
impl<F: Field> KeccakCircuit<F> {
pub fn new(num_rows: usize, inputs: Vec<Vec<u8>>) -> Self {
KeccakCircuit {
inputs,
num_rows,
_marker: PhantomData,
}
}
pub fn capacity(&self) -> Option<usize> {
if self.num_rows > 0 {
Some(self.num_rows / ((NUM_ROUNDS + 1) * get_num_rows_per_round()) - 2)
} else {
None
}
}
pub(crate) fn generate_witness(&self, challenges: Challenges<Value<F>>) -> Vec<KeccakRow<F>> {
multi_keccak(self.inputs.as_slice(), challenges, self.capacity())
.expect("Too many inputs for given capacity")
}
}