#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FieldElement(pub(crate) [u64; 5]);
impl core::ops::Add for FieldElement {
type Output = Self;
fn add(self, other: Self) -> Self {
FieldElement([
self.0[0] ^ other.0[0],
self.0[1] ^ other.0[1],
self.0[2] ^ other.0[2],
self.0[3] ^ other.0[3],
self.0[4] ^ other.0[4],
])
}
}
impl FieldElement {
pub const ZERO: FieldElement = FieldElement([0, 0, 0, 0, 0]);
pub const ONE: FieldElement = FieldElement([1, 0, 0, 0, 0]);
#[must_use]
pub fn from_be_bytes(bytes: &[u8]) -> Self {
let mut limbs = [0u64; 5];
for (i, &byte) in bytes.iter().rev().enumerate() {
let limb = i / 8;
let shift = (i % 8) * 8;
limbs[limb] |= u64::from(byte) << shift;
}
FieldElement(limbs)
}
#[must_use]
#[allow(clippy::cast_possible_truncation)] pub fn to_be_bytes(self) -> [u8; 33] {
let mut out = [0u8; 33];
for (i, byte) in out.iter_mut().rev().enumerate() {
let limb = i / 8;
let shift = (i % 8) * 8;
*byte = (self.0[limb] >> shift) as u8;
}
out
}
#[must_use]
pub fn multiply(self, other: Self) -> Self {
#[cfg(all(
feature = "std",
not(kani),
any(target_arch = "x86_64", target_arch = "aarch64")
))]
if crate::hazmat::gf2m_wide::clmul_native::feature_available() {
let wide = unsafe { poly_mul_wide_hw(&self.0, &other.0) };
return reduce(wide);
}
reduce(poly_mul_wide(&self.0, &other.0))
}
#[must_use]
pub fn square(self) -> Self {
reduce(square_wide(&self.0))
}
#[must_use]
pub fn invert(self) -> Self {
let sq_n = |mut x: Self, n: u32| -> Self {
for _ in 0..n {
x = x.square();
}
x
};
let t1 = self; let t2 = sq_n(t1, 1).multiply(t1); let t4 = sq_n(t2, 2).multiply(t2); let t8 = sq_n(t4, 4).multiply(t4); let t16 = sq_n(t8, 8).multiply(t8); let t32 = sq_n(t16, 16).multiply(t16); let t64 = sq_n(t32, 32).multiply(t32); let t128 = sq_n(t64, 64).multiply(t64); let t256 = sq_n(t128, 128).multiply(t128);
t256.square() }
}
fn poly_mul_wide(a: &[u64; 5], b: &[u64; 5]) -> [u64; 10] {
let mut acc = [0u64; 10];
let mut shifted = [b[0], b[1], b[2], b[3], b[4], 0, 0, 0, 0, 0];
for bit_index in 0..257u32 {
let limb = (bit_index / 64) as usize;
let bit = bit_index % 64;
let bit_value = (a[limb] >> bit) & 1;
let mask = 0u64.wrapping_sub(bit_value); for i in 0..10 {
acc[i] ^= shifted[i] & mask;
}
shl1(&mut shifted);
}
acc
}
#[cfg(all(feature = "std", target_arch = "x86_64"))]
#[target_feature(enable = "pclmulqdq")]
#[allow(
clippy::cast_possible_wrap,
clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
unsafe fn poly_mul_wide_hw(a: &[u64; 5], b: &[u64; 5]) -> [u64; 10] {
use std::arch::x86_64::{
_mm_clmulepi64_si128, _mm_cvtsi128_si64, _mm_set_epi64x, _mm_srli_si128,
};
let mut out = [0u64; 10];
for i in 0..5 {
for j in 0..5 {
let ma = _mm_set_epi64x(0, a[i] as i64);
let mb = _mm_set_epi64x(0, b[j] as i64);
let prod = _mm_clmulepi64_si128(ma, mb, 0x00);
let lo = _mm_cvtsi128_si64(prod) as u64;
let hi = _mm_cvtsi128_si64(_mm_srli_si128::<8>(prod)) as u64;
out[i + j] ^= lo;
out[i + j + 1] ^= hi;
}
}
out
}
#[cfg(all(feature = "std", target_arch = "aarch64"))]
#[target_feature(enable = "aes")]
unsafe fn poly_mul_wide_hw(a: &[u64; 5], b: &[u64; 5]) -> [u64; 10] {
use std::arch::aarch64::vmull_p64;
let mut out = [0u64; 10];
for i in 0..5 {
for j in 0..5 {
let prod: u128 = vmull_p64(a[i], b[j]);
out[i + j] ^= prod as u64;
out[i + j + 1] ^= (prod >> 64) as u64;
}
}
out
}
#[cfg(test)]
fn multiply_sw(a: FieldElement, b: FieldElement) -> FieldElement {
reduce(poly_mul_wide(&a.0, &b.0))
}
fn spread32to64(x: u32) -> u64 {
let mut x = u64::from(x);
x = (x | (x << 16)) & 0x0000_FFFF_0000_FFFF;
x = (x | (x << 8)) & 0x00FF_00FF_00FF_00FF;
x = (x | (x << 4)) & 0x0F0F_0F0F_0F0F_0F0F;
x = (x | (x << 2)) & 0x3333_3333_3333_3333;
x = (x | (x << 1)) & 0x5555_5555_5555_5555;
x
}
fn square_wide(a: &[u64; 5]) -> [u64; 10] {
let mut out = [0u64; 10];
for i in 0..5 {
#[allow(clippy::cast_possible_truncation)]
let lo = a[i] as u32;
#[allow(clippy::cast_possible_truncation)]
let hi = (a[i] >> 32) as u32;
out[2 * i] = spread32to64(lo);
out[2 * i + 1] = spread32to64(hi);
}
out
}
fn shl1(x: &mut [u64; 10]) {
let mut carry = 0u64;
for limb in x.iter_mut() {
let next_carry = *limb >> 63;
*limb = (*limb << 1) | carry;
carry = next_carry;
}
}
fn reduce(c: [u64; 10]) -> FieldElement {
let mut h1 = [0u64; 5];
for i in 0..5 {
h1[i] = (c[i + 4] >> 1) | (c[i + 5] << 63);
}
let lo1 = [c[0], c[1], c[2], c[3], c[4] & 1];
let mut h1_shifted = [0u64; 5];
h1_shifted[0] = h1[0] << 12;
for i in 1..5 {
h1_shifted[i] = (h1[i] << 12) | (h1[i - 1] >> 52);
}
let mut pass1 = [0u64; 5];
for i in 0..5 {
pass1[i] = lo1[i] ^ h1[i] ^ h1_shifted[i];
}
let h2 = pass1[4] >> 1;
let h2_shifted = h2 << 12;
let mut result = [pass1[0], pass1[1], pass1[2], pass1[3], pass1[4] & 1];
result[0] ^= h2 ^ h2_shifted;
FieldElement(result)
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[test]
fn spread32to64_places_each_bit_at_double_position() {
for bit in 0..32u32 {
let x = 1u32 << bit;
assert_eq!(spread32to64(x), 1u64 << (2 * bit), "bit {bit}");
}
}
#[test]
fn spread32to64_of_zero_and_all_ones() {
assert_eq!(spread32to64(0), 0);
assert_eq!(spread32to64(u32::MAX), 0x5555_5555_5555_5555);
}
#[test]
fn square_wide_matches_multiply_wide_at_limb_boundaries() {
for bit in [0u32, 1, 63, 64, 65, 127, 128, 191, 192, 255, 256] {
let limb = (bit / 64) as usize;
let shift = bit % 64;
let mut a = [0u64; 5];
a[limb] = 1u64 << shift;
assert_eq!(square_wide(&a), poly_mul_wide(&a, &a), "bit {bit}");
}
}
#[test]
fn square_wide_matches_multiply_wide_for_all_bits_set() {
let a = [u64::MAX, u64::MAX, u64::MAX, u64::MAX, 1u64];
assert_eq!(square_wide(&a), poly_mul_wide(&a, &a));
}
fn reduce_naive(c: [u64; 10]) -> FieldElement {
let mut c = c;
for bit in (257..513).rev() {
let limb = bit / 64;
let shift = bit % 64;
let is_set = (c[limb] >> shift) & 1 == 1;
if is_set {
c[limb] ^= 1u64 << shift;
let t = bit - 257;
let t12 = t + 12;
c[t12 / 64] ^= 1u64 << (t12 % 64);
c[t / 64] ^= 1u64 << (t % 64);
}
}
FieldElement([c[0], c[1], c[2], c[3], c[4]])
}
#[test]
fn reduce_matches_naive_bit_at_a_time_reduction_at_boundaries() {
for bit in [0u32, 1, 12, 63, 64, 256, 257, 267, 268, 300, 400, 512] {
let limb = (bit / 64) as usize;
let shift = bit % 64;
let mut c = [0u64; 10];
c[limb] = 1u64 << shift;
assert_eq!(reduce(c), reduce_naive(c), "bit {bit}");
}
}
proptest! {
#[test]
fn reduce_matches_naive_bit_at_a_time_reduction_for_random_wide_values(
limbs in prop::collection::vec(any::<u64>(), 9)
) {
let mut c = [0u64; 10];
c[..9].copy_from_slice(&limbs);
c[8] &= 1; prop_assert_eq!(reduce(c), reduce_naive(c));
}
}
fn invert_direct(a: FieldElement) -> FieldElement {
let mut result = FieldElement::ONE;
for _ in 0..256 {
result = result.square();
result = result.multiply(a);
}
result.square()
}
proptest! {
#[test]
fn invert_matches_invert_direct(bytes in prop::collection::vec(any::<u8>(), 33)) {
let mut arr = [0u8; 33];
arr.copy_from_slice(&bytes);
arr[0] &= 0x01; let a = FieldElement::from_be_bytes(&arr);
prop_assume!(a != FieldElement::ZERO);
prop_assert_eq!(a.invert(), invert_direct(a));
}
}
proptest! {
#[test]
fn multiply_matches_explicit_software_path(
a_bytes in prop::collection::vec(any::<u8>(), 33),
b_bytes in prop::collection::vec(any::<u8>(), 33),
) {
let mut a_arr = [0u8; 33];
a_arr.copy_from_slice(&a_bytes);
a_arr[0] &= 0x01;
let mut b_arr = [0u8; 33];
b_arr.copy_from_slice(&b_bytes);
b_arr[0] &= 0x01;
let a = FieldElement::from_be_bytes(&a_arr);
let b = FieldElement::from_be_bytes(&b_arr);
prop_assert_eq!(a.multiply(b), multiply_sw(a, b));
}
}
proptest! {
#[test]
fn multiply_sw_is_commutative(
a_bytes in prop::collection::vec(any::<u8>(), 33),
b_bytes in prop::collection::vec(any::<u8>(), 33),
) {
let mut a_arr = [0u8; 33];
a_arr.copy_from_slice(&a_bytes);
a_arr[0] &= 0x01;
let mut b_arr = [0u8; 33];
b_arr.copy_from_slice(&b_bytes);
b_arr[0] &= 0x01;
let a = FieldElement::from_be_bytes(&a_arr);
let b = FieldElement::from_be_bytes(&b_arr);
prop_assert_eq!(multiply_sw(a, b), multiply_sw(b, a));
}
}
proptest! {
#[test]
fn multiply_sw_is_associative(
a_bytes in prop::collection::vec(any::<u8>(), 33),
b_bytes in prop::collection::vec(any::<u8>(), 33),
c_bytes in prop::collection::vec(any::<u8>(), 33),
) {
let mut a_arr = [0u8; 33];
a_arr.copy_from_slice(&a_bytes);
a_arr[0] &= 0x01;
let mut b_arr = [0u8; 33];
b_arr.copy_from_slice(&b_bytes);
b_arr[0] &= 0x01;
let mut c_arr = [0u8; 33];
c_arr.copy_from_slice(&c_bytes);
c_arr[0] &= 0x01;
let a = FieldElement::from_be_bytes(&a_arr);
let b = FieldElement::from_be_bytes(&b_arr);
let c = FieldElement::from_be_bytes(&c_arr);
prop_assert_eq!(multiply_sw(multiply_sw(a, b), c), multiply_sw(a, multiply_sw(b, c)));
}
}
proptest! {
#[test]
fn multiply_sw_distributes_over_add(
a_bytes in prop::collection::vec(any::<u8>(), 33),
b_bytes in prop::collection::vec(any::<u8>(), 33),
c_bytes in prop::collection::vec(any::<u8>(), 33),
) {
let mut a_arr = [0u8; 33];
a_arr.copy_from_slice(&a_bytes);
a_arr[0] &= 0x01;
let mut b_arr = [0u8; 33];
b_arr.copy_from_slice(&b_bytes);
b_arr[0] &= 0x01;
let mut c_arr = [0u8; 33];
c_arr.copy_from_slice(&c_bytes);
c_arr[0] &= 0x01;
let a = FieldElement::from_be_bytes(&a_arr);
let b = FieldElement::from_be_bytes(&b_arr);
let c = FieldElement::from_be_bytes(&c_arr);
prop_assert_eq!(multiply_sw(a, b + c), multiply_sw(a, b) + multiply_sw(a, c));
}
}
proptest! {
#[test]
fn multiply_sw_by_one_is_identity(bytes in prop::collection::vec(any::<u8>(), 33)) {
let mut arr = [0u8; 33];
arr.copy_from_slice(&bytes);
arr[0] &= 0x01;
let a = FieldElement::from_be_bytes(&arr);
prop_assert_eq!(multiply_sw(a, FieldElement::ONE), a);
}
}
}