use alloc::{vec, vec::Vec};
use miden_core::Felt;
pub const SPONGE_PERIOD: usize = 32;
pub const NUM_PERIODIC_COLS: usize = 11;
pub const COL_IDX: usize = 0;
pub const COL_FIRST: usize = 1;
pub const COL_LAST: usize = 2;
pub const COL_RATE_BLOCK: usize = 3;
pub const COL_CAPACITY: usize = 4;
pub const COL_RC_ACTIVE: usize = 5;
pub const COL_SQUEEZE_ACTIVE: usize = 6;
pub const COL_PAD_0X80: usize = 7;
pub const COL_RC_LO: usize = 8;
pub const COL_RC_HI: usize = 9;
pub const COL_EXTRA: usize = 10;
pub const RATE_BLOCK_BEGIN: usize = 0;
pub const RATE_BLOCK_LEN: usize = 17;
pub const CAPACITY_BLOCK_BEGIN: usize = RATE_BLOCK_BEGIN + RATE_BLOCK_LEN;
pub const CAPACITY_BLOCK_LEN: usize = 8;
pub const LANE_16_0X80_SLOT: usize = CAPACITY_BLOCK_BEGIN + CAPACITY_BLOCK_LEN;
pub const EXTRA_BLOCK_BEGIN: usize = LANE_16_0X80_SLOT + 1;
pub const EXTRA_BLOCK_LEN: usize = 3;
pub const NOP_SLACK_BEGIN: usize = EXTRA_BLOCK_BEGIN + EXTRA_BLOCK_LEN;
pub const NOP_SLACK_LEN: usize = SPONGE_PERIOD - NOP_SLACK_BEGIN;
pub const NUM_RC: usize = 24;
pub const KECCAK_RC: [u64; NUM_RC] = [
0x0000_0000_0000_0001,
0x0000_0000_0000_8082,
0x8000_0000_0000_808a,
0x8000_0000_8000_8000,
0x0000_0000_0000_808b,
0x0000_0000_8000_0001,
0x8000_0000_8000_8081,
0x8000_0000_0000_8009,
0x0000_0000_0000_008a,
0x0000_0000_0000_0088,
0x0000_0000_8000_8009,
0x0000_0000_8000_000a,
0x0000_0000_8000_808b,
0x8000_0000_0000_008b,
0x8000_0000_0000_8089,
0x8000_0000_0000_8003,
0x8000_0000_0000_8002,
0x8000_0000_0000_0080,
0x0000_0000_0000_800a,
0x8000_0000_8000_000a,
0x8000_0000_8000_8081,
0x8000_0000_0000_8080,
0x0000_0000_8000_0001,
0x8000_0000_8000_8008,
];
pub fn sponge_program() -> [Vec<Felt>; NUM_PERIODIC_COLS] {
let mut cols: [Vec<Felt>; NUM_PERIODIC_COLS] =
core::array::from_fn(|_| vec![Felt::ZERO; SPONGE_PERIOD]);
for slot in 0..SPONGE_PERIOD {
cols[COL_IDX][slot] = Felt::from(slot as u32);
cols[COL_FIRST][slot] = Felt::from((slot == 0) as u8);
cols[COL_LAST][slot] = Felt::from((slot == SPONGE_PERIOD - 1) as u8);
cols[COL_RATE_BLOCK][slot] = Felt::from((slot < CAPACITY_BLOCK_BEGIN) as u8);
cols[COL_CAPACITY][slot] =
Felt::from(((CAPACITY_BLOCK_BEGIN..LANE_16_0X80_SLOT).contains(&slot)) as u8);
cols[COL_RC_ACTIVE][slot] = Felt::from((slot < NUM_RC) as u8);
cols[COL_SQUEEZE_ACTIVE][slot] = Felt::from(((4..LANE_16_0X80_SLOT).contains(&slot)) as u8);
cols[COL_PAD_0X80][slot] = Felt::from((slot == LANE_16_0X80_SLOT) as u8);
cols[COL_EXTRA][slot] =
Felt::from(((EXTRA_BLOCK_BEGIN..NOP_SLACK_BEGIN).contains(&slot)) as u8);
if slot < NUM_RC {
let rc = KECCAK_RC[slot];
cols[COL_RC_LO][slot] = Felt::from(rc as u32);
cols[COL_RC_HI][slot] = Felt::from((rc >> 32) as u32);
}
}
cols
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn periodic_columns_have_expected_lengths() {
let cols = sponge_program();
for col in &cols {
assert_eq!(col.len(), SPONGE_PERIOD);
}
}
#[test]
fn p_idx_enumerates_0_to_period() {
let cols = sponge_program();
for (slot, value) in cols[COL_IDX].iter().enumerate().take(SPONGE_PERIOD) {
assert_eq!(*value, Felt::from(slot as u32));
}
}
#[test]
fn row_class_flags_partition_the_period() {
let cols = sponge_program();
for (slot, _) in cols[COL_IDX].iter().enumerate().take(SPONGE_PERIOD) {
let rb = u32_at(&cols[COL_RATE_BLOCK], slot);
let cap = u32_at(&cols[COL_CAPACITY], slot);
let pad80 = u32_at(&cols[COL_PAD_0X80], slot);
let extra = u32_at(&cols[COL_EXTRA], slot);
let nop_slack = (slot >= NOP_SLACK_BEGIN) as u32;
assert_eq!(
rb + cap + pad80 + extra + nop_slack,
1,
"slot {slot} not covered exactly once",
);
}
}
#[test]
fn p_extra_fires_on_slots_26_through_28() {
let cols = sponge_program();
for (slot, value) in cols[COL_EXTRA].iter().enumerate().take(SPONGE_PERIOD) {
let expected = if (EXTRA_BLOCK_BEGIN..NOP_SLACK_BEGIN).contains(&slot) {
Felt::ONE
} else {
Felt::ZERO
};
assert_eq!(*value, expected, "slot {slot}");
}
}
#[test]
fn p_first_fires_only_at_slot_0() {
let cols = sponge_program();
assert_eq!(cols[COL_FIRST][0], Felt::ONE);
for (slot, value) in cols[COL_FIRST].iter().enumerate().take(SPONGE_PERIOD).skip(1) {
assert_eq!(*value, Felt::ZERO, "slot {slot}");
}
}
#[test]
fn p_last_fires_only_at_last_slot() {
let cols = sponge_program();
for (slot, value) in cols[COL_LAST].iter().enumerate().take(SPONGE_PERIOD - 1) {
assert_eq!(*value, Felt::ZERO, "slot {slot}");
}
assert_eq!(cols[COL_LAST][SPONGE_PERIOD - 1], Felt::ONE);
}
#[test]
fn p_rc_active_covers_first_24_slots() {
let cols = sponge_program();
for (slot, value) in cols[COL_RC_ACTIVE].iter().enumerate().take(NUM_RC) {
assert_eq!(*value, Felt::ONE, "slot {slot}");
}
for (slot, value) in cols[COL_RC_ACTIVE].iter().enumerate().take(SPONGE_PERIOD).skip(NUM_RC)
{
assert_eq!(*value, Felt::ZERO, "slot {slot}");
}
}
#[test]
fn p_squeeze_active_covers_slots_4_through_24() {
let cols = sponge_program();
for (slot, value) in cols[COL_SQUEEZE_ACTIVE].iter().enumerate().take(4) {
assert_eq!(*value, Felt::ZERO, "slot {slot}");
}
for (slot, value) in
cols[COL_SQUEEZE_ACTIVE].iter().enumerate().take(LANE_16_0X80_SLOT).skip(4)
{
assert_eq!(*value, Felt::ONE, "slot {slot}");
}
for (slot, value) in cols[COL_SQUEEZE_ACTIVE]
.iter()
.enumerate()
.take(SPONGE_PERIOD)
.skip(LANE_16_0X80_SLOT)
{
assert_eq!(*value, Felt::ZERO, "slot {slot}");
}
}
#[test]
fn p_pad_0x80_fires_only_at_slot_25() {
let cols = sponge_program();
for (slot, value) in cols[COL_PAD_0X80].iter().enumerate().take(SPONGE_PERIOD) {
let expected = if slot == LANE_16_0X80_SLOT {
Felt::ONE
} else {
Felt::ZERO
};
assert_eq!(*value, expected, "slot {slot}");
}
}
#[test]
fn rc_values_match_keccak_constants_on_active_rows() {
let cols = sponge_program();
for (slot, &rc) in KECCAK_RC.iter().enumerate().take(NUM_RC) {
assert_eq!(cols[COL_RC_LO][slot], Felt::from(rc as u32));
assert_eq!(cols[COL_RC_HI][slot], Felt::from((rc >> 32) as u32));
}
for (slot, value) in cols[COL_RC_LO].iter().enumerate().take(SPONGE_PERIOD).skip(NUM_RC) {
assert_eq!(*value, Felt::ZERO, "slot {slot}");
assert_eq!(cols[COL_RC_HI][slot], Felt::ZERO, "slot {slot}");
}
}
fn u32_at(col: &[Felt], slot: usize) -> u32 {
let value = col[slot];
if value == Felt::ZERO {
0
} else if value == Felt::ONE {
1
} else {
panic!("non-binary periodic value at slot {slot}");
}
}
}