use super::kupyna::{kupyna_padding, KupynaCore};
use subtle::ConstantTimeEq;
use zeroize::Zeroize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KmacError {
WrongKeyLength,
TagMismatch,
}
fn kmac_generic(
key: &[u8],
message: &[u8],
columns: usize,
rounds: usize,
last_row_shift: usize,
output_bytes: usize,
) -> Result<[u8; 64], KmacError> {
if key.len() != output_bytes {
return Err(KmacError::WrongKeyLength);
}
let mut core = KupynaCore::new(columns, rounds, last_row_shift);
let block_bytes = core.block_bytes();
#[allow(clippy::cast_possible_truncation)] let (padded_key, padded_key_len) = kupyna_padding(key, (key.len() as u64) * 8, block_bytes);
core.update(&padded_key[..padded_key_len]);
core.update(message);
#[allow(clippy::cast_possible_truncation)] let (pad_m, pad_m_len) =
kupyna_padding(core.buffered(), (message.len() as u64) * 8, block_bytes);
let already_buffered = core.buffered().len();
core.update(&pad_m[already_buffered..pad_m_len]);
let mut inverted_key = [0u8; 64];
for (dst, &src) in inverted_key[..key.len()].iter_mut().zip(key) {
*dst = !src;
}
core.update(&inverted_key[..key.len()]);
inverted_key.zeroize();
Ok(core.finalize(output_bytes))
}
macro_rules! kmac_variant {
($name:ident, $columns:literal, $rounds:literal, $last_row_shift:literal, $mac_len:literal) => {
pub struct $name;
impl $name {
pub fn mac(key: &[u8], message: &[u8]) -> Result<[u8; $mac_len], KmacError> {
let full =
kmac_generic(key, message, $columns, $rounds, $last_row_shift, $mac_len)?;
let mut out = [0u8; $mac_len];
out.copy_from_slice(&full[..$mac_len]);
Ok(out)
}
pub fn verify(
key: &[u8],
message: &[u8],
expected: &[u8; $mac_len],
) -> Result<(), KmacError> {
let mac = Self::mac(key, message)?;
if mac.ct_eq(expected).into() {
Ok(())
} else {
Err(KmacError::TagMismatch)
}
}
}
};
}
kmac_variant!(Kupyna256Kmac, 8, 10, 7, 32);
kmac_variant!(Kupyna384Kmac, 16, 14, 11, 48);
kmac_variant!(Kupyna512Kmac, 16, 14, 11, 64);