Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

polyval: Add runtime PCLMULQDQ detection #11

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -34,10 +34,10 @@ matrix:
script: cd polyval && cargo test --release --tests

# no_std build
- name: "Rust: stable (thumbv7em-none-eabihf)"
- name: "Rust: stable (thumbv7em-none-eabi)"
rust: stable
install: rustup target add thumbv7em-none-eabihf
script: cargo build --all --target thumbv7em-none-eabihf --release
install: rustup target add thumbv7em-none-eabi
script: ./build_nostd.sh
- name "Rust: nightly (benches)"
rust: nightly
script: cargo build --all-features --benches
24 changes: 24 additions & 0 deletions build_nostd.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash

set -eux

# Due to the fact that cargo does not disable default features when we use
# cargo build --all --no-default-features we have to explicitly iterate over
# all crates (see https://github.com/rust-lang/cargo/issues/4753 )
DIRS=`ls -d */`
TARGET="thumbv7em-none-eabi"
cargo clean

for dir in $DIRS; do
if [ $dir = "target/" ]
then
continue
fi

pushd $dir
cargo build --no-default-features --verbose --target $TARGET || {
echo $dir failed
exit 1
}
popd
done
7 changes: 5 additions & 2 deletions ghash/Cargo.toml
Original file line number Diff line number Diff line change
@@ -14,13 +14,16 @@ keywords = ["aes-gcm", "crypto", "universal-hashing"]
categories = ["cryptography", "no-std"]
edition = "2018"

[dependencies]
polyval = { version = "0.0.1", path = "../polyval" }
[dependencies.polyval]
version = "0.0.1"
default-features = false
path = "../polyval"

[dev-dependencies]
hex-literal = "0.1"

[features]
default = ["std"]
std = ["polyval/std"]

[badges]
1 change: 1 addition & 0 deletions polyval/Cargo.toml
Original file line number Diff line number Diff line change
@@ -22,6 +22,7 @@ zeroize = { version = "0.10", optional = true, default-features = false }
hex-literal = "0.1"

[features]
default = ["std"]
std = ["universal-hash/std"]

[badges]
124 changes: 93 additions & 31 deletions polyval/src/field.rs
Original file line number Diff line number Diff line change
@@ -14,10 +14,23 @@
//!
//! [RFC 8452 Section 3]: https://tools.ietf.org/html/rfc8452#section-3
pub mod backend;
#[cfg(all(
target_feature = "pclmulqdq",
target_feature = "sse2",
target_feature = "sse4.1",
any(target_arch = "x86", target_arch = "x86_64")
))]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The conditional gating this implementation is using is gross. Open to alternatives... perhaps the cfg-if crate?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried cfg-if. Unfortunately it doesn't seem to support nesting, and the resulting code ended up looking just as bad

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made things a little better by using cfg!. Still a lot of annoying, redundant gating though

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My kingdom for cfg_alias: rust-lang/cargo#7260 (comment)

mod pclmulqdq;
mod soft;

use self::backend::Backend;
use core::ops::{Add, Mul};
#[cfg(all(
target_feature = "pclmulqdq",
target_feature = "sse2",
target_feature = "sse4.1",
any(target_arch = "x86", target_arch = "x86_64")
))]
use self::pclmulqdq::M128i;
use self::soft::U64x2;

/// Size of GF(2^128) in bytes (16-bytes).
pub const FIELD_SIZE: usize = 16;
@@ -27,29 +40,64 @@ pub type Block = [u8; FIELD_SIZE];

/// POLYVAL field element.
#[derive(Copy, Clone)]
pub struct Element<B: Backend>(B);
pub enum Element {
#[cfg(all(
target_feature = "pclmulqdq",
target_feature = "sse2",
target_feature = "sse4.1",
any(target_arch = "x86", target_arch = "x86_64")
))]
/// (P)CLMUL(QDQ)-accelerated backend on supported x86 architectures
Clmul(M128i),

impl<B: Backend> Element<B> {
/// Portable software fallback
Soft(U64x2),
}

impl Element {
/// Load a `FieldElement` from its bytestring representation.
#[cfg(all(
target_feature = "pclmulqdq",
target_feature = "sse2",
target_feature = "sse4.1",
any(target_arch = "x86", target_arch = "x86_64")
))]
pub fn from_bytes(bytes: Block) -> Self {
Element(bytes.into())
if cfg!(feature = "std") {
if is_x86_feature_detected!("pclmulqdq") {
Copy link
Member

@newpavlov newpavlov Sep 19, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite like that it does not work on no_std targets. I think we better to use RDRAND directly and cache results in atomic.

Element::Clmul(bytes.into())
} else {
Element::Soft(bytes.into())
}
} else {
Element::Clmul(bytes.into())
}
}

/// Serialize this `FieldElement` as a bytestring.
pub fn to_bytes(self) -> Block {
self.0.into()
/// Load a `FieldElement` from its bytestring representation.
#[cfg(not(all(
target_feature = "pclmulqdq",
target_feature = "sse2",
target_feature = "sse4.1",
any(target_arch = "x86", target_arch = "x86_64")
)))]
pub fn from_bytes(bytes: Block) -> Self {
Element::Soft(bytes.into())
}
}

