diff --git a/.travis.yml b/.travis.yml index 9ee1ba6..89484a7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,11 +5,11 @@ sudo: required matrix: include: - rust: 1.27.0 - script: cargo test --verbose --all + script: cargo test --verbose --all --exclude=polyval - rust: stable - script: cargo test --verbose --all + script: cargo test --verbose --all --exclude=polyval - rust: nightly - script: cargo test --verbose --all + script: cargo test --verbose --all --exclude=polyval - env: TARGET=i686-unknown-linux-gnu rust: stable @@ -17,19 +17,28 @@ matrix: rust: stable - env: TARGET=powerpc64-unknown-linux-gnu rust: stable + # tests if crates truly can be built without std - env: TARGET=thumbv7em-none-eabi - rust: nightly - script: xargo build --verbose --target $TARGET - install: - - cargo install xargo || true - - rustup target install armv7-unknown-linux-gnueabihf - - rustup component add rust-src + rust: stable + install: rustup target install $TARGET + script: + - cargo build --verbose --all --exclude=polyval --tests + + # polyval presently needs either RUSTFLAGS or non-default features + - name: polyval + rust: + - 1.27.0 + - stable + script: + - cargo test --package=polyval --all-features + - RUSTFLAGS="-Ctarget-cpu=sandybridge -Ctarget-feature=+sse2,+sse4.1" cargo test --package polyval --tests + - RUSTFLAGS="-Ctarget-cpu=sandybridge -Ctarget-feature=+sse2,+sse4.1" cargo test --package polyval --all-features install: - cargo install cross || true script: - - cross test --verbose --all --target $TARGET + - cross test --verbose --all --exclude=polyval --target $TARGET cache: cargo diff --git a/Cargo.toml b/Cargo.toml index 5c5a885..4fc543c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,5 +5,5 @@ members = [ "hmac", "pmac", "poly1305", - "polyval" + "polyval", ] diff --git a/polyval/Cargo.toml b/polyval/Cargo.toml index a0fc47d..26afaed 100644 --- a/polyval/Cargo.toml +++ b/polyval/Cargo.toml @@ -3,13 +3,28 @@ name = "polyval" version = "0.0.0" authors = ["RustCrypto Developers"] license = "MIT/Apache-2.0" -description = "GHASH-like universal hash over GF(2^128)" +description = """ +POLYVAL is a GHASH-like universal hash over GF(2^128) useful for constructing +a Message Authentication Code (MAC) +""" documentation = "https://docs.rs/polyval" repository = "https://github.com/RustCrypto/MACs" keywords = ["aes-gcm-siv", "crypto", "ghash", "gcm", "universal-hashing"] categories = ["cryptography", "no-std"] [dependencies] +byteorder = { version = "1", optional = true, default-features = false } +crypto-mac = "0.7" + +[dev-dependencies] +crypto-mac = { version = "0.7", features = ["dev"] } +hex-literal = "0.1" + +[features] +insecure-soft = ["byteorder"] [badges] travis-ci = { repository = "RustCrypto/hashes" } + +[package.metadata.docs.rs] +all-features = true diff --git a/polyval/benches/polyval.rs b/polyval/benches/polyval.rs new file mode 100644 index 0000000..49c1335 --- /dev/null +++ b/polyval/benches/polyval.rs @@ -0,0 +1,87 @@ +#![feature(test)] +#[macro_use] +extern crate crypto_mac; +extern crate polyval; + +use crypto_mac::generic_array::{typenum::U16, GenericArray}; +use crypto_mac::MacResult; +use polyval::{Block, Polyval}; +use std::{cmp::min, convert::TryInto}; + +bench!(PolyvalMac); + +/// POLYVAL isn't a traditional MAC and for that reason doesn't impl the +/// `crypto_mac::Mac` trait. +/// +/// This type is a newtype that impls a pseudo-MAC to leverage the benchmark +/// functionality. +/// +/// This is just for benchmarking! Don't copy and paste this into your program +/// unless you really know what you're doing!!! +#[derive(Clone)] +struct PolyvalMac { + poly: Polyval, + leftover: usize, + buffer: Block, +} + +impl Mac for PolyvalMac { + type OutputSize = U16; + type KeySize = U16; + + fn new(key: &GenericArray) -> PolyvalMac { + let poly = Polyval::new(key.as_slice().try_into().unwrap()); + + PolyvalMac { + poly, + leftover: 0, + buffer: Block::default(), + } + } + + fn input(&mut self, data: &[u8]) { + let mut m = data; + + if self.leftover > 0 { + let want = min(16 - self.leftover, m.len()); + + for (i, byte) in m.iter().cloned().enumerate().take(want) { + self.buffer[self.leftover + i] = byte; + } + + m = &m[want..]; + self.leftover += want; + + if self.leftover < 16 { + return; + } + + self.block(); + self.leftover = 0; + } + + while m.len() >= 16 { + self.block(); + m = &m[16..]; + } + + self.buffer[..m.len()].copy_from_slice(m); + self.leftover = m.len(); + } + + fn reset(&mut self) { + unimplemented!(); + } + + fn result(self) -> MacResult { + self.poly.result() + } +} + +impl PolyvalMac { + /// Input the current internal buffer into POLYVAL + fn block(&mut self) { + let elem = self.buffer; + self.poly.input(elem) + } +} diff --git a/polyval/src/field/backend/mod.rs b/polyval/src/field/backend/mod.rs new file mode 100644 index 0000000..1354da1 --- /dev/null +++ b/polyval/src/field/backend/mod.rs @@ -0,0 +1,64 @@ +//! Field arithmetic backends + +#[cfg(all( + target_feature = "pclmulqdq", + target_feature = "sse2", + target_feature = "sse4.1", + any(target_arch = "x86", target_arch = "x86_64") +))] +mod pclmulqdq; + +#[cfg(feature = "insecure-soft")] +mod soft; + +use super::clmul::Clmul; +use core::ops::BitXor; +use Block; + +#[cfg(not(any( + all( + target_feature = "pclmulqdq", + target_feature = "sse2", + target_feature = "sse4.1", + any(target_arch = "x86", target_arch = "x86_64") + ), + feature = "insecure-soft" +)))] +compile_error!( + "no backends available! On x86/x86-64 platforms, enable intrinsics with \ + RUSTFLAGS=\"-Ctarget-cpu=sandybridge -Ctarget-feature=+sse2,+sse4.1\" or \ + enable **INSECURE** portable emulation with the `insecure-soft` feature" +); + +#[cfg(all( + target_feature = "pclmulqdq", + target_feature = "sse2", + target_feature = "sse4.1", + any(target_arch = "x86", target_arch = "x86_64") +))] +pub(crate) use self::pclmulqdq::M128i; + +#[cfg(all( + not(all( + target_feature = "pclmulqdq", + target_feature = "sse2", + target_feature = "sse4.1", + any(target_arch = "x86", target_arch = "x86_64") + )), + feature = "insecure-soft" +))] +pub(crate) use self::soft::U64x2 as M128i; + +/// Trait representing the arithmetic operations we expect on the XMM registers +pub trait Xmm: + BitXor + Clmul + Copy + From + Into + From +{ + /// Swap the hi and low 64-bit halves of the register + fn shuffle(self) -> Self; + + /// Shift the contents of the register left by 64-bits + fn shl64(self) -> Self; + + /// Shift the contents of the register right by 64-bits + fn shr64(self) -> Self; +} diff --git a/polyval/src/field/backend/pclmulqdq.rs b/polyval/src/field/backend/pclmulqdq.rs new file mode 100644 index 0000000..091511d --- /dev/null +++ b/polyval/src/field/backend/pclmulqdq.rs @@ -0,0 +1,107 @@ +//! Support for the PCLMULQDQ CPU intrinsic on `x86` and `x86_64` target +//! architectures. + +// The code below uses `loadu`/`storeu` to support unaligned loads/stores +#![allow(clippy::cast_ptr_alignment)] + +#[cfg(target_arch = "x86")] +use core::arch::x86::*; +#[cfg(target_arch = "x86_64")] +use core::arch::x86_64::*; + +use super::Xmm; +use core::ops::BitXor; +use field::clmul::{self, Clmul}; +use Block; + +/// Wrapper for `__m128i` - a 128-bit XMM register (SSE2) +#[repr(align(16))] +#[derive(Copy, Clone)] +pub struct M128i(__m128i); + +impl From for M128i { + fn from(bytes: Block) -> M128i { + M128i(unsafe { _mm_loadu_si128(bytes.as_ptr() as *const __m128i) }) + } +} + +impl From for Block { + fn from(xmm: M128i) -> Block { + let mut result = Block::default(); + + unsafe { + _mm_storeu_si128(result.as_mut_ptr() as *mut __m128i, xmm.0); + } + + result + } +} + +impl From for M128i { + fn from(x: u128) -> M128i { + M128i(unsafe { _mm_loadu_si128(&x as *const u128 as *const __m128i) }) + } +} + +impl BitXor for M128i { + type Output = Self; + + fn bitxor(self, rhs: Self) -> Self::Output { + M128i(unsafe { xor(self.0, rhs.0) }) + } +} + +impl Clmul for M128i { + fn clmul(self, rhs: Self, imm: I) -> Self + where + I: Into, + { + M128i(unsafe { pclmulqdq(self.0, rhs.0, imm.into()) }) + } +} + +impl Xmm for M128i { + fn shuffle(self) -> Self { + M128i(unsafe { shufpd1(self.0) }) + } + + fn shl64(self) -> Self { + M128i(unsafe { pslldq8(self.0) }) + } + + fn shr64(self) -> Self { + M128i(unsafe { psrldq8(self.0) }) + } +} + +#[target_feature(enable = "sse2", enable = "sse4.1")] +unsafe fn xor(a: __m128i, b: __m128i) -> __m128i { + _mm_xor_si128(a, b) +} + +#[target_feature(enable = "sse2", enable = "sse4.1")] +unsafe fn shufpd1(a: __m128i) -> __m128i { + let a = _mm_castsi128_pd(a); + _mm_castpd_si128(_mm_shuffle_pd(a, a, 1)) +} + +#[target_feature(enable = "sse2", enable = "sse4.1")] +unsafe fn pslldq8(a: __m128i) -> __m128i { + _mm_bslli_si128(a, 8) +} + +#[target_feature(enable = "sse2", enable = "sse4.1")] +unsafe fn psrldq8(a: __m128i) -> __m128i { + _mm_bsrli_si128(a, 8) +} + +// TODO(tarcieri): _mm256_clmulepi64_epi128 (vpclmulqdq) +#[target_feature(enable = "pclmulqdq", enable = "sse2", enable = "sse4.1")] +unsafe fn pclmulqdq(a: __m128i, b: __m128i, op: clmul::PseudoOp) -> __m128i { + match op { + clmul::PseudoOp::PCLMULLQLQDQ => _mm_clmulepi64_si128(a, b, 0x00), + clmul::PseudoOp::PCLMULHQLQDQ => _mm_clmulepi64_si128(a, b, 0x01), + clmul::PseudoOp::PCLMULLQHQDQ => _mm_clmulepi64_si128(a, b, 0x10), + clmul::PseudoOp::PCLMULHQHQDQ => _mm_clmulepi64_si128(a, b, 0x11), + } +} diff --git a/polyval/src/field/backend/soft.rs b/polyval/src/field/backend/soft.rs new file mode 100644 index 0000000..0d06f64 --- /dev/null +++ b/polyval/src/field/backend/soft.rs @@ -0,0 +1,101 @@ +//! Software emulation support for CLMUL hardware intrinsics. +//! +//! WARNING: Not constant time! Should be made constant-time or disabled by default. + +// TODO(tarcieri): performance-oriented constant-time implementation +// See: + +use super::Xmm; +use byteorder::{ByteOrder, LE}; +use core::ops::BitXor; +use field::clmul::{self, Clmul}; +use Block; + +/// 2 x `u64` values emulating an XMM register +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct U64x2([u64; 2]); + +impl From for U64x2 { + fn from(bytes: Block) -> U64x2 { + let mut u64x2 = [0u64; 2]; + LE::read_u64_into(&bytes, &mut u64x2); + U64x2(u64x2) + } +} + +impl From for Block { + fn from(u64x2: U64x2) -> Block { + let x: u128 = u64x2.into(); + let mut result = Block::default(); + LE::write_u128(&mut result, x); + result + } +} + +impl From for U64x2 { + fn from(x: u128) -> U64x2 { + let lo = (x & 0xFFFF_FFFFF) as u64; + let hi = (x >> 64) as u64; + U64x2([lo, hi]) + } +} + +impl From for u128 { + fn from(u64x2: U64x2) -> u128 { + u128::from(u64x2.0[0]) | (u128::from(u64x2.0[1]) << 64) + } +} + +impl BitXor for U64x2 { + type Output = Self; + + fn bitxor(self, rhs: Self) -> Self::Output { + U64x2([self.0[0] ^ rhs.0[0], self.0[1] ^ rhs.0[1]]) + } +} + +impl Clmul for U64x2 { + fn clmul(self, other: Self, imm: I) -> Self + where + I: Into, + { + let (a, b) = match imm.into() { + clmul::PseudoOp::PCLMULLQLQDQ => (self.0[0], other.0[0]), + clmul::PseudoOp::PCLMULHQLQDQ => (self.0[1], other.0[0]), + clmul::PseudoOp::PCLMULLQHQDQ => (self.0[0], other.0[1]), + clmul::PseudoOp::PCLMULHQHQDQ => (self.0[1], other.0[1]), + }; + + let mut result = [0u64; 2]; + + for i in 0..64 { + if b & (1 << i) != 0 { + result[1] ^= a; + } + + result[0] >>= 1; + + if result[1] & 1 != 0 { + result[0] ^= 1 << 63; + } + + result[1] >>= 1; + } + + U64x2(result) + } +} + +impl Xmm for U64x2 { + fn shuffle(self) -> Self { + U64x2([self.0[1], self.0[0]]) + } + + fn shl64(self) -> Self { + U64x2([0, self.0[0]]) + } + + fn shr64(self) -> Self { + U64x2([self.0[1], 0]) + } +} diff --git a/polyval/src/field/clmul.rs b/polyval/src/field/clmul.rs new file mode 100644 index 0000000..7b9e5a7 --- /dev/null +++ b/polyval/src/field/clmul.rs @@ -0,0 +1,55 @@ +//! Carry-less multiplication support. +//! +//! Modern `x86` and `x86_64` CPUs support hardware instructions for +//! carry-less multiplication which are necessary for efficient implementations +//! of GHASH and POLYVAL. + +/// Carry-less multiplication trait - allows field arithmetic to be generic +/// across both the `hard` and `soft` backends +pub trait Clmul: Copy { + /// Performs carry-less multiplication of two 64-bit polynomials over the + /// finite field GF(2^k). + fn clmul>(self, other: Self, imm: I) -> Self; +} + +/// Pseudo-Op: selected by bits 4 and 0 of the immediate byte (`imm8`). +/// +/// PCLMULQDQ performs carry-less multiplication of two quadwords which are +/// selected from both operands according to the value of `imm8`. +/// +/// Bits 4 and 0 of `imm8` are used to select which 64-bit half of each operand +/// to use. Each of the possibilities has a named CLMUL Pseudo-Op, which is +/// represented by this enum. +#[repr(u8)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum PseudoOp { + /// Low-Low: `clmul(a[0..8], b[0..8])` + PCLMULLQLQDQ = 0x00, + + /// High-Low: `clmul(a[8..16], b[0..8])` + PCLMULHQLQDQ = 0x01, + + /// Low-High: `clmul(a[0..8], b[8..16])` + PCLMULLQHQDQ = 0x10, + + /// High-High: `clmul(a[8..16], b[8..16])` + PCLMULHQHQDQ = 0x11, +} + +impl From for PseudoOp { + fn from(imm8: u8) -> PseudoOp { + match imm8 { + 0x00 => PseudoOp::PCLMULLQLQDQ, + 0x01 => PseudoOp::PCLMULHQLQDQ, + 0x10 => PseudoOp::PCLMULLQHQDQ, + 0x11 => PseudoOp::PCLMULHQHQDQ, + _ => panic!("invalid imm8 value: 0x{:02x}", imm8), + } + } +} + +impl From for u8 { + fn from(op: PseudoOp) -> u8 { + op as u8 + } +} diff --git a/polyval/src/field/mod.rs b/polyval/src/field/mod.rs new file mode 100644 index 0000000..9db0aab --- /dev/null +++ b/polyval/src/field/mod.rs @@ -0,0 +1,103 @@ +//! Implementation of POLYVAL's finite field. +//! +//! From [RFC 8452 Section 3] which defines POLYVAL for use in AES-GCM_SIV: +//! +//! > "POLYVAL, like GHASH (the authenticator in AES-GCM; ...), operates in a +//! > binary field of size 2^128. The field is defined by the irreducible +//! > polynomial x^128 + x^127 + x^126 + x^121 + 1." +//! +//! This implementation provides multiplication over GF(2^128) optimized using +//! Shay Gueron's PCLMULQDQ-based techniques. +//! +//! For more information on how these techniques work, see: +//! +//! +//! [RFC 8452 Section 3]: https://tools.ietf.org/html/rfc8452#section-3 + +pub mod backend; +pub mod clmul; + +use self::backend::Xmm; +use super::Block; +use core::ops::{Add, Mul}; + +/// Size of GF(2^128) in bytes (16-bytes). +pub const FIELD_SIZE: usize = 16; + +/// Mask value used when performing Montgomery fast reduction. +/// This corresponds to POLYVAL's polynomial with the highest bit unset. +/// +/// See: +const MASK: u128 = 1 << 127 | 1 << 126 | 1 << 121 | 1; + +/// POLYVAL field element. +#[derive(Copy, Clone)] +pub(crate) struct FieldElement(X); + +impl FieldElement { + /// Load a `FieldElement` from its bytestring representation. + pub fn from_bytes(bytes: Block) -> Self { + FieldElement(bytes.into()) + } + + /// Serialize this `FieldElement` as a bytestring. + pub fn to_bytes(self) -> Block { + self.0.into() + } + + /// Fast reduction modulo x^128 + x^127 + x^126 +x^121 + 1 (Gueron 2012) + /// Algorithm 4: "Montgomery reduction" + fn reduce(self) -> Self { + let mask = X::from(MASK); + let a = mask.clmul(self.0, 0x01); + let b = self.0.shuffle() ^ a; + let c = mask.clmul(b, 0x01); + let d = b.shuffle() ^ c; + FieldElement(d) + } +} + +#[allow(clippy::suspicious_arithmetic_impl)] +impl Add for FieldElement { + type Output = Self; + + /// Adds two POLYVAL field elements. + /// + /// From [RFC 8452 Section 3]: + /// + /// > "The sum of any two elements in the field is the result of XORing them." + /// + /// [RFC 8452 Section 3]: https://tools.ietf.org/html/rfc8452#section-3 + fn add(self, rhs: Self) -> Self { + FieldElement(self.0 ^ rhs.0) + } +} + +#[allow(clippy::suspicious_arithmetic_impl)] +impl Mul for FieldElement { + type Output = Self; + + /// Computes POLYVAL multiplication over GF(2^128). + /// + /// From [RFC 8452 Section 3]: + /// + /// > "The product of any two elements is calculated using standard + /// > (binary) polynomial multiplication followed by reduction modulo the + /// > irreducible polynomial." + /// + /// [RFC 8452 Section 3]: https://tools.ietf.org/html/rfc8452#section-3 + fn mul(self, rhs: Self) -> Self { + let t1 = self.0.clmul(rhs.0, 0x00); + let t2 = self.0.clmul(rhs.0, 0x01); + let t3 = self.0.clmul(rhs.0, 0x10); + let t4 = self.0.clmul(rhs.0, 0x11); + let t5 = t2 ^ t3; + FieldElement(t4 ^ t5.shr64()) + FieldElement(t1 ^ t5.shl64()).reduce() + } +} + +impl From for FieldElement { + fn from(element: X) -> FieldElement { + FieldElement(element) + } +} diff --git a/polyval/src/lib.rs b/polyval/src/lib.rs index 1b3bed6..b7ab94b 100644 --- a/polyval/src/lib.rs +++ b/polyval/src/lib.rs @@ -1 +1,113 @@ -//! POLYVAL +//! **POLYVAL** is a GHASH-like universal hash over GF(2^128) useful for +//! implementing [AES-GCM-SIV] or [AES-GCM/GMAC]. +//! +//! From [RFC 8452 Section 3] which defines POLYVAL for use in AES-GCM_SIV: +//! +//! > "POLYVAL, like GHASH (the authenticator in AES-GCM; ...), operates in a +//! > binary field of size 2^128. The field is defined by the irreducible +//! > polynomial x^128 + x^127 + x^126 + x^121 + 1." +//! +//! By multiplying (in the finite field sense) a sequence of 128-bit blocks of +//! input data data by a field element `H`, POLYVAL can be used to authenticate +//! the message sequence as powers (in a finite field sense) of `H`. +//! +//! ## Requirements +//! +//! - Rust 1.27.0 or newer +//! - `RUSTFLAGS` with `-Ctarget-cpu` and `-Ctarget-feature`: +//! - x86(-64) CPU: `target-cpu=sandybridge` or newer +//! - SSE2 + SSE4.1: `target-feature=+sse2,+sse4.1` +//! +//! An **INSECURE** (variable timing) portable implementation is gated behind +//! the `insecure-soft` cargo feature. Use of this implementation is +//! **NOT RECOMMENDED** and may potentially leak the POLYVAL key! +//! +//! ## Relationship to GHASH +//! +//! POLYVAL can be thought of as the little endian equivalent of GHASH, which +//! affords it a small performance advantage over GHASH when used on little +//! endian architectures. +//! +//! It has also been designed so it can also be used to compute GHASH and with +//! it GMAC, the Message Authentication Code (MAC) used by AES-GCM. +//! +//! From [RFC 8452 Appendix A]: +//! +//! > "GHASH and POLYVAL both operate in GF(2^128), although with different +//! > irreducible polynomials: POLYVAL works modulo x^128 + x^127 + x^126 + +//! > x^121 + 1 and GHASH works modulo x^128 + x^7 + x^2 + x + 1. Note +//! > that these irreducible polynomials are the 'reverse' of each other." +//! +//! [AES-GCM-SIV]: https://en.wikipedia.org/wiki/AES-GCM-SIV +//! [AES-GCM/GMAC]: https://en.wikipedia.org/wiki/Galois/Counter_Mode +//! [RFC 8452 Section 3]: https://tools.ietf.org/html/rfc8452#section-3 +//! [RFC 8452 Appendix A]: https://tools.ietf.org/html/rfc8452#appendix-A + +#![no_std] +#![doc(html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo_small.png")] +#![deny(missing_docs)] + +extern crate crypto_mac; + +// TODO: replace with `u64::from_le_bytes`/`u128::to_le_bytes` in libcore (1.32+) +#[cfg(feature = "insecure-soft")] +extern crate byteorder; + +pub mod field; + +use self::field::FieldElement; +use crypto_mac::generic_array::typenum::U16; +use crypto_mac::MacResult; + +// TODO(tarcieri): runtime selection of CLMUL vs soft backend when both are available +use self::field::backend::M128i; + +/// Size of a POLYVAL block (128-bits) +pub const BLOCK_SIZE: usize = 16; + +/// POLYVAL blocks (16-bytes) +pub type Block = [u8; BLOCK_SIZE]; + +/// POLYVAL authentication tags +pub type Tag = MacResult; + +/// **POLYVAL**: GHASH-like universal hash over GF(2^128). +#[allow(non_snake_case)] +#[derive(Clone)] +#[repr(align(16))] +pub struct Polyval { + /// GF(2^128) field element input blocks are multiplied by + H: FieldElement, + + /// Field element representing the computed universal hash + S: FieldElement, +} + +impl Polyval { + /// Initialize POLYVAL with the given `H` field element + pub fn new(h: Block) -> Self { + Self { + H: FieldElement::from_bytes(h), + S: FieldElement::from_bytes(Block::default()), + } + } + + /// Input a field element `X` to be authenticated into POLYVAL. + pub fn input(&mut self, x: Block) { + // "The sum of any two elements in the field is the result of XORing them." + // -- RFC 8452 Section 3 + let sum = self.S + FieldElement::from_bytes(x); + self.S = sum * self.H; + } + + /// Process input blocks in a chained manner + pub fn chain(mut self, x: Block) -> Self { + self.input(x); + self + } + + /// Get POLYVAL result (i.e. computed `S` field element) + pub fn result(self) -> Tag { + Tag::new(self.S.to_bytes().into()) + } +} diff --git a/polyval/tests/lib.rs b/polyval/tests/lib.rs new file mode 100644 index 0000000..e590783 --- /dev/null +++ b/polyval/tests/lib.rs @@ -0,0 +1,23 @@ +#[macro_use] +extern crate hex_literal; +extern crate polyval; + +use polyval::{Block, Polyval}; + +// +// Test vectors or POLYVAL from RFC 8452 Appendix A +// +// + +const H: Block = hex!("25629347589242761d31f826ba4b757b"); +const X_1: Block = hex!("4f4f95668c83dfb6401762bb2d01a262"); +const X_2: Block = hex!("d1a24ddd2721d006bbe45f20d3c9f362"); + +/// POLYVAL(H, X_1, X_2) +const POLYVAL_RESULT: Block = hex!("f7a3b47b846119fae5b7866cf5e5b77e"); + +#[test] +fn rfc_8452_test_vector() { + let result = Polyval::new(H).chain(X_1).chain(X_2).result(); + assert_eq!(result.code().as_slice(), &POLYVAL_RESULT); +}