use core::arch::x86_64::*;
#[target_feature(enable = "pclmulqdq,ssse3")]
pub unsafe fn mul(x: &mut [u8; 16], h: &[u8; 16]) {
let mask = _mm_set_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
let (a, b) = unsafe {
(
_mm_shuffle_epi8(_mm_loadu_si128(x.as_ptr() as *const __m128i), mask),
_mm_shuffle_epi8(_mm_loadu_si128(h.as_ptr() as *const __m128i), mask),
)
};
let mut lo = _mm_clmulepi64_si128(a, b, 0x00);
let mut hi = _mm_clmulepi64_si128(a, b, 0x11);
let mid = _mm_xor_si128(
_mm_clmulepi64_si128(a, b, 0x10),
_mm_clmulepi64_si128(a, b, 0x01),
);
lo = _mm_xor_si128(lo, _mm_slli_si128(mid, 8));
hi = _mm_xor_si128(hi, _mm_srli_si128(mid, 8));
let carry_lo = _mm_srli_epi32(lo, 31);
let carry_hi = _mm_srli_epi32(hi, 31);
lo = _mm_slli_epi32(lo, 1);
hi = _mm_slli_epi32(hi, 1);
lo = _mm_or_si128(lo, _mm_slli_si128(carry_lo, 4));
hi = _mm_or_si128(hi, _mm_slli_si128(carry_hi, 4));
hi = _mm_or_si128(hi, _mm_srli_si128(carry_lo, 12));
let t = _mm_xor_si128(
_mm_xor_si128(_mm_slli_epi32(lo, 31), _mm_slli_epi32(lo, 30)),
_mm_slli_epi32(lo, 25),
);
let spill = _mm_srli_si128(t, 4);
lo = _mm_xor_si128(lo, _mm_slli_si128(t, 12));
let fold = _mm_xor_si128(
_mm_xor_si128(_mm_srli_epi32(lo, 1), _mm_srli_epi32(lo, 2)),
_mm_xor_si128(_mm_srli_epi32(lo, 7), spill),
);
lo = _mm_xor_si128(lo, fold);
let result = _mm_xor_si128(hi, lo);
unsafe {
_mm_storeu_si128(
x.as_mut_ptr() as *mut __m128i,
_mm_shuffle_epi8(result, mask),
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::gcm::portable_ghash_mul;
fn available() -> bool {
std::arch::is_x86_feature_detected!("pclmulqdq")
&& std::arch::is_x86_feature_detected!("ssse3")
}
fn pseudo_random(seed: &mut u64) -> [u8; 16] {
let mut out = [0u8; 16];
for chunk in out.chunks_mut(8) {
*seed ^= *seed << 13;
*seed ^= *seed >> 7;
*seed ^= *seed << 17;
chunk.copy_from_slice(&seed.to_be_bytes());
}
out
}
#[test]
fn matches_the_portable_multiply() {
if !available() {
return;
}
let mut seed = 0x243f_6a88_85a3_08d3u64;
for i in 0..512 {
let x = pseudo_random(&mut seed);
let h = pseudo_random(&mut seed);
let mut a = x;
let mut b = x;
portable_ghash_mul(&mut a, &h);
unsafe { mul(&mut b, &h) };
assert_eq!(a, b, "mismatch on iteration {i}");
}
}
#[test]
fn matches_on_edge_cases() {
if !available() {
return;
}
let mut one = [0u8; 16];
one[0] = 0x80; let cases: [[u8; 16]; 5] = [
[0u8; 16],
one,
[0xffu8; 16],
{
let mut v = [0u8; 16];
v[15] = 1;
v
},
{
let mut v = [0u8; 16];
v[0] = 1;
v
},
];
for x in cases {
for h in cases {
let mut a = x;
let mut b = x;
portable_ghash_mul(&mut a, &h);
unsafe { mul(&mut b, &h) };
assert_eq!(a, b, "x={x:02x?} h={h:02x?}");
}
}
}
#[test]
fn identity_is_a_no_op() {
if !available() {
return;
}
let mut one = [0u8; 16];
one[0] = 0x80;
let mut seed = 0x9e37_79b9_7f4a_7c15u64;
for _ in 0..32 {
let x = pseudo_random(&mut seed);
let mut got = x;
unsafe { mul(&mut got, &one) };
assert_eq!(got, x);
}
}
}