From 9f53da4936000544e635cfea85100a91908f0c86 Mon Sep 17 00:00:00 2001 From: Alexandr Kitaev Date: Fri, 28 Aug 2026 13:47:36 +0300 Subject: [PATCH 1/3] ghash: add `FieldElement` --- Cargo.lock | 1 + ghash/Cargo.toml | 4 +- ghash/src/field_element.rs | 165 +++++++++++++++++++++++++++++++++++++ ghash/src/hazmat.rs | 8 ++ ghash/src/lib.rs | 6 ++ 5 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 ghash/src/field_element.rs create mode 100644 ghash/src/hazmat.rs diff --git a/Cargo.lock b/Cargo.lock index 529fc38..c32428d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,6 +125,7 @@ version = "0.6.0" dependencies = [ "hex-literal", "polyval", + "zeroize", ] [[package]] diff --git a/ghash/Cargo.toml b/ghash/Cargo.toml index e48ff2d..4e19546 100644 --- a/ghash/Cargo.toml +++ b/ghash/Cargo.toml @@ -17,9 +17,11 @@ as in the AES-GCM authenticated encryption cipher. [dependencies] polyval = { version = "0.7", features = ["hazmat"] } +zeroize = { version = "1", optional = true, default-features = false } [features] -zeroize = ["polyval/zeroize"] +hazmat = [] +zeroize = ["dep:zeroize", "polyval/zeroize"] [dev-dependencies] hex-literal = "1" diff --git a/ghash/src/field_element.rs b/ghash/src/field_element.rs new file mode 100644 index 0000000..5f13861 --- /dev/null +++ b/ghash/src/field_element.rs @@ -0,0 +1,165 @@ +//! GHASH field element implementation. +//! +//! This module implements GHASH's field in terms of POLYVAL's, which is its little endian +//! equivalent. Elements are stored in POLYVAL's representation, i.e. as +//! `mulX_POLYVAL(ByteReverse(a))` as described in [RFC 8452 Appendix A], the inverse conversion +//! being `ByteReverse(divX_POLYVAL(a))`. +//! +//! This representation preserves both addition and multiplication (the latter because POLYVAL's +//! multiplication includes a Montgomery factor of `x^-128`, which cancels out the `x^127` +//! introduced by the byte reversal), so the arithmetic is a direct delegation to +//! [`polyval::hazmat::FieldElement`]. +//! +//! [RFC 8452 Appendix A]: https://tools.ietf.org/html/rfc8452#appendix-A + +use crate::Block; +use core::{ + fmt::{self, Debug}, + ops::{Add, Mul, MulAssign}, +}; +use polyval::{BLOCK_SIZE, hazmat::FieldElement as PolyvalElement}; + +#[cfg(feature = "zeroize")] +use zeroize::Zeroize; + +/// An element in GHASH's field. +/// +/// This type represents an element of the binary field GF(2^128) modulo the irreducible polynomial +/// `x^128 + x^7 + x^2 + x + 1` as described in [NIST SP 800-38D §6.3]. +/// +/// Arithmetic in GHASH's field has the following properties: +/// - All arithmetic operations are performed modulo the polynomial above. +/// - Addition is equivalent to the XOR operation applied to the two field elements +/// - Multiplication is carryless +/// +/// Note that elements are stored internally in POLYVAL's field (see the module-level +/// documentation), and thus converting to and from a byte representation is not free. +/// +/// [NIST SP 800-38D §6.3]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf +#[derive(Clone, Copy, Default)] +pub struct FieldElement(PolyvalElement); + +impl FieldElement { + /// Convert this field element back into GHASH's representation. + #[inline] + fn to_bytes(self) -> [u8; BLOCK_SIZE] { + self.0.divx().reverse().into() + } +} + +impl Debug for FieldElement { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "FieldElement(")?; + for byte in self.to_bytes() { + write!(f, "{:02x}", byte)?; + } + write!(f, ")") + } +} + +impl From<[u8; BLOCK_SIZE]> for FieldElement { + /// Convert a GHASH field element into POLYVAL's representation. + #[inline] + fn from(bytes: [u8; BLOCK_SIZE]) -> Self { + Self(PolyvalElement::from(bytes).reverse().mulx()) + } +} + +impl From for [u8; BLOCK_SIZE] { + #[inline] + fn from(fe: FieldElement) -> Self { + fe.to_bytes() + } +} + +impl From for FieldElement { + #[inline] + fn from(block: Block) -> Self { + Self::from(<[u8; BLOCK_SIZE]>::from(block)) + } +} + +impl From for Block { + #[inline] + fn from(fe: FieldElement) -> Self { + fe.to_bytes().into() + } +} + +impl Add for FieldElement { + type Output = Self; + + /// Adds two GHASH field elements. + #[inline] + fn add(self, rhs: Self) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl Mul for FieldElement { + type Output = Self; + + /// Perform carryless multiplication within GHASH's field modulo its polynomial. + #[inline] + fn mul(self, rhs: Self) -> Self { + Self(self.0 * rhs.0) + } +} + +impl MulAssign for FieldElement { + #[inline] + fn mul_assign(&mut self, rhs: Self) { + *self = *self * rhs; + } +} + +#[cfg(feature = "zeroize")] +impl Zeroize for FieldElement { + fn zeroize(&mut self) { + self.0.zeroize(); + } +} + +#[cfg(test)] +impl PartialEq for FieldElement { + fn eq(&self, other: &Self) -> bool { + self.to_bytes() == other.to_bytes() + } +} + +#[cfg(test)] +mod tests { + use super::FieldElement; + use hex_literal::hex; + + // Test vectors for GHASH from RFC 8452 Appendix A + // https://tools.ietf.org/html/rfc8452#appendix-A + + const H: [u8; 16] = hex!("25629347589242761d31f826ba4b757b"); + const X_1: [u8; 16] = hex!("4f4f95668c83dfb6401762bb2d01a262"); + const X_2: [u8; 16] = hex!("d1a24ddd2721d006bbe45f20d3c9f362"); + + /// GHASH(H, X_1, X_2) + const GHASH_RESULT: [u8; 16] = hex!("bd9b3997046731fb96251b91f9c99d7a"); + + /// Converting to POLYVAL's field and back is the identity. + #[test] + fn roundtrip() { + assert_eq!(FieldElement::from(H).to_bytes(), H); + } + + /// Addition is the XOR of the GHASH representations. + #[test] + fn fe_add() { + let expected = FieldElement::from(hex!("9eedd8bbaba20fb0fbf33d9bfec85100")); + assert_eq!(FieldElement::from(X_1) + FieldElement::from(X_2), expected); + } + + /// GHASH is `((X_1 * H) + X_2) * H`. + #[test] + fn fe_mul() { + let h = FieldElement::from(H); + let y = (FieldElement::from(X_1) * h + FieldElement::from(X_2)) * h; + assert_eq!(y.to_bytes(), GHASH_RESULT); + } +} diff --git a/ghash/src/hazmat.rs b/ghash/src/hazmat.rs new file mode 100644 index 0000000..2b1a8d5 --- /dev/null +++ b/ghash/src/hazmat.rs @@ -0,0 +1,8 @@ +//! Hazardous materials: functionality which can be misused and needs to be used with care. +//! +//!
+//! Functionality provided in this module is low-level and intended for constructing higher-level +//! primitives as opposed to being used directly. +//!
+ +pub use crate::field_element::FieldElement; diff --git a/ghash/src/lib.rs b/ghash/src/lib.rs index d24a991..1198476 100644 --- a/ghash/src/lib.rs +++ b/ghash/src/lib.rs @@ -6,6 +6,12 @@ )] #![warn(missing_docs)] +#[cfg(feature = "hazmat")] +pub mod hazmat; + +#[cfg(feature = "hazmat")] +mod field_element; + pub use polyval::universal_hash; use polyval::{Polyval, hazmat::FieldElement}; From 2fdc65d8f299c4fab5f6d77402616d839dafbafc Mon Sep 17 00:00:00 2001 From: Alexandr Kitaev Date: Fri, 28 Aug 2026 16:59:38 +0300 Subject: [PATCH 2/3] ghash: cleanup inherit methods --- ghash/src/field_element.rs | 57 +++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/ghash/src/field_element.rs b/ghash/src/field_element.rs index 5f13861..628b627 100644 --- a/ghash/src/field_element.rs +++ b/ghash/src/field_element.rs @@ -39,21 +39,37 @@ use zeroize::Zeroize; #[derive(Clone, Copy, Default)] pub struct FieldElement(PolyvalElement); -impl FieldElement { - /// Convert this field element back into GHASH's representation. +impl Debug for FieldElement { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Debug::fmt(&self.0, f) + } +} + +impl From for FieldElement { #[inline] - fn to_bytes(self) -> [u8; BLOCK_SIZE] { - self.0.divx().reverse().into() + fn from(block: Block) -> Self { + Self::from(<[u8; BLOCK_SIZE]>::from(block)) } } -impl Debug for FieldElement { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "FieldElement(")?; - for byte in self.to_bytes() { - write!(f, "{:02x}", byte)?; - } - write!(f, ")") +impl From<&Block> for FieldElement { + #[inline] + fn from(block: &Block) -> Self { + Self::from(*block) + } +} + +impl From for Block { + #[inline] + fn from(fe: FieldElement) -> Self { + <[u8; BLOCK_SIZE]>::from(fe).into() + } +} + +impl From<&FieldElement> for Block { + #[inline] + fn from(fe: &FieldElement) -> Self { + Self::from(*fe) } } @@ -66,23 +82,24 @@ impl From<[u8; BLOCK_SIZE]> for FieldElement { } impl From for [u8; BLOCK_SIZE] { + /// Convert a POLYVAL field element back into GHASH's representation. #[inline] fn from(fe: FieldElement) -> Self { - fe.to_bytes() + fe.0.divx().reverse().into() } } -impl From for FieldElement { +impl From for FieldElement { #[inline] - fn from(block: Block) -> Self { - Self::from(<[u8; BLOCK_SIZE]>::from(block)) + fn from(x: u128) -> Self { + Self::from(x.to_be_bytes()) } } -impl From for Block { +impl From for u128 { #[inline] fn from(fe: FieldElement) -> Self { - fe.to_bytes().into() + u128::from_be_bytes(fe.into()) } } @@ -123,7 +140,7 @@ impl Zeroize for FieldElement { #[cfg(test)] impl PartialEq for FieldElement { fn eq(&self, other: &Self) -> bool { - self.to_bytes() == other.to_bytes() + <[u8; BLOCK_SIZE]>::from(*self) == <[u8; BLOCK_SIZE]>::from(*other) } } @@ -145,7 +162,7 @@ mod tests { /// Converting to POLYVAL's field and back is the identity. #[test] fn roundtrip() { - assert_eq!(FieldElement::from(H).to_bytes(), H); + assert_eq!(<[u8; 16]>::from(FieldElement::from(H)), H); } /// Addition is the XOR of the GHASH representations. @@ -160,6 +177,6 @@ mod tests { fn fe_mul() { let h = FieldElement::from(H); let y = (FieldElement::from(X_1) * h + FieldElement::from(X_2)) * h; - assert_eq!(y.to_bytes(), GHASH_RESULT); + assert_eq!(<[u8; 16]>::from(y), GHASH_RESULT); } } From e9026bdd70d7a1bb20643f11ac2d0b13d7ed21c8 Mon Sep 17 00:00:00 2001 From: Alexandr Kitaev Date: Fri, 28 Aug 2026 17:26:10 +0300 Subject: [PATCH 3/3] ghash: add #[repr(transparent)] on `FieldElement` --- ghash/src/field_element.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ghash/src/field_element.rs b/ghash/src/field_element.rs index 628b627..1f24fe4 100644 --- a/ghash/src/field_element.rs +++ b/ghash/src/field_element.rs @@ -37,6 +37,7 @@ use zeroize::Zeroize; /// /// [NIST SP 800-38D §6.3]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf #[derive(Clone, Copy, Default)] +#[repr(transparent)] pub struct FieldElement(PolyvalElement); impl Debug for FieldElement {