From 3df688bd912d07d14dbf7d419ad16e278b70d754 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Wed, 15 Oct 2025 08:11:22 -0600 Subject: [PATCH] polyval: refactor field element encoding/decoding; add tests Instead of using `From` impls, uses an inherent method that reflects the endianness of the input, so big endian support can be added later --- polyval/src/backend/soft.rs | 37 +++++++++++++++++++++++++++--- polyval/src/backend/soft/soft32.rs | 24 +++++-------------- polyval/src/backend/soft/soft64.rs | 31 +++++++------------------ 3 files changed, 48 insertions(+), 44 deletions(-) diff --git a/polyval/src/backend/soft.rs b/polyval/src/backend/soft.rs index 232cc6a..7d5b752 100644 --- a/polyval/src/backend/soft.rs +++ b/polyval/src/backend/soft.rs @@ -54,7 +54,7 @@ impl Polyval { /// Initialize POLYVAL with the given `H` field element and initial block pub fn new_with_init_block(h: &Key, init_block: u128) -> Self { Self { - h: h.into(), + h: FieldElement::from_le_bytes(h), s: init_block.into(), } } @@ -81,7 +81,7 @@ impl ParBlocksSizeUser for Polyval { impl UhfBackend for Polyval { fn proc_block(&mut self, x: &Block) { - let x = FieldElement::from(x); + let x = FieldElement::from_le_bytes(x); self.s = (self.s + x) * self.h; } } @@ -93,7 +93,7 @@ impl UniversalHash for Polyval { /// Get POLYVAL result (i.e. computed `S` field element) fn finalize(self) -> Tag { - self.s.into() + self.s.to_le_bytes() } } @@ -110,3 +110,34 @@ impl Drop for Polyval { self.s.zeroize(); } } + +#[cfg(test)] +mod tests { + use super::*; + use hex_literal::hex; + + const A: [u8; 16] = hex!("66e94bd4ef8a2c3b884cfa59ca342b2e"); + const B: [u8; 16] = hex!("ff000000000000000000000000000000"); + + #[test] + fn fe_add() { + let a = FieldElement::from_le_bytes(&A.into()); + let b = FieldElement::from_le_bytes(&B.into()); + + let expected = + FieldElement::from_le_bytes(&hex!("99e94bd4ef8a2c3b884cfa59ca342b2e").into()); + assert_eq!(a + b, expected); + assert_eq!(b + a, expected); + } + + #[test] + fn fe_mul() { + let a = FieldElement::from_le_bytes(&A.into()); + let b = FieldElement::from_le_bytes(&B.into()); + + let expected = + FieldElement::from_le_bytes(&hex!("ebe563401e7e91ea3ad6426b8140c394").into()); + assert_eq!(a * b, expected); + assert_eq!(b * a, expected); + } +} diff --git a/polyval/src/backend/soft/soft32.rs b/polyval/src/backend/soft/soft32.rs index e1d9e51..b4bca40 100644 --- a/polyval/src/backend/soft/soft32.rs +++ b/polyval/src/backend/soft/soft32.rs @@ -44,14 +44,9 @@ use zeroize::Zeroize; pub(super) struct FieldElement(u32, u32, u32, u32); impl FieldElement { - /// Iterate over the integer components of this field element. - fn iter(&self) -> impl Iterator { - [self.0, self.1, self.2, self.3].into_iter() - } -} - -impl From<&Block> for FieldElement { - fn from(bytes: &Block) -> FieldElement { + /// Decode field element from little endian bytestring representation. + #[inline] + pub(super) fn from_le_bytes(bytes: &Block) -> FieldElement { FieldElement( u32::from_le_bytes(bytes[..4].try_into().unwrap()), u32::from_le_bytes(bytes[4..8].try_into().unwrap()), @@ -59,20 +54,13 @@ impl From<&Block> for FieldElement { u32::from_le_bytes(bytes[12..].try_into().unwrap()), ) } -} -impl From for Block { + /// Encode field element as little endian bytestring representation. #[inline] - fn from(fe: FieldElement) -> Block { - Block::from(&fe) - } -} - -impl From<&FieldElement> for Block { - fn from(fe: &FieldElement) -> Block { + pub(super) fn to_le_bytes(self) -> Block { let mut block = Block::default(); - for (chunk, i) in block.chunks_mut(4).zip(fe.iter()) { + for (chunk, i) in block.chunks_mut(4).zip([self.0, self.1, self.2, self.3]) { chunk.copy_from_slice(&i.to_le_bytes()); } diff --git a/polyval/src/backend/soft/soft64.rs b/polyval/src/backend/soft/soft64.rs index dba125d..92369fb 100644 --- a/polyval/src/backend/soft/soft64.rs +++ b/polyval/src/backend/soft/soft64.rs @@ -24,36 +24,21 @@ use zeroize::Zeroize; pub(super) struct FieldElement(u64, u64); impl FieldElement { - /// Iterate over the integer components of this field element. - fn iter(&self) -> impl Iterator { - [self.0, self.1].into_iter() - } -} - -impl From<&Block> for FieldElement { - fn from(bytes: &Block) -> FieldElement { - FieldElement( + /// Decode field element from little endian bytestring representation. + #[inline] + pub(super) fn from_le_bytes(bytes: &Block) -> FieldElement { + Self( u64::from_le_bytes(bytes[..8].try_into().unwrap()), u64::from_le_bytes(bytes[8..].try_into().unwrap()), ) } -} -impl From for Block { + /// Encode field element as little endian bytestring representation. #[inline] - fn from(fe: FieldElement) -> Block { - Block::from(&fe) - } -} - -impl From<&FieldElement> for Block { - fn from(fe: &FieldElement) -> Block { + pub(super) fn to_le_bytes(self) -> Block { let mut block = Block::default(); - - for (chunk, i) in block.chunks_mut(8).zip(fe.iter()) { - chunk.copy_from_slice(&i.to_le_bytes()); - } - + block[..8].copy_from_slice(&self.0.to_le_bytes()); + block[8..].copy_from_slice(&self.1.to_le_bytes()); block } }