diff --git a/benches/dalek_benchmarks.rs b/benches/dalek_benchmarks.rs index 71792a3..bb47634 100644 --- a/benches/dalek_benchmarks.rs +++ b/benches/dalek_benchmarks.rs @@ -2,10 +2,12 @@ extern crate rand; use rand::rngs::OsRng; +use rand::thread_rng; #[macro_use] extern crate criterion; +use criterion::BatchSize; use criterion::Criterion; extern crate curve25519_dalek; @@ -20,14 +22,10 @@ mod edwards_benches { use super::*; use curve25519_dalek::edwards; use curve25519_dalek::edwards::EdwardsPoint; - use curve25519_dalek::traits::MultiscalarMul; - use curve25519_dalek::traits::VartimeMultiscalarMul; fn compress(c: &mut Criterion) { let B = &constants::ED25519_BASEPOINT_POINT; - c.bench_function("EdwardsPoint compression", move |b| { - b.iter(|| B.compress()) - }); + c.bench_function("EdwardsPoint compression", move |b| b.iter(|| B.compress())); } fn decompress(c: &mut Criterion) { @@ -55,25 +53,65 @@ mod edwards_benches { fn vartime_double_base_scalar_mul(c: &mut Criterion) { c.bench_function("Variable-time aA+bB, A variable, B fixed", |bench| { - let B = &constants::ED25519_BASEPOINT_POINT; - let a = Scalar::from(298374928u64).invert(); - let b = Scalar::from(897987897u64).invert(); - let A = B * (b * a); - bench.iter(|| EdwardsPoint::vartime_double_scalar_mul_basepoint(&a, &A, &b)); + let mut rng = thread_rng(); + let A = &Scalar::random(&mut rng) * &constants::ED25519_BASEPOINT_TABLE; + bench.iter_batched( + || (Scalar::random(&mut rng), Scalar::random(&mut rng)), + |(a, b)| EdwardsPoint::vartime_double_scalar_mul_basepoint(&a, &A, &b), + BatchSize::SmallInput, + ); }); } + criterion_group! { + name = edwards_benches; + config = Criterion::default(); + targets = + compress, + decompress, + consttime_fixed_base_scalar_mul, + consttime_variable_base_scalar_mul, + vartime_double_base_scalar_mul, + } +} + +mod multiscalar_benches { + use super::*; + use curve25519_dalek::edwards; + use curve25519_dalek::edwards::EdwardsPoint; + use curve25519_dalek::edwards::VartimeEdwardsPrecomputation; + use curve25519_dalek::traits::MultiscalarMul; + use curve25519_dalek::traits::VartimeMultiscalarMul; + use curve25519_dalek::traits::VartimePrecomputedMultiscalarMul; + + fn construct_scalars(n: usize) -> Vec { + let mut rng = thread_rng(); + (0..n).map(|_| Scalar::random(&mut rng)).collect() + } + + fn construct_points(n: usize) -> Vec { + let mut rng = thread_rng(); + (0..n) + .map(|_| &Scalar::random(&mut rng) * &constants::ED25519_BASEPOINT_TABLE) + .collect() + } + + fn construct(n: usize) -> (Vec, Vec) { + (construct_scalars(n), construct_points(n)) + } + fn consttime_multiscalar_mul(c: &mut Criterion) { c.bench_function_over_inputs( "Constant-time variable-base multiscalar multiplication", |b, &&size| { - let mut rng = OsRng::new().unwrap(); - let scalars: Vec = (0..size).map(|_| Scalar::random(&mut rng)).collect(); - let points: Vec = scalars - .iter() - .map(|s| s * &constants::ED25519_BASEPOINT_TABLE) - .collect(); - b.iter(|| EdwardsPoint::multiscalar_mul(&scalars, &points)); + let points = construct_points(size); + // This is supposed to be constant-time, but we might as well + // rerandomize the scalars for every call just in case. + b.iter_batched( + || construct_scalars(size), + |scalars| EdwardsPoint::multiscalar_mul(&scalars, &points), + BatchSize::SmallInput, + ); }, &MULTISCALAR_SIZES, ); @@ -83,29 +121,107 @@ mod edwards_benches { c.bench_function_over_inputs( "Variable-time variable-base multiscalar multiplication", |b, &&size| { - let mut rng = OsRng::new().unwrap(); - let scalars: Vec = (0..size).map(|_| Scalar::random(&mut rng)).collect(); - let points: Vec = scalars - .iter() - .map(|s| s * &constants::ED25519_BASEPOINT_TABLE) - .collect(); - b.iter(|| EdwardsPoint::vartime_multiscalar_mul(&scalars, &points)); + let points = construct_points(size); + // Rerandomize the scalars for every call to prevent + // false timings from better caching (e.g., the CPU + // cache lifts exactly the right table entries for the + // benchmark into the highest cache levels). + b.iter_batched( + || construct_scalars(size), + |scalars| EdwardsPoint::vartime_multiscalar_mul(&scalars, &points), + BatchSize::SmallInput, + ); }, &MULTISCALAR_SIZES, ); } - criterion_group!{ - name = edwards_benches; - config = Criterion::default(); + fn vartime_precomputed_pure_static(c: &mut Criterion) { + c.bench_function_over_inputs( + "Variable-time fixed-base multiscalar multiplication", + move |b, &&total_size| { + let static_size = total_size; + + let static_points = construct_points(static_size); + let precomp = VartimeEdwardsPrecomputation::new(&static_points); + // Rerandomize the scalars for every call to prevent + // false timings from better caching (e.g., the CPU + // cache lifts exactly the right table entries for the + // benchmark into the highest cache levels). + b.iter_batched( + || construct_scalars(static_size), + |scalars| precomp.vartime_multiscalar_mul(&scalars), + BatchSize::SmallInput, + ); + }, + &MULTISCALAR_SIZES, + ); + } + + fn vartime_precomputed_helper(c: &mut Criterion, dynamic_fraction: f64) { + let label = format!( + "Variable-time mixed-base multiscalar multiplication ({:.0}pct dyn)", + 100.0 * dynamic_fraction, + ); + c.bench_function_over_inputs( + &label, + move |b, &&total_size| { + let dynamic_size = ((total_size as f64) * dynamic_fraction) as usize; + let static_size = total_size - dynamic_size; + + let static_points = construct_points(static_size); + let dynamic_points = construct_points(dynamic_size); + let precomp = VartimeEdwardsPrecomputation::new(&static_points); + // Rerandomize the scalars for every call to prevent + // false timings from better caching (e.g., the CPU + // cache lifts exactly the right table entries for the + // benchmark into the highest cache levels). Timings + // should be independent of points so we don't + // randomize them. + b.iter_batched( + || { + ( + construct_scalars(static_size), + construct_scalars(dynamic_size), + ) + }, + |(static_scalars, dynamic_scalars)| { + precomp.vartime_mixed_multiscalar_mul( + &static_scalars, + &dynamic_scalars, + &dynamic_points, + ) + }, + BatchSize::SmallInput, + ); + }, + &MULTISCALAR_SIZES, + ); + } + + fn vartime_precomputed_00_pct_dynamic(c: &mut Criterion) { + vartime_precomputed_helper(c, 0.0); + } + + fn vartime_precomputed_20_pct_dynamic(c: &mut Criterion) { + vartime_precomputed_helper(c, 0.2); + } + + fn vartime_precomputed_50_pct_dynamic(c: &mut Criterion) { + vartime_precomputed_helper(c, 0.5); + } + + criterion_group! { + name = multiscalar_benches; + // Lower the sample size to run the benchmarks faster + config = Criterion::default().sample_size(15); targets = - compress, - decompress, - consttime_fixed_base_scalar_mul, - consttime_variable_base_scalar_mul, - vartime_double_base_scalar_mul, consttime_multiscalar_mul, vartime_multiscalar_mul, + vartime_precomputed_pure_static, + vartime_precomputed_00_pct_dynamic, + vartime_precomputed_20_pct_dynamic, + vartime_precomputed_50_pct_dynamic, } } @@ -141,7 +257,7 @@ mod ristretto_benches { ); } - criterion_group!{ + criterion_group! { name = ristretto_benches; config = Criterion::default(); targets = @@ -162,7 +278,7 @@ mod montgomery_benches { }); } - criterion_group!{ + criterion_group! { name = montgomery_benches; config = Criterion::default(); targets = montgomery_ladder, @@ -194,7 +310,7 @@ mod scalar_benches { ); } - criterion_group!{ + criterion_group! { name = scalar_benches; config = Criterion::default(); targets = @@ -208,4 +324,5 @@ criterion_main!( montgomery_benches::montgomery_benches, ristretto_benches::ristretto_benches, edwards_benches::edwards_benches, + multiscalar_benches::multiscalar_benches, ); diff --git a/src/backend/serial/scalar_mul/mod.rs b/src/backend/serial/scalar_mul/mod.rs index 1421f41..bec874b 100644 --- a/src/backend/serial/scalar_mul/mod.rs +++ b/src/backend/serial/scalar_mul/mod.rs @@ -21,4 +21,8 @@ pub mod variable_base; #[cfg(feature = "stage2_build")] pub mod vartime_double_base; +#[cfg(feature = "alloc")] pub mod straus; + +#[cfg(feature = "alloc")] +pub mod precomputed_straus; diff --git a/src/backend/serial/scalar_mul/precomputed_straus.rs b/src/backend/serial/scalar_mul/precomputed_straus.rs new file mode 100644 index 0000000..4019b14 --- /dev/null +++ b/src/backend/serial/scalar_mul/precomputed_straus.rs @@ -0,0 +1,114 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-dalek. +// Copyright (c) 2019 Henry de Valence. +// See LICENSE for licensing information. +// +// Authors: +// - Henry de Valence + +//! Precomputation for Straus's method. + +#![allow(non_snake_case)] + +use core::borrow::Borrow; + +use backend::serial::curve_models::{ + AffineNielsPoint, CompletedPoint, ProjectiveNielsPoint, ProjectivePoint, +}; +use edwards::EdwardsPoint; +use scalar::Scalar; +use traits::Identity; +use traits::VartimePrecomputedMultiscalarMul; +use window::{NafLookupTable5, NafLookupTable8}; + +#[allow(unused_imports)] +use prelude::*; + +pub struct VartimePrecomputedStraus { + static_lookup_tables: Vec>, +} + +impl VartimePrecomputedMultiscalarMul for VartimePrecomputedStraus { + type Point = EdwardsPoint; + + fn new(static_points: I) -> Self + where + I: IntoIterator, + I::Item: Borrow, + { + Self { + static_lookup_tables: static_points + .into_iter() + .map(|P| NafLookupTable8::::from(P.borrow())) + .collect(), + } + } + + fn optional_mixed_multiscalar_mul( + &self, + static_scalars: I, + dynamic_scalars: J, + dynamic_points: K, + ) -> Option + where + I: IntoIterator, + I::Item: Borrow, + J: IntoIterator, + J::Item: Borrow, + K: IntoIterator>, + { + let static_nafs = static_scalars + .into_iter() + .map(|c| c.borrow().non_adjacent_form(5)) + .collect::>(); + let dynamic_nafs: Vec<_> = dynamic_scalars + .into_iter() + .map(|c| c.borrow().non_adjacent_form(5)) + .collect::>(); + + let dynamic_lookup_tables = match dynamic_points + .into_iter() + .map(|P_opt| P_opt.map(|P| NafLookupTable5::::from(&P))) + .collect::>>() + { + Some(x) => x, + None => return None, + }; + + let sp = self.static_lookup_tables.len(); + let dp = dynamic_lookup_tables.len(); + assert_eq!(sp, static_nafs.len()); + assert_eq!(dp, dynamic_nafs.len()); + + // We could save some doublings by looking for the highest + // nonzero NAF coefficient, but since we might have a lot of + // them to search, it's not clear it's worthwhile to check. + let mut S = ProjectivePoint::identity(); + for j in (0..255).rev() { + let mut R: CompletedPoint = S.double(); + + for i in 0..dp { + let t_ij = dynamic_nafs[i][j]; + if t_ij > 0 { + R = &R.to_extended() + &dynamic_lookup_tables[i].select(t_ij as usize); + } else if t_ij < 0 { + R = &R.to_extended() - &dynamic_lookup_tables[i].select(-t_ij as usize); + } + } + + for i in 0..sp { + let t_ij = static_nafs[i][j]; + if t_ij > 0 { + R = &R.to_extended() + &self.static_lookup_tables[i].select(t_ij as usize); + } else if t_ij < 0 { + R = &R.to_extended() - &self.static_lookup_tables[i].select(-t_ij as usize); + } + } + + S = R.to_projective(); + } + + Some(S.to_extended()) + } +} diff --git a/src/backend/serial/scalar_mul/straus.rs b/src/backend/serial/scalar_mul/straus.rs index 2b7a306..4053ea3 100644 --- a/src/backend/serial/scalar_mul/straus.rs +++ b/src/backend/serial/scalar_mul/straus.rs @@ -12,16 +12,11 @@ #![allow(non_snake_case)] -#[cfg(any(feature = "alloc", feature = "std"))] use core::borrow::Borrow; -#[cfg(any(feature = "alloc", feature = "std"))] use edwards::EdwardsPoint; -#[cfg(any(feature = "alloc", feature = "std"))] use scalar::Scalar; -#[cfg(any(feature = "alloc", feature = "std"))] use traits::MultiscalarMul; -#[cfg(any(feature = "alloc", feature = "std"))] use traits::VartimeMultiscalarMul; #[allow(unused_imports)] @@ -48,10 +43,8 @@ use prelude::*; /// /// [solution]: https://www.jstor.org/stable/2310929 /// [problem]: https://www.jstor.org/stable/2312273 -#[cfg(any(feature = "alloc", feature = "std"))] pub struct Straus {} -#[cfg(feature = "alloc")] impl MultiscalarMul for Straus { type Point = EdwardsPoint; @@ -148,7 +141,6 @@ impl MultiscalarMul for Straus { } } -#[cfg(feature = "alloc")] impl VartimeMultiscalarMul for Straus { type Point = EdwardsPoint; diff --git a/src/backend/vector/avx2/constants.rs b/src/backend/vector/avx2/constants.rs index 19204e4..614286c 100644 --- a/src/backend/vector/avx2/constants.rs +++ b/src/backend/vector/avx2/constants.rs @@ -35,7 +35,7 @@ pub(crate) static CACHEDPOINT_IDENTITY: CachedPoint = CachedPoint(FieldElement26 ])); /// The low limbs of (2p, 2p, 2p, 2p), so that -/// ```no_run +/// ```ascii,no_run /// (2p, 2p, 2p, 2p) = [P_TIMES_2_LO, P_TIMES_2_HI, P_TIMES_2_HI, P_TIMES_2_HI, P_TIMES_2_HI] /// ``` pub(crate) static P_TIMES_2_LO: u32x8 = u32x8::new( @@ -50,7 +50,7 @@ pub(crate) static P_TIMES_2_LO: u32x8 = u32x8::new( ); /// The high limbs of (2p, 2p, 2p, 2p), so that -/// ```no_run +/// ```ascii,no_run /// (2p, 2p, 2p, 2p) = [P_TIMES_2_LO, P_TIMES_2_HI, P_TIMES_2_HI, P_TIMES_2_HI, P_TIMES_2_HI] /// ``` pub(crate) static P_TIMES_2_HI: u32x8 = u32x8::new( @@ -65,7 +65,7 @@ pub(crate) static P_TIMES_2_HI: u32x8 = u32x8::new( ); /// The low limbs of (16p, 16p, 16p, 16p), so that -/// ```no_run +/// ```ascii,no_run /// (16p, 16p, 16p, 16p) = [P_TIMES_16_LO, P_TIMES_16_HI, P_TIMES_16_HI, P_TIMES_16_HI, P_TIMES_16_HI] /// ``` pub(crate) static P_TIMES_16_LO: u32x8 = u32x8::new( @@ -80,7 +80,7 @@ pub(crate) static P_TIMES_16_LO: u32x8 = u32x8::new( ); /// The high limbs of (16p, 16p, 16p, 16p), so that -/// ```no_run +/// ```ascii,no_run /// (16p, 16p, 16p, 16p) = [P_TIMES_16_LO, P_TIMES_16_HI, P_TIMES_16_HI, P_TIMES_16_HI, P_TIMES_16_HI] /// ``` pub(crate) static P_TIMES_16_HI: u32x8 = u32x8::new( diff --git a/src/backend/vector/avx2/field.rs b/src/backend/vector/avx2/field.rs index 35cf21c..e7ee916 100644 --- a/src/backend/vector/avx2/field.rs +++ b/src/backend/vector/avx2/field.rs @@ -46,11 +46,11 @@ use backend::vector::avx2::constants::{P_TIMES_16_HI, P_TIMES_16_LO, P_TIMES_2_H use backend::serial::u64::field::FieldElement51; /// Unpack 32-bit lanes into 64-bit lanes: -/// ``` +/// ```ascii,no_run /// (a0, b0, a1, b1, c0, d0, c1, d1) /// ``` /// into -/// ``` +/// ```ascii,no_run /// (a0, 0, b0, 0, c0, 0, d0, 0) /// (a1, 0, b1, 0, c1, 0, d1, 0) /// ``` @@ -69,12 +69,12 @@ fn unpack_pair(src: u32x8) -> (u32x8, u32x8) { } /// Repack 64-bit lanes into 32-bit lanes: -/// ``` +/// ```ascii,no_run /// (a0, 0, b0, 0, c0, 0, d0, 0) /// (a1, 0, b1, 0, c1, 0, d1, 0) /// ``` /// into -/// ``` +/// ```ascii,no_run /// (a0, b0, a1, b1, c0, d0, c1, d1) /// ``` #[inline(always)] diff --git a/src/backend/vector/scalar_mul/mod.rs b/src/backend/vector/scalar_mul/mod.rs index 9293e3e..5c8734d 100644 --- a/src/backend/vector/scalar_mul/mod.rs +++ b/src/backend/vector/scalar_mul/mod.rs @@ -12,4 +12,8 @@ pub mod variable_base; pub mod vartime_double_base; +#[cfg(feature = "alloc")] pub mod straus; + +#[cfg(feature = "alloc")] +pub mod precomputed_straus; diff --git a/src/backend/vector/scalar_mul/precomputed_straus.rs b/src/backend/vector/scalar_mul/precomputed_straus.rs new file mode 100644 index 0000000..49d1be4 --- /dev/null +++ b/src/backend/vector/scalar_mul/precomputed_straus.rs @@ -0,0 +1,111 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-dalek. +// Copyright (c) 2019 Henry de Valence. +// See LICENSE for licensing information. +// +// Authors: +// - Henry de Valence + +//! Precomputation for Straus's method. + +#![allow(non_snake_case)] + +use core::borrow::Borrow; + +use backend::vector::{CachedPoint, ExtendedPoint}; +use edwards::EdwardsPoint; +use scalar::Scalar; +use traits::Identity; +use traits::VartimePrecomputedMultiscalarMul; +use window::{NafLookupTable5, NafLookupTable8}; + +#[allow(unused_imports)] +use prelude::*; + + +pub struct VartimePrecomputedStraus { + static_lookup_tables: Vec>, +} + +impl VartimePrecomputedMultiscalarMul for VartimePrecomputedStraus { + type Point = EdwardsPoint; + + fn new(static_points: I) -> Self + where + I: IntoIterator, + I::Item: Borrow, + { + Self { + static_lookup_tables: static_points + .into_iter() + .map(|P| NafLookupTable8::::from(P.borrow())) + .collect(), + } + } + + fn optional_mixed_multiscalar_mul( + &self, + static_scalars: I, + dynamic_scalars: J, + dynamic_points: K, + ) -> Option + where + I: IntoIterator, + I::Item: Borrow, + J: IntoIterator, + J::Item: Borrow, + K: IntoIterator>, + { + let static_nafs = static_scalars + .into_iter() + .map(|c| c.borrow().non_adjacent_form(5)) + .collect::>(); + let dynamic_nafs: Vec<_> = dynamic_scalars + .into_iter() + .map(|c| c.borrow().non_adjacent_form(5)) + .collect::>(); + + let dynamic_lookup_tables = match dynamic_points + .into_iter() + .map(|P_opt| P_opt.map(|P| NafLookupTable5::::from(&P))) + .collect::>>() + { + Some(x) => x, + None => return None, + }; + + let sp = self.static_lookup_tables.len(); + let dp = dynamic_lookup_tables.len(); + assert_eq!(sp, static_nafs.len()); + assert_eq!(dp, dynamic_nafs.len()); + + // We could save some doublings by looking for the highest + // nonzero NAF coefficient, but since we might have a lot of + // them to search, it's not clear it's worthwhile to check. + let mut R = ExtendedPoint::identity(); + for j in (0..255).rev() { + R = R.double(); + + for i in 0..dp { + let t_ij = dynamic_nafs[i][j]; + if t_ij > 0 { + R = &R + &dynamic_lookup_tables[i].select(t_ij as usize); + } else if t_ij < 0 { + R = &R - &dynamic_lookup_tables[i].select(-t_ij as usize); + } + } + + for i in 0..sp { + let t_ij = static_nafs[i][j]; + if t_ij > 0 { + R = &R + &self.static_lookup_tables[i].select(t_ij as usize); + } else if t_ij < 0 { + R = &R - &self.static_lookup_tables[i].select(-t_ij as usize); + } + } + } + + Some(R.into()) + } +} diff --git a/src/backend/vector/scalar_mul/straus.rs b/src/backend/vector/scalar_mul/straus.rs index 9fea5a8..506693d 100644 --- a/src/backend/vector/scalar_mul/straus.rs +++ b/src/backend/vector/scalar_mul/straus.rs @@ -33,7 +33,6 @@ use prelude::*; /// point representation on the fly. pub struct Straus {} -#[cfg(feature = "alloc")] impl MultiscalarMul for Straus { type Point = EdwardsPoint; @@ -71,7 +70,6 @@ impl MultiscalarMul for Straus { } } -#[cfg(feature = "alloc")] impl VartimeMultiscalarMul for Straus { type Point = EdwardsPoint; diff --git a/src/edwards.rs b/src/edwards.rs index 3b65e25..10b4614 100644 --- a/src/edwards.rs +++ b/src/edwards.rs @@ -112,23 +112,23 @@ use scalar::Scalar; use montgomery::MontgomeryPoint; -use backend::serial::curve_models::ProjectivePoint; -use backend::serial::curve_models::CompletedPoint; use backend::serial::curve_models::AffineNielsPoint; +use backend::serial::curve_models::CompletedPoint; use backend::serial::curve_models::ProjectiveNielsPoint; +use backend::serial::curve_models::ProjectivePoint; use window::LookupTable; #[allow(unused_imports)] use prelude::*; -use traits::{Identity, IsIdentity}; use traits::ValidityCheck; +use traits::{Identity, IsIdentity}; #[cfg(any(feature = "alloc", feature = "std"))] use traits::MultiscalarMul; #[cfg(any(feature = "alloc", feature = "std"))] -use traits::VartimeMultiscalarMul; +use traits::{VartimeMultiscalarMul, VartimePrecomputedMultiscalarMul}; #[cfg(not(all( feature = "simd_backend", @@ -677,6 +677,43 @@ impl VartimeMultiscalarMul for EdwardsPoint { } } +/// Precomputation for variable-time multiscalar multiplication with `EdwardsPoint`s. +// This wraps the inner implementation in a facade type so that we can +// decouple stability of the inner type from the stability of the +// outer type. +#[cfg(feature = "alloc")] +pub struct VartimeEdwardsPrecomputation(scalar_mul::precomputed_straus::VartimePrecomputedStraus); + +#[cfg(feature = "alloc")] +impl VartimePrecomputedMultiscalarMul for VartimeEdwardsPrecomputation { + type Point = EdwardsPoint; + + fn new(static_points: I) -> Self + where + I: IntoIterator, + I::Item: Borrow, + { + Self(scalar_mul::precomputed_straus::VartimePrecomputedStraus::new(static_points)) + } + + fn optional_mixed_multiscalar_mul( + &self, + static_scalars: I, + dynamic_scalars: J, + dynamic_points: K, + ) -> Option + where + I: IntoIterator, + I::Item: Borrow, + J: IntoIterator, + J::Item: Borrow, + K: IntoIterator>, + { + self.0 + .optional_mixed_multiscalar_mul(static_scalars, dynamic_scalars, dynamic_points) + } +} + impl EdwardsPoint { /// Compute \\(aA + bB\\) in variable time, where \\(B\\) is the Ed25519 basepoint. #[cfg(feature = "stage2_build")] @@ -1207,6 +1244,49 @@ mod test { assert!(P1.compress().to_bytes() == P2.compress().to_bytes()); } + #[test] + fn vartime_precomputed_vs_nonprecomputed_multiscalar() { + let mut rng = rand::thread_rng(); + + let B = &::constants::ED25519_BASEPOINT_TABLE; + + let static_scalars = (0..128) + .map(|_| Scalar::random(&mut rng)) + .collect::>(); + + let dynamic_scalars = (0..128) + .map(|_| Scalar::random(&mut rng)) + .collect::>(); + + let check_scalar: Scalar = static_scalars + .iter() + .chain(dynamic_scalars.iter()) + .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 precomputation = VartimeEdwardsPrecomputation::new(static_points.iter()); + + let P = precomputation.vartime_mixed_multiscalar_mul( + &static_scalars, + &dynamic_scalars, + &dynamic_points, + ); + + use traits::VartimeMultiscalarMul; + let Q = EdwardsPoint::vartime_multiscalar_mul( + static_scalars.iter().chain(dynamic_scalars.iter()), + static_points.iter().chain(dynamic_points.iter()), + ); + + let R = &check_scalar * B; + + assert_eq!(P.compress(), R.compress()); + assert_eq!(Q.compress(), R.compress()); + } + mod vartime { use super::super::*; use super::{A_SCALAR, B_SCALAR, A_TIMES_BASEPOINT, DOUBLE_SCALAR_MULT_RESULT}; diff --git a/src/ristretto.rs b/src/ristretto.rs index 2daabaf..3851949 100644 --- a/src/ristretto.rs +++ b/src/ristretto.rs @@ -187,7 +187,18 @@ use scalar::Scalar; use traits::Identity; #[cfg(any(feature = "alloc", feature = "std"))] -use traits::{MultiscalarMul, VartimeMultiscalarMul}; +use traits::{MultiscalarMul, VartimeMultiscalarMul, VartimePrecomputedMultiscalarMul}; + +#[cfg(not(all( + feature = "simd_backend", + any(target_feature = "avx2", target_feature = "avx512ifma") +)))] +use backend::serial::scalar_mul; +#[cfg(all( + feature = "simd_backend", + any(target_feature = "avx2", target_feature = "avx512ifma") +))] +use backend::vector::scalar_mul; // ------------------------------------------------------------------------ // Compressed points @@ -891,8 +902,53 @@ impl VartimeMultiscalarMul for RistrettoPoint { { let extended_points = points.into_iter().map(|opt_P| opt_P.map(|P| P.borrow().0)); - EdwardsPoint::optional_multiscalar_mul(scalars, extended_points) - .map(|P| RistrettoPoint(P)) + EdwardsPoint::optional_multiscalar_mul(scalars, extended_points).map(|P| RistrettoPoint(P)) + } +} + +/// Precomputation for variable-time multiscalar multiplication with `RistrettoPoint`s. +// This wraps the inner implementation in a facade type so that we can +// decouple stability of the inner type from the stability of the +// outer type. +#[cfg(feature = "alloc")] +pub struct VartimeRistrettoPrecomputation(scalar_mul::precomputed_straus::VartimePrecomputedStraus); + +#[cfg(feature = "alloc")] +impl VartimePrecomputedMultiscalarMul for VartimeRistrettoPrecomputation { + type Point = RistrettoPoint; + + fn new(static_points: I) -> Self + where + I: IntoIterator, + I::Item: Borrow, + { + Self( + scalar_mul::precomputed_straus::VartimePrecomputedStraus::new( + static_points.into_iter().map(|P| P.borrow().0), + ), + ) + } + + fn optional_mixed_multiscalar_mul( + &self, + static_scalars: I, + dynamic_scalars: J, + dynamic_points: K, + ) -> Option + where + I: IntoIterator, + I::Item: Borrow, + J: IntoIterator, + J::Item: Borrow, + K: IntoIterator>, + { + self.0 + .optional_mixed_multiscalar_mul( + static_scalars, + dynamic_scalars, + dynamic_points.into_iter().map(|P_opt| P_opt.map(|P| P.0)), + ) + .map(|P_ed| RistrettoPoint(P_ed)) } } @@ -1263,4 +1319,47 @@ mod test { P.compress(); } } + + #[test] + fn vartime_precomputed_vs_nonprecomputed_multiscalar() { + let mut rng = rand::thread_rng(); + + let B = &::constants::RISTRETTO_BASEPOINT_TABLE; + + let static_scalars = (0..128) + .map(|_| Scalar::random(&mut rng)) + .collect::>(); + + let dynamic_scalars = (0..128) + .map(|_| Scalar::random(&mut rng)) + .collect::>(); + + let check_scalar: Scalar = static_scalars + .iter() + .chain(dynamic_scalars.iter()) + .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 precomputation = VartimeRistrettoPrecomputation::new(static_points.iter()); + + let P = precomputation.vartime_mixed_multiscalar_mul( + &static_scalars, + &dynamic_scalars, + &dynamic_points, + ); + + use traits::VartimeMultiscalarMul; + let Q = RistrettoPoint::vartime_multiscalar_mul( + static_scalars.iter().chain(dynamic_scalars.iter()), + static_points.iter().chain(dynamic_points.iter()), + ); + + let R = &check_scalar * B; + + assert_eq!(P.compress(), R.compress()); + assert_eq!(Q.compress(), R.compress()); + } } diff --git a/src/traits.rs b/src/traits.rs index 8db963a..5297aa4 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -219,11 +219,149 @@ pub trait VartimeMultiscalarMul { { Self::optional_multiscalar_mul( scalars, - points.into_iter().map(|P| Some(P.borrow().clone())) - ).unwrap() + points.into_iter().map(|P| Some(P.borrow().clone())), + ) + .unwrap() } } +/// A trait for variable-time multiscalar multiplication with precomputation. +/// +/// A general multiscalar multiplication with precomputation can be written as +/// $$ +/// Q = a_1 A_1 + \cdots + a_n A_n + b_1 B_1 + \cdots + b_m B_m, +/// $$ +/// where the \\(B_i\\) are *static* points, for which precomputation +/// is possible, and the \\(A_j\\) are *dynamic* points, for which +/// precomputation is not possible. +/// +/// This trait has three methods for performing this computation: +/// +/// * [`vartime_multiscalar_mul`], which handles the special case +/// where \\(n = 0\\) and there are no dynamic points; +/// +/// * [`vartime_mixed_multiscalar_mul`], which takes the dynamic +/// points as already-validated `Point`s and is infallible; +/// +/// * [`optional_mixed_multiscalar_mul`], which takes the dynamic +/// points as `Option`s and returns an `Option`, +/// allowing decompression to be composed into the input iterators. +/// +/// All methods require that the lengths of the input iterators be +/// known and matching, as if they were `ExactSizeIterator`s. (It +/// does not require `ExactSizeIterator` only because that trait is +/// broken). +pub trait VartimePrecomputedMultiscalarMul: Sized { + /// The type of point to be multiplied, e.g., `RistrettoPoint`. + type Point: Clone; + + /// Given the static points \\( B_i \\), perform precomputation + /// and return the precomputation data. + fn new(static_points: I) -> Self + where + I: IntoIterator, + I::Item: Borrow; + + /// Given `static_scalars`, an iterator of public scalars + /// \\(b_i\\), compute + /// $$ + /// Q = b_1 B_1 + \cdots + b_m B_m, + /// $$ + /// where the \\(B_j\\) are the points that were supplied to `new`. + /// + /// It is an error to call this function with iterators of + /// inconsistent lengths. + /// + /// The trait bound aims for maximum flexibility: the input must + /// be convertable to iterators (`I: IntoIter`), and the + /// iterator's items must be `Borrow`, to allow iterators + /// returning either `Scalar`s or `&Scalar`s. + fn vartime_multiscalar_mul(&self, static_scalars: I) -> Self::Point + where + I: IntoIterator, + I::Item: Borrow, + { + use core::iter; + + Self::vartime_mixed_multiscalar_mul( + self, + static_scalars, + iter::empty::(), + iter::empty::(), + ) + } + + /// Given `static_scalars`, an iterator of public scalars + /// \\(b_i\\), `dynamic_scalars`, an iterator of public scalars + /// \\(a_i\\), and `dynamic_points`, an iterator of points + /// \\(A_i\\), compute + /// $$ + /// Q = a_1 A_1 + \cdots + a_n A_n + b_1 B_1 + \cdots + b_m B_m, + /// $$ + /// where the \\(B_j\\) are the points that were supplied to `new`. + /// + /// It is an error to call this function with iterators of + /// inconsistent lengths. + /// + /// The trait bound aims for maximum flexibility: the inputs must be + /// convertable to iterators (`I: IntoIter`), and the iterator's items + /// must be `Borrow` (or `Borrow`), to allow + /// iterators returning either `Scalar`s or `&Scalar`s. + fn vartime_mixed_multiscalar_mul( + &self, + static_scalars: I, + dynamic_scalars: J, + dynamic_points: K, + ) -> Self::Point + where + I: IntoIterator, + I::Item: Borrow, + J: IntoIterator, + J::Item: Borrow, + K: IntoIterator, + K::Item: Borrow, + { + Self::optional_mixed_multiscalar_mul( + self, + static_scalars, + dynamic_scalars, + dynamic_points.into_iter().map(|P| Some(P.borrow().clone())), + ) + .unwrap() + } + + /// Given `static_scalars`, an iterator of public scalars + /// \\(b_i\\), `dynamic_scalars`, an iterator of public scalars + /// \\(a_i\\), and `dynamic_points`, an iterator of points + /// \\(A_i\\), compute + /// $$ + /// Q = a_1 A_1 + \cdots + a_n A_n + b_1 B_1 + \cdots + b_m B_m, + /// $$ + /// where the \\(B_j\\) are the points that were supplied to `new`. + /// + /// If any of the dynamic points were `None`, return `None`. + /// + /// It is an error to call this function with iterators of + /// inconsistent lengths. + /// + /// This function is particularly useful when verifying statements + /// involving compressed points. Accepting `Option` allows + /// inlining point decompression into the multiscalar call, + /// avoiding the need for temporary buffers. + fn optional_mixed_multiscalar_mul( + &self, + static_scalars: I, + dynamic_scalars: J, + dynamic_points: K, + ) -> Option + where + I: IntoIterator, + I::Item: Borrow, + J: IntoIterator, + J::Item: Borrow, + K: IntoIterator>; +} + // ------------------------------------------------------------------------ // Private Traits // ------------------------------------------------------------------------