Rework backend selection code.

Each backend can now be selected by an individual feature:

- `u32_backend` for `backend::u32`;
- `u64_backend` for `backend::u64`;
- `avx2_backend` for `backend::avx2`;

The `u64_backend` is selected by default, since most people use X64 and we have
no way to select based on target (see discussion in #126).  However, these
changes mean that it is possible to select the backend explicitly, and if we
had the ability to select target-default features, we could do so easily.
This commit is contained in:
Henry de Valence 2018-05-14 17:35:34 -07:00
parent 9b6c932635
commit 34c43c20a9
10 changed files with 70 additions and 60 deletions

View file

@ -2,34 +2,28 @@ language: rust
rust: rust:
- stable - stable
- beta
- nightly - nightly
env: env:
- TEST_COMMAND=test EXTRA_FLAGS='' FEATURES='' # Tests the u32 backend
- TEST_COMMAND=test EXTRA_FLAGS='--no-default-features' FEATURES='std u32_backend'
# Tests the u64 backend
- TEST_COMMAND=test EXTRA_FLAGS='--no-default-features' FEATURES='std u64_backend'
# Tests the avx2 backend
- TEST_COMMAND=test EXTRA_FLAGS='--no-default-features' FEATURES='std avx2_backend yolocrypto'
# Tests serde support and default feature selection
- TEST_COMMAND=test EXTRA_FLAGS='' FEATURES='serde' - TEST_COMMAND=test EXTRA_FLAGS='' FEATURES='serde'
- TEST_COMMAND=test EXTRA_FLAGS='' FEATURES='nightly' # Tests building without std
- TEST_COMMAND=test EXTRA_FLAGS='' FEATURES='yolocrypto nightly'
- TEST_COMMAND=build EXTRA_FLAGS=--no-default-features FEATURES='' - TEST_COMMAND=build EXTRA_FLAGS=--no-default-features FEATURES=''
matrix: matrix:
exclude: exclude:
# Test nightly features, such as radix_51, only on nightly. # Test the avx2 backend only on nightly
- rust: stable - rust: stable
env: TEST_COMMAND=test EXTRA_FLAGS='' FEATURES='nightly' env: TEST_COMMAND=test EXTRA_FLAGS='--no-default-features' FEATURES='std avx2_backend yolocrypto'
- rust: beta
env: TEST_COMMAND=test EXTRA_FLAGS='' FEATURES='nightly'
- rust: stable
env: TEST_COMMAND=test EXTRA_FLAGS='' FEATURES='yolocrypto nightly'
- rust: beta
env: TEST_COMMAND=test EXTRA_FLAGS='' FEATURES='yolocrypto nightly'
# Test no_std only on nightly. # Test no_std only on nightly.
- rust: stable - rust: stable
env: TEST_COMMAND=build EXTRA_FLAGS=--no-default-features FEATURES='' env: TEST_COMMAND=build EXTRA_FLAGS=--no-default-features FEATURES=''
- rust: beta
env: TEST_COMMAND=build EXTRA_FLAGS=--no-default-features FEATURES=''
- rust: nightly
env: TEST_COMMAND=build EXTRA_FLAGS=--no-default-features FEATURES='alloc'
script: script:
- cargo $TEST_COMMAND --features="$FEATURES" $EXTRA_FLAGS - cargo $TEST_COMMAND --features="$FEATURES" $EXTRA_FLAGS

View file

@ -60,15 +60,20 @@ serde = { version = "1.0", optional = true }
rand = { version = "0.4", optional = false } rand = { version = "0.4", optional = false }
[features] [features]
nightly = ["radix_51", "subtle/nightly", "clear_on_drop/nightly"] nightly = ["subtle/nightly", "clear_on_drop/nightly"]
default = ["std"] default = ["std", "u64_backend"]
std = ["rand", "subtle/std"] std = ["rand", "subtle/std"]
alloc = [] alloc = []
yolocrypto = ["avx2_backend"] yolocrypto = []
# Radix-51 arithmetic using u128
radix_51 = [] # The u32 backend uses u32s with u64 products.
# experimental avx2 support u32_backend = []
avx2_backend = ["nightly"] # The u64 backend uses u64s with u128 products.
u64_backend = []
# The AVX2 backend uses u32x8s with u64x4 products.
# It uses the u64 code for serial operations.
avx2_backend = ["nightly", "u64_backend"]
# Signals that we're in the main build stage. This is off by default, # Signals that we're in the main build stage. This is off by default,
# to signal stage 1 of the build, where build.rs loads the library # to signal stage 1 of the build, where build.rs loads the library
# into the build script. Then, the build.rs emits the stage2_build # into the build script. Then, the build.rs emits the stage2_build

View file

@ -59,25 +59,38 @@ extern crate curve25519_dalek;
# Backends and Features # Backends and Features
The `yolocrypto` feature enables experimental features. The name `yolocrypto`
is meant to indicate that it is not considered production-ready, and we do not
consider `yolocrypto` features to be covered by semver guarantees.
The `std` feature is enabled by default, but it can be disabled.
The `nightly` feature enables nightly-only features. **It is recommended for security**.
Curve arithmetic is implemented using one of the following backends: Curve arithmetic is implemented using one of the following backends:
* a `u32` backend using `u64` products; * a `u32` backend using `u64` products;
* a `u64` backend using `u128` products, available using the `nightly` feature; * a `u64` backend using `u128` products;
* an experimental AVX2 backend, available using the `yolocrypto` feature when * an experimental AVX2 backend, available using the `yolocrypto` feature when
compiling for a target with `target_feature=+avx2`. compiling for a target with `target_feature=+avx2`.
By default the `u64` backend is selected. To select a specific backend, use:
```sh
cargo build --no-default-features --features "std u32_backend"
cargo build --no-default-features --features "std u64_backend"
cargo build --no-default-features --features "std avx2_backend yolocrypto"
```
Benchmarks are run using [`criterion.rs`][criterion]: Benchmarks are run using [`criterion.rs`][criterion]:
```sh ```sh
cargo bench # u32 backend # You must set RUSTFLAGS to enable AVX2 support.
cargo bench --features="nightly" # u64 backend export RUSTFLAGS="-C target_cpu=native"
cargo bench --features="nightly yolocrypto" # u64 or avx2 if available cargo bench --no-default-features --features "std u32_backend"
cargo bench --no-default-features --features "std u64_backend"
cargo bench --no-default-features --features "std avx2_backend yolocrypto"
``` ```
The `yolocrypto` feature enables experimental features. The name `yolocrypto`
is meant to indicate that it is not considered production-ready, and we do not
consider `yolocrypto` features to be covered by semver guarantees.
# Contributing # Contributing
Please see [CONTRIBUTING.md][contributing]. Please see [CONTRIBUTING.md][contributing].

View file

@ -1,4 +1,3 @@
#![cfg_attr(feature = "nightly", feature(i128_type))]
#![cfg_attr(feature = "nightly", feature(cfg_target_feature))] #![cfg_attr(feature = "nightly", feature(cfg_target_feature))]
#![cfg_attr(all(feature = "nightly", feature = "yolocrypto"), feature(stdsimd))] #![cfg_attr(all(feature = "nightly", feature = "yolocrypto"), feature(stdsimd))]
#![allow(unused_variables)] #![allow(unused_variables)]
@ -75,12 +74,12 @@ fn main() {
f.write_all( f.write_all(
format!( format!(
"\n "\n
#[cfg(feature=\"radix_51\")] #[cfg(feature = \"u32_backend\")]
use backend::u64::field::FieldElement64;
#[cfg(not(feature=\"radix_51\"))]
use backend::u32::field::FieldElement32; use backend::u32::field::FieldElement32;
#[cfg(feature = \"u64_backend\")]
use backend::u64::field::FieldElement64;
use edwards::EdwardsBasepointTable; use edwards::EdwardsBasepointTable;
use curve_models::AffineNielsPoint; use curve_models::AffineNielsPoint;

View file

@ -21,12 +21,12 @@
//! `32bit` since identifiers can't start with letters, and the backends //! `32bit` since identifiers can't start with letters, and the backends
//! do use `u32`/`u64`, so this seems like a least-bad option. //! do use `u32`/`u64`, so this seems like a least-bad option.
#[cfg(not(feature="radix_51"))] #[cfg(feature = "u32_backend")]
pub mod u32; pub mod u32;
#[cfg(feature="radix_51")] #[cfg(feature = "u64_backend")]
pub mod u64; pub mod u64;
#[cfg(all(feature="nightly", all(feature="avx2_backend", target_feature="avx2")))] #[cfg(all(feature = "avx2_backend", feature = "yolocrypto", target_feature = "avx2"))]
pub mod avx2; pub mod avx2;

View file

@ -33,9 +33,9 @@ use ristretto::CompressedRistretto;
use montgomery::MontgomeryPoint; use montgomery::MontgomeryPoint;
use scalar::Scalar; use scalar::Scalar;
#[cfg(feature="radix_51")] #[cfg(feature = "u64_backend")]
pub use backend::u64::constants::*; pub use backend::u64::constants::*;
#[cfg(not(feature="radix_51"))] #[cfg(feature = "u32_backend")]
pub use backend::u32::constants::*; pub use backend::u32::constants::*;
/// The Ed25519 basepoint, in `CompressedEdwardsY` format. /// The Ed25519 basepoint, in `CompressedEdwardsY` format.
@ -149,8 +149,8 @@ mod test {
} }
/// Test that d = -121665/121666 /// Test that d = -121665/121666
#[cfg(not(feature="radix_51"))]
#[test] #[test]
#[cfg(feature = "u32_backend")]
fn test_d_vs_ratio() { fn test_d_vs_ratio() {
use backend::u32::field::FieldElement32; use backend::u32::field::FieldElement32;
let a = -&FieldElement32([121665,0,0,0,0,0,0,0,0,0]); let a = -&FieldElement32([121665,0,0,0,0,0,0,0,0,0]);
@ -162,8 +162,8 @@ mod test {
} }
/// Test that d = -121665/121666 /// Test that d = -121665/121666
#[cfg(feature="radix_51")]
#[test] #[test]
#[cfg(feature = "u64_backend")]
fn test_d_vs_ratio() { fn test_d_vs_ratio() {
use backend::u64::field::FieldElement64; use backend::u64::field::FieldElement64;
let a = -&FieldElement64([121665,0,0,0,0]); let a = -&FieldElement64([121665,0,0,0,0]);

View file

@ -490,13 +490,13 @@ impl<'a, 'b> Mul<&'b Scalar> for &'a EdwardsPoint {
/// `EdwardsBasepointTable` is approximately 4x faster. /// `EdwardsBasepointTable` is approximately 4x faster.
fn mul(self, scalar: &'b Scalar) -> EdwardsPoint { fn mul(self, scalar: &'b Scalar) -> EdwardsPoint {
// If we built with AVX2, use the AVX2 backend. // If we built with AVX2, use the AVX2 backend.
#[cfg(all(feature="nightly", all(feature="avx2_backend", target_feature="avx2")))] #[cfg(all(feature="avx2_backend", target_feature="avx2"))]
{ {
use backend::avx2::scalar_mul::variable_base::mul; use backend::avx2::scalar_mul::variable_base::mul;
mul(self, scalar) mul(self, scalar)
} }
// Otherwise, use the serial backend: // Otherwise, use the serial backend:
#[cfg(not(all(feature="nightly", all(feature="avx2_backend", target_feature="avx2"))))] #[cfg(not(all(feature="avx2_backend", target_feature="avx2")))]
{ {
use scalar_mul::variable_base::mul; use scalar_mul::variable_base::mul;
mul(self, scalar) mul(self, scalar)
@ -571,13 +571,13 @@ pub fn multiscalar_mul<I, J>(scalars: I, points: J) -> EdwardsPoint
// delegate based on the iter's size hint -- hdevalence // delegate based on the iter's size hint -- hdevalence
// If we built with AVX2, use the AVX2 backend. // If we built with AVX2, use the AVX2 backend.
#[cfg(all(feature="nightly", all(feature="avx2_backend", target_feature="avx2")))] #[cfg(all(feature="avx2_backend", target_feature="avx2"))]
{ {
use backend::avx2::scalar_mul::straus::multiscalar_mul; use backend::avx2::scalar_mul::straus::multiscalar_mul;
multiscalar_mul(scalars, points) multiscalar_mul(scalars, points)
} }
// Otherwise, proceed as normal: // Otherwise, proceed as normal:
#[cfg(not(all(feature="nightly", all(feature="avx2_backend", target_feature="avx2"))))] #[cfg(not(all(feature="avx2_backend", target_feature="avx2")))]
{ {
use scalar_mul::straus::multiscalar_mul; use scalar_mul::straus::multiscalar_mul;
multiscalar_mul(scalars, points) multiscalar_mul(scalars, points)
@ -844,13 +844,13 @@ pub mod vartime {
// XXX later when we do more fancy multiscalar mults, we can delegate // XXX later when we do more fancy multiscalar mults, we can delegate
// based on the iter's size hint -- hdevalence // based on the iter's size hint -- hdevalence
// If we built with AVX2, use the AVX2 backend. // If we built with AVX2, use the AVX2 backend.
#[cfg(all(feature="nightly", all(feature="avx2_backend", target_feature="avx2")))] #[cfg(all(feature="avx2_backend", target_feature="avx2"))]
{ {
use backend::avx2::scalar_mul::vartime_straus::multiscalar_mul; use backend::avx2::scalar_mul::vartime_straus::multiscalar_mul;
multiscalar_mul(scalars, points) multiscalar_mul(scalars, points)
} }
// Otherwise, proceed as normal: // Otherwise, proceed as normal:
#[cfg(not(all(feature="nightly", all(feature="avx2_backend", target_feature="avx2"))))] #[cfg(not(all(feature="avx2_backend", target_feature="avx2")))]
{ {
use scalar_mul::vartime_straus::multiscalar_mul; use scalar_mul::vartime_straus::multiscalar_mul;
multiscalar_mul(scalars, points) multiscalar_mul(scalars, points)
@ -861,13 +861,13 @@ pub mod vartime {
#[cfg(feature="stage2_build")] #[cfg(feature="stage2_build")]
pub fn double_scalar_mul_basepoint(a: &Scalar, A: &EdwardsPoint, b: &Scalar) -> EdwardsPoint { pub fn double_scalar_mul_basepoint(a: &Scalar, A: &EdwardsPoint, b: &Scalar) -> EdwardsPoint {
// If we built with AVX2, use the AVX2 backend. // If we built with AVX2, use the AVX2 backend.
#[cfg(all(feature="nightly", all(feature="avx2_backend", target_feature="avx2")))] #[cfg(all(feature="avx2_backend", target_feature="avx2"))]
{ {
use backend::avx2::scalar_mul::vartime_double_base::mul; use backend::avx2::scalar_mul::vartime_double_base::mul;
mul(a, A, b) mul(a, A, b)
} }
// Otherwise, proceed as normal: // Otherwise, proceed as normal:
#[cfg(not(all(feature="nightly", all(feature="avx2_backend", target_feature="avx2"))))] #[cfg(not(all(feature="avx2_backend", target_feature="avx2")))]
{ {
use scalar_mul::vartime_double_base::mul; use scalar_mul::vartime_double_base::mul;
mul(a, A, b) mul(a, A, b)
@ -1169,7 +1169,7 @@ mod test {
/// and enable `debug_assert!()`. This performs many scalar /// and enable `debug_assert!()`. This performs many scalar
/// multiplications to attempt to trigger possible overflows etc. /// multiplications to attempt to trigger possible overflows etc.
/// ///
/// For instance, the `radix_51` `Mul` implementation for /// For instance, the `u64` `Mul` implementation for
/// `FieldElements` requires the input `Limb`s to be bounded by /// `FieldElements` requires the input `Limb`s to be bounded by
/// 2^54, but we cannot enforce this dynamically at runtime, or /// 2^54, but we cannot enforce this dynamically at runtime, or
/// statically at compile time (until Rust gets type-level /// statically at compile time (until Rust gets type-level

View file

@ -32,24 +32,24 @@ use subtle::ConstantTimeEq;
use constants; use constants;
use backend; use backend;
#[cfg(feature="radix_51")] #[cfg(feature = "u64_backend")]
pub use backend::u64::field::*; pub use backend::u64::field::*;
/// A `FieldElement` represents an element of the field /// A `FieldElement` represents an element of the field
/// \\( \mathbb Z / (2\^{255} - 19)\\). /// \\( \mathbb Z / (2\^{255} - 19)\\).
/// ///
/// The `FieldElement` type is an alias for one of the platform-specific /// The `FieldElement` type is an alias for one of the platform-specific
/// implementations. /// implementations.
#[cfg(feature="radix_51")] #[cfg(feature = "u64_backend")]
pub type FieldElement = backend::u64::field::FieldElement64; pub type FieldElement = backend::u64::field::FieldElement64;
#[cfg(not(feature="radix_51"))] #[cfg(feature = "u32_backend")]
pub use backend::u32::field::*; pub use backend::u32::field::*;
/// A `FieldElement` represents an element of the field /// A `FieldElement` represents an element of the field
/// \\( \mathbb Z / (2\^{255} - 19)\\). /// \\( \mathbb Z / (2\^{255} - 19)\\).
/// ///
/// The `FieldElement` type is an alias for one of the platform-specific /// The `FieldElement` type is an alias for one of the platform-specific
/// implementations. /// implementations.
#[cfg(not(feature="radix_51"))] #[cfg(feature = "u32_backend")]
pub type FieldElement = backend::u32::field::FieldElement32; pub type FieldElement = backend::u32::field::FieldElement32;
impl Eq for FieldElement {} impl Eq for FieldElement {}

View file

@ -12,7 +12,6 @@
#![cfg_attr(feature = "alloc", feature(alloc))] #![cfg_attr(feature = "alloc", feature(alloc))]
#![cfg_attr(feature = "nightly", feature(i128_type))]
#![cfg_attr(feature = "nightly", feature(cfg_target_feature))] #![cfg_attr(feature = "nightly", feature(cfg_target_feature))]
#![cfg_attr(feature = "nightly", feature(external_doc))] #![cfg_attr(feature = "nightly", feature(external_doc))]
#![cfg_attr(all(feature = "nightly", feature = "yolocrypto"), feature(stdsimd))] #![cfg_attr(all(feature = "nightly", feature = "yolocrypto"), feature(stdsimd))]

View file

@ -39,14 +39,14 @@ use constants;
/// ///
/// This is a type alias for one of the scalar types in the `backend` /// This is a type alias for one of the scalar types in the `backend`
/// module. /// module.
#[cfg(feature="radix_51")] #[cfg(feature = "u64_backend")]
type UnpackedScalar = backend::u64::scalar::Scalar64; type UnpackedScalar = backend::u64::scalar::Scalar64;
/// An `UnpackedScalar` represents an element of the field GF(l), optimized for speed. /// An `UnpackedScalar` represents an element of the field GF(l), optimized for speed.
/// ///
/// This is a type alias for one of the scalar types in the `backend` /// This is a type alias for one of the scalar types in the `backend`
/// module. /// module.
#[cfg(not(feature="radix_51"))] #[cfg(feature = "u32_backend")]
type UnpackedScalar = backend::u32::scalar::Scalar32; type UnpackedScalar = backend::u32::scalar::Scalar32;