#![allow(clippy::indexing_slicing)]
#[cfg(test)]
use super::polyval::clmul128_reduce;
#[cfg(test)]
pub(crate) const BLOCK_SIZE: usize = 16;
pub(crate) const KEY_SIZE: usize = 16;
const POLYVAL_FEEDBACK: u128 = (1u128 << 127) | (1u128 << 126) | (1u128 << 121) | 1;
#[inline]
fn mul_x_polyval(v: u128) -> u128 {
let carry = v >> 127;
let shifted = v << 1;
shifted ^ (0u128.wrapping_sub(carry) & POLYVAL_FEEDBACK)
}
#[inline]
pub(crate) fn h_to_polyval(h_bytes: &[u8; KEY_SIZE]) -> u128 {
let h = u128::from_be_bytes(*h_bytes);
mul_x_polyval(h)
}
#[cfg(test)]
pub(crate) struct Ghash {
h: u128,
acc: u128,
}
#[cfg(test)]
impl Ghash {
#[inline]
pub(crate) fn new(h_bytes: &[u8; KEY_SIZE]) -> Self {
let h = u128::from_be_bytes(*h_bytes);
let h = mul_x_polyval(h);
Self { h, acc: 0 }
}
#[inline]
pub(crate) fn update_block(&mut self, block: &[u8; BLOCK_SIZE]) {
let block = u128::from_be_bytes(*block);
self.acc ^= block;
self.acc = clmul128_reduce(self.acc, self.h);
}
pub(crate) fn update_padded(&mut self, data: &[u8]) {
let (blocks, remainder) = data.as_chunks::<BLOCK_SIZE>();
for block in blocks {
self.update_block(block);
}
if !remainder.is_empty() {
let mut block = [0u8; BLOCK_SIZE];
block[..remainder.len()].copy_from_slice(remainder);
self.update_block(&block);
}
}
#[inline]
pub(crate) fn finalize(self) -> [u8; BLOCK_SIZE] {
self.acc.to_be_bytes()
}
}
#[cfg(test)]
impl Drop for Ghash {
fn drop(&mut self) {
unsafe {
core::ptr::write_volatile(&mut self.acc, 0);
core::ptr::write_volatile(&mut self.h, 0);
}
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ghash_empty() {
let h = [0x42u8; 16];
let gh = Ghash::new(&h);
assert_eq!(gh.finalize(), [0u8; 16]);
}
#[test]
fn ghash_zero_key() {
let h = [0u8; 16];
let x = [0xffu8; 16];
let mut gh = Ghash::new(&h);
gh.update_block(&x);
assert_eq!(gh.finalize(), [0u8; 16]);
}
#[test]
fn ghash_padded_matches_manual() {
let h = hex_to_16("66e94bd4ef8a2c3b884cfa59ca342b2e");
let data = b"Hello, World! This is test data for GHASH padding.";
let mut manual = Ghash::new(&h);
let mut offset = 0;
while offset + 16 <= data.len() {
let block: [u8; 16] = data[offset..offset + 16].try_into().unwrap();
manual.update_block(&block);
offset += 16;
}
if offset < data.len() {
let mut block = [0u8; 16];
block[..data.len() - offset].copy_from_slice(&data[offset..]);
manual.update_block(&block);
}
let manual_result = manual.finalize();
let mut padded = Ghash::new(&h);
padded.update_padded(data);
let padded_result = padded.finalize();
assert_eq!(manual_result, padded_result);
}
#[test]
fn mul_x_zero() {
assert_eq!(mul_x_polyval(0), 0);
}
#[test]
fn mul_x_one() {
assert_eq!(mul_x_polyval(1), 2);
}
#[test]
fn mul_x_high_bit() {
let v = 1u128 << 127;
let result = mul_x_polyval(v);
assert_eq!(
result, POLYVAL_FEEDBACK,
"mulX(x^127) should reduce to feedback polynomial"
);
}
fn hex_to_16(hex: &str) -> [u8; 16] {
let mut out = [0u8; 16];
let mut i = 0;
while i < 16 {
out[i] = u8::from_str_radix(&hex[2 * i..2 * i + 2], 16).unwrap();
i = i.strict_add(1);
}
out
}
}