use crate::utilities::{BLOCK_SIZE, aes256_encrypt, xor_arrays};
use constant_time_eq::constant_time_eq;
use zeroize::Zeroize;
#[cfg(feature = "logging")]
use log;
pub const DEFAULT_DIGEST_SIZE: usize = 2usize * BLOCK_SIZE;
pub const DEFAULT_PERMUTE_ROUNDS: usize = 3usize;
#[cfg(feature = "logging")]
macro_rules! log {
($self:tt, $arg:tt) => {
log::trace!(
"SpongeHash256@{:p}: {} --> {:02x?} {:02x?} {:02x?}",
&$self,
$arg,
&$self.state0,
&$self.state1,
&$self.state2
);
};
}
#[cfg(not(feature = "logging"))]
macro_rules! log {
($self:tt, $arg:tt) => {};
}
struct ValidDigestSize<const N: usize>;
impl<const N: usize> ValidDigestSize<N> {
const OK: () = assert!((N > 0) && (N <= 2048), "Digest size must be in the [0..=2048] range!");
}
pub struct SpongeHash256 {
state0: [u8; BLOCK_SIZE],
state1: [u8; BLOCK_SIZE],
state2: [u8; BLOCK_SIZE],
rounds: usize,
offset: usize,
}
impl SpongeHash256 {
pub fn new() -> Self {
Self::with_rounds(DEFAULT_PERMUTE_ROUNDS)
}
pub fn with_rounds(rounds: usize) -> Self {
if rounds < 1usize {
panic!("Number of permutation rounds must be a positive value!")
}
Self {
state0: [0x00u8; BLOCK_SIZE],
state1: [0x36u8; BLOCK_SIZE],
state2: [0x5Cu8; BLOCK_SIZE],
rounds,
offset: 0usize,
}
}
pub fn update(&mut self, message_chunk: &[u8]) {
log!(self, "update::enter");
for byte in message_chunk {
self.state0[self.offset] ^= byte;
self.offset += 1usize;
if self.offset >= BLOCK_SIZE {
self.permute();
self.offset = 0usize;
}
}
log!(self, "update::leave");
}
pub fn digest<const N: usize>(self) -> [u8; N] {
let () = ValidDigestSize::<N>::OK;
let mut digest = [0u8; N];
self.digest_to_slice(&mut digest);
digest
}
pub fn digest_to_slice<const N: usize>(mut self, digest_out: &mut [u8; N]) {
let () = ValidDigestSize::<N>::OK;
assert!(self.offset < BLOCK_SIZE);
log!(self, "digest::enter");
let padding = (BLOCK_SIZE - self.offset) as u8;
while self.offset < BLOCK_SIZE {
self.state0[self.offset] ^= padding;
self.offset += 1usize;
}
let mut pos = 0usize;
while pos < N {
let copy_len = BLOCK_SIZE.min(N - pos);
self.permute();
digest_out[pos..(pos + copy_len)].copy_from_slice(&self.state0[..copy_len]);
pos += copy_len;
}
log!(self, "digest::leave");
}
fn permute(&mut self) {
log!(self, "permfn::enter");
let mut temp0 = [0u8; BLOCK_SIZE];
let mut temp1 = [0u8; BLOCK_SIZE];
let mut temp2 = [0u8; BLOCK_SIZE];
for _ in 0..self.rounds {
aes256_encrypt(&mut temp0, &self.state0, &self.state1, &self.state2);
aes256_encrypt(&mut temp1, &self.state1, &self.state2, &self.state0);
aes256_encrypt(&mut temp2, &self.state2, &self.state0, &self.state1);
xor_arrays(&mut self.state0, &temp0);
xor_arrays(&mut self.state1, &temp1);
xor_arrays(&mut self.state2, &temp2);
if constant_time_eq(&self.state1, &self.state2) {
self.state1[0usize] &= 0x7Fu8;
self.state2[0usize] |= 0x80u8;
}
}
temp0.zeroize();
temp1.zeroize();
temp2.zeroize();
log!(self, "permfn::leave");
}
}
impl Default for SpongeHash256 {
fn default() -> Self {
Self::new()
}
}
impl Drop for SpongeHash256 {
fn drop(&mut self) {
self.state0.zeroize();
self.state1.zeroize();
self.state2.zeroize();
}
}
pub fn compute<const N: usize>(message: &[u8]) -> [u8; N] {
let mut state = SpongeHash256::new();
state.update(message);
state.digest()
}
pub fn compute_to_slice<const N: usize>(digest_out: &mut [u8; N], message: &[u8]) {
let mut state = SpongeHash256::new();
state.update(message);
state.digest_to_slice(digest_out);
}