use alloc::{vec, vec::Vec};
use core::array;
use miden_core::{Felt, utils::RowMajorMatrix};
use super::*;
use crate::{
hash::keccak::reference::KECCAK_RC,
primitives::byte_pair_lut::{BytePairLutRequires, BytePairOp, require_logic64},
};
fn interleave_lanes(lane_cells: &[Vec<Felt>; NUM_LANES], height: usize) -> RowMajorMatrix<Felt> {
let mut trace = vec![Felt::ZERO; height * NUM_MAIN_COLS];
for r in 0..height {
let row_start = r * NUM_MAIN_COLS;
for (lane, cells) in lane_cells.iter().enumerate() {
let base = lane_base(lane);
let src = &cells[r * LANE_WIDTH..(r + 1) * LANE_WIDTH];
trace[row_start + base..row_start + base + LANE_WIDTH].copy_from_slice(src);
}
}
RowMajorMatrix::new(trace, NUM_MAIN_COLS)
}
pub const IP_BOUNDARY: u64 = 25;
pub const NUM_ROUNDS: usize = 24;
pub const PERM_CYCLE: usize = (NUM_ROUNDS + 1) * ROUND_PERIOD;
fn simulate_logic(op: Op, a: u64, b: u64) -> u64 {
match op {
Op::Nop | Op::Rol(_) => a,
Op::Xor | Op::XorRol(_) => a ^ b,
Op::Andnot => (!a) & b,
}
}
fn simulate_rotate(op: Op, r: u64) -> u64 {
match op {
Op::Rol(s) | Op::XorRol(s) => r.rotate_left(s),
_ => r,
}
}
fn bytes_le(x: u64) -> [Felt; 8] {
x.to_le_bytes().map(Felt::from)
}
fn rot_limbs_for(r: u64, shift: u32) -> [u16; 8] {
let k = 1u64 << shift;
let r_lo = r & 0xffff_ffff;
let r_hi = r >> 32;
let lo_offset_k = (r_lo + (1u64 << 32)).wrapping_mul(k);
let hi_offset_k = (r_hi + (1u64 << 32)).wrapping_mul(k);
let lo_limbs = u64_as_four_u16_limbs(lo_offset_k);
let hi_limbs = u64_as_four_u16_limbs(hi_offset_k);
[
lo_limbs[0],
lo_limbs[1],
lo_limbs[2],
lo_limbs[3],
hi_limbs[0],
hi_limbs[1],
hi_limbs[2],
hi_limbs[3],
]
}
fn u64_as_four_u16_limbs(x: u64) -> [u16; 4] {
[
(x & 0xffff) as u16,
((x >> 16) & 0xffff) as u16,
((x >> 32) & 0xffff) as u16,
((x >> 48) & 0xffff) as u16,
]
}
fn push_row(
trace: &mut Vec<Felt>,
bpl_req: &mut BytePairLutRequires,
ip: u64,
spec: &Slot,
a: u64,
b: u64,
act: bool,
) {
let is_andnot = matches!(spec.op, Op::Andnot);
let logic_active = matches!(spec.op, Op::Xor | Op::Andnot | Op::XorRol(_));
let reads_a = !matches!(spec.op, Op::Nop);
let is_rol = matches!(spec.op, Op::Rol(_) | Op::XorRol(_));
let b_eff = if logic_active { b } else { 0 };
let r = simulate_logic(spec.op, a, b_eff);
if act && reads_a {
let bpl_op = if is_andnot { BytePairOp::AndNot } else { BytePairOp::Xor };
require_logic64(bpl_req, bpl_op, a, b_eff);
}
let mut rot_limbs = [0u16; 8];
if let Op::Rol(s) | Op::XorRol(s) = spec.op {
let (shift, _swap) = program::rol_decompose(s);
rot_limbs = rot_limbs_for(r, shift);
if act {
for limb in rot_limbs {
bpl_req.require_range16(limb);
}
}
}
trace.push(Felt::new(ip).expect("ip fits in canonical Goldilocks"));
trace.extend(bytes_le(a));
trace.extend(bytes_le(b_eff));
trace.extend(bytes_le(r));
trace.extend(rot_limbs.map(Felt::from));
trace.push(Felt::from(act as u8));
let _ = is_rol;
}
pub fn generate_trace_from_states(
states: &[[u64; 25]],
rcs: &[u64; NUM_ROUNDS],
) -> RowMajorMatrix<Felt> {
let mut scratch = BytePairLutRequires::new();
generate_trace_from_states_inner(states, rcs, &mut scratch)
}
fn generate_trace_from_states_inner(
states: &[[u64; 25]],
rcs: &[u64; NUM_ROUNDS],
bpl_req: &mut BytePairLutRequires,
) -> RowMajorMatrix<Felt> {
assert!(!states.is_empty(), "at least one perm required");
let num_perms = states.len();
let active_rows_per_cycle = NUM_ROUNDS * ROUND_PERIOD;
let perms_per_lane = num_perms.div_ceil(NUM_LANES);
let height = (perms_per_lane * PERM_CYCLE).next_power_of_two().max(2);
let program = slots();
let mem_size = IP_BOUNDARY as usize + NUM_LANES * perms_per_lane * PERM_CYCLE + 1;
let mut memory = vec![0u64; mem_size];
for (n, state) in states.iter().enumerate() {
let perm_base = (n * PERM_CYCLE) as u64;
for (idx, &lane) in state.iter().enumerate() {
memory[(perm_base + idx as u64) as usize] = lane;
}
for r in 0..NUM_ROUNDS {
memory[(IP_BOUNDARY + perm_base + (r * ROUND_PERIOD) as u64) as usize] = rcs[r];
}
}
let lane_cells: [Vec<Felt>; NUM_LANES] = array::from_fn(|lane| {
let base_perm = lane * perms_per_lane;
let lane_perms = num_perms.saturating_sub(base_perm).min(perms_per_lane);
let row_offset = base_perm * PERM_CYCLE;
let mut cells = Vec::with_capacity(height * LANE_WIDTH);
for r in 0..height {
let ip = IP_BOUNDARY + (row_offset + r) as u64;
let perm_in_lane = r / PERM_CYCLE;
let row_in_cycle = r % PERM_CYCLE;
if perm_in_lane >= lane_perms {
push_row(
&mut cells,
bpl_req,
ip,
&Slot {
op: Op::Nop,
back_a: 0,
back_b: 0,
dst_mult: 0,
},
0,
0,
false,
);
continue;
}
let spec = program[r % ROUND_PERIOD];
let act = row_in_cycle < active_rows_per_cycle;
let reads_a = !matches!(spec.op, Op::Nop);
let reads_b = matches!(spec.op, Op::Xor | Op::Andnot | Op::XorRol(_));
let a = if reads_a {
memory[ip.wrapping_sub(spec.back_a) as usize]
} else {
0
};
let b = if reads_b {
memory[ip.wrapping_sub(spec.back_b) as usize]
} else {
0
};
let r_val = simulate_logic(spec.op, a, b);
let c_val = simulate_rotate(spec.op, r_val);
if act && spec.dst_mult > 0 {
memory[ip as usize] = c_val;
}
push_row(&mut cells, bpl_req, ip, &spec, a, b, act);
}
cells
});
interleave_lanes(&lane_cells, height)
}
pub fn extract_outputs(states: &[[u64; 25]], rcs: &[u64; NUM_ROUNDS]) -> Vec<[u64; 25]> {
assert!(!states.is_empty(), "at least one perm required");
let num_perms = states.len();
let active_rows_per_cycle = NUM_ROUNDS * ROUND_PERIOD;
let total_rows = num_perms * PERM_CYCLE;
let program = slots();
let mut memory = vec![0u64; IP_BOUNDARY as usize + total_rows];
for (n, state) in states.iter().enumerate() {
let perm_base = (n * PERM_CYCLE) as u64;
for (idx, &lane) in state.iter().enumerate() {
memory[(perm_base + idx as u64) as usize] = lane;
}
for r in 0..NUM_ROUNDS {
memory[(IP_BOUNDARY + perm_base + (r * ROUND_PERIOD) as u64) as usize] = rcs[r];
}
}
for row in 0..total_rows {
let row_in_cycle = row % PERM_CYCLE;
if row_in_cycle >= active_rows_per_cycle {
continue;
}
let slot = row % ROUND_PERIOD;
let ip = IP_BOUNDARY + row as u64;
let spec = program[slot];
let reads_a = !matches!(spec.op, Op::Nop);
let reads_b = matches!(spec.op, Op::Xor | Op::Andnot | Op::XorRol(_));
let a = if reads_a {
memory[ip.wrapping_sub(spec.back_a) as usize]
} else {
0
};
let b = if reads_b {
memory[ip.wrapping_sub(spec.back_b) as usize]
} else {
0
};
let r = simulate_logic(spec.op, a, b);
let c = simulate_rotate(spec.op, r);
if spec.dst_mult > 0 {
memory[ip as usize] = c;
}
}
let mut outputs = Vec::with_capacity(num_perms);
for n in 0..num_perms {
let perm_base = (n * PERM_CYCLE) as u64;
let last_round_base = IP_BOUNDARY + perm_base + (23 * ROUND_PERIOD) as u64;
let mut out = [0u64; 25];
for (idx, out_limb) in out.iter_mut().enumerate() {
let slot = if idx == 0 {
program::SLOT_IOTA
} else {
program::SLOT_CHI_XOR_BEGIN + (idx - 1)
};
*out_limb = memory[(last_round_base + slot as u64) as usize];
}
outputs.push(out);
}
outputs
}
pub fn extract_output(state: &[u64; 25], rcs: &[u64; NUM_ROUNDS]) -> [u64; 25] {
extract_outputs(core::slice::from_ref(state), rcs)
.into_iter()
.next()
.expect("single-perm extract")
}
#[derive(Debug, Default, Clone)]
pub struct RoundRequires {
rounds: Vec<[u64; 25]>,
}
impl RoundRequires {
pub fn new() -> Self {
Self::default()
}
pub fn require_round(&mut self, state_in: [u64; 25]) {
self.rounds.push(state_in);
}
pub fn total_rounds(&self) -> u32 {
self.rounds.len() as u32
}
pub fn total_perms(&self) -> u32 {
self.total_rounds() / NUM_ROUNDS as u32
}
}
pub fn generate_trace(
requires: RoundRequires,
bpl_req: &mut BytePairLutRequires,
) -> RowMajorMatrix<Felt> {
assert!(
requires.rounds.len().is_multiple_of(NUM_ROUNDS),
"RoundRequires must hold a multiple of {NUM_ROUNDS} rounds (got {})",
requires.rounds.len(),
);
let num_perms = requires.total_perms() as usize;
let active_rows_per_cycle = NUM_ROUNDS * ROUND_PERIOD;
let perms_per_lane = num_perms.max(1).div_ceil(NUM_LANES);
let height = (perms_per_lane * PERM_CYCLE).next_power_of_two().max(2);
let program = slots();
let mem_size = IP_BOUNDARY as usize + NUM_LANES * perms_per_lane * PERM_CYCLE + 1;
let mut memory = vec![0u64; mem_size];
for n in 0..num_perms {
let perm_base = (n * PERM_CYCLE) as u64;
let round0_state = &requires.rounds[n * NUM_ROUNDS];
for (idx, &lane) in round0_state.iter().enumerate() {
memory[(perm_base + idx as u64) as usize] = lane;
}
for r in 0..NUM_ROUNDS {
memory[(IP_BOUNDARY + perm_base + (r * ROUND_PERIOD) as u64) as usize] = KECCAK_RC[r];
}
}
let lane_cells: [Vec<Felt>; NUM_LANES] = array::from_fn(|lane| {
let base_perm = lane * perms_per_lane;
let lane_perms = num_perms.saturating_sub(base_perm).min(perms_per_lane);
let row_offset = base_perm * PERM_CYCLE;
let mut cells = Vec::with_capacity(height * LANE_WIDTH);
for r in 0..height {
let ip = IP_BOUNDARY + (row_offset + r) as u64;
let perm_in_lane = r / PERM_CYCLE;
let row_in_cycle = r % PERM_CYCLE;
if perm_in_lane >= lane_perms {
push_row(
&mut cells,
bpl_req,
ip,
&Slot {
op: Op::Nop,
back_a: 0,
back_b: 0,
dst_mult: 0,
},
0,
0,
false,
);
continue;
}
let spec = program[r % ROUND_PERIOD];
let act = row_in_cycle < active_rows_per_cycle;
let reads_a = !matches!(spec.op, Op::Nop);
let reads_b = matches!(spec.op, Op::Xor | Op::Andnot | Op::XorRol(_));
let a = if reads_a {
memory[ip.wrapping_sub(spec.back_a) as usize]
} else {
0
};
let b = if reads_b {
memory[ip.wrapping_sub(spec.back_b) as usize]
} else {
0
};
let r_val = simulate_logic(spec.op, a, b);
let c_val = simulate_rotate(spec.op, r_val);
if act && spec.dst_mult > 0 {
memory[ip as usize] = c_val;
}
push_row(&mut cells, bpl_req, ip, &spec, a, b, act);
}
cells
});
interleave_lanes(&lane_cells, height)
}