diff --git a/Cargo.lock b/Cargo.lock index 1ecbedd2..2b8118e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,6 +130,7 @@ dependencies = [ "belt-block", "belt-ctr", "hex-literal", + "polyval", "subtle", "universal-hash", "zeroize", diff --git a/belt-dwp/Cargo.toml b/belt-dwp/Cargo.toml index b1908d64..18c807e9 100644 --- a/belt-dwp/Cargo.toml +++ b/belt-dwp/Cargo.toml @@ -16,6 +16,7 @@ aead = { version = "0.6", default-features = false } belt-block = "0.2" belt-ctr = "0.2" universal-hash = "0.6" +polyval = { version = "0.7", default-features = false, features = ["hazmat"] } subtle = { version = "2", default-features = false } zeroize = { version = "1.8", default-features = false, optional = true } @@ -30,7 +31,7 @@ bytes = ["aead/bytes"] getrandom = ["aead/getrandom"] rand_core = ["aead/rand_core"] reduced-round = [] -zeroize = ["dep:zeroize", "belt-ctr/zeroize"] +zeroize = ["dep:zeroize", "belt-ctr/zeroize", "polyval/zeroize"] [lints] workspace = true diff --git a/belt-dwp/src/gf.rs b/belt-dwp/src/gf.rs deleted file mode 100644 index 48d3ea3b..00000000 --- a/belt-dwp/src/gf.rs +++ /dev/null @@ -1,46 +0,0 @@ -use aead::array::{Array, ArraySize}; - -mod utils; - -pub(crate) mod gf128_soft64; - -pub trait GfElement { - type N: ArraySize; - - fn new() -> Self; - fn into_bytes(self) -> Array; - fn mul_sum(&mut self, a: &Array, b: &Array); -} - -/// Tests from Appendix A, table 18 of [STB 34.101.31-2020](https://apmi.bsu.by/assets/files/std/belt-spec372.pdf) -#[test] -fn test_a18() { - use crate::gf::gf128_soft64::Element; - use aead::consts::U16; - use hex_literal::hex; - - type Block = Array; - - let test_vectors = [ - ( - hex!("34904055 11BE3297 1343724C 5AB793E9"), - hex!("22481783 8761A9D6 E3EC9689 110FB0F3"), - hex!("0001D107 FC67DE40 04DC2C80 3DFD95C3"), - ), - ( - hex!("703FCCF0 95EE8DF1 C1ABF8EE 8DF1C1AB"), - hex!("2055704E 2EDB48FE 87E74075 A5E77EB1"), - hex!("4A5C9593 8B3FE8F6 74D59BC1 EB356079"), - ), - ]; - for (u, v, w) in test_vectors { - let a = Block::try_from(&u[..]).unwrap(); - let b = Block::try_from(&v[..]).unwrap(); - let c = Block::try_from(&w[..]).unwrap(); - - let mut elem = Element::new(); - elem.mul_sum(&a, &b); - - assert_eq!(c, elem.into_bytes()); - } -} diff --git a/belt-dwp/src/gf/gf128_soft64.rs b/belt-dwp/src/gf/gf128_soft64.rs deleted file mode 100644 index ad14328c..00000000 --- a/belt-dwp/src/gf/gf128_soft64.rs +++ /dev/null @@ -1,106 +0,0 @@ -#![allow(clippy::cast_possible_truncation, reason = "TODO")] - -use aead::{array::Array, consts::U16}; -use core::ops::{Add, Mul}; - -use super::{GfElement, utils::bmul64}; - -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] -pub struct Element(u64, u64); - -type Block = Array; - -impl GfElement for Element { - type N = U16; - - #[inline(always)] - fn new() -> Self { - Self(0, 0) - } - - #[inline(always)] - fn into_bytes(self) -> Block { - let mut block = Block::default(); - block[8..].copy_from_slice(&self.0.to_le_bytes()); - block[..8].copy_from_slice(&self.1.to_le_bytes()); - block - } - - #[allow(clippy::many_single_char_names)] - fn mul_sum(&mut self, a: &Block, b: &Block) { - let [a1, a0] = from_block(a); - let [b1, b0] = from_block(b); - - let a2 = a1 ^ a0; - let b2 = b1 ^ b0; - - // Multiply using Karatsuba multiplication - let c = bmul64(a1, b1); - let d = bmul64(a0, b0); - let e = bmul64(a2, b2); - let t = c ^ d ^ e; - let v0 = d as u64; - let v1 = ((d >> 64) ^ t) as u64; - let v2 = (c ^ (t >> 64)) as u64; - let v3 = (c >> 64) as u64; - - // reduce over polynomial f(w) = w^128 + w^7 + w^2 + w + 1 - let d = v2 ^ (v3 >> 63) ^ (v3 >> 62) ^ (v3 >> 57); - self.1 ^= v0 ^ d ^ (d << 1) ^ (d << 2) ^ (d << 7); - self.0 ^= v1 ^ v3 ^ (v3 << 1) ^ (v3 << 2) ^ (v3 << 7) ^ (d >> 63) ^ (d >> 62) ^ (d >> 57); - } -} - -impl From for Element { - fn from(x: u128) -> Self { - Self((x >> 64) as u64, x as u64) - } -} - -impl From for Element { - fn from(block: Block) -> Self { - let [a, b] = from_block(&block); - Self(a, b) - } -} - -impl From for Block { - fn from(element: Element) -> Self { - element.into_bytes() - } -} - -impl From<&Block> for Element { - fn from(block: &Block) -> Self { - let [a, b] = from_block(block); - Self(a, b) - } -} - -#[inline(always)] -#[allow(clippy::unwrap_used, reason = "use as_chunks when MSRV 1.88")] -fn from_block(block: &Block) -> [u64; 2] { - let (a, b) = block.split_at(8); - [ - u64::from_le_bytes(b.try_into().unwrap()), - u64::from_le_bytes(a.try_into().unwrap()), - ] -} - -impl Add for Element { - type Output = Self; - - fn add(self, rhs: Self) -> Self { - Self(self.0 ^ rhs.0, self.1 ^ rhs.1) - } -} - -impl Mul for Element { - type Output = Self; - - fn mul(self, rhs: Self) -> Self { - let mut res = Self::new(); - res.mul_sum(&self.into_bytes(), &rhs.into_bytes()); - res - } -} diff --git a/belt-dwp/src/gf/utils.rs b/belt-dwp/src/gf/utils.rs deleted file mode 100644 index 2f059352..00000000 --- a/belt-dwp/src/gf/utils.rs +++ /dev/null @@ -1,29 +0,0 @@ -use core::num::Wrapping; - -/// Multiplication in GF(2)[X], truncated to the low 64-bits, with β€œholes” -/// (sequences of zeroes) to avoid carry spilling. -/// -/// When carries do occur, they wind up in a "hole" and are subsequently masked -/// out of the result. -pub(super) fn bmul64(x: u64, y: u64) -> u128 { - let x0 = Wrapping(u128::from(x & 0x1111_1111_1111_1111)); - let x1 = Wrapping(u128::from(x & 0x2222_2222_2222_2222)); - let x2 = Wrapping(u128::from(x & 0x4444_4444_4444_4444)); - let x3 = Wrapping(u128::from(x & 0x8888_8888_8888_8888)); - let y0 = Wrapping(u128::from(y & 0x1111_1111_1111_1111)); - let y1 = Wrapping(u128::from(y & 0x2222_2222_2222_2222)); - let y2 = Wrapping(u128::from(y & 0x4444_4444_4444_4444)); - let y3 = Wrapping(u128::from(y & 0x8888_8888_8888_8888)); - - let mut z0 = ((x0 * y0) ^ (x1 * y3) ^ (x2 * y2) ^ (x3 * y1)).0; - let mut z1 = ((x0 * y1) ^ (x1 * y0) ^ (x2 * y3) ^ (x3 * y2)).0; - let mut z2 = ((x0 * y2) ^ (x1 * y1) ^ (x2 * y0) ^ (x3 * y3)).0; - let mut z3 = ((x0 * y3) ^ (x1 * y2) ^ (x2 * y1) ^ (x3 * y0)).0; - - z0 &= 0x1111_1111_1111_1111_1111_1111_1111_1111; - z1 &= 0x2222_2222_2222_2222_2222_2222_2222_2222; - z2 &= 0x4444_4444_4444_4444_4444_4444_4444_4444; - z3 &= 0x8888_8888_8888_8888_8888_8888_8888_8888; - - z0 | z1 | z2 | z3 -} diff --git a/belt-dwp/src/ghash.rs b/belt-dwp/src/ghash.rs index 1b864a93..e514bcea 100644 --- a/belt-dwp/src/ghash.rs +++ b/belt-dwp/src/ghash.rs @@ -1,10 +1,9 @@ use aead::array::Array; -use aead::consts::{U1, U16}; +use aead::consts::{U4, U16}; use aead::{KeyInit, KeySizeUser}; use belt_block::cipher::{BlockSizeUser, ParBlocksSizeUser}; -use universal_hash::{Reset, UhfBackend, UhfClosure, UniversalHash}; - -use crate::gf::gf128_soft64::Element; +use polyval::{Polyval, hazmat::FieldElement}; +use universal_hash::{ParBlocks, UhfBackend, UhfClosure, UniversalHash}; /// GHASH keys (16-bytes) pub type Key = Array; @@ -15,10 +14,17 @@ pub type Block = Array; /// GHASH tags (16-bytes) pub type Tag = Array; +/// Convert a block between the STB and POLYVAL +#[inline(always)] +fn convert(block: &Block) -> u128 { + u128::from_le_bytes((*block).into()).reverse_bits() +} + #[derive(Clone)] pub struct GHash { - s: Element, - h: Element, + polyval: Polyval, + /// Initial `t` value in POLYVAL's representation, folded into the first processed block. + init: u128, } impl KeySizeUser for GHash { @@ -37,20 +43,32 @@ impl KeyInit for GHash { impl GHash { pub(crate) fn new_with_init_block(h: &Key, s: u128) -> Self { + let h = FieldElement::from(convert(h)).mulx(); + Self { - s: Element::from(s), - h: Element::from(h), + polyval: Polyval::new(&h.into()), + init: s, } } } impl ParBlocksSizeUser for GHash { - type ParBlocksSize = U1; + type ParBlocksSize = U4; } impl UhfBackend for GHash { fn proc_block(&mut self, x: &Block) { - self.s = (self.s + Element::from(x)) * self.h; + let x = convert(x) ^ core::mem::take(&mut self.init); + self.polyval.proc_block(&x.to_le_bytes().into()); + } + + fn proc_par_blocks(&mut self, blocks: &ParBlocks) { + let init = core::mem::take(&mut self.init); + let blocks = ParBlocks::::from_fn(|i| { + let x = convert(&blocks[i]) ^ if i == 0 { init } else { 0 }; + x.to_le_bytes().into() + }); + self.polyval.proc_par_blocks(&blocks); } } @@ -62,12 +80,31 @@ impl UniversalHash for GHash { /// Get GHASH output #[inline] fn finalize(self) -> Tag { - self.s.into() + convert(&self.polyval.finalize()).to_le_bytes().into() } } -impl Reset for GHash { - fn reset(&mut self) { - self.s = Element::default(); +/// Tests from Appendix A, table 18 of [STB 34.101.31-2020](https://apmi.bsu.by/assets/files/std/belt-spec372.pdf) +#[test] +fn test_a18() { + use hex_literal::hex; + + let test_vectors = [ + ( + hex!("34904055 11BE3297 1343724C 5AB793E9"), + hex!("22481783 8761A9D6 E3EC9689 110FB0F3"), + hex!("0001D107 FC67DE40 04DC2C80 3DFD95C3"), + ), + ( + hex!("703FCCF0 95EE8DF1 C1ABF8EE 8DF1C1AB"), + hex!("2055704E 2EDB48FE 87E74075 A5E77EB1"), + hex!("4A5C9593 8B3FE8F6 74D59BC1 EB356079"), + ), + ]; + + for (u, v, w) in test_vectors { + let mut hash = GHash::new(&Block::from(v)); + hash.update(&[Block::from(u)]); + assert_eq!(hash.finalize(), Block::from(w)); } } diff --git a/belt-dwp/src/lib.rs b/belt-dwp/src/lib.rs index 3572843c..3d7c5f76 100644 --- a/belt-dwp/src/lib.rs +++ b/belt-dwp/src/lib.rs @@ -92,13 +92,12 @@ use universal_hash::typenum::{IsLessOrEqual, NonZero}; /// Nonce type for [`Dwp`] pub type Nonce = aead::Nonce; -mod gf; mod ghash; use ghash::GHash; -/// Constant `T` from the STB 34.101.31-2020 -const T: u128 = 0xE45D_4A58_8E00_6D36_3BF5_080A_C8BA_94B1; +/// Constant `T` from the STB 34.101.31-2020, in POLYVAL's representation +const T: u128 = 0xE45D_4A58_8E00_6D36_3BF5_080A_C8BA_94B1_u128.reverse_bits(); /// `belt-dwp` authenticated encryption with associated data (AEAD) cipher, /// defined in STB 34.101.31-2020. @@ -182,7 +181,7 @@ where ghash.update_padded(&sizes_block); // 6. 𝑑 ← belt-block(𝑑 * π‘Ÿ, 𝐾). - let mut tag = ghash.finalize_reset(); + let mut tag = ghash.finalize(); self.cipher.encrypt_block(&mut tag); tag[..TagSize::USIZE].try_into().map_err(|_| Error) @@ -222,7 +221,7 @@ where ghash.update_padded(&sizes_block); // 6. 𝑑 ← belt-block(𝑑 * π‘Ÿ, 𝐾). - let mut tag_exact = ghash.finalize_reset(); + let mut tag_exact = ghash.finalize(); self.cipher.encrypt_block(&mut tag_exact); use subtle::ConstantTimeEq;