use alloc::{vec, vec::Vec};
use miden_core::Felt;
use crate::relations::ProvideMult;
pub const ROUND_PERIOD: usize = 128;
pub const NUM_PERIODIC_COLS: usize = 10;
pub const COL_IS_XOR: usize = 0;
pub const COL_IS_ANDNOT: usize = 1;
pub const COL_IS_ROL: usize = 2;
pub const COL_BACK_A: usize = 3;
pub const COL_BACK_B: usize = 4;
pub const COL_K: usize = 5;
pub const COL_DST_MULT: usize = 6;
pub const COL_IS_XORROL: usize = 8;
pub const COL_SWAP: usize = 9;
pub const COL_P_LAST: usize = 7;
pub const SLOT_RC: usize = 0;
pub const SLOT_ZERO: usize = 1;
pub const SLOT_C_BEGIN: usize = 2;
pub const SLOT_D_ROL_BEGIN: usize = 22;
pub const SLOT_D_XOR_BEGIN: usize = 27;
pub const SLOT_APPLY_RPI_BEGIN: usize = 32;
pub const SLOT_CHI_ANDNOT_BEGIN: usize = 69;
pub const SLOT_CHI00: usize = 102;
pub const SLOT_IOTA: usize = 103;
pub const SLOT_CHI_XOR_BEGIN: usize = 104;
const fn slot_c(x: usize) -> usize {
SLOT_C_BEGIN + 4 * x + 3
}
const fn slot_d_rol(x: usize) -> usize {
SLOT_D_ROL_BEGIN + x
}
const fn slot_d(x: usize) -> usize {
SLOT_D_XOR_BEGIN + x
}
const fn slot_lane_prev(x: usize, y: usize) -> usize {
if x == 0 && y == 0 {
SLOT_IOTA
} else {
SLOT_CHI_XOR_BEGIN + (x + 5 * y - 1)
}
}
const fn slot_b(x: usize, y: usize) -> usize {
let lane = x + 5 * y;
[
32, 34, 36, 37, 38, 39, 40, 41, 43, 46, 47, 48, 49, 50, 51, 52, 54, 55, 56, 58, 61, 63, 65, 67, 68, ][lane]
}
const fn pi_inverse(out_x: usize, out_y: usize) -> (usize, usize) {
((3 * out_y + out_x) % 5, out_x)
}
const fn slot_t(x: usize, y: usize) -> usize {
SLOT_CHI_ANDNOT_BEGIN + (x + 5 * y)
}
#[derive(Debug, Clone, Copy)]
enum Source {
Local(usize),
Lane(usize, usize),
None,
}
impl Source {
fn back_off(self, read_slot: usize) -> u64 {
match self {
Source::Local(s) => (read_slot - s) as u64,
Source::Lane(x, y) => (ROUND_PERIOD + read_slot - slot_lane_prev(x, y)) as u64,
Source::None => 0,
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum Op {
Nop,
Xor,
Andnot,
Rol(u32),
XorRol(u32),
}
#[derive(Debug, Clone, Copy)]
struct SlotSpec {
op: Op,
src_a: Source,
src_b: Source,
dst_mult: ProvideMult,
}
impl SlotSpec {
const NOP: SlotSpec = SlotSpec {
op: Op::Nop,
src_a: Source::None,
src_b: Source::None,
dst_mult: 0,
};
}
#[derive(Debug, Clone, Copy)]
pub struct Slot {
pub op: Op,
pub back_a: u64,
pub back_b: u64,
pub dst_mult: ProvideMult,
}
pub fn slots() -> [Slot; ROUND_PERIOD] {
let table = slot_table();
core::array::from_fn(|i| Slot {
op: table[i].op,
back_a: table[i].src_a.back_off(i),
back_b: table[i].src_b.back_off(i),
dst_mult: table[i].dst_mult,
})
}
fn slot_table() -> [SlotSpec; ROUND_PERIOD] {
let mut s = [SlotSpec::NOP; ROUND_PERIOD];
for x in 0..5 {
let base = SLOT_C_BEGIN + 4 * x;
s[base] = SlotSpec {
op: Op::Xor,
src_a: Source::Lane(x, 0),
src_b: Source::Lane(x, 1),
dst_mult: 1,
};
s[base + 1] = SlotSpec {
op: Op::Xor,
src_a: Source::Local(base),
src_b: Source::Lane(x, 2),
dst_mult: 1,
};
s[base + 2] = SlotSpec {
op: Op::Xor,
src_a: Source::Local(base + 1),
src_b: Source::Lane(x, 3),
dst_mult: 1,
};
s[base + 3] = SlotSpec {
op: Op::Xor,
src_a: Source::Local(base + 2),
src_b: Source::Lane(x, 4),
dst_mult: 2,
};
}
for i in 0..5 {
s[SLOT_D_ROL_BEGIN + i] = SlotSpec {
op: Op::Rol(1),
src_a: Source::Local(slot_c((i + 1) % 5)),
src_b: Source::None,
dst_mult: 1, };
}
for i in 0..5 {
s[SLOT_D_XOR_BEGIN + i] = SlotSpec {
op: Op::Xor,
src_a: Source::Local(slot_c((i + 4) % 5)),
src_b: Source::Local(slot_d_rol(i)),
dst_mult: 5, };
}
for out_y in 0..5 {
for out_x in 0..5 {
emit_apply_rpi(&mut s, out_x, out_y);
}
}
for y in 0..5 {
for x in 0..5 {
s[slot_t(x, y)] = SlotSpec {
op: Op::Andnot,
src_a: Source::Local(slot_b((x + 1) % 5, y)),
src_b: Source::Local(slot_b((x + 2) % 5, y)),
dst_mult: 1, };
}
}
s[SLOT_CHI00] = SlotSpec {
op: Op::Xor,
src_a: Source::Local(slot_t(0, 0)),
src_b: Source::Local(slot_b(0, 0)),
dst_mult: 1, };
s[SLOT_IOTA] = SlotSpec {
op: Op::Xor,
src_a: Source::Local(SLOT_CHI00),
src_b: Source::Local(SLOT_RC),
dst_mult: 2, };
for lane_idx in 1..25 {
let x = lane_idx % 5;
let y = lane_idx / 5;
s[SLOT_CHI_XOR_BEGIN + (lane_idx - 1)] = SlotSpec {
op: Op::Xor,
src_a: Source::Local(slot_t(x, y)),
src_b: Source::Local(slot_b(x, y)),
dst_mult: 2, };
}
s
}
fn emit_apply_rpi(s: &mut [SlotSpec; ROUND_PERIOD], out_x: usize, out_y: usize) {
const RHO: [[u32; 5]; 5] = [
[0, 36, 3, 41, 18],
[1, 44, 10, 45, 2],
[62, 6, 43, 15, 61],
[28, 55, 25, 21, 56],
[27, 20, 39, 8, 14],
];
let (in_x, in_y) = pi_inverse(out_x, out_y);
let rho = RHO[in_x][in_y];
let b_slot = slot_b(out_x, out_y);
let d_slot = slot_d(in_x);
let a_src = Source::Lane(in_x, in_y);
let d_src = Source::Local(d_slot);
let (apply_a, apply_b) = if in_y == 0 { (d_src, a_src) } else { (a_src, d_src) };
let final_mult = 3;
let op = if rho == 0 {
Op::Xor } else {
Op::XorRol(rho)
};
s[b_slot] = SlotSpec {
op,
src_a: apply_a,
src_b: apply_b,
dst_mult: final_mult,
};
}
pub fn rol_decompose(s: u32) -> (u32, bool) {
if s >= 32 { (s - 32, true) } else { (s, false) }
}
pub fn round_program() -> [Vec<Felt>; NUM_PERIODIC_COLS] {
let table = slot_table();
let mut cols: [Vec<Felt>; NUM_PERIODIC_COLS] =
core::array::from_fn(|_| vec![Felt::ZERO; ROUND_PERIOD]);
for (slot, spec) in table.iter().enumerate() {
let (is_xor, is_andnot, is_rol, is_xorrol, k, swap) = match spec.op {
Op::Nop => (0, 0, 0, 0, 0u64, false),
Op::Xor => (1, 0, 0, 0, 0, false),
Op::Andnot => (0, 1, 0, 0, 0, false),
Op::Rol(s) => (0, 0, 1, 0, 1u64 << s, false),
Op::XorRol(s) => {
let (shift, swap) = rol_decompose(s);
(1, 0, 1, 1, 1u64 << shift, swap)
},
};
cols[COL_IS_XOR][slot] = Felt::from(is_xor as u8);
cols[COL_IS_ANDNOT][slot] = Felt::from(is_andnot as u8);
cols[COL_IS_ROL][slot] = Felt::from(is_rol as u8);
cols[COL_IS_XORROL][slot] = Felt::from(is_xorrol as u8);
cols[COL_SWAP][slot] = Felt::from(swap as u8);
cols[COL_BACK_A][slot] =
Felt::new(spec.src_a.back_off(slot)).expect("back_a fits in canonical Goldilocks");
cols[COL_BACK_B][slot] =
Felt::new(spec.src_b.back_off(slot)).expect("back_b fits in canonical Goldilocks");
cols[COL_K][slot] = Felt::new(k).expect("k fits in canonical Goldilocks");
cols[COL_DST_MULT][slot] = Felt::from(spec.dst_mult);
}
cols[COL_P_LAST][ROUND_PERIOD - 1] = Felt::ONE;
cols
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn op_counts_match_design() {
let table = slot_table();
let mut nop = 0;
let mut xor = 0;
let mut andnot = 0;
let mut rol = 0;
let mut xorrol = 0;
for spec in &table {
match spec.op {
Op::Nop => nop += 1,
Op::Xor => xor += 1,
Op::Andnot => andnot += 1,
Op::Rol(_) => rol += 1,
Op::XorRol(_) => xorrol += 1,
}
}
assert_eq!(nop, 22, "nop");
assert_eq!(xor, 52, "xor");
assert_eq!(andnot, 25, "andnot");
assert_eq!(rol, 5, "rol");
assert_eq!(xorrol, 24, "xorrol");
assert_eq!(nop + xor + andnot + rol + xorrol, ROUND_PERIOD);
}
#[test]
fn b_slot_table_unique_and_in_range() {
let mut seen = std::collections::HashSet::new();
for x in 0..5 {
for y in 0..5 {
let slot = slot_b(x, y);
assert!(
(SLOT_APPLY_RPI_BEGIN..SLOT_CHI_ANDNOT_BEGIN).contains(&slot),
"B[{x}][{y}] at slot {slot} outside apply+ρπ block",
);
assert!(seen.insert(slot), "B slot {slot} reused");
}
}
}
#[test]
fn round_program_lengths() {
let cols = round_program();
for (i, col) in cols.iter().enumerate() {
assert_eq!(col.len(), ROUND_PERIOD, "col {i} length");
}
}
}