#[inline]
const fn reverse_byte(b: u8) -> u8 {
let b = ((b & 0xF0) >> 4) | ((b & 0x0F) << 4);
let b = ((b & 0xCC) >> 2) | ((b & 0x33) << 2);
((b & 0xAA) >> 1) | ((b & 0x55) << 1)
}
#[inline]
const fn load_natural(b: &[u8; 16]) -> u128 {
let mut buf = [0u8; 16];
let mut i = 0;
while i < 16 {
buf[i] = reverse_byte(b[i]);
i += 1;
}
u128::from_le_bytes(buf)
}
#[inline]
const fn store_natural(v: u128) -> [u8; 16] {
let bytes = v.to_le_bytes();
let mut out = [0u8; 16];
let mut i = 0;
while i < 16 {
out[i] = reverse_byte(bytes[i]);
i += 1;
}
out
}
#[must_use]
#[allow(clippy::many_single_char_names)]
pub fn ghash_mul_software(h: &[u8; 16], x: &[u8; 16]) -> [u8; 16] {
let h_nat = load_natural(h);
let x_nat = load_natural(x);
let mut z: u128 = 0;
let mut v: u128 = h_nat;
let mut i = 0;
while i < 128 {
let x_bit = (x_nat >> i) & 1;
let mask = 0u128.wrapping_sub(x_bit);
z ^= v & mask;
let carry = (v >> 127) & 1;
v <<= 1;
v ^= 0x87u128 & 0u128.wrapping_sub(carry);
i += 1;
}
store_natural(z)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reverse_byte_involution() {
for b in 0u8..=255 {
assert_eq!(reverse_byte(reverse_byte(b)), b);
}
}
#[test]
fn natural_round_trip() {
let inputs: [[u8; 16]; 4] = [
[0u8; 16],
[0xFFu8; 16],
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
0x0e, 0x0f,
],
[
0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab,
0xcd, 0xef,
],
];
for input in &inputs {
let v = load_natural(input);
let recovered = store_natural(v);
assert_eq!(&recovered, input);
}
}
#[test]
fn ghash_zero_x_is_zero() {
let h = [
0x66, 0xe9, 0x4b, 0xd4, 0xef, 0x8a, 0x2c, 0x3b, 0x88, 0x4c, 0xfa, 0x59, 0xca, 0x34,
0x2b, 0x2e,
];
let zero = [0u8; 16];
assert_eq!(ghash_mul_software(&h, &zero), zero);
}
#[test]
fn ghash_zero_h_is_zero() {
let zero = [0u8; 16];
let x = [
0x03, 0x88, 0xda, 0xce, 0x60, 0xb6, 0xa3, 0x92, 0xf3, 0x28, 0xc2, 0xb9, 0x71, 0xb2,
0xfe, 0x78,
];
assert_eq!(ghash_mul_software(&zero, &x), zero);
}
#[test]
fn ghash_linear_in_x() {
let h = [
0x66, 0xe9, 0x4b, 0xd4, 0xef, 0x8a, 0x2c, 0x3b, 0x88, 0x4c, 0xfa, 0x59, 0xca, 0x34,
0x2b, 0x2e,
];
let a = [
0x03, 0x88, 0xda, 0xce, 0x60, 0xb6, 0xa3, 0x92, 0xf3, 0x28, 0xc2, 0xb9, 0x71, 0xb2,
0xfe, 0x78,
];
let b = [
0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab,
0xcd, 0xef,
];
let mut xor_ab = [0u8; 16];
for i in 0..16 {
xor_ab[i] = a[i] ^ b[i];
}
let lhs = ghash_mul_software(&h, &xor_ab);
let ga = ghash_mul_software(&h, &a);
let gb = ghash_mul_software(&h, &b);
let mut rhs = [0u8; 16];
for i in 0..16 {
rhs[i] = ga[i] ^ gb[i];
}
assert_eq!(lhs, rhs);
}
#[test]
fn ghash_linear_in_h() {
let a = [
0x66, 0xe9, 0x4b, 0xd4, 0xef, 0x8a, 0x2c, 0x3b, 0x88, 0x4c, 0xfa, 0x59, 0xca, 0x34,
0x2b, 0x2e,
];
let b = [
0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54,
0x32, 0x10,
];
let x = [
0x03, 0x88, 0xda, 0xce, 0x60, 0xb6, 0xa3, 0x92, 0xf3, 0x28, 0xc2, 0xb9, 0x71, 0xb2,
0xfe, 0x78,
];
let mut xor_ab = [0u8; 16];
for i in 0..16 {
xor_ab[i] = a[i] ^ b[i];
}
let lhs = ghash_mul_software(&xor_ab, &x);
let ga = ghash_mul_software(&a, &x);
let gb = ghash_mul_software(&b, &x);
let mut rhs = [0u8; 16];
for i in 0..16 {
rhs[i] = ga[i] ^ gb[i];
}
assert_eq!(lhs, rhs);
}
#[test]
fn ghash_commutative() {
let h = [
0x66, 0xe9, 0x4b, 0xd4, 0xef, 0x8a, 0x2c, 0x3b, 0x88, 0x4c, 0xfa, 0x59, 0xca, 0x34,
0x2b, 0x2e,
];
let x = [
0x03, 0x88, 0xda, 0xce, 0x60, 0xb6, 0xa3, 0x92, 0xf3, 0x28, 0xc2, 0xb9, 0x71, 0xb2,
0xfe, 0x78,
];
assert_eq!(ghash_mul_software(&h, &x), ghash_mul_software(&x, &h));
}
#[test]
fn ghash_squaring_smoke() {
let h = [
0xb8, 0x3b, 0x53, 0x37, 0x08, 0xbf, 0x53, 0x5d, 0x0a, 0xa6, 0xe5, 0x29, 0x80, 0xd5,
0x3b, 0x78,
];
let h_squared = ghash_mul_software(&h, &h);
assert_eq!(h_squared, ghash_mul_software(&h, &h));
let mut xor = [0u8; 16];
for i in 0..16 {
xor[i] = h_squared[i] ^ h_squared[i];
}
assert_eq!(xor, [0u8; 16]);
}
}