diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 1ca7282..e889a91 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -29,8 +29,14 @@ jobs: - run: ${{ matrix.deps }} - run: cargo test --target ${{ matrix.target }} --no-default-features - run: cargo test --target ${{ matrix.target }} --no-default-features --features alloc + - run: cargo test --target ${{ matrix.target }} --no-default-features --features digest + - run: cargo test --target ${{ matrix.target }} --no-default-features --features basepoint-tables + - run: cargo test --target ${{ matrix.target }} --no-default-features --features rand_core + - run: cargo test --target ${{ matrix.target }} --no-default-features --features serde - run: cargo test --target ${{ matrix.target }} --no-default-features --features zeroize - run: cargo test --target ${{ matrix.target }} + - run: cargo test --target ${{ matrix.target }} --features digest + - run: cargo test --target ${{ matrix.target }} --features rand_core - run: cargo test --target ${{ matrix.target }} --features serde - env: RUSTFLAGS: '--cfg curve25519_dalek_backend="fiat"' diff --git a/CHANGELOG.md b/CHANGELOG.md index 44615a7..98a6879 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ major series. #### Other changes +* Add `basepoint-tables` feature * Update Maintenance Policies for SemVer * Migrate documentation to docs.rs hosted * Fix backend documentation generation diff --git a/Cargo.toml b/Cargo.toml index 219d09a..ec61fab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,8 +63,9 @@ fiat-crypto = "0.1.6" packed_simd = { version = "0.3.4", package = "packed_simd_2", features = ["into_bits"] } [features] -default = ["alloc", "zeroize"] +default = ["alloc", "basepoint-tables", "zeroize"] alloc = ["zeroize?/alloc"] +basepoint-tables = [] [profile.dev] opt-level = 2 diff --git a/README.md b/README.md index 4aa5bf5..37a656f 100644 --- a/README.md +++ b/README.md @@ -48,13 +48,14 @@ curve25519-dalek = "4.0.0-pre.5" ## Feature Flags -| Feature | Default? | Description | -| :--- | :---: | :--- | -| `alloc` | ✓ | Enables Edwards and Ristretto multiscalar multiplication, batch scalar inversion, and batch Ristretto double-and-compress. Also enables `zeroize`. | -| `zeroize` | ✓ | Enables [`Zeroize`][zeroize-trait] for all scalar and curve point types. | -| `rand_core` | | Enables `Scalar::random` and `RistrettoPoint::random`. This is an optional dependency whose version is not subject to SemVer. See [below](#public-api-semver-exemptions) for more details. | -| `digest` | | Enables `RistrettoPoint::{from_hash, hash_from_bytes}` and `Scalar::{from_hash, hash_from_bytes}`. This is an optional dependency whose version is not subject to SemVer. See [below](#public-api-semver-exemptions) for more details. | -| `serde` | | Enables `serde` serialization/deserialization for all the point and scalar types. | +| Feature | Default? | Description | +| :--- | :---: | :--- | +| `alloc` | ✓ | Enables Edwards and Ristretto multiscalar multiplication, batch scalar inversion, and batch Ristretto double-and-compress. Also enables `zeroize`. | +| `zeroize` | ✓ | Enables [`Zeroize`][zeroize-trait] for all scalar and curve point types. | +| `basepoint-tables` | ✓ | Includes precomputed basepoint multiplication tables. This speeds up `EdwardsPoint::mul_base` and `RistrettoPoint::mul_base` by ~4x, at the cost of ~30KB added to the code size. | +| `rand_core` | | Enables `Scalar::random` and `RistrettoPoint::random`. This is an optional dependency whose version is not subject to SemVer. See [below](#public-api-semver-exemptions) for more details. | +| `digest` | | Enables `RistrettoPoint::{from_hash, hash_from_bytes}` and `Scalar::{from_hash, hash_from_bytes}`. This is an optional dependency whose version is not subject to SemVer. See [below](#public-api-semver-exemptions) for more details. | +| `serde` | | Enables `serde` serialization/deserialization for all the point and scalar types. | To disable the default features when using `curve25519-dalek` as a dependency, add `default-features = false` to the dependency in your `Cargo.toml`. To diff --git a/src/backend/serial/u32/constants.rs b/src/backend/serial/u32/constants.rs index c4baed9..94a27f8 100644 --- a/src/backend/serial/u32/constants.rs +++ b/src/backend/serial/u32/constants.rs @@ -16,8 +16,11 @@ use super::field::FieldElement2625; use super::scalar::Scalar29; use crate::backend::serial::curve_models::AffineNielsPoint; -use crate::edwards::{EdwardsBasepointTable, EdwardsPoint}; -use crate::window::{LookupTable, NafLookupTable8}; +use crate::edwards::EdwardsPoint; +use crate::window::NafLookupTable8; + +#[cfg(feature = "basepoint-tables")] +use crate::{edwards::EdwardsBasepointTable, window::LookupTable}; /// The value of minus one, equal to `-&FieldElement::ONE` pub(crate) const MINUS_ONE: FieldElement2625 = FieldElement2625([ @@ -234,11 +237,13 @@ pub const EIGHT_TORSION_INNER_DOC_HIDDEN: [EdwardsPoint; 8] = [ ]; /// Table containing precomputed multiples of the Ed25519 basepoint \\(B = (x, 4/5)\\). +#[cfg(feature = "basepoint-tables")] pub static ED25519_BASEPOINT_TABLE: &'static EdwardsBasepointTable = &ED25519_BASEPOINT_TABLE_INNER_DOC_HIDDEN; /// Inner constant, used to avoid filling the docs with precomputed points. #[doc(hidden)] +#[cfg(feature = "basepoint-tables")] static ED25519_BASEPOINT_TABLE_INNER_DOC_HIDDEN: EdwardsBasepointTable = EdwardsBasepointTable([ LookupTable([ AffineNielsPoint { diff --git a/src/backend/serial/u64/constants.rs b/src/backend/serial/u64/constants.rs index 22afefc..3d0057d 100644 --- a/src/backend/serial/u64/constants.rs +++ b/src/backend/serial/u64/constants.rs @@ -14,8 +14,11 @@ use super::field::FieldElement51; use super::scalar::Scalar52; use crate::backend::serial::curve_models::AffineNielsPoint; -use crate::edwards::{EdwardsBasepointTable, EdwardsPoint}; -use crate::window::{LookupTable, NafLookupTable8}; +use crate::edwards::EdwardsPoint; +use crate::window::NafLookupTable8; + +#[cfg(feature = "basepoint-tables")] +use crate::{edwards::EdwardsBasepointTable, window::LookupTable}; /// The value of minus one, equal to `-&FieldElement::ONE` pub(crate) const MINUS_ONE: FieldElement51 = FieldElement51([ @@ -321,11 +324,13 @@ pub const EIGHT_TORSION_INNER_DOC_HIDDEN: [EdwardsPoint; 8] = [ ]; /// Table containing precomputed multiples of the Ed25519 basepoint \\(B = (x, 4/5)\\). +#[cfg(feature = "basepoint-tables")] pub static ED25519_BASEPOINT_TABLE: &'static EdwardsBasepointTable = &ED25519_BASEPOINT_TABLE_INNER_DOC_HIDDEN; /// Inner constant, used to avoid filling the docs with precomputed points. #[doc(hidden)] +#[cfg(feature = "basepoint-tables")] static ED25519_BASEPOINT_TABLE_INNER_DOC_HIDDEN: EdwardsBasepointTable = EdwardsBasepointTable([ LookupTable([ AffineNielsPoint { diff --git a/src/constants.rs b/src/constants.rs index 413cc06..b0f8c56 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -15,7 +15,8 @@ //! `LONG_DESCRIPTIVE_UPPER_CASE_NAMES`, but they can be brought into //! scope using a `let` binding: //! -//! ``` +#![cfg_attr(feature = "basepoint-tables", doc = "```")] +#![cfg_attr(not(feature = "basepoint-tables"), doc = "```ignore")] //! use curve25519_dalek::constants; //! use curve25519_dalek::traits::IsIdentity; //! @@ -30,12 +31,14 @@ use cfg_if::cfg_if; -use crate::edwards::{CompressedEdwardsY, EdwardsBasepointTable}; +use crate::edwards::CompressedEdwardsY; use crate::montgomery::MontgomeryPoint; -use crate::ristretto::CompressedRistretto; -use crate::ristretto::RistrettoPoint; +use crate::ristretto::{CompressedRistretto, RistrettoPoint}; use crate::scalar::Scalar; +#[cfg(feature = "basepoint-tables")] +use crate::edwards::EdwardsBasepointTable; + cfg_if! { if #[cfg(curve25519_dalek_backend = "fiat")] { #[cfg(curve25519_dalek_bits = "32")] @@ -91,8 +94,11 @@ pub const BASEPOINT_ORDER: Scalar = Scalar { ], }; +#[cfg(feature = "basepoint-tables")] use crate::ristretto::RistrettoBasepointTable; + /// The Ristretto basepoint, as a `RistrettoBasepointTable` for scalar multiplication. +#[cfg(feature = "basepoint-tables")] pub static RISTRETTO_BASEPOINT_TABLE: &'static RistrettoBasepointTable = unsafe { // SAFETY: `RistrettoBasepointTable` is a `#[repr(transparent)]` newtype of // `EdwardsBasepointTable` diff --git a/src/edwards.rs b/src/edwards.rs index 664dfe7..b80274e 100644 --- a/src/edwards.rs +++ b/src/edwards.rs @@ -101,6 +101,8 @@ use core::ops::{Add, Neg, Sub}; use core::ops::{AddAssign, SubAssign}; use core::ops::{Mul, MulAssign}; +use cfg_if::cfg_if; + #[cfg(feature = "digest")] use digest::{generic_array::typenum::U64, Digest}; @@ -124,13 +126,15 @@ use crate::backend::serial::curve_models::CompletedPoint; use crate::backend::serial::curve_models::ProjectiveNielsPoint; use crate::backend::serial::curve_models::ProjectivePoint; -use crate::window::LookupTableRadix128; -use crate::window::LookupTableRadix16; -use crate::window::LookupTableRadix256; -use crate::window::LookupTableRadix32; -use crate::window::LookupTableRadix64; +#[cfg(feature = "basepoint-tables")] +use crate::window::{ + LookupTableRadix128, LookupTableRadix16, LookupTableRadix256, LookupTableRadix32, + LookupTableRadix64, +}; +#[cfg(feature = "basepoint-tables")] use crate::traits::BasepointTable; + use crate::traits::ValidityCheck; use crate::traits::{Identity, IsIdentity}; @@ -702,6 +706,24 @@ impl<'a, 'b> Mul<&'b EdwardsPoint> for &'a Scalar { } } +impl EdwardsPoint { + /// Fixed-base scalar multiplication by the Ed25519 base point. + /// + /// Uses precomputed basepoint tables when the `basepoint-tables` feature + /// is enabled, trading off increased code size for ~4x better performance. + pub fn mul_base(scalar: &Scalar) -> Self { + #[cfg(not(feature = "basepoint-tables"))] + { + scalar * constants::ED25519_BASEPOINT_POINT + } + + #[cfg(feature = "basepoint-tables")] + { + scalar * constants::ED25519_BASEPOINT_TABLE + } + } +} + // ------------------------------------------------------------------------ // Multiscalar Multiplication impls // ------------------------------------------------------------------------ @@ -824,6 +846,7 @@ impl EdwardsPoint { } } +#[cfg(feature = "basepoint-tables")] macro_rules! impl_basepoint_table { (Name = $name:ident, LookupTable = $table:ident, Point = $point:ty, Radix = $radix:expr, Additions = $adds:expr) => { /// A precomputed table of multiples of a basepoint, for accelerating @@ -837,7 +860,7 @@ macro_rules! impl_basepoint_table { /// /// * [`EdwardsBasepointTableRadix16`]: 30KB, 64A /// (this is the default size, and is used for - /// [`ED25519_BASEPOINT_TABLE`]) + /// [`constants::ED25519_BASEPOINT_TABLE`]) /// * [`EdwardsBasepointTableRadix64`]: 120KB, 43A /// * [`EdwardsBasepointTableRadix128`]: 240KB, 37A /// * [`EdwardsBasepointTableRadix256`]: 480KB, 33A @@ -896,10 +919,14 @@ macro_rules! impl_basepoint_table { /// $$ /// with /// $$ - /// \frac{-w}{2} \leq a_i < \frac{w}{2}, \cdots, \frac{-w}{2} \leq a\_{x} \leq \frac{w}{2} + /// \begin{aligned} + /// \frac{-w}{2} \leq a_i < \frac{w}{2} + /// &&\cdots&& + /// \frac{-w}{2} \leq a\_{x} \leq \frac{w}{2} + /// \end{aligned} /// $$ - /// and the number of additions, \\(x\\), is given by \\(x = \lceil \frac{256}{w} \rceil\\). - /// Then + /// and the number of additions, \\(x\\), is given by + /// \\(x = \lceil \frac{256}{w} \rceil\\). Then /// $$ /// a B = a\_0 B + a\_1 w\^1 B + \cdots + a\_{x-1} w\^{x-1} B. /// $$ @@ -914,7 +941,7 @@ macro_rules! impl_basepoint_table { /// $$ /// For each \\(i = 0 \ldots 31\\), we create a lookup table of /// $$ - /// [w\^{2i} B, \ldots, \frac{w}{2}\cdotw\^{2i} B], + /// [w\^{2i} B, \ldots, \frac{w}{2}\cdot w\^{2i} B], /// $$ /// and use it to select \\( y \cdot w\^{2i} \cdot B \\) in constant time. /// @@ -922,7 +949,7 @@ macro_rules! impl_basepoint_table { /// by \\(2\^{255}\\), which is always the case. /// /// The above algorithm is trivially generalised to other powers-of-2 radices. - fn basepoint_mul(&self, scalar: &Scalar) -> $point { + fn mul_base(&self, scalar: &Scalar) -> $point { let a = scalar.as_radix_2w($radix); let tables = &self.0; @@ -949,7 +976,7 @@ macro_rules! impl_basepoint_table { /// computing the multiple \\(aB\\) of this basepoint \\(B\\). fn mul(self, scalar: &'b Scalar) -> $point { // delegate to a private function so that its documentation appears in internal docs - self.basepoint_mul(scalar) + self.mul_base(scalar) } } @@ -976,20 +1003,55 @@ macro_rules! impl_basepoint_table { } // End macro_rules! impl_basepoint_table // The number of additions required is ceil(256/w) where w is the radix representation. -impl_basepoint_table! {Name = EdwardsBasepointTable, LookupTable = LookupTableRadix16, Point = EdwardsPoint, Radix = 4, Additions = 64} -impl_basepoint_table! {Name = EdwardsBasepointTableRadix32, LookupTable = LookupTableRadix32, Point = EdwardsPoint, Radix = 5, Additions = 52} -impl_basepoint_table! {Name = EdwardsBasepointTableRadix64, LookupTable = LookupTableRadix64, Point = EdwardsPoint, Radix = 6, Additions = 43} -impl_basepoint_table! {Name = EdwardsBasepointTableRadix128, LookupTable = LookupTableRadix128, Point = EdwardsPoint, Radix = 7, Additions = 37} -impl_basepoint_table! {Name = EdwardsBasepointTableRadix256, LookupTable = LookupTableRadix256, Point = EdwardsPoint, Radix = 8, Additions = 33} +cfg_if! { + if #[cfg(feature = "basepoint-tables")] { + impl_basepoint_table! { + Name = EdwardsBasepointTable, + LookupTable = LookupTableRadix16, + Point = EdwardsPoint, + Radix = 4, + Additions = 64 + } + impl_basepoint_table! { + Name = EdwardsBasepointTableRadix32, + LookupTable = LookupTableRadix32, + Point = EdwardsPoint, + Radix = 5, + Additions = 52 + } + impl_basepoint_table! { + Name = EdwardsBasepointTableRadix64, + LookupTable = LookupTableRadix64, + Point = EdwardsPoint, + Radix = 6, + Additions = 43 + } + impl_basepoint_table! { + Name = EdwardsBasepointTableRadix128, + LookupTable = LookupTableRadix128, + Point = EdwardsPoint, + Radix = 7, + Additions = 37 + } + impl_basepoint_table! { + Name = EdwardsBasepointTableRadix256, + LookupTable = LookupTableRadix256, + Point = EdwardsPoint, + Radix = 8, + Additions = 33 + } -/// A type-alias for [`EdwardsBasepointTable`] because the latter is -/// used as a constructor in the [`constants`] module. -// -// Same as for `LookupTableRadix16`, we have to define `EdwardsBasepointTable` -// first, because it's used as a constructor, and then provide a type alias for -// it. -pub type EdwardsBasepointTableRadix16 = EdwardsBasepointTable; + /// A type-alias for [`EdwardsBasepointTable`] because the latter is + /// used as a constructor in the [`constants`] module. + // + // Same as for `LookupTableRadix16`, we have to define `EdwardsBasepointTable` + // first, because it's used as a constructor, and then provide a type alias for + // it. + pub type EdwardsBasepointTableRadix16 = EdwardsBasepointTable; + } +} +#[cfg(feature = "basepoint-tables")] macro_rules! impl_basepoint_table_conversions { (LHS = $lhs:ty, RHS = $rhs:ty) => { impl<'a> From<&'a $lhs> for $rhs { @@ -1006,19 +1068,57 @@ macro_rules! impl_basepoint_table_conversions { }; } -impl_basepoint_table_conversions! {LHS = EdwardsBasepointTableRadix16, RHS = EdwardsBasepointTableRadix32} -impl_basepoint_table_conversions! {LHS = EdwardsBasepointTableRadix16, RHS = EdwardsBasepointTableRadix64} -impl_basepoint_table_conversions! {LHS = EdwardsBasepointTableRadix16, RHS = EdwardsBasepointTableRadix128} -impl_basepoint_table_conversions! {LHS = EdwardsBasepointTableRadix16, RHS = EdwardsBasepointTableRadix256} +cfg_if! { + if #[cfg(feature = "basepoint-tables")] { + // Conversions from radix 16 + impl_basepoint_table_conversions! { + LHS = EdwardsBasepointTableRadix16, + RHS = EdwardsBasepointTableRadix32 + } + impl_basepoint_table_conversions! { + LHS = EdwardsBasepointTableRadix16, + RHS = EdwardsBasepointTableRadix64 + } + impl_basepoint_table_conversions! { + LHS = EdwardsBasepointTableRadix16, + RHS = EdwardsBasepointTableRadix128 + } + impl_basepoint_table_conversions! { + LHS = EdwardsBasepointTableRadix16, + RHS = EdwardsBasepointTableRadix256 + } -impl_basepoint_table_conversions! {LHS = EdwardsBasepointTableRadix32, RHS = EdwardsBasepointTableRadix64} -impl_basepoint_table_conversions! {LHS = EdwardsBasepointTableRadix32, RHS = EdwardsBasepointTableRadix128} -impl_basepoint_table_conversions! {LHS = EdwardsBasepointTableRadix32, RHS = EdwardsBasepointTableRadix256} + // Conversions from radix 32 + impl_basepoint_table_conversions! { + LHS = EdwardsBasepointTableRadix32, + RHS = EdwardsBasepointTableRadix64 + } + impl_basepoint_table_conversions! { + LHS = EdwardsBasepointTableRadix32, + RHS = EdwardsBasepointTableRadix128 + } + impl_basepoint_table_conversions! { + LHS = EdwardsBasepointTableRadix32, + RHS = EdwardsBasepointTableRadix256 + } -impl_basepoint_table_conversions! {LHS = EdwardsBasepointTableRadix64, RHS = EdwardsBasepointTableRadix128} -impl_basepoint_table_conversions! {LHS = EdwardsBasepointTableRadix64, RHS = EdwardsBasepointTableRadix256} + // Conversions from radix 64 + impl_basepoint_table_conversions! { + LHS = EdwardsBasepointTableRadix64, + RHS = EdwardsBasepointTableRadix128 + } + impl_basepoint_table_conversions! { + LHS = EdwardsBasepointTableRadix64, + RHS = EdwardsBasepointTableRadix256 + } -impl_basepoint_table_conversions! {LHS = EdwardsBasepointTableRadix128, RHS = EdwardsBasepointTableRadix256} + // Conversions from radix 128 + impl_basepoint_table_conversions! { + LHS = EdwardsBasepointTableRadix128, + RHS = EdwardsBasepointTableRadix256 + } + } +} impl EdwardsPoint { /// Multiply by the cofactor: return \\(\[8\]P\\). @@ -1118,7 +1218,6 @@ impl Debug for EdwardsPoint { #[cfg(test)] mod test { use super::*; - use crate::constants::ED25519_BASEPOINT_TABLE; use crate::field::FieldElement; use crate::scalar::Scalar; use subtle::ConditionallySelectable; @@ -1126,6 +1225,9 @@ mod test { #[cfg(feature = "alloc")] use alloc::vec::Vec; + #[cfg(feature = "basepoint-tables")] + use crate::constants::ED25519_BASEPOINT_TABLE; + /// X coordinate of the basepoint. /// = 15112221349535400772501151409588531511454012693041857206046113283949847762202 static BASE_X_COORD_BYTES: [u8; 32] = [ @@ -1212,6 +1314,7 @@ mod test { } /// Test that computing 1*basepoint gives the correct basepoint. + #[cfg(feature = "basepoint-tables")] #[test] fn basepoint_mult_one_vs_basepoint() { let bp = ED25519_BASEPOINT_TABLE * &Scalar::ONE; @@ -1220,6 +1323,7 @@ mod test { } /// Test that `EdwardsBasepointTable::basepoint()` gives the correct basepoint. + #[cfg(feature = "basepoint-tables")] #[test] fn basepoint_table_basepoint_function_correct() { let bp = ED25519_BASEPOINT_TABLE.basepoint(); @@ -1271,6 +1375,7 @@ mod test { } /// Sanity check for conversion to precomputed points + #[cfg(feature = "basepoint-tables")] #[test] fn to_affine_niels_clears_denominators() { // construct a point as aB so it has denominators (ie. Z != 1) @@ -1280,22 +1385,22 @@ mod test { assert_eq!(aB.compress(), also_aB.compress()); } - /// Test basepoint_mult versus a known scalar multiple from ed25519.py + /// Test mul_base versus a known scalar multiple from ed25519.py #[test] fn basepoint_mult_vs_ed25519py() { - let aB = ED25519_BASEPOINT_TABLE * &A_SCALAR; + let aB = EdwardsPoint::mul_base(&A_SCALAR); assert_eq!(aB.compress(), A_TIMES_BASEPOINT); } /// Test that multiplication by the basepoint order kills the basepoint #[test] fn basepoint_mult_by_basepoint_order() { - let B = ED25519_BASEPOINT_TABLE; - let should_be_id = B * &constants::BASEPOINT_ORDER; + let should_be_id = EdwardsPoint::mul_base(&constants::BASEPOINT_ORDER); assert!(should_be_id.is_identity()); } /// Test precomputed basepoint mult + #[cfg(feature = "basepoint-tables")] #[test] fn test_precomputed_basepoint_mult() { let aB_1 = ED25519_BASEPOINT_TABLE * &A_SCALAR; @@ -1323,11 +1428,12 @@ mod test { #[test] fn basepoint_mult_two_vs_basepoint2() { let two = Scalar::from(2u64); - let bp2 = ED25519_BASEPOINT_TABLE * &two; + let bp2 = EdwardsPoint::mul_base(&two); assert_eq!(bp2.compress(), BASE2_CMPRSSD); } /// Test that all the basepoint table types compute the same results. + #[cfg(feature = "basepoint-tables")] #[test] fn basepoint_tables() { let P = &constants::ED25519_BASEPOINT_POINT; @@ -1353,7 +1459,8 @@ mod test { assert_eq!(aP128, aP256); } - // Check a unreduced scalar multiplication by the basepoint tables. + /// Check a unreduced scalar multiplication by the basepoint tables. + #[cfg(feature = "basepoint-tables")] #[test] fn basepoint_tables_unreduced_scalar() { let P = &constants::ED25519_BASEPOINT_POINT; @@ -1517,17 +1624,14 @@ mod test { let check = xs.iter().map(|xi| xi * xi).sum::(); // Construct points G_i = x_i * B - let Gs = xs - .iter() - .map(|xi| xi * ED25519_BASEPOINT_TABLE) - .collect::>(); + let Gs = xs.iter().map(EdwardsPoint::mul_base).collect::>(); // Compute H1 = (consttime) let H1 = EdwardsPoint::multiscalar_mul(&xs, &Gs); // Compute H2 = (vartime) let H2 = EdwardsPoint::vartime_multiscalar_mul(&xs, &Gs); // Compute H3 = = sum(xi^2) * B - let H3 = &check * ED25519_BASEPOINT_TABLE; + let H3 = EdwardsPoint::mul_base(&check); assert_eq!(H1, H3); assert_eq!(H2, H3); @@ -1577,8 +1681,6 @@ mod test { fn vartime_precomputed_vs_nonprecomputed_multiscalar() { let mut rng = rand::thread_rng(); - let B = ED25519_BASEPOINT_TABLE; - let static_scalars = (0..128) .map(|_| Scalar::random(&mut rng)) .collect::>(); @@ -1593,8 +1695,14 @@ mod test { .map(|s| s * s) .sum(); - let static_points = static_scalars.iter().map(|s| s * B).collect::>(); - let dynamic_points = dynamic_scalars.iter().map(|s| s * B).collect::>(); + let static_points = static_scalars + .iter() + .map(EdwardsPoint::mul_base) + .collect::>(); + let dynamic_points = dynamic_scalars + .iter() + .map(EdwardsPoint::mul_base) + .collect::>(); let precomputation = VartimeEdwardsPrecomputation::new(static_points.iter()); @@ -1610,7 +1718,7 @@ mod test { static_points.iter().chain(dynamic_points.iter()), ); - let R = &check_scalar * B; + let R = EdwardsPoint::mul_base(&check_scalar); assert_eq!(P.compress(), R.compress()); assert_eq!(Q.compress(), R.compress()); diff --git a/src/montgomery.rs b/src/montgomery.rs index a1143d0..9aab60e 100644 --- a/src/montgomery.rs +++ b/src/montgomery.rs @@ -476,7 +476,7 @@ mod test { let mut csprng: OsRng = OsRng; let s: Scalar = Scalar::random(&mut csprng); - let p_edwards: EdwardsPoint = constants::ED25519_BASEPOINT_TABLE * &s; + let p_edwards = EdwardsPoint::mul_base(&s); let p_montgomery: MontgomeryPoint = p_edwards.to_montgomery(); let expected = s * p_edwards; diff --git a/src/ristretto.rs b/src/ristretto.rs index 3df1766..6804697 100644 --- a/src/ristretto.rs +++ b/src/ristretto.rs @@ -190,11 +190,13 @@ use subtle::ConstantTimeEq; #[cfg(feature = "zeroize")] use zeroize::Zeroize; +#[cfg(feature = "basepoint-tables")] use crate::edwards::EdwardsBasepointTable; use crate::edwards::EdwardsPoint; use crate::scalar::Scalar; +#[cfg(feature = "basepoint-tables")] use crate::traits::BasepointTable; use crate::traits::Identity; #[cfg(feature = "alloc")] @@ -924,6 +926,24 @@ impl<'a, 'b> Mul<&'b RistrettoPoint> for &'a Scalar { } } +impl RistrettoPoint { + /// Fixed-base scalar multiplication by the Ristretto base point. + /// + /// Uses precomputed basepoint tables when the `basepoint-tables` feature + /// is enabled, trading off increased code size for ~4x better performance. + pub fn mul_base(scalar: &Scalar) -> Self { + #[cfg(not(feature = "basepoint-tables"))] + { + scalar * constants::RISTRETTO_BASEPOINT_POINT + } + + #[cfg(feature = "basepoint-tables")] + { + scalar * constants::RISTRETTO_BASEPOINT_TABLE + } + } +} + define_mul_assign_variants!(LHS = RistrettoPoint, RHS = Scalar); define_mul_variants!(LHS = RistrettoPoint, RHS = Scalar, Output = RistrettoPoint); @@ -1040,10 +1060,12 @@ impl RistrettoPoint { /// let a = Scalar::from(87329482u64); /// let P = &a * RISTRETTO_BASEPOINT_TABLE; /// ``` +#[cfg(feature = "basepoint-tables")] #[derive(Clone)] #[repr(transparent)] pub struct RistrettoBasepointTable(pub(crate) EdwardsBasepointTable); +#[cfg(feature = "basepoint-tables")] impl<'a, 'b> Mul<&'b Scalar> for &'a RistrettoBasepointTable { type Output = RistrettoPoint; @@ -1052,6 +1074,7 @@ impl<'a, 'b> Mul<&'b Scalar> for &'a RistrettoBasepointTable { } } +#[cfg(feature = "basepoint-tables")] impl<'a, 'b> Mul<&'a RistrettoBasepointTable> for &'b Scalar { type Output = RistrettoPoint; @@ -1060,6 +1083,7 @@ impl<'a, 'b> Mul<&'a RistrettoBasepointTable> for &'b Scalar { } } +#[cfg(feature = "basepoint-tables")] impl RistrettoBasepointTable { /// Create a precomputed table of multiples of the given `basepoint`. pub fn create(basepoint: &RistrettoPoint) -> RistrettoBasepointTable { @@ -1155,14 +1179,13 @@ impl Zeroize for RistrettoPoint { #[cfg(test)] mod test { - use rand_core::OsRng; - use super::*; - use crate::constants::RISTRETTO_BASEPOINT_TABLE; use crate::edwards::CompressedEdwardsY; use crate::scalar::Scalar; use crate::traits::Identity; + use rand_core::OsRng; + #[test] #[cfg(feature = "serde")] fn serde_bincode_basepoint_roundtrip() { @@ -1355,8 +1378,7 @@ mod test { #[test] fn four_torsion_random() { let mut rng = OsRng; - let B = RISTRETTO_BASEPOINT_TABLE; - let P = B * &Scalar::random(&mut rng); + let P = RistrettoPoint::mul_base(&Scalar::random(&mut rng)); let P_coset = P.coset4(); for point in P_coset { assert_eq!(P, RistrettoPoint(point)); @@ -1681,9 +1703,8 @@ mod test { #[test] fn random_roundtrip() { let mut rng = OsRng; - let B = RISTRETTO_BASEPOINT_TABLE; for _ in 0..100 { - let P = B * &Scalar::random(&mut rng); + let P = RistrettoPoint::mul_base(&Scalar::random(&mut rng)); let compressed_P = P.compress(); let Q = compressed_P.decompress().unwrap(); assert_eq!(P, Q); @@ -1691,7 +1712,7 @@ mod test { } #[test] - #[cfg(feature = "alloc")] + #[cfg(all(feature = "alloc", feature = "rand_core"))] fn double_and_compress_1024_random_points() { let mut rng = OsRng; @@ -1712,8 +1733,6 @@ mod test { fn vartime_precomputed_vs_nonprecomputed_multiscalar() { let mut rng = rand::thread_rng(); - let B = RISTRETTO_BASEPOINT_TABLE; - let static_scalars = (0..128) .map(|_| Scalar::random(&mut rng)) .collect::>(); @@ -1728,8 +1747,14 @@ mod test { .map(|s| s * s) .sum(); - let static_points = static_scalars.iter().map(|s| s * B).collect::>(); - let dynamic_points = dynamic_scalars.iter().map(|s| s * B).collect::>(); + let static_points = static_scalars + .iter() + .map(RistrettoPoint::mul_base) + .collect::>(); + let dynamic_points = dynamic_scalars + .iter() + .map(RistrettoPoint::mul_base) + .collect::>(); let precomputation = VartimeRistrettoPrecomputation::new(static_points.iter()); @@ -1745,7 +1770,7 @@ mod test { static_points.iter().chain(dynamic_points.iter()), ); - let R = &check_scalar * B; + let R = RistrettoPoint::mul_base(&check_scalar); assert_eq!(P.compress(), R.compress()); assert_eq!(Q.compress(), R.compress()); diff --git a/src/scalar.rs b/src/scalar.rs index 0f27b0c..0469f2e 100644 --- a/src/scalar.rs +++ b/src/scalar.rs @@ -1015,7 +1015,7 @@ impl Scalar { /// Returns a size hint indicating how many entries of the return /// value of `to_radix_2w` are nonzero. - #[cfg(any(feature = "alloc", test))] + #[cfg(any(feature = "alloc", all(test, feature = "basepoint-tables")))] pub(crate) fn to_radix_2w_size_hint(w: usize) -> usize { debug_assert!(w >= 4); debug_assert!(w <= 8); @@ -1051,6 +1051,7 @@ impl Scalar { /// $$ /// with \\(-2\^w/2 \leq a_i < 2\^w/2\\) for \\(0 \leq i < (n-1)\\) and \\(-2\^w/2 \leq a_{n-1} \leq 2\^w/2\\). /// + #[cfg(any(feature = "alloc", feature = "basepoint-tables"))] pub(crate) fn as_radix_2w(&self, w: usize) -> [i8; 64] { debug_assert!(w >= 4); debug_assert!(w <= 8); @@ -1764,6 +1765,7 @@ mod test { } } + #[cfg(feature = "basepoint-tables")] fn test_pippenger_radix_iter(scalar: Scalar, w: usize) { let digits_count = Scalar::to_radix_2w_size_hint(w); let digits = scalar.as_radix_2w(w); @@ -1788,6 +1790,7 @@ mod test { } #[test] + #[cfg(feature = "basepoint-tables")] fn test_pippenger_radix() { use core::iter; // For each valid radix it tests that 1000 random-ish scalars can be restored diff --git a/src/traits.rs b/src/traits.rs index ce770ea..31ba9be 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -60,7 +60,7 @@ pub trait BasepointTable { fn basepoint(&self) -> Self::Point; /// Multiply a `scalar` by this precomputed basepoint table, in constant time. - fn basepoint_mul(&self, scalar: &Scalar) -> Self::Point; + fn mul_base(&self, scalar: &Scalar) -> Self::Point; } /// A trait for constant-time multiscalar multiplication without precomputation. diff --git a/src/window.rs b/src/window.rs index 65b3e3f..9315116 100644 --- a/src/window.rs +++ b/src/window.rs @@ -15,6 +15,8 @@ use core::fmt::Debug; +use cfg_if::cfg_if; + use subtle::Choice; use subtle::ConditionallyNegatable; use subtle::ConditionallySelectable; @@ -126,14 +128,55 @@ macro_rules! impl_lookup_table { } // End macro_rules! impl_lookup_table // The first one has to be named "LookupTable" because it's used as a constructor for consts. -impl_lookup_table! {Name = LookupTable, Size = 8, SizeNeg = -8, SizeRange = 1 .. 9, ConversionRange = 0 .. 7} // radix-16 -impl_lookup_table! {Name = LookupTableRadix32, Size = 16, SizeNeg = -16, SizeRange = 1 .. 17, ConversionRange = 0 .. 15} // radix-32 -impl_lookup_table! {Name = LookupTableRadix64, Size = 32, SizeNeg = -32, SizeRange = 1 .. 33, ConversionRange = 0 .. 31} // radix-64 -impl_lookup_table! {Name = LookupTableRadix128, Size = 64, SizeNeg = -64, SizeRange = 1 .. 65, ConversionRange = 0 .. 63} // radix-128 -impl_lookup_table! {Name = LookupTableRadix256, Size = 128, SizeNeg = -128, SizeRange = 1 .. 129, ConversionRange = 0 .. 127} // radix-256 +// This is radix-16 +impl_lookup_table! { + Name = LookupTable, + Size = 8, + SizeNeg = -8, + SizeRange = 1..9, + ConversionRange = 0..7 +} -// For homogeneity we then alias it to "LookupTableRadix16". -pub type LookupTableRadix16 = LookupTable; +// The rest only get used to make basepoint tables +cfg_if! { + if #[cfg(feature = "basepoint-tables")] { + // radix-32 + impl_lookup_table! { + Name = LookupTableRadix32, + Size = 16, + SizeNeg = -16, + SizeRange = 1..17, + ConversionRange = 0..15 + } + // radix-64 + impl_lookup_table! { + Name = LookupTableRadix64, + Size = 32, + SizeNeg = -32, + SizeRange = 1..33, + ConversionRange = 0..31 + } + // radix-128 + impl_lookup_table! { + Name = LookupTableRadix128, + Size = 64, + SizeNeg = -64, + SizeRange = 1..65, + ConversionRange = 0..63 + } + // radix-256 + impl_lookup_table! { + Name = LookupTableRadix256, + Size = 128, + SizeNeg = -128, + SizeRange = 1..129, + ConversionRange = 0..127 + } + + // For homogeneity we then alias it to "LookupTableRadix16". + pub(crate) type LookupTableRadix16 = LookupTable; + } +} /// Holds odd multiples 1A, 3A, ..., 15A of a point A. #[derive(Copy, Clone)]