use core::ops::Index;
use crate::traits::*;
#[cfg(hax)]
use crate::proof_utils::{slices_same_len, valid_rate};
#[cfg(hax)]
use hax_lib::{constructors::from_bool, int::ToInt};
pub(crate) mod xof;
pub(crate) mod constants;
use constants::*;
#[cfg(feature = "simd128")]
pub(crate) mod simd128;
#[cfg(feature = "simd256")]
pub(crate) mod simd256;
pub(crate) mod portable;
#[derive(Copy, Clone)]
pub(crate) struct KeccakState<const N: usize, T: KeccakItem<N>> {
pub(crate) st: [T; 25],
}
#[hax_lib::attributes]
impl<const N: usize, T: KeccakItem<N>> KeccakState<N, T> {
#[inline(always)]
pub(crate) fn new() -> Self {
Self {
st: [T::zero(); 25],
}
}
#[hax_lib::requires(i < 5 && j < 5)]
fn set(&mut self, i: usize, j: usize, v: T) {
set_ij(&mut self.st, i, j, v);
}
#[inline(always)]
fn theta(&mut self) -> [T; 5] {
let c: [T; 5] = [
T::xor5(
self[(0, 0)],
self[(1, 0)],
self[(2, 0)],
self[(3, 0)],
self[(4, 0)],
),
T::xor5(
self[(0, 1)],
self[(1, 1)],
self[(2, 1)],
self[(3, 1)],
self[(4, 1)],
),
T::xor5(
self[(0, 2)],
self[(1, 2)],
self[(2, 2)],
self[(3, 2)],
self[(4, 2)],
),
T::xor5(
self[(0, 3)],
self[(1, 3)],
self[(2, 3)],
self[(3, 3)],
self[(4, 3)],
),
T::xor5(
self[(0, 4)],
self[(1, 4)],
self[(2, 4)],
self[(3, 4)],
self[(4, 4)],
),
];
#[allow(clippy::identity_op)]
[
T::rotate_left1_and_xor(c[(0 + 4) % 5], c[(0 + 1) % 5]),
T::rotate_left1_and_xor(c[(1 + 4) % 5], c[(1 + 1) % 5]),
T::rotate_left1_and_xor(c[(2 + 4) % 5], c[(2 + 1) % 5]),
T::rotate_left1_and_xor(c[(3 + 4) % 5], c[(3 + 1) % 5]),
T::rotate_left1_and_xor(c[(4 + 4) % 5], c[(4 + 1) % 5]),
]
}
#[inline(always)]
fn rho_0(&mut self, t: [T; 5]) {
self.set(0, 0, T::xor(self[(0, 0)], t[0]));
self.set(1, 0, T::xor_and_rotate::<36, 28>(self[(1, 0)], t[0]));
self.set(2, 0, T::xor_and_rotate::<3, 61>(self[(2, 0)], t[0]));
self.set(3, 0, T::xor_and_rotate::<41, 23>(self[(3, 0)], t[0]));
self.set(4, 0, T::xor_and_rotate::<18, 46>(self[(4, 0)], t[0]));
}
#[inline(always)]
fn rho_1(&mut self, t: [T; 5]) {
self.set(0, 1, T::xor_and_rotate::<1, 63>(self[(0, 1)], t[1]));
self.set(1, 1, T::xor_and_rotate::<44, 20>(self[(1, 1)], t[1]));
self.set(2, 1, T::xor_and_rotate::<10, 54>(self[(2, 1)], t[1]));
self.set(3, 1, T::xor_and_rotate::<45, 19>(self[(3, 1)], t[1]));
self.set(4, 1, T::xor_and_rotate::<2, 62>(self[(4, 1)], t[1]));
}
#[inline(always)]
fn rho_2(&mut self, t: [T; 5]) {
self.set(0, 2, T::xor_and_rotate::<62, 2>(self[(0, 2)], t[2]));
self.set(1, 2, T::xor_and_rotate::<6, 58>(self[(1, 2)], t[2]));
self.set(2, 2, T::xor_and_rotate::<43, 21>(self[(2, 2)], t[2]));
self.set(3, 2, T::xor_and_rotate::<15, 49>(self[(3, 2)], t[2]));
self.set(4, 2, T::xor_and_rotate::<61, 3>(self[(4, 2)], t[2]));
}
#[inline(always)]
fn rho_3(&mut self, t: [T; 5]) {
self.set(0, 3, T::xor_and_rotate::<28, 36>(self[(0, 3)], t[3]));
self.set(1, 3, T::xor_and_rotate::<55, 9>(self[(1, 3)], t[3]));
self.set(2, 3, T::xor_and_rotate::<25, 39>(self[(2, 3)], t[3]));
self.set(3, 3, T::xor_and_rotate::<21, 43>(self[(3, 3)], t[3]));
self.set(4, 3, T::xor_and_rotate::<56, 8>(self[(4, 3)], t[3]));
}
#[inline(always)]
fn rho_4(&mut self, t: [T; 5]) {
self.set(0, 4, T::xor_and_rotate::<27, 37>(self[(0, 4)], t[4]));
self.set(1, 4, T::xor_and_rotate::<20, 44>(self[(1, 4)], t[4]));
self.set(2, 4, T::xor_and_rotate::<39, 25>(self[(2, 4)], t[4]));
self.set(3, 4, T::xor_and_rotate::<8, 56>(self[(3, 4)], t[4]));
self.set(4, 4, T::xor_and_rotate::<14, 50>(self[(4, 4)], t[4]));
}
#[inline(always)]
fn rho(&mut self, t: [T; 5]) {
self.rho_0(t);
self.rho_1(t);
self.rho_2(t);
self.rho_3(t);
self.rho_4(t);
}
#[inline(always)]
fn pi_0(&mut self, old: KeccakState<N, T>) {
self.set(1, 0, old[(0, 3)]);
self.set(2, 0, old[(0, 1)]);
self.set(3, 0, old[(0, 4)]);
self.set(4, 0, old[(0, 2)]);
}
#[inline(always)]
fn pi_1(&mut self, old: KeccakState<N, T>) {
self.set(0, 1, old[(1, 1)]);
self.set(1, 1, old[(1, 4)]);
self.set(2, 1, old[(1, 2)]);
self.set(3, 1, old[(1, 0)]);
self.set(4, 1, old[(1, 3)]);
}
#[inline(always)]
fn pi_2(&mut self, old: KeccakState<N, T>) {
self.set(0, 2, old[(2, 2)]);
self.set(1, 2, old[(2, 0)]);
self.set(2, 2, old[(2, 3)]);
self.set(3, 2, old[(2, 1)]);
self.set(4, 2, old[(2, 4)]);
}
#[inline(always)]
fn pi_3(&mut self, old: KeccakState<N, T>) {
self.set(0, 3, old[(3, 3)]);
self.set(1, 3, old[(3, 1)]);
self.set(2, 3, old[(3, 4)]);
self.set(3, 3, old[(3, 2)]);
self.set(4, 3, old[(3, 0)]);
}
#[inline(always)]
fn pi_4(&mut self, old: KeccakState<N, T>) {
self.set(0, 4, old[(4, 4)]);
self.set(1, 4, old[(4, 2)]);
self.set(2, 4, old[(4, 0)]);
self.set(3, 4, old[(4, 3)]);
self.set(4, 4, old[(4, 1)]);
}
#[inline(always)]
fn pi(&mut self) {
let old: KeccakState<N, T> = *self;
self.pi_0(old);
self.pi_1(old);
self.pi_2(old);
self.pi_3(old);
self.pi_4(old);
}
#[inline(always)]
fn chi(&mut self) {
let old = *self;
#[allow(clippy::needless_range_loop)]
for i in 0..5 {
for j in 0..5 {
self.set(
i,
j,
T::and_not_xor(self[(i, j)], old[(i, (j + 2) % 5)], old[(i, (j + 1) % 5)]),
);
}
}
}
#[inline(always)]
#[hax_lib::requires(i < ROUNDCONSTANTS.len())]
fn iota(&mut self, i: usize) {
self.set(0, 0, T::xor_constant(self[(0, 0)], ROUNDCONSTANTS[i]));
}
#[inline(always)]
fn keccakf1600(&mut self) {
for i in 0..24 {
let t = self.theta();
self.rho(t);
self.pi();
self.chi();
self.iota(i);
}
}
#[inline(always)]
#[hax_lib::requires(
from_bool(
N != 0 &&
valid_rate(RATE) &&
start.to_int() + RATE.to_int() <= input[0].len().to_int()
).and(
slices_same_len(input)
)
)]
fn absorb_block<const RATE: usize>(&mut self, input: &[&[u8]; N], start: usize)
where
Self: Absorb<N>,
{
#[cfg(not(any(eurydice, hax)))]
debug_assert!(input.iter().all(|buf| buf.len() == input[0].len()));
self.load_block::<RATE>(input, start);
self.keccakf1600()
}
#[inline(always)]
#[hax_lib::requires(
from_bool(
N != 0 &&
valid_rate(RATE) &&
len < RATE &&
start.to_int() + len.to_int() <= input[0].len().to_int()
).and(
slices_same_len(input)
)
)]
pub(crate) fn absorb_final<const RATE: usize, const DELIM: u8>(
&mut self,
input: &[&[u8]; N],
start: usize,
len: usize,
) where
Self: Absorb<N>,
{
#[cfg(not(eurydice))]
debug_assert!(N > 0 && len < RATE);
#[cfg(not(any(eurydice, hax)))]
debug_assert!(input.iter().all(|buf| buf.len() == input[0].len()));
self.load_last::<RATE, DELIM>(input, start, len);
self.keccakf1600()
}
}
#[hax_lib::attributes]
impl<const N: usize, T: KeccakItem<N>> Index<(usize, usize)> for KeccakState<N, T> {
type Output = T;
#[hax_lib::requires(index.0 < 5 && index.1 < 5)]
fn index(&self, index: (usize, usize)) -> &Self::Output {
get_ij(&self.st, index.0, index.1)
}
}
#[cfg(test)]
mod cross_spec_tests {
use super::*;
use hacspec_sha3::keccak_f as spec_kf;
use hacspec_sha3::sponge as spec_sponge;
fn wrap(st: [u64; 25]) -> KeccakState<1, u64> {
KeccakState { st }
}
fn test_state() -> [u64; 25] {
let mut st = [0u64; 25];
st[0] = 1;
spec_kf::keccak_f(st)
}
#[test]
fn round_constants_match() {
assert_eq!(ROUNDCONSTANTS, spec_kf::ROUND_CONSTANTS);
}
#[test]
fn theta_rho_matches_spec() {
for &st in &[[0u64; 25], test_state()] {
let mut s = wrap(st);
let t = s.theta();
s.rho(t);
assert_eq!(s.st, spec_kf::rho(spec_kf::theta(st)));
}
}
#[test]
fn pi_matches_spec() {
let st = test_state();
let mut s = wrap(st);
s.pi();
assert_eq!(s.st, spec_kf::pi(st));
}
#[test]
fn chi_matches_spec() {
let st = test_state();
let mut s = wrap(st);
s.chi();
assert_eq!(s.st, spec_kf::chi(st));
}
#[test]
fn iota_matches_spec() {
for round in 0..24 {
let st = test_state();
let mut s = wrap(st);
s.iota(round);
assert_eq!(s.st, spec_kf::iota(st, round));
}
}
#[test]
fn keccak_f_matches_spec() {
for &st in &[[0u64; 25], test_state()] {
let mut s = wrap(st);
s.keccakf1600();
assert_eq!(s.st, spec_kf::keccak_f(st));
}
}
#[test]
fn single_round_matches_spec() {
let st = test_state();
let spec = spec_kf::iota(
spec_kf::chi(spec_kf::pi(spec_kf::rho(spec_kf::theta(st)))),
0,
);
let mut s = wrap(st);
let t = s.theta();
s.rho(t);
s.pi();
s.chi();
s.iota(0);
assert_eq!(s.st, spec);
}
#[test]
fn load_block_matches_spec() {
let block = [0xABu8; 200];
let mut impl_st = [0u64; 25];
crate::simd::portable::load_block::<136>(&mut impl_st, &block, 0);
let spec_st = spec_sponge::xor_block_into_state([0u64; 25], &block, 136);
assert_eq!(impl_st, spec_st);
}
#[test]
fn load_block_with_offset() {
let mut data = [0u8; 400];
for (i, b) in data.iter_mut().enumerate() {
*b = (i & 0xFF) as u8;
}
let mut impl_st = [0u64; 25];
crate::simd::portable::load_block::<136>(&mut impl_st, &data, 136);
let spec_st = spec_sponge::xor_block_into_state([0u64; 25], &data[136..272], 136);
assert_eq!(impl_st, spec_st);
}
#[test]
fn store_block_matches_spec() {
let state = test_state();
let mut impl_out = [0u8; 200];
crate::simd::portable::store_block::<136>(&state, &mut impl_out, 0, 136);
let mut spec_out = [0u8; 200];
spec_out = spec_sponge::squeeze_state(&state, spec_out, 0, 136);
assert_eq!(impl_out, spec_out);
}
#[test]
fn load_last_matches_spec_padding() {
let rate = 136usize;
for len in [0, 1, 7, 8, 9, 15, 16, 17, 64, 100, 135] {
let mut msg = [0u8; 135];
for i in 0..len {
msg[i] = (i & 0xFF) as u8;
}
let mut impl_st = [0u64; 25];
crate::simd::portable::load_last::<136, 0x06>(&mut impl_st, &msg[..len], 0, len);
let mut last_block = [0u8; 200];
last_block[..len].copy_from_slice(&msg[..len]);
last_block[len] = 0x06;
last_block[rate - 1] |= 0x80;
let spec_st = spec_sponge::xor_block_into_state([0u64; 25], &last_block, rate);
assert_eq!(impl_st, spec_st, "load_last mismatch at len={len}");
}
}
#[test]
fn load_block_all_rates() {
let data = [0xCDu8; 200];
macro_rules! test_rate {
($rate:expr) => {
let mut impl_st = [0u64; 25];
crate::simd::portable::load_block::<$rate>(&mut impl_st, &data, 0);
let spec_st = spec_sponge::xor_block_into_state([0u64; 25], &data, $rate);
assert_eq!(impl_st, spec_st, "load_block mismatch at rate={}", $rate);
};
}
test_rate!(72); test_rate!(104); test_rate!(136); test_rate!(144); test_rate!(168); }
#[test]
fn store_block_partial_len() {
let state = test_state();
for &len in &[8, 16, 64, 72, 104, 136] {
let mut impl_out = [0u8; 200];
crate::simd::portable::store_block::<136>(&state, &mut impl_out, 0, len);
let mut spec_out = [0u8; 200];
spec_out = spec_sponge::squeeze_state(&state, spec_out, 0, len);
assert_eq!(impl_out, spec_out, "store_block mismatch at len={len}");
}
}
#[test]
fn store_block_with_offset() {
let state = test_state();
let len = 72;
let offset = 64;
let mut impl_out = [0u8; 200];
crate::simd::portable::store_block::<136>(&state, &mut impl_out, offset, len);
let mut spec_out = [0u8; 200];
spec_out = spec_sponge::squeeze_state(&state, spec_out, offset, len);
assert_eq!(impl_out, spec_out);
}
}
#[cfg(all(test, feature = "simd128"))]
mod neon_to_spec_tests {
use super::*;
use hacspec_sha3::keccak_f as spec_kf;
use libcrux_intrinsics::arm64::*;
fn extract_lane(state: &KeccakState<2, _uint64x2_t>, lane: usize) -> [u64; 25] {
assert!(lane < 2);
let mut out = [0u64; 25];
for i in 0..25 {
let mut tmp = [0u64; 2];
_vst1q_u64(&mut tmp, state.st[i]);
out[i] = tmp[lane];
}
out
}
fn to_spec(state: &KeccakState<2, _uint64x2_t>) -> [[u64; 25]; 2] {
[extract_lane(state, 0), extract_lane(state, 1)]
}
fn from_spec(lanes: [[u64; 25]; 2]) -> KeccakState<2, _uint64x2_t> {
let mut st = [_vdupq_n_u64(0); 25];
for i in 0..25 {
let arr = [lanes[0][i], lanes[1][i]];
st[i] = _vld1q_u64(&arr);
}
KeccakState { st }
}
fn test_states() -> [[u64; 25]; 2] {
let mut st0 = [0u64; 25];
st0[0] = 1;
let st0 = spec_kf::keccak_f(st0);
let mut st1 = [0u64; 25];
st1[0] = 0xDEAD_BEEF_CAFE_BABEu64;
let st1 = spec_kf::keccak_f(st1);
[st0, st1]
}
#[test]
fn to_spec_roundtrip() {
let lanes = test_states();
let packed = from_spec(lanes);
let extracted = to_spec(&packed);
assert_eq!(extracted, lanes, "to_spec(from_spec(x)) != x");
}
#[test]
fn neon_theta_rho_to_spec() {
let lanes = test_states();
let mut s = from_spec(lanes);
let t = s.theta();
s.rho(t);
let result = to_spec(&s);
for l in 0..2 {
let spec = spec_kf::rho(spec_kf::theta(lanes[l]));
assert_eq!(result[l], spec, "theta+rho mismatch on lane {l}");
}
}
#[test]
fn neon_pi_to_spec() {
let lanes = test_states();
let mut s = from_spec(lanes);
s.pi();
let result = to_spec(&s);
for l in 0..2 {
let spec = spec_kf::pi(lanes[l]);
assert_eq!(result[l], spec, "pi mismatch on lane {l}");
}
}
#[test]
fn neon_chi_to_spec() {
let lanes = test_states();
let mut s = from_spec(lanes);
s.chi();
let result = to_spec(&s);
for l in 0..2 {
let spec = spec_kf::chi(lanes[l]);
assert_eq!(result[l], spec, "chi mismatch on lane {l}");
}
}
#[test]
fn neon_iota_to_spec() {
for round in 0..24 {
let lanes = test_states();
let mut s = from_spec(lanes);
s.iota(round);
let result = to_spec(&s);
for l in 0..2 {
let spec = spec_kf::iota(lanes[l], round);
assert_eq!(result[l], spec, "iota mismatch on lane {l}, round {round}");
}
}
}
#[test]
fn neon_single_round_to_spec() {
let lanes = test_states();
let mut s = from_spec(lanes);
let t = s.theta();
s.rho(t);
s.pi();
s.chi();
s.iota(0);
let result = to_spec(&s);
for l in 0..2 {
let spec = spec_kf::iota(
spec_kf::chi(spec_kf::pi(spec_kf::rho(spec_kf::theta(lanes[l])))),
0,
);
assert_eq!(result[l], spec, "single round mismatch on lane {l}");
}
}
#[test]
fn neon_keccakf1600_to_spec() {
let lanes = test_states();
let mut s = from_spec(lanes);
s.keccakf1600();
let result = to_spec(&s);
for l in 0..2 {
let spec = spec_kf::keccak_f(lanes[l]);
assert_eq!(result[l], spec, "keccakf1600 mismatch on lane {l}");
}
}
#[test]
fn neon_keccakf1600_zero_state() {
let lanes = [[0u64; 25]; 2];
let mut s = from_spec(lanes);
s.keccakf1600();
let result = to_spec(&s);
let spec = spec_kf::keccak_f([0u64; 25]);
for l in 0..2 {
assert_eq!(
result[l], spec,
"keccakf1600 zero-state mismatch on lane {l}"
);
}
}
#[test]
fn neon_keccakf1600_iterated() {
let mut lanes = test_states();
let mut s = from_spec(lanes);
for _ in 0..5 {
s.keccakf1600();
lanes[0] = spec_kf::keccak_f(lanes[0]);
lanes[1] = spec_kf::keccak_f(lanes[1]);
let result = to_spec(&s);
assert_eq!(result[0], lanes[0], "iterated keccakf1600 lane 0 diverged");
assert_eq!(result[1], lanes[1], "iterated keccakf1600 lane 1 diverged");
}
}
}