impl<B: Backend> Default for Element<B> {
fn default() -> Self {
Self::from_bytes(Block::default())
/// Serialize this `FieldElement` as a bytestring.
pub fn to_bytes(self) -> Block {
match self {
#[cfg(all(
target_feature = "pclmulqdq",
target_feature = "sse2",
target_feature = "sse4.1",
any(target_arch = "x86", target_arch = "x86_64")
))]
Element::Clmul(m128i) => m128i.into(),
Element::Soft(u64x2) => u64x2.into(),
}
}
}

#[allow(clippy::suspicious_arithmetic_impl)]
impl<B: Backend> Add for Element<B> {
type Output = Self;

/// Adds two POLYVAL field elements.
///
@@ -58,16 +106,21 @@ impl<B: Backend> Add for Element<B> {
/// > "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 {
Element(self.0 + rhs.0)
#[allow(clippy::should_implement_trait)]
pub fn add(self, other: Block) -> Self {
match self {
#[cfg(all(
target_feature = "pclmulqdq",
target_feature = "sse2",
target_feature = "sse4.1",
any(target_arch = "x86", target_arch = "x86_64")
))]
Element::Clmul(m128i) => Element::Clmul(m128i + M128i::from(other)),
Element::Soft(u64x2) => Element::Soft(u64x2 + U64x2::from(other)),
}
}
}

#[allow(clippy::suspicious_arithmetic_impl)]
impl<B: Backend> Mul for Element<B> {
type Output = Self;

/// Computes POLYVAL multiplication over GF(2^128).
/// Computes carryless POLYVAL multiplication over GF(2^128).
///
/// From [RFC 8452 Section 3]:
///
@@ -76,13 +129,22 @@ impl<B: Backend> Mul for Element<B> {
/// > irreducible polynomial."
///
/// [RFC 8452 Section 3]: https://tools.ietf.org/html/rfc8452#section-3
fn mul(self, rhs: Self) -> Self {
Element(self.0 * rhs.0)
pub fn clmul(self, other: Block) -> Self {
match self {
#[cfg(all(
target_feature = "pclmulqdq",
target_feature = "sse2",
target_feature = "sse4.1",
any(target_arch = "x86", target_arch = "x86_64")
))]
Element::Clmul(m128i) => Element::Clmul(m128i * M128i::from(other)),
Element::Soft(u64x2) => Element::Soft(u64x2 * U64x2::from(other)),
}
}
}

impl<B: Backend> From<B> for Element<B> {
fn from(element: B) -> Element<B> {
Element(element)
impl Default for Element {
fn default() -> Self {
Self::from_bytes(Block::default())
}
}
40 changes: 0 additions & 40 deletions polyval/src/field/backend.rs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -6,7 +6,6 @@ use core::arch::x86::*;
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::*;

use super::Backend;
use crate::field::Block;
use core::ops::{Add, Mul};

@@ -15,8 +14,6 @@ use core::ops::{Add, Mul};
#[derive(Copy, Clone)]
pub struct M128i(__m128i);

impl Backend for M128i {}

impl From<Block> for M128i {
// `_mm_loadu_si128` performs an unaligned load
#[allow(clippy::cast_ptr_alignment)]
12 changes: 1 addition & 11 deletions polyval/src/field/backend/soft.rs → polyval/src/field/soft.rs
Original file line number Diff line number Diff line change
@@ -5,7 +5,6 @@
//!
//! Copyright (c) 2016 Thomas Pornin <[email protected]>
use super::Backend;
use crate::field::Block;
use core::{
convert::TryInto,
@@ -16,8 +15,6 @@ use core::{
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct U64x2(u64, u64);

impl Backend for U64x2 {}

impl From<Block> for U64x2 {
fn from(bytes: Block) -> U64x2 {
U64x2(
@@ -29,14 +26,7 @@ impl From<Block> for U64x2 {

impl From<U64x2> for Block {
fn from(u64x2: U64x2) -> Block {
let x: u128 = u64x2.into();
x.to_le_bytes()
}
}

impl From<U64x2> for u128 {
fn from(u64x2: U64x2) -> u128 {
u128::from(u64x2.0) | (u128::from(u64x2.1) << 64)
(u128::from(u64x2.0) | (u128::from(u64x2.1) << 64)).to_le_bytes()
}
}

20 changes: 13 additions & 7 deletions polyval/src/lib.rs
Original file line number Diff line number Diff line change
@@ -46,26 +46,33 @@
#![doc(html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo_small.png")]
#![warn(missing_docs, rust_2018_idioms)]

#[cfg(all(
feature = "std",
target_feature = "pclmulqdq",
target_feature = "sse2",
target_feature = "sse4.1",
any(target_arch = "x86", target_arch = "x86_64")
))]
#[macro_use]
extern crate std;

pub mod field;

pub use universal_hash;

use universal_hash::generic_array::{typenum::U16, GenericArray};
use universal_hash::{Output, UniversalHash};

// TODO(tarcieri): runtime selection of CLMUL vs soft backend when both are available
use field::backend::M128i;

/// **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: field::Element<M128i>,
H: field::Element,

/// Field element representing the computed universal hash
S: field::Element<M128i>,
S: field::Element,
}

impl UniversalHash for Polyval {
@@ -82,8 +89,7 @@ impl UniversalHash for Polyval {

/// Input a field element `X` to be authenticated
fn update_block(&mut self, x: &GenericArray<u8, U16>) {
let x = field::Element::from_bytes(x.clone().into());
self.S = (self.S + x) * self.H;
self.S = self.H.clmul(self.S.add(x.clone().into()).to_bytes());
}

/// Reset internal state