Implement simplified backend selection (#428)

As proposed in #414, this commit changes the backend selection approach,
introspecting `target_pointer_width` to select `u32_backend` vs
`u64_backend` (or `fiat_u32_backend`/`fiat_u64_backend` if the
`fiat_backend` feature is enabled).

This helps eliminate the use of non-additive features, and also the
rather confusing errors that happen if multiple backends are selected
(i.e. thousands of lines of rustc errors).

The selection logic checks if `target_pointer_width = "64"` and uses the
64-bit backend, or falls back to the 32-bit backend otherwise. This
means the crate will always have a valid backend regardless of the
pointer width, although there may be odd edge cases for exotic platforms
which would optimally use the 64-bit backend but have a non-"64" target
pointer width for whatever reason. We can handle those cases as they
come up.
This commit is contained in:
Tony Arcieri 2022-11-13 10:17:42 -07:00 committed by GitHub
parent 977eb0d3b7
commit 081f632d91
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 159 additions and 147 deletions

View file

@ -10,21 +10,24 @@ env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
jobs: jobs:
test-u32: test:
name: Test u32 backend
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: strategy:
- uses: actions/checkout@v3 matrix:
- uses: dtolnay/rust-toolchain@stable include:
- run: cargo test --no-default-features --features "std u32_backend" # 32-bit target
- target: i686-unknown-linux-gnu
deps: sudo apt update && sudo apt install gcc-multilib
test-u64: # 64-bit target
name: Test u64 backend - target: x86_64-unknown-linux-gnu
runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
- run: cargo test --no-default-features --features "std u64_backend" - run: rustup target add ${{ matrix.target }}
- run: ${{ matrix.deps }}
- run: cargo test --target ${{ matrix.target }}
- run: cargo test --target ${{ matrix.target }} --features fiat_backend
build-simd: build-simd:
name: Build simd backend (nightly) name: Build simd backend (nightly)
@ -54,7 +57,9 @@ jobs:
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
- run: cargo test --lib --no-default-features --features "alloc u32_backend" - run: rustup target add i686-unknown-linux-gnu
- run: sudo apt update && sudo apt install gcc-multilib
- run: cargo test --lib --no-default-features --features alloc --target i686-unknown-linux-gnu
nightly: nightly:
name: Test nightly compiler name: Test nightly compiler
@ -72,11 +77,11 @@ jobs:
# First run `cargo +nightly -Z minimal-verisons check` in order to get a # First run `cargo +nightly -Z minimal-verisons check` in order to get a
# Cargo.lock with the oldest possible deps # Cargo.lock with the oldest possible deps
- uses: dtolnay/rust-toolchain@nightly - uses: dtolnay/rust-toolchain@nightly
- run: cargo -Z minimal-versions check --no-default-features --features "fiat_u64_backend serde" - run: cargo -Z minimal-versions check --no-default-features --features fiat_backend,serde
# Now check that `cargo build` works with respect to the oldest possible # Now check that `cargo build` works with respect to the oldest possible
# deps and the stated MSRV # deps and the stated MSRV
- uses: dtolnay/rust-toolchain@1.56.1 - uses: dtolnay/rust-toolchain@1.56.1
- run: cargo build --no-default-features --features "fiat_u64_backend serde" - run: cargo build --no-default-features --features fiat_backend,serde
bench: bench:
name: Check that benchmarks compile name: Check that benchmarks compile

View file

@ -43,6 +43,7 @@ name = "dalek_benchmarks"
harness = false harness = false
[dependencies] [dependencies]
cfg-if = "1"
rand_core = { version = "0.6", default-features = false } rand_core = { version = "0.6", default-features = false }
digest = { version = "0.10", default-features = false } digest = { version = "0.10", default-features = false }
subtle = { version = "^2.2.1", default-features = false } subtle = { version = "^2.2.1", default-features = false }
@ -55,20 +56,11 @@ fiat-crypto = { version = "0.1.6", optional = true}
[features] [features]
nightly = ["subtle/nightly"] nightly = ["subtle/nightly"]
default = ["std", "u64_backend"] default = ["std"]
std = ["alloc", "subtle/std", "rand_core/std"] std = ["alloc", "subtle/std", "rand_core/std"]
alloc = ["zeroize/alloc"] alloc = ["zeroize/alloc"]
# The u32 backend uses u32s with u64 products. # fiat-crypto backend with formally-verified field arithmetic
u32_backend = [] fiat_backend = ["fiat-crypto"]
# The u64 backend uses u64s with u128 products.
u64_backend = []
# fiat-u64 backend (with formally-verified field arith) uses u64s with u128 products.
fiat_u64_backend = ["fiat-crypto"]
# fiat-u32 backend (with formally-verified field arith) uses u32s with u64 products.
fiat_u32_backend = ["fiat-crypto"]
# The SIMD backend uses parallel formulas, using either AVX2 or AVX512-IFMA. # The SIMD backend uses parallel formulas, using either AVX2 or AVX512-IFMA.
simd_backend = ["nightly", "u64_backend", "packed_simd"] simd_backend = ["nightly", "packed_simd"]
# DEPRECATED: this is now an alias for `simd_backend` and may be removed
# in some future release.
avx2_backend = ["simd_backend"]

View file

@ -82,9 +82,13 @@ version, and in terms of non-breaking changes it includes:
### 4.x (current alpha) ### 4.x (current alpha)
The `4.x` series has an API largely unchanged from `3.x`, with a breaking change The `4.x` series has an API largely unchanged from `3.x`, with a breaking change
to update the `rand` dependency crates. It also requires including a new trait, to update the `rand` dependency crates.
`use curve25519_dalek::traits::BasepointTable`, whenever using `EdwardsBasepointTable`
or `RistrettoBasepointTable`. It also requires including a new trait,
`use curve25519_dalek::traits::BasepointTable`, whenever using
`EdwardsBasepointTable` or `RistrettoBasepointTable`.
Backend selection has also been updated to be more automatic. See below.
# Backends and Features # Backends and Features
@ -98,24 +102,26 @@ Curve arithmetic is implemented using one of the following backends:
* a `u64` backend using serial formulas and `u128` products; * a `u64` backend using serial formulas and `u128` products;
* an `avx2` backend using [parallel formulas][parallel_doc] and `avx2` instructions (sets speed records); * an `avx2` backend using [parallel formulas][parallel_doc] and `avx2` instructions (sets speed records);
* an `ifma` backend using [parallel formulas][parallel_doc] and `ifma` instructions (sets speed records); * an `ifma` backend using [parallel formulas][parallel_doc] and `ifma` instructions (sets speed records);
* a `fiat` backend using formally verified field arithmetic from [fiat-crypto];
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"
# Requires nightly, RUSTFLAGS="-C target_feature=+avx2" to use avx2
cargo build --no-default-features --features "std simd_backend"
# Requires nightly, RUSTFLAGS="-C target_feature=+avx512ifma" to use ifma
cargo build --no-default-features --features "std simd_backend"
```
Crates using `curve25519-dalek` can either select a backend on behalf of their
users, or expose feature flags that control the `curve25519-dalek` backend.
The `std` feature is enabled by default, but it can be disabled for no-`std` The `std` feature is enabled by default, but it can be disabled for no-`std`
builds using `--no-default-features`. Note that this requires explicitly builds using `--no-default-features`. Note that this requires explicitly
selecting an arithmetic backend using one of the `_backend` features. selecting an arithmetic backend using one of the `_backend` features.
If no backend is selected, compilation will fail. If no backend is selected, compilation will fail.
## Backend selection
Backend selection is done automatically. E.g., if you're compiling on a
64-bit machine, then the `u64` backend is automatically chosen. And
if the `fiat_backend` feature is set, then the fiat `u64` backend is
chosen.
If you need a `u32` backend on a `u64` machine, then simple
cross-compiling will work on an x86-64 Linux machine:
* `sudo apt install gcc-multilib` (or whatever package manager you use)
* `rustup target add i686-unknown-linux-gnu`
* `cargo build --target i686-unknown-linux-gnu`
# Minimum Supported Rust Version # Minimum Supported Rust Version
@ -166,11 +172,10 @@ compiled with appropriate `target_feature`s, so this cannot occur.
Benchmarks are run using [`criterion.rs`][criterion]: Benchmarks are run using [`criterion.rs`][criterion]:
```sh ```sh
cargo bench --no-default-features --features "std u32_backend" cargo bench --no-default-features
cargo bench --no-default-features --features "std u64_backend"
# Uses avx2 or ifma only if compiled for an appropriate target. # Uses avx2 or ifma only if compiled for an appropriate target.
export RUSTFLAGS="-C target_cpu=native" export RUSTFLAGS="-C target_cpu=native"
cargo bench --no-default-features --features "std simd_backend" cargo +nightly bench --no-default-features --features simd_backend
``` ```
Performance is a secondary goal behind correctness, safety, and Performance is a secondary goal behind correctness, safety, and
@ -227,10 +232,9 @@ optimised batch inversion was contributed by Sean Bowe and Daira Hopwood.
The `no_std` and `zeroize` support was contributed by Tony Arcieri. The `no_std` and `zeroize` support was contributed by Tony Arcieri.
The formally verified backends, `fiat_u32_backend` and `fiat_u64_backend`, which The formally verified `fiat_backend` integrates Rust code generated by the
integrate with the Rust generated by the [Fiat Crypto project](https://github.com/mit-plv/fiat-crypto) and was
[Fiat Crypto project](https://github.com/mit-plv/fiat-crypto) were contributed contributed by François Garillot.
by François Garillot.
Thanks also to Ashley Hauck, Lucas Salibian, Manish Goregaokar, Jack Grigg, Thanks also to Ashley Hauck, Lucas Salibian, Manish Goregaokar, Jack Grigg,
Pratyush Mishra, Michael Rosenberg, and countless others for their Pratyush Mishra, Michael Rosenberg, and countless others for their
@ -244,3 +248,4 @@ contributions.
[criterion]: https://github.com/japaric/criterion.rs [criterion]: https://github.com/japaric/criterion.rs
[parallel_doc]: https://doc-internal.dalek.rs/curve25519_dalek/backend/vector/avx2/index.html [parallel_doc]: https://doc-internal.dalek.rs/curve25519_dalek/backend/vector/avx2/index.html
[subtle_doc]: https://doc.dalek.rs/subtle/ [subtle_doc]: https://doc.dalek.rs/subtle/
[fiat-crypto]: https://github.com/mit-plv/fiat-crypto

View file

@ -34,18 +34,6 @@
//! The [`vector`] backend is selected by the `simd_backend` cargo //! The [`vector`] backend is selected by the `simd_backend` cargo
//! feature; it uses the [`serial`] backend for non-vectorized operations. //! feature; it uses the [`serial`] backend for non-vectorized operations.
#[cfg(not(any(
feature = "u32_backend",
feature = "u64_backend",
feature = "fiat_u32_backend",
feature = "fiat_u64_backend",
feature = "simd_backend",
)))]
compile_error!(
"no curve25519-dalek backend cargo feature enabled! \
please enable one of: u32_backend, u64_backend, fiat_u32_backend, fiat_u64_backend, simd_backend"
);
pub mod serial; pub mod serial;
#[cfg(any( #[cfg(any(

View file

@ -19,32 +19,24 @@
//! //!
//! When the vector backend is enabled, the field and scalar //! When the vector backend is enabled, the field and scalar
//! implementations are still used for non-vectorized operations. //! implementations are still used for non-vectorized operations.
//!
//! Note: at this time the `u32` and `u64` backends cannot be built
//! together.
#[cfg(not(any( use cfg_if::cfg_if;
feature = "u32_backend",
feature = "u64_backend",
feature = "fiat_u32_backend",
feature = "fiat_u64_backend"
)))]
compile_error!(
"no curve25519-dalek backend cargo feature enabled! \
please enable one of: u32_backend, u64_backend, fiat_u32_backend, fiat_u64_backend"
);
#[cfg(feature = "u32_backend")] cfg_if! {
pub mod u32; if #[cfg(feature = "fiat_backend")] {
#[cfg(not(target_pointer_width = "64"))]
pub mod fiat_u32;
#[cfg(feature = "u64_backend")] #[cfg(target_pointer_width = "64")]
pub mod u64; pub mod fiat_u64;
} else {
#[cfg(not(target_pointer_width = "64"))]
pub mod u32;
#[cfg(feature = "fiat_u32_backend")] #[cfg(target_pointer_width = "64")]
pub mod fiat_u32; pub mod u64;
}
#[cfg(feature = "fiat_u64_backend")] }
pub mod fiat_u64;
pub mod curve_models; pub mod curve_models;

View file

@ -28,20 +28,27 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use cfg_if::cfg_if;
use crate::edwards::CompressedEdwardsY; use crate::edwards::CompressedEdwardsY;
use crate::montgomery::MontgomeryPoint; use crate::montgomery::MontgomeryPoint;
use crate::ristretto::CompressedRistretto; use crate::ristretto::CompressedRistretto;
use crate::ristretto::RistrettoPoint; use crate::ristretto::RistrettoPoint;
use crate::scalar::Scalar; use crate::scalar::Scalar;
#[cfg(feature = "fiat_u32_backend")] cfg_if! {
pub use crate::backend::serial::fiat_u32::constants::*; if #[cfg(feature = "fiat_backend")] {
#[cfg(feature = "fiat_u64_backend")] #[cfg(not(target_pointer_width = "64"))]
pub use crate::backend::serial::fiat_u64::constants::*; pub use crate::backend::serial::fiat_u32::constants::*;
#[cfg(feature = "u32_backend")] #[cfg(target_pointer_width = "64")]
pub use crate::backend::serial::u32::constants::*; pub use crate::backend::serial::fiat_u64::constants::*;
#[cfg(feature = "u64_backend")] } else {
pub use crate::backend::serial::u64::constants::*; #[cfg(not(target_pointer_width = "64"))]
pub use crate::backend::serial::u32::constants::*;
#[cfg(target_pointer_width = "64")]
pub use crate::backend::serial::u64::constants::*;
}
}
/// The Ed25519 basepoint, in `CompressedEdwardsY` format. /// The Ed25519 basepoint, in `CompressedEdwardsY` format.
/// ///
@ -142,7 +149,7 @@ mod test {
/// Test that d = -121665/121666 /// Test that d = -121665/121666
#[test] #[test]
#[cfg(feature = "u32_backend")] #[cfg(all(not(target_pointer_width = "64"), not(feature = "fiat_backend")))]
fn test_d_vs_ratio() { fn test_d_vs_ratio() {
use crate::backend::serial::u32::field::FieldElement2625; use crate::backend::serial::u32::field::FieldElement2625;
let a = -&FieldElement2625([121665, 0, 0, 0, 0, 0, 0, 0, 0, 0]); let a = -&FieldElement2625([121665, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
@ -155,7 +162,7 @@ mod test {
/// Test that d = -121665/121666 /// Test that d = -121665/121666
#[test] #[test]
#[cfg(feature = "u64_backend")] #[cfg(all(target_pointer_width = "64", not(feature = "fiat_backend")))]
fn test_d_vs_ratio() { fn test_d_vs_ratio() {
use crate::backend::serial::u64::field::FieldElement51; use crate::backend::serial::u64::field::FieldElement51;
let a = -&FieldElement51([121665, 0, 0, 0, 0]); let a = -&FieldElement51([121665, 0, 0, 0, 0]);

View file

@ -25,6 +25,8 @@
use core::cmp::{Eq, PartialEq}; use core::cmp::{Eq, PartialEq};
use cfg_if::cfg_if;
use subtle::Choice; use subtle::Choice;
use subtle::ConditionallyNegatable; use subtle::ConditionallyNegatable;
use subtle::ConditionallySelectable; use subtle::ConditionallySelectable;
@ -33,40 +35,52 @@ use subtle::ConstantTimeEq;
use crate::backend; use crate::backend;
use crate::constants; use crate::constants;
#[cfg(feature = "fiat_u32_backend")] cfg_if! {
pub use backend::serial::fiat_u32::field::*; if #[cfg(feature = "fiat_backend")] {
#[cfg(feature = "fiat_u64_backend")] #[cfg(not(target_pointer_width = "64"))]
pub use backend::serial::fiat_u64::field::*; pub use backend::serial::fiat_u32::field::*;
/// A `FieldElement` represents an element of the field #[cfg(target_pointer_width = "64")]
/// \\( \mathbb Z / (2\^{255} - 19)\\). pub use backend::serial::fiat_u64::field::*;
///
/// The `FieldElement` type is an alias for one of the platform-specific
/// implementations.
/// Using formally-verified field arithmetic from fiat-crypto
#[cfg(feature = "fiat_u32_backend")]
pub type FieldElement = backend::serial::fiat_u32::field::FieldElement2625;
#[cfg(feature = "fiat_u64_backend")]
pub type FieldElement = backend::serial::fiat_u64::field::FieldElement51;
#[cfg(feature = "u64_backend")] /// A `FieldElement` represents an element of the field
pub use crate::backend::serial::u64::field::*; /// \\( \mathbb Z / (2\^{255} - 19)\\).
/// A `FieldElement` represents an element of the field ///
/// \\( \mathbb Z / (2\^{255} - 19)\\). /// The `FieldElement` type is an alias for one of the platform-specific
/// /// implementations.
/// The `FieldElement` type is an alias for one of the platform-specific ///
/// implementations. /// Using formally-verified field arithmetic from fiat-crypto.
#[cfg(feature = "u64_backend")] #[cfg(not(target_pointer_width = "64"))]
pub type FieldElement = backend::serial::u64::field::FieldElement51; pub type FieldElement = backend::serial::fiat_u32::field::FieldElement2625;
#[cfg(feature = "u32_backend")] /// A `FieldElement` represents an element of the field
pub use backend::serial::u32::field::*; /// \\( \mathbb Z / (2\^{255} - 19)\\).
/// A `FieldElement` represents an element of the field ///
/// \\( \mathbb Z / (2\^{255} - 19)\\). /// The `FieldElement` type is an alias for one of the platform-specific
/// /// implementations.
/// The `FieldElement` type is an alias for one of the platform-specific ///
/// implementations. /// Using formally-verified field arithmetic from fiat-crypto.
#[cfg(feature = "u32_backend")] #[cfg(target_pointer_width = "64")]
pub type FieldElement = backend::serial::u32::field::FieldElement2625; pub type FieldElement = backend::serial::fiat_u64::field::FieldElement51;
} else if #[cfg(target_pointer_width = "64")] {
pub use crate::backend::serial::u64::field::*;
/// A `FieldElement` represents an element of the field
/// \\( \mathbb Z / (2\^{255} - 19)\\).
///
/// The `FieldElement` type is an alias for one of the platform-specific
/// implementations.
pub type FieldElement = backend::serial::u64::field::FieldElement51;
} else {
pub use backend::serial::u32::field::*;
/// A `FieldElement` represents an element of the field
/// \\( \mathbb Z / (2\^{255} - 19)\\).
///
/// The `FieldElement` type is an alias for one of the platform-specific
/// implementations.
pub type FieldElement = backend::serial::u32::field::FieldElement2625;
}
}
impl Eq for FieldElement {} impl Eq for FieldElement {}

View file

@ -150,6 +150,8 @@ use core::ops::{Sub, SubAssign};
#[allow(unused_imports)] #[allow(unused_imports)]
use crate::prelude::*; use crate::prelude::*;
use cfg_if::cfg_if;
use rand_core::{CryptoRng, RngCore}; use rand_core::{CryptoRng, RngCore};
use digest::generic_array::typenum::U64; use digest::generic_array::typenum::U64;
@ -164,28 +166,35 @@ use zeroize::Zeroize;
use crate::backend; use crate::backend;
use crate::constants; use crate::constants;
/// An `UnpackedScalar` represents an element of the field GF(l), optimized for speed. cfg_if! {
/// if #[cfg(feature = "fiat_backend")] {
/// This is a type alias for one of the scalar types in the `backend` /// An `UnpackedScalar` represents an element of the field GF(l), optimized for speed.
/// module. ///
#[cfg(feature = "fiat_u32_backend")] /// This is a type alias for one of the scalar types in the `backend`
type UnpackedScalar = backend::serial::fiat_u32::scalar::Scalar29; /// module.
#[cfg(feature = "fiat_u64_backend")] #[cfg(not(target_pointer_width = "64"))]
type UnpackedScalar = backend::serial::fiat_u64::scalar::Scalar52; type UnpackedScalar = backend::serial::fiat_u32::scalar::Scalar29;
/// 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(feature = "u64_backend")] #[cfg(target_pointer_width = "64")]
type UnpackedScalar = backend::serial::u64::scalar::Scalar52; type UnpackedScalar = backend::serial::fiat_u64::scalar::Scalar52;
} else if #[cfg(target_pointer_width = "64")] {
/// 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(feature = "u32_backend")] type UnpackedScalar = backend::serial::u64::scalar::Scalar52;
type UnpackedScalar = backend::serial::u32::scalar::Scalar29; } else {
/// 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`
/// module.
type UnpackedScalar = backend::serial::u32::scalar::Scalar29;
}
}
/// The `Scalar` struct holds an integer \\(s < 2\^{255} \\) which /// The `Scalar` struct holds an integer \\(s < 2\^{255} \\) which
/// represents an element of \\(\mathbb Z / \ell\\). /// represents an element of \\(\mathbb Z / \ell\\).