From 98e713ef91947070b9224a5c8a59dc49d841089f Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Thu, 3 May 2018 15:31:08 -0700 Subject: [PATCH 01/12] Add a trait for multiscalar multiplication with precomputation. --- src/traits.rs | 160 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) diff --git a/src/traits.rs b/src/traits.rs index 8db963a..630e510 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -224,6 +224,166 @@ pub trait VartimeMultiscalarMul { } } +/// A trait for constant-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. +pub trait PrecomputedMultiscalarMul: Sized { + /// The type of point to be multiplied, e.g., `RistrettoPoint`. + type Point; + + /// 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 (possibly secret) + /// scalars \\(b_i\\), `dynamic_scalars`, an iterator of (possibly + /// secret) 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 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; + + /// Given `static_scalars`, an iterator of (possibly secret) + /// 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 multiscalar_mul(&self, static_scalars: I) -> Self::Point + where + I: IntoIterator, + I::Item: Borrow, + { + use core::iter; + + Self::mixed_multiscalar_mul( + self, + static_scalars, + iter::empty::(), + iter::empty::(), + ) + } +} + +/// 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. +pub trait VartimePrecomputedMultiscalarMul: Sized { + /// The type of point to be multiplied, e.g., `RistrettoPoint`. + type Point; + + /// 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\\), `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; + + /// 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::(), + ) + } +} + // ------------------------------------------------------------------------ // Private Traits // ------------------------------------------------------------------------ From 00675b4c560e1264608a80b505ef1eb0072a524f Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Thu, 24 Jan 2019 13:29:28 -0800 Subject: [PATCH 02/12] Add benchmarks for precomputed multiscalar multiplication. --- benches/dalek_benchmarks.rs | 150 ++++++++++++++++++++++++++++++------ 1 file changed, 128 insertions(+), 22 deletions(-) diff --git a/benches/dalek_benchmarks.rs b/benches/dalek_benchmarks.rs index 71792a3..011e6a6 100644 --- a/benches/dalek_benchmarks.rs +++ b/benches/dalek_benchmarks.rs @@ -25,9 +25,7 @@ mod edwards_benches { 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) { @@ -63,16 +61,40 @@ mod edwards_benches { }); } + 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::traits::MultiscalarMul; + use curve25519_dalek::traits::VartimeMultiscalarMul; + + fn construct(n: usize) -> (Vec, Vec) { + let mut rng = OsRng::new().unwrap(); + let scalars: Vec = (0..n).map(|_| Scalar::random(&mut rng)).collect(); + let points: Vec = scalars + .iter() + .map(|s| s * &constants::ED25519_BASEPOINT_TABLE) + .collect(); + (scalars, points) + } + 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(); + let (scalars, points) = construct(size); b.iter(|| EdwardsPoint::multiscalar_mul(&scalars, &points)); }, &MULTISCALAR_SIZES, @@ -83,29 +105,112 @@ 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(); + let (scalars, points) = construct(size); b.iter(|| EdwardsPoint::vartime_multiscalar_mul(&scalars, &points)); }, &MULTISCALAR_SIZES, ); } + fn precomputed_ct_straus_helper(c: &mut Criterion, dynamic_fraction: f64) { + let label = format!( + "Constant-time mixed-base Straus ({:.2}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_scalars, static_points) = construct(static_size); + let (dynamic_scalars, dynamic_points) = construct(dynamic_size); + + use curve25519_dalek::edwards::PrecomputedStraus; + use curve25519_dalek::traits::PrecomputedMultiscalarMul; + + let precomp = PrecomputedStraus::new(&static_points); + + b.iter(|| { + precomp.mixed_multiscalar_mul( + &static_scalars, + &dynamic_scalars, + &dynamic_points, + ) + }); + }, + &MULTISCALAR_SIZES, + ); + } + + fn precomputed_vt_straus_helper(c: &mut Criterion, dynamic_fraction: f64) { + let label = format!( + "Variable-time mixed-base Straus ({:.2}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_scalars, static_points) = construct(static_size); + let (dynamic_scalars, dynamic_points) = construct(dynamic_size); + + use curve25519_dalek::edwards::VartimePrecomputedStraus; + use curve25519_dalek::traits::VartimePrecomputedMultiscalarMul; + + let precomp = VartimePrecomputedStraus::new(&static_points); + + b.iter(|| { + precomp.vartime_mixed_multiscalar_mul( + &static_scalars, + &dynamic_scalars, + &dynamic_points, + ) + }); + }, + &MULTISCALAR_SIZES, + ); + } + + fn precomputed_ct_straus_00_pct_dynamic(c: &mut Criterion) { + precomputed_ct_straus_helper(c, 0.0); + } + + fn precomputed_ct_straus_20_pct_dynamic(c: &mut Criterion) { + precomputed_ct_straus_helper(c, 0.2); + } + + fn precomputed_ct_straus_50_pct_dynamic(c: &mut Criterion) { + precomputed_ct_straus_helper(c, 0.5); + } + + fn precomputed_vt_straus_00_pct_dynamic(c: &mut Criterion) { + precomputed_vt_straus_helper(c, 0.0); + } + + fn precomputed_vt_straus_20_pct_dynamic(c: &mut Criterion) { + precomputed_vt_straus_helper(c, 0.2); + } + + fn precomputed_vt_straus_50_pct_dynamic(c: &mut Criterion) { + precomputed_vt_straus_helper(c, 0.5); + } + criterion_group!{ - name = edwards_benches; - config = Criterion::default(); + 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, + precomputed_ct_straus_00_pct_dynamic, + precomputed_ct_straus_20_pct_dynamic, + precomputed_ct_straus_50_pct_dynamic, + precomputed_vt_straus_00_pct_dynamic, + precomputed_vt_straus_20_pct_dynamic, + precomputed_vt_straus_50_pct_dynamic, } } @@ -208,4 +313,5 @@ criterion_main!( montgomery_benches::montgomery_benches, ristretto_benches::ristretto_benches, edwards_benches::edwards_benches, + multiscalar_benches::multiscalar_benches, ); From 5daff6607975ca86f562a1f2ffb5f2ae51e392c0 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Mon, 11 Feb 2019 07:25:16 -0800 Subject: [PATCH 03/12] Move cfgs into outer module. --- src/backend/serial/scalar_mul/mod.rs | 1 + src/backend/serial/scalar_mul/straus.rs | 8 -------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/backend/serial/scalar_mul/mod.rs b/src/backend/serial/scalar_mul/mod.rs index 1421f41..2aba024 100644 --- a/src/backend/serial/scalar_mul/mod.rs +++ b/src/backend/serial/scalar_mul/mod.rs @@ -21,4 +21,5 @@ pub mod variable_base; #[cfg(feature = "stage2_build")] pub mod vartime_double_base; +#[cfg(feature = "alloc")] pub mod straus; 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; From c6acdfd5e2b74e6b2d29cc320f1c8defe979f624 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 12 Feb 2019 11:18:03 -0800 Subject: [PATCH 04/12] Add serial implementation of precomputation. --- benches/dalek_benchmarks.rs | 10 +- src/backend/serial/scalar_mul/mod.rs | 3 + .../serial/scalar_mul/precomputed_straus.rs | 202 ++++++++++++++++++ src/edwards.rs | 176 ++++++++++++++- 4 files changed, 380 insertions(+), 11 deletions(-) create mode 100644 src/backend/serial/scalar_mul/precomputed_straus.rs diff --git a/benches/dalek_benchmarks.rs b/benches/dalek_benchmarks.rs index 011e6a6..118a809 100644 --- a/benches/dalek_benchmarks.rs +++ b/benches/dalek_benchmarks.rs @@ -20,8 +20,6 @@ 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; @@ -126,10 +124,10 @@ mod multiscalar_benches { let (static_scalars, static_points) = construct(static_size); let (dynamic_scalars, dynamic_points) = construct(dynamic_size); - use curve25519_dalek::edwards::PrecomputedStraus; + use curve25519_dalek::edwards::EdwardsPrecomputation; use curve25519_dalek::traits::PrecomputedMultiscalarMul; - let precomp = PrecomputedStraus::new(&static_points); + let precomp = EdwardsPrecomputation::new(&static_points); b.iter(|| { precomp.mixed_multiscalar_mul( @@ -157,10 +155,10 @@ mod multiscalar_benches { let (static_scalars, static_points) = construct(static_size); let (dynamic_scalars, dynamic_points) = construct(dynamic_size); - use curve25519_dalek::edwards::VartimePrecomputedStraus; + use curve25519_dalek::edwards::VartimeEdwardsPrecomputation; use curve25519_dalek::traits::VartimePrecomputedMultiscalarMul; - let precomp = VartimePrecomputedStraus::new(&static_points); + let precomp = VartimeEdwardsPrecomputation::new(&static_points); b.iter(|| { precomp.vartime_mixed_multiscalar_mul( diff --git a/src/backend/serial/scalar_mul/mod.rs b/src/backend/serial/scalar_mul/mod.rs index 2aba024..bec874b 100644 --- a/src/backend/serial/scalar_mul/mod.rs +++ b/src/backend/serial/scalar_mul/mod.rs @@ -23,3 +23,6 @@ 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..989d92a --- /dev/null +++ b/src/backend/serial/scalar_mul/precomputed_straus.rs @@ -0,0 +1,202 @@ +// -*- 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 clear_on_drop::ClearOnDrop; + +use backend::serial::curve_models::{ + AffineNielsPoint, CompletedPoint, ProjectiveNielsPoint, ProjectivePoint, +}; +use edwards::EdwardsPoint; +use scalar::Scalar; +use traits::Identity; +use traits::{PrecomputedMultiscalarMul, VartimePrecomputedMultiscalarMul}; +use window::{LookupTable, NafLookupTable5, NafLookupTable8}; + +#[allow(unused_imports)] +use prelude::*; + +pub struct PrecomputedStraus { + static_lookup_tables: Vec>, +} + +impl PrecomputedMultiscalarMul for PrecomputedStraus { + type Point = EdwardsPoint; + + fn new(static_points: I) -> Self + where + I: IntoIterator, + I::Item: Borrow, + { + PrecomputedStraus { + static_lookup_tables: static_points + .into_iter() + .map(|point| LookupTable::::from(point.borrow())) + .collect(), + } + } + + fn 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, + { + let mut static_scalars = static_scalars.into_iter(); + let mut dynamic_scalars = dynamic_scalars.into_iter(); + let mut dynamic_points = dynamic_points.into_iter(); + + // Check that the input lengths are consistent with each other: + let (ss_lo, ss_hi) = static_scalars.by_ref().size_hint(); + let (ds_lo, ds_hi) = dynamic_scalars.by_ref().size_hint(); + let (dp_lo, dp_hi) = dynamic_points.by_ref().size_hint(); + + // Static points match static scalars + let sp = self.static_lookup_tables.len(); + assert_eq!(ss_lo, sp); + assert_eq!(ss_hi, Some(sp)); + + // Dynamic points match dynamic scalars + assert_eq!(ds_lo, dp_lo); + assert_eq!(ds_hi, Some(ds_lo)); + assert_eq!(ds_hi, dp_hi); + let dp = dp_lo; + + // This does two allocs for the scalar digits instead of + // putting them in a contiguous array, which makes handling + // the two kinds of lookup tables slightly easier. + // Use a ClearOnDrop wrapper. + + let static_scalar_digits_vec: Vec<_> = + static_scalars.map(|s| s.borrow().to_radix_16()).collect(); + let static_scalar_digits = ClearOnDrop::new(static_scalar_digits_vec); + + let dynamic_scalar_digits_vec: Vec<_> = + dynamic_scalars.map(|s| s.borrow().to_radix_16()).collect(); + let dynamic_scalar_digits = ClearOnDrop::new(dynamic_scalar_digits_vec); + + // Build lookup tables for dynamic points + let dynamic_lookup_tables: Vec<_> = dynamic_points + .map(|point| LookupTable::::from(point.borrow())) + .collect(); + + let mut R = EdwardsPoint::identity(); + for j in (0..64).rev() { + R = R.mul_by_pow_2(4); + for i in 0..dp { + let t_ij = dynamic_scalar_digits[i][j]; + R = (&R + &dynamic_lookup_tables[i].select(t_ij)).to_extended(); + } + for i in 0..sp { + let s_ij = static_scalar_digits[i][j]; + R = (&R + &self.static_lookup_tables[i].select(s_ij)).to_extended(); + } + } + + R + } +} + +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 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, + { + 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 = dynamic_points + .into_iter() + .map(|P| NafLookupTable5::::from(P.borrow())) + .collect::>(); + + 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(); + } + + S.to_extended() + } +} diff --git a/src/edwards.rs b/src/edwards.rs index 102a5ea..fa7029e 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; +use traits::{MultiscalarMul, PrecomputedMultiscalarMul}; #[cfg(any(feature = "alloc", feature = "std"))] -use traits::VartimeMultiscalarMul; +use traits::{VartimeMultiscalarMul, VartimePrecomputedMultiscalarMul}; #[cfg(not(all( feature = "simd_backend", @@ -673,6 +673,84 @@ impl VartimeMultiscalarMul for EdwardsPoint { } } +/// Precomputation for 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(all(feature = "alloc", feature = "yolocrypto"))] +pub struct EdwardsPrecomputation(scalar_mul::precomputed_straus::PrecomputedStraus); + +/// 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(all(feature = "alloc", feature = "yolocrypto"))] +pub struct VartimeEdwardsPrecomputation(scalar_mul::precomputed_straus::VartimePrecomputedStraus); + +#[cfg(all(feature = "alloc", feature = "yolocrypto"))] +impl PrecomputedMultiscalarMul for EdwardsPrecomputation { + type Point = EdwardsPoint; + + fn new(static_points: I) -> Self + where + I: IntoIterator, + I::Item: Borrow, + { + Self(scalar_mul::precomputed_straus::PrecomputedStraus::new( + static_points, + )) + } + + fn 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.0 + .mixed_multiscalar_mul(static_scalars, dynamic_scalars, dynamic_points) + } +} + +#[cfg(all(feature = "alloc", feature = "yolocrypto"))] +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 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.0 + .vartime_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")] @@ -1203,6 +1281,94 @@ mod test { assert!(P1.compress().to_bytes() == P2.compress().to_bytes()); } + #[test] + #[cfg(feature = "yolocrypto")] + fn 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 = EdwardsPrecomputation::new(static_points.iter()); + + let P = precomputation.mixed_multiscalar_mul( + &static_scalars, + &dynamic_scalars, + &dynamic_points, + ); + + use traits::MultiscalarMul; + let Q = EdwardsPoint::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()); + } + + #[test] + #[cfg(feature = "yolocrypto")] + 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}; From 8b0ad2b03dd9f44994d955fb6d9eedeaf8af8465 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 12 Feb 2019 11:36:34 -0800 Subject: [PATCH 05/12] Add vector implementation of precomputation. --- src/backend/vector/scalar_mul/mod.rs | 4 + .../vector/scalar_mul/precomputed_straus.rs | 198 ++++++++++++++++++ src/backend/vector/scalar_mul/straus.rs | 2 - 3 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 src/backend/vector/scalar_mul/precomputed_straus.rs 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..dc3fc3f --- /dev/null +++ b/src/backend/vector/scalar_mul/precomputed_straus.rs @@ -0,0 +1,198 @@ +// -*- 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 clear_on_drop::ClearOnDrop; + +use backend::vector::{CachedPoint, ExtendedPoint}; +use edwards::EdwardsPoint; +use scalar::Scalar; +use traits::Identity; +use traits::{PrecomputedMultiscalarMul, VartimePrecomputedMultiscalarMul}; +use window::{LookupTable, NafLookupTable5, NafLookupTable8}; + +#[allow(unused_imports)] +use prelude::*; + +pub struct PrecomputedStraus { + static_lookup_tables: Vec>, +} + +impl PrecomputedMultiscalarMul for PrecomputedStraus { + type Point = EdwardsPoint; + + fn new(static_points: I) -> Self + where + I: IntoIterator, + I::Item: Borrow, + { + PrecomputedStraus { + static_lookup_tables: static_points + .into_iter() + .map(|point| LookupTable::::from(point.borrow())) + .collect(), + } + } + + fn 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, + { + let mut static_scalars = static_scalars.into_iter(); + let mut dynamic_scalars = dynamic_scalars.into_iter(); + let mut dynamic_points = dynamic_points.into_iter(); + + // Check that the input lengths are consistent with each other: + let (ss_lo, ss_hi) = static_scalars.by_ref().size_hint(); + let (ds_lo, ds_hi) = dynamic_scalars.by_ref().size_hint(); + let (dp_lo, dp_hi) = dynamic_points.by_ref().size_hint(); + + // Static points match static scalars + let sp = self.static_lookup_tables.len(); + assert_eq!(ss_lo, sp); + assert_eq!(ss_hi, Some(sp)); + + // Dynamic points match dynamic scalars + assert_eq!(ds_lo, dp_lo); + assert_eq!(ds_hi, Some(ds_lo)); + assert_eq!(ds_hi, dp_hi); + let dp = dp_lo; + + // This does two allocs for the scalar digits instead of + // putting them in a contiguous array, which makes handling + // the two kinds of lookup tables slightly easier. + // Use a ClearOnDrop wrapper. + + let static_scalar_digits_vec: Vec<_> = + static_scalars.map(|s| s.borrow().to_radix_16()).collect(); + let static_scalar_digits = ClearOnDrop::new(static_scalar_digits_vec); + + let dynamic_scalar_digits_vec: Vec<_> = + dynamic_scalars.map(|s| s.borrow().to_radix_16()).collect(); + let dynamic_scalar_digits = ClearOnDrop::new(dynamic_scalar_digits_vec); + + // Build lookup tables for dynamic points + let dynamic_lookup_tables: Vec<_> = dynamic_points + .map(|point| LookupTable::::from(point.borrow())) + .collect(); + + let mut R = ExtendedPoint::identity(); + for j in (0..64).rev() { + R = R.mul_by_pow_2(4); + for i in 0..dp { + let t_ij = dynamic_scalar_digits[i][j]; + R = &R + &dynamic_lookup_tables[i].select(t_ij); + } + for i in 0..sp { + let s_ij = static_scalar_digits[i][j]; + R = &R + &self.static_lookup_tables[i].select(s_ij); + } + } + + R.into() + } +} + +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 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, + { + 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 = dynamic_points + .into_iter() + .map(|P| NafLookupTable5::::from(P.borrow())) + .collect::>(); + + 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); + } + } + } + + 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; From e693d7f020fd4c7732f646ddb5693d805f8ae3c5 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 12 Feb 2019 11:38:39 -0800 Subject: [PATCH 06/12] fixup AVX2 ascii blocks so they don't run as doctests --- src/backend/vector/avx2/constants.rs | 8 ++++---- src/backend/vector/avx2/field.rs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) 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)] From 727ba862924e702be95db7eb6ce59e8cb71b2df4 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 12 Feb 2019 12:28:24 -0800 Subject: [PATCH 07/12] Rework the vartime precomputation trait to be Option-al --- .../serial/scalar_mul/precomputed_straus.rs | 19 +-- .../vector/scalar_mul/precomputed_straus.rs | 19 +-- src/edwards.rs | 9 +- src/traits.rs | 124 +++++++++++++----- 4 files changed, 117 insertions(+), 54 deletions(-) diff --git a/src/backend/serial/scalar_mul/precomputed_straus.rs b/src/backend/serial/scalar_mul/precomputed_straus.rs index 989d92a..ceeb8b9 100644 --- a/src/backend/serial/scalar_mul/precomputed_straus.rs +++ b/src/backend/serial/scalar_mul/precomputed_straus.rs @@ -136,19 +136,18 @@ impl VartimePrecomputedMultiscalarMul for VartimePrecomputedStraus { } } - fn vartime_mixed_multiscalar_mul( + fn optional_mixed_multiscalar_mul( &self, static_scalars: I, dynamic_scalars: J, dynamic_points: K, - ) -> Self::Point + ) -> Option where I: IntoIterator, I::Item: Borrow, J: IntoIterator, J::Item: Borrow, - K: IntoIterator, - K::Item: Borrow, + K: IntoIterator>, { let static_nafs = static_scalars .into_iter() @@ -159,10 +158,14 @@ impl VartimePrecomputedMultiscalarMul for VartimePrecomputedStraus { .map(|c| c.borrow().non_adjacent_form(5)) .collect::>(); - let dynamic_lookup_tables = dynamic_points + let dynamic_lookup_tables = match dynamic_points .into_iter() - .map(|P| NafLookupTable5::::from(P.borrow())) - .collect::>(); + .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(); @@ -197,6 +200,6 @@ impl VartimePrecomputedMultiscalarMul for VartimePrecomputedStraus { S = R.to_projective(); } - S.to_extended() + Some(S.to_extended()) } } diff --git a/src/backend/vector/scalar_mul/precomputed_straus.rs b/src/backend/vector/scalar_mul/precomputed_straus.rs index dc3fc3f..2ddfa24 100644 --- a/src/backend/vector/scalar_mul/precomputed_straus.rs +++ b/src/backend/vector/scalar_mul/precomputed_straus.rs @@ -134,19 +134,18 @@ impl VartimePrecomputedMultiscalarMul for VartimePrecomputedStraus { } } - fn vartime_mixed_multiscalar_mul( + fn optional_mixed_multiscalar_mul( &self, static_scalars: I, dynamic_scalars: J, dynamic_points: K, - ) -> Self::Point + ) -> Option where I: IntoIterator, I::Item: Borrow, J: IntoIterator, J::Item: Borrow, - K: IntoIterator, - K::Item: Borrow, + K: IntoIterator>, { let static_nafs = static_scalars .into_iter() @@ -157,10 +156,14 @@ impl VartimePrecomputedMultiscalarMul for VartimePrecomputedStraus { .map(|c| c.borrow().non_adjacent_form(5)) .collect::>(); - let dynamic_lookup_tables = dynamic_points + let dynamic_lookup_tables = match dynamic_points .into_iter() - .map(|P| NafLookupTable5::::from(P.borrow())) - .collect::>(); + .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(); @@ -193,6 +196,6 @@ impl VartimePrecomputedMultiscalarMul for VartimePrecomputedStraus { } } - R.into() + Some(R.into()) } } diff --git a/src/edwards.rs b/src/edwards.rs index fa7029e..04c6565 100644 --- a/src/edwards.rs +++ b/src/edwards.rs @@ -732,22 +732,21 @@ impl VartimePrecomputedMultiscalarMul for VartimeEdwardsPrecomputation { Self(scalar_mul::precomputed_straus::VartimePrecomputedStraus::new(static_points)) } - fn vartime_mixed_multiscalar_mul( + fn optional_mixed_multiscalar_mul( &self, static_scalars: I, dynamic_scalars: J, dynamic_points: K, - ) -> Self::Point + ) -> Option where I: IntoIterator, I::Item: Borrow, J: IntoIterator, J::Item: Borrow, - K: IntoIterator, - K::Item: Borrow, + K: IntoIterator>, { self.0 - .vartime_mixed_multiscalar_mul(static_scalars, dynamic_scalars, dynamic_points) + .optional_mixed_multiscalar_mul(static_scalars, dynamic_scalars, dynamic_points) } } diff --git a/src/traits.rs b/src/traits.rs index 630e510..c1166f5 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -219,8 +219,9 @@ 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() } } @@ -313,9 +314,26 @@ pub trait PrecomputedMultiscalarMul: Sized { /// 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; + type Point: Clone; /// Given the static points \\( B_i \\), perform precomputation /// and return the precomputation data. @@ -324,36 +342,6 @@ pub trait VartimePrecomputedMultiscalarMul: Sized { I: IntoIterator, I::Item: Borrow; - /// 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; - /// Given `static_scalars`, an iterator of public scalars /// \\(b_i\\), compute /// $$ @@ -382,6 +370,76 @@ pub trait VartimePrecomputedMultiscalarMul: Sized { 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>; } // ------------------------------------------------------------------------ From 8adcfb7fa341a02b029b12cf8d1789ebbbaf1e3d Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 12 Feb 2019 12:35:01 -0800 Subject: [PATCH 08/12] Simplify length checking. --- .../serial/scalar_mul/precomputed_straus.rs | 38 +++++++------------ .../vector/scalar_mul/precomputed_straus.rs | 38 +++++++------------ 2 files changed, 28 insertions(+), 48 deletions(-) diff --git a/src/backend/serial/scalar_mul/precomputed_straus.rs b/src/backend/serial/scalar_mul/precomputed_straus.rs index ceeb8b9..905b168 100644 --- a/src/backend/serial/scalar_mul/precomputed_straus.rs +++ b/src/backend/serial/scalar_mul/precomputed_straus.rs @@ -61,44 +61,34 @@ impl PrecomputedMultiscalarMul for PrecomputedStraus { K: IntoIterator, K::Item: Borrow, { - let mut static_scalars = static_scalars.into_iter(); - let mut dynamic_scalars = dynamic_scalars.into_iter(); - let mut dynamic_points = dynamic_points.into_iter(); - - // Check that the input lengths are consistent with each other: - let (ss_lo, ss_hi) = static_scalars.by_ref().size_hint(); - let (ds_lo, ds_hi) = dynamic_scalars.by_ref().size_hint(); - let (dp_lo, dp_hi) = dynamic_points.by_ref().size_hint(); - - // Static points match static scalars - let sp = self.static_lookup_tables.len(); - assert_eq!(ss_lo, sp); - assert_eq!(ss_hi, Some(sp)); - - // Dynamic points match dynamic scalars - assert_eq!(ds_lo, dp_lo); - assert_eq!(ds_hi, Some(ds_lo)); - assert_eq!(ds_hi, dp_hi); - let dp = dp_lo; - // This does two allocs for the scalar digits instead of // putting them in a contiguous array, which makes handling // the two kinds of lookup tables slightly easier. // Use a ClearOnDrop wrapper. - let static_scalar_digits_vec: Vec<_> = - static_scalars.map(|s| s.borrow().to_radix_16()).collect(); + let static_scalar_digits_vec: Vec<_> = static_scalars + .into_iter() + .map(|s| s.borrow().to_radix_16()) + .collect(); let static_scalar_digits = ClearOnDrop::new(static_scalar_digits_vec); - let dynamic_scalar_digits_vec: Vec<_> = - dynamic_scalars.map(|s| s.borrow().to_radix_16()).collect(); + let dynamic_scalar_digits_vec: Vec<_> = dynamic_scalars + .into_iter() + .map(|s| s.borrow().to_radix_16()) + .collect(); let dynamic_scalar_digits = ClearOnDrop::new(dynamic_scalar_digits_vec); // Build lookup tables for dynamic points let dynamic_lookup_tables: Vec<_> = dynamic_points + .into_iter() .map(|point| LookupTable::::from(point.borrow())) .collect(); + let sp = self.static_lookup_tables.len(); + let dp = dynamic_lookup_tables.len(); + assert_eq!(sp, static_scalar_digits.len()); + assert_eq!(dp, dynamic_scalar_digits.len()); + let mut R = EdwardsPoint::identity(); for j in (0..64).rev() { R = R.mul_by_pow_2(4); diff --git a/src/backend/vector/scalar_mul/precomputed_straus.rs b/src/backend/vector/scalar_mul/precomputed_straus.rs index 2ddfa24..9ca767f 100644 --- a/src/backend/vector/scalar_mul/precomputed_straus.rs +++ b/src/backend/vector/scalar_mul/precomputed_straus.rs @@ -59,44 +59,34 @@ impl PrecomputedMultiscalarMul for PrecomputedStraus { K: IntoIterator, K::Item: Borrow, { - let mut static_scalars = static_scalars.into_iter(); - let mut dynamic_scalars = dynamic_scalars.into_iter(); - let mut dynamic_points = dynamic_points.into_iter(); - - // Check that the input lengths are consistent with each other: - let (ss_lo, ss_hi) = static_scalars.by_ref().size_hint(); - let (ds_lo, ds_hi) = dynamic_scalars.by_ref().size_hint(); - let (dp_lo, dp_hi) = dynamic_points.by_ref().size_hint(); - - // Static points match static scalars - let sp = self.static_lookup_tables.len(); - assert_eq!(ss_lo, sp); - assert_eq!(ss_hi, Some(sp)); - - // Dynamic points match dynamic scalars - assert_eq!(ds_lo, dp_lo); - assert_eq!(ds_hi, Some(ds_lo)); - assert_eq!(ds_hi, dp_hi); - let dp = dp_lo; - // This does two allocs for the scalar digits instead of // putting them in a contiguous array, which makes handling // the two kinds of lookup tables slightly easier. // Use a ClearOnDrop wrapper. - let static_scalar_digits_vec: Vec<_> = - static_scalars.map(|s| s.borrow().to_radix_16()).collect(); + let static_scalar_digits_vec: Vec<_> = static_scalars + .into_iter() + .map(|s| s.borrow().to_radix_16()) + .collect(); let static_scalar_digits = ClearOnDrop::new(static_scalar_digits_vec); - let dynamic_scalar_digits_vec: Vec<_> = - dynamic_scalars.map(|s| s.borrow().to_radix_16()).collect(); + let dynamic_scalar_digits_vec: Vec<_> = dynamic_scalars + .into_iter() + .map(|s| s.borrow().to_radix_16()) + .collect(); let dynamic_scalar_digits = ClearOnDrop::new(dynamic_scalar_digits_vec); // Build lookup tables for dynamic points let dynamic_lookup_tables: Vec<_> = dynamic_points + .into_iter() .map(|point| LookupTable::::from(point.borrow())) .collect(); + let sp = self.static_lookup_tables.len(); + let dp = dynamic_lookup_tables.len(); + assert_eq!(sp, static_scalar_digits.len()); + assert_eq!(dp, dynamic_scalar_digits.len()); + let mut R = ExtendedPoint::identity(); for j in (0..64).rev() { R = R.mul_by_pow_2(4); From 27daa5215e7baacf934b2b80ebd4a6c50eb71cf8 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 12 Feb 2019 12:45:56 -0800 Subject: [PATCH 09/12] Add Ristretto precomputation facade. --- src/edwards.rs | 10 +-- src/ristretto.rs | 194 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 195 insertions(+), 9 deletions(-) diff --git a/src/edwards.rs b/src/edwards.rs index 04c6565..3ab3e60 100644 --- a/src/edwards.rs +++ b/src/edwards.rs @@ -677,17 +677,17 @@ impl VartimeMultiscalarMul for EdwardsPoint { // 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(all(feature = "alloc", feature = "yolocrypto"))] +#[cfg(feature = "alloc")] pub struct EdwardsPrecomputation(scalar_mul::precomputed_straus::PrecomputedStraus); /// 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(all(feature = "alloc", feature = "yolocrypto"))] +#[cfg(feature = "alloc")] pub struct VartimeEdwardsPrecomputation(scalar_mul::precomputed_straus::VartimePrecomputedStraus); -#[cfg(all(feature = "alloc", feature = "yolocrypto"))] +#[cfg(feature = "alloc")] impl PrecomputedMultiscalarMul for EdwardsPrecomputation { type Point = EdwardsPoint; @@ -720,7 +720,7 @@ impl PrecomputedMultiscalarMul for EdwardsPrecomputation { } } -#[cfg(all(feature = "alloc", feature = "yolocrypto"))] +#[cfg(feature = "alloc")] impl VartimePrecomputedMultiscalarMul for VartimeEdwardsPrecomputation { type Point = EdwardsPoint; @@ -1281,7 +1281,6 @@ mod test { } #[test] - #[cfg(feature = "yolocrypto")] fn precomputed_vs_nonprecomputed_multiscalar() { let mut rng = rand::thread_rng(); @@ -1325,7 +1324,6 @@ mod test { } #[test] - #[cfg(feature = "yolocrypto")] fn vartime_precomputed_vs_nonprecomputed_multiscalar() { let mut rng = rand::thread_rng(); diff --git a/src/ristretto.rs b/src/ristretto.rs index 2daabaf..34e3c33 100644 --- a/src/ristretto.rs +++ b/src/ristretto.rs @@ -187,7 +187,21 @@ use scalar::Scalar; use traits::Identity; #[cfg(any(feature = "alloc", feature = "std"))] -use traits::{MultiscalarMul, VartimeMultiscalarMul}; +use traits::{ + MultiscalarMul, PrecomputedMultiscalarMul, 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 +905,96 @@ 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 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 RistrettoPrecomputation(scalar_mul::precomputed_straus::PrecomputedStraus); + +/// 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 PrecomputedMultiscalarMul for RistrettoPrecomputation { + type Point = RistrettoPoint; + + fn new(static_points: I) -> Self + where + I: IntoIterator, + I::Item: Borrow, + { + Self(scalar_mul::precomputed_straus::PrecomputedStraus::new( + static_points.into_iter().map(|P| P.borrow().0), + )) + } + + fn 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, + { + RistrettoPoint(self.0.mixed_multiscalar_mul( + static_scalars, + dynamic_scalars, + dynamic_points.into_iter().map(|P| P.borrow().0), + )) + } +} + +#[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 +1365,90 @@ mod test { P.compress(); } } + + #[test] + fn 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 = RistrettoPrecomputation::new(static_points.iter()); + + let P = precomputation.mixed_multiscalar_mul( + &static_scalars, + &dynamic_scalars, + &dynamic_points, + ); + + use traits::MultiscalarMul; + let Q = RistrettoPoint::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()); + } + + #[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()); + } } From 092ff52cb0927696ccda4100203446c2374e1d3a Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Wed, 13 Feb 2019 11:10:47 -0800 Subject: [PATCH 10/12] Remove constant-time multiscalar precomputation. This doesn't (yet) give any speedup over the non-precomputed multiscalar multiplication, and it's not clear that it's a good idea to commit to supporting it in the future. Removing it means that it's not committed-to as part of the public API, but the source is still there in the tree if we want to revisit it later. --- benches/dalek_benchmarks.rs | 46 ---------- .../serial/scalar_mul/precomputed_straus.rs | 85 +---------------- .../vector/scalar_mul/precomputed_straus.rs | 84 +---------------- src/edwards.rs | 85 +---------------- src/ristretto.rs | 91 +------------------ src/traits.rs | 80 ---------------- 6 files changed, 6 insertions(+), 465 deletions(-) diff --git a/benches/dalek_benchmarks.rs b/benches/dalek_benchmarks.rs index 118a809..e2e3f5b 100644 --- a/benches/dalek_benchmarks.rs +++ b/benches/dalek_benchmarks.rs @@ -110,37 +110,6 @@ mod multiscalar_benches { ); } - fn precomputed_ct_straus_helper(c: &mut Criterion, dynamic_fraction: f64) { - let label = format!( - "Constant-time mixed-base Straus ({:.2}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_scalars, static_points) = construct(static_size); - let (dynamic_scalars, dynamic_points) = construct(dynamic_size); - - use curve25519_dalek::edwards::EdwardsPrecomputation; - use curve25519_dalek::traits::PrecomputedMultiscalarMul; - - let precomp = EdwardsPrecomputation::new(&static_points); - - b.iter(|| { - precomp.mixed_multiscalar_mul( - &static_scalars, - &dynamic_scalars, - &dynamic_points, - ) - }); - }, - &MULTISCALAR_SIZES, - ); - } - fn precomputed_vt_straus_helper(c: &mut Criterion, dynamic_fraction: f64) { let label = format!( "Variable-time mixed-base Straus ({:.2}pct dyn)", @@ -172,18 +141,6 @@ mod multiscalar_benches { ); } - fn precomputed_ct_straus_00_pct_dynamic(c: &mut Criterion) { - precomputed_ct_straus_helper(c, 0.0); - } - - fn precomputed_ct_straus_20_pct_dynamic(c: &mut Criterion) { - precomputed_ct_straus_helper(c, 0.2); - } - - fn precomputed_ct_straus_50_pct_dynamic(c: &mut Criterion) { - precomputed_ct_straus_helper(c, 0.5); - } - fn precomputed_vt_straus_00_pct_dynamic(c: &mut Criterion) { precomputed_vt_straus_helper(c, 0.0); } @@ -203,9 +160,6 @@ mod multiscalar_benches { targets = consttime_multiscalar_mul, vartime_multiscalar_mul, - precomputed_ct_straus_00_pct_dynamic, - precomputed_ct_straus_20_pct_dynamic, - precomputed_ct_straus_50_pct_dynamic, precomputed_vt_straus_00_pct_dynamic, precomputed_vt_straus_20_pct_dynamic, precomputed_vt_straus_50_pct_dynamic, diff --git a/src/backend/serial/scalar_mul/precomputed_straus.rs b/src/backend/serial/scalar_mul/precomputed_straus.rs index 905b168..4019b14 100644 --- a/src/backend/serial/scalar_mul/precomputed_straus.rs +++ b/src/backend/serial/scalar_mul/precomputed_straus.rs @@ -13,99 +13,18 @@ use core::borrow::Borrow; -use clear_on_drop::ClearOnDrop; - use backend::serial::curve_models::{ AffineNielsPoint, CompletedPoint, ProjectiveNielsPoint, ProjectivePoint, }; use edwards::EdwardsPoint; use scalar::Scalar; use traits::Identity; -use traits::{PrecomputedMultiscalarMul, VartimePrecomputedMultiscalarMul}; -use window::{LookupTable, NafLookupTable5, NafLookupTable8}; +use traits::VartimePrecomputedMultiscalarMul; +use window::{NafLookupTable5, NafLookupTable8}; #[allow(unused_imports)] use prelude::*; -pub struct PrecomputedStraus { - static_lookup_tables: Vec>, -} - -impl PrecomputedMultiscalarMul for PrecomputedStraus { - type Point = EdwardsPoint; - - fn new(static_points: I) -> Self - where - I: IntoIterator, - I::Item: Borrow, - { - PrecomputedStraus { - static_lookup_tables: static_points - .into_iter() - .map(|point| LookupTable::::from(point.borrow())) - .collect(), - } - } - - fn 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, - { - // This does two allocs for the scalar digits instead of - // putting them in a contiguous array, which makes handling - // the two kinds of lookup tables slightly easier. - // Use a ClearOnDrop wrapper. - - let static_scalar_digits_vec: Vec<_> = static_scalars - .into_iter() - .map(|s| s.borrow().to_radix_16()) - .collect(); - let static_scalar_digits = ClearOnDrop::new(static_scalar_digits_vec); - - let dynamic_scalar_digits_vec: Vec<_> = dynamic_scalars - .into_iter() - .map(|s| s.borrow().to_radix_16()) - .collect(); - let dynamic_scalar_digits = ClearOnDrop::new(dynamic_scalar_digits_vec); - - // Build lookup tables for dynamic points - let dynamic_lookup_tables: Vec<_> = dynamic_points - .into_iter() - .map(|point| LookupTable::::from(point.borrow())) - .collect(); - - let sp = self.static_lookup_tables.len(); - let dp = dynamic_lookup_tables.len(); - assert_eq!(sp, static_scalar_digits.len()); - assert_eq!(dp, dynamic_scalar_digits.len()); - - let mut R = EdwardsPoint::identity(); - for j in (0..64).rev() { - R = R.mul_by_pow_2(4); - for i in 0..dp { - let t_ij = dynamic_scalar_digits[i][j]; - R = (&R + &dynamic_lookup_tables[i].select(t_ij)).to_extended(); - } - for i in 0..sp { - let s_ij = static_scalar_digits[i][j]; - R = (&R + &self.static_lookup_tables[i].select(s_ij)).to_extended(); - } - } - - R - } -} - pub struct VartimePrecomputedStraus { static_lookup_tables: Vec>, } diff --git a/src/backend/vector/scalar_mul/precomputed_straus.rs b/src/backend/vector/scalar_mul/precomputed_straus.rs index 9ca767f..49d1be4 100644 --- a/src/backend/vector/scalar_mul/precomputed_straus.rs +++ b/src/backend/vector/scalar_mul/precomputed_straus.rs @@ -13,96 +13,16 @@ use core::borrow::Borrow; -use clear_on_drop::ClearOnDrop; - use backend::vector::{CachedPoint, ExtendedPoint}; use edwards::EdwardsPoint; use scalar::Scalar; use traits::Identity; -use traits::{PrecomputedMultiscalarMul, VartimePrecomputedMultiscalarMul}; -use window::{LookupTable, NafLookupTable5, NafLookupTable8}; +use traits::VartimePrecomputedMultiscalarMul; +use window::{NafLookupTable5, NafLookupTable8}; #[allow(unused_imports)] use prelude::*; -pub struct PrecomputedStraus { - static_lookup_tables: Vec>, -} - -impl PrecomputedMultiscalarMul for PrecomputedStraus { - type Point = EdwardsPoint; - - fn new(static_points: I) -> Self - where - I: IntoIterator, - I::Item: Borrow, - { - PrecomputedStraus { - static_lookup_tables: static_points - .into_iter() - .map(|point| LookupTable::::from(point.borrow())) - .collect(), - } - } - - fn 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, - { - // This does two allocs for the scalar digits instead of - // putting them in a contiguous array, which makes handling - // the two kinds of lookup tables slightly easier. - // Use a ClearOnDrop wrapper. - - let static_scalar_digits_vec: Vec<_> = static_scalars - .into_iter() - .map(|s| s.borrow().to_radix_16()) - .collect(); - let static_scalar_digits = ClearOnDrop::new(static_scalar_digits_vec); - - let dynamic_scalar_digits_vec: Vec<_> = dynamic_scalars - .into_iter() - .map(|s| s.borrow().to_radix_16()) - .collect(); - let dynamic_scalar_digits = ClearOnDrop::new(dynamic_scalar_digits_vec); - - // Build lookup tables for dynamic points - let dynamic_lookup_tables: Vec<_> = dynamic_points - .into_iter() - .map(|point| LookupTable::::from(point.borrow())) - .collect(); - - let sp = self.static_lookup_tables.len(); - let dp = dynamic_lookup_tables.len(); - assert_eq!(sp, static_scalar_digits.len()); - assert_eq!(dp, dynamic_scalar_digits.len()); - - let mut R = ExtendedPoint::identity(); - for j in (0..64).rev() { - R = R.mul_by_pow_2(4); - for i in 0..dp { - let t_ij = dynamic_scalar_digits[i][j]; - R = &R + &dynamic_lookup_tables[i].select(t_ij); - } - for i in 0..sp { - let s_ij = static_scalar_digits[i][j]; - R = &R + &self.static_lookup_tables[i].select(s_ij); - } - } - - R.into() - } -} pub struct VartimePrecomputedStraus { static_lookup_tables: Vec>, diff --git a/src/edwards.rs b/src/edwards.rs index 3ab3e60..06f53ea 100644 --- a/src/edwards.rs +++ b/src/edwards.rs @@ -126,7 +126,7 @@ use traits::ValidityCheck; use traits::{Identity, IsIdentity}; #[cfg(any(feature = "alloc", feature = "std"))] -use traits::{MultiscalarMul, PrecomputedMultiscalarMul}; +use traits::MultiscalarMul; #[cfg(any(feature = "alloc", feature = "std"))] use traits::{VartimeMultiscalarMul, VartimePrecomputedMultiscalarMul}; @@ -673,13 +673,6 @@ impl VartimeMultiscalarMul for EdwardsPoint { } } -/// Precomputation for 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 EdwardsPrecomputation(scalar_mul::precomputed_straus::PrecomputedStraus); - /// 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 @@ -687,39 +680,6 @@ pub struct EdwardsPrecomputation(scalar_mul::precomputed_straus::PrecomputedStra #[cfg(feature = "alloc")] pub struct VartimeEdwardsPrecomputation(scalar_mul::precomputed_straus::VartimePrecomputedStraus); -#[cfg(feature = "alloc")] -impl PrecomputedMultiscalarMul for EdwardsPrecomputation { - type Point = EdwardsPoint; - - fn new(static_points: I) -> Self - where - I: IntoIterator, - I::Item: Borrow, - { - Self(scalar_mul::precomputed_straus::PrecomputedStraus::new( - static_points, - )) - } - - fn 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.0 - .mixed_multiscalar_mul(static_scalars, dynamic_scalars, dynamic_points) - } -} - #[cfg(feature = "alloc")] impl VartimePrecomputedMultiscalarMul for VartimeEdwardsPrecomputation { type Point = EdwardsPoint; @@ -1280,49 +1240,6 @@ mod test { assert!(P1.compress().to_bytes() == P2.compress().to_bytes()); } - #[test] - fn 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 = EdwardsPrecomputation::new(static_points.iter()); - - let P = precomputation.mixed_multiscalar_mul( - &static_scalars, - &dynamic_scalars, - &dynamic_points, - ); - - use traits::MultiscalarMul; - let Q = EdwardsPoint::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()); - } - #[test] fn vartime_precomputed_vs_nonprecomputed_multiscalar() { let mut rng = rand::thread_rng(); diff --git a/src/ristretto.rs b/src/ristretto.rs index 34e3c33..3851949 100644 --- a/src/ristretto.rs +++ b/src/ristretto.rs @@ -187,10 +187,7 @@ use scalar::Scalar; use traits::Identity; #[cfg(any(feature = "alloc", feature = "std"))] -use traits::{ - MultiscalarMul, PrecomputedMultiscalarMul, VartimeMultiscalarMul, - VartimePrecomputedMultiscalarMul, -}; +use traits::{MultiscalarMul, VartimeMultiscalarMul, VartimePrecomputedMultiscalarMul}; #[cfg(not(all( feature = "simd_backend", @@ -909,13 +906,6 @@ impl VartimeMultiscalarMul for RistrettoPoint { } } -/// Precomputation for 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 RistrettoPrecomputation(scalar_mul::precomputed_straus::PrecomputedStraus); - /// 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 @@ -923,42 +913,6 @@ pub struct RistrettoPrecomputation(scalar_mul::precomputed_straus::PrecomputedSt #[cfg(feature = "alloc")] pub struct VartimeRistrettoPrecomputation(scalar_mul::precomputed_straus::VartimePrecomputedStraus); -#[cfg(feature = "alloc")] -impl PrecomputedMultiscalarMul for RistrettoPrecomputation { - type Point = RistrettoPoint; - - fn new(static_points: I) -> Self - where - I: IntoIterator, - I::Item: Borrow, - { - Self(scalar_mul::precomputed_straus::PrecomputedStraus::new( - static_points.into_iter().map(|P| P.borrow().0), - )) - } - - fn 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, - { - RistrettoPoint(self.0.mixed_multiscalar_mul( - static_scalars, - dynamic_scalars, - dynamic_points.into_iter().map(|P| P.borrow().0), - )) - } -} - #[cfg(feature = "alloc")] impl VartimePrecomputedMultiscalarMul for VartimeRistrettoPrecomputation { type Point = RistrettoPoint; @@ -1366,49 +1320,6 @@ mod test { } } - #[test] - fn 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 = RistrettoPrecomputation::new(static_points.iter()); - - let P = precomputation.mixed_multiscalar_mul( - &static_scalars, - &dynamic_scalars, - &dynamic_points, - ); - - use traits::MultiscalarMul; - let Q = RistrettoPoint::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()); - } - #[test] fn vartime_precomputed_vs_nonprecomputed_multiscalar() { let mut rng = rand::thread_rng(); diff --git a/src/traits.rs b/src/traits.rs index c1166f5..5297aa4 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -225,86 +225,6 @@ pub trait VartimeMultiscalarMul { } } -/// A trait for constant-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. -pub trait PrecomputedMultiscalarMul: Sized { - /// The type of point to be multiplied, e.g., `RistrettoPoint`. - type Point; - - /// 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 (possibly secret) - /// scalars \\(b_i\\), `dynamic_scalars`, an iterator of (possibly - /// secret) 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 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; - - /// Given `static_scalars`, an iterator of (possibly secret) - /// 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 multiscalar_mul(&self, static_scalars: I) -> Self::Point - where - I: IntoIterator, - I::Item: Borrow, - { - use core::iter; - - Self::mixed_multiscalar_mul( - self, - static_scalars, - iter::empty::(), - iter::empty::(), - ) - } -} - /// A trait for variable-time multiscalar multiplication with precomputation. /// /// A general multiscalar multiplication with precomputation can be written as From 2c3629b30eba4671991cfe5157135df775c3a16c Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Thu, 14 Feb 2019 07:16:20 -0800 Subject: [PATCH 11/12] Add a separate benchmark for pure-fixed multiscalar mul. --- benches/dalek_benchmarks.rs | 54 +++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/benches/dalek_benchmarks.rs b/benches/dalek_benchmarks.rs index e2e3f5b..ad1e0e6 100644 --- a/benches/dalek_benchmarks.rs +++ b/benches/dalek_benchmarks.rs @@ -59,7 +59,7 @@ mod edwards_benches { }); } - criterion_group!{ + criterion_group! { name = edwards_benches; config = Criterion::default(); targets = @@ -110,10 +110,29 @@ mod multiscalar_benches { ); } - fn precomputed_vt_straus_helper(c: &mut Criterion, dynamic_fraction: f64) { + 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_scalars, static_points) = construct(static_size); + + use curve25519_dalek::edwards::VartimeEdwardsPrecomputation; + use curve25519_dalek::traits::VartimePrecomputedMultiscalarMul; + + let precomp = VartimeEdwardsPrecomputation::new(&static_points); + + b.iter(|| precomp.vartime_multiscalar_mul(&static_scalars)); + }, + &MULTISCALAR_SIZES, + ); + } + + fn vartime_precomputed_helper(c: &mut Criterion, dynamic_fraction: f64) { let label = format!( - "Variable-time mixed-base Straus ({:.2}pct dyn)", - 100.0*dynamic_fraction, + "Variable-time mixed-base multiscalar multiplication ({:.0}pct dyn)", + 100.0 * dynamic_fraction, ); c.bench_function_over_inputs( &label, @@ -141,28 +160,29 @@ mod multiscalar_benches { ); } - fn precomputed_vt_straus_00_pct_dynamic(c: &mut Criterion) { - precomputed_vt_straus_helper(c, 0.0); + fn vartime_precomputed_00_pct_dynamic(c: &mut Criterion) { + vartime_precomputed_helper(c, 0.0); } - fn precomputed_vt_straus_20_pct_dynamic(c: &mut Criterion) { - precomputed_vt_straus_helper(c, 0.2); + fn vartime_precomputed_20_pct_dynamic(c: &mut Criterion) { + vartime_precomputed_helper(c, 0.2); } - fn precomputed_vt_straus_50_pct_dynamic(c: &mut Criterion) { - precomputed_vt_straus_helper(c, 0.5); + fn vartime_precomputed_50_pct_dynamic(c: &mut Criterion) { + vartime_precomputed_helper(c, 0.5); } - criterion_group!{ + criterion_group! { name = multiscalar_benches; // Lower the sample size to run the benchmarks faster config = Criterion::default().sample_size(15); targets = consttime_multiscalar_mul, vartime_multiscalar_mul, - precomputed_vt_straus_00_pct_dynamic, - precomputed_vt_straus_20_pct_dynamic, - precomputed_vt_straus_50_pct_dynamic, + vartime_precomputed_pure_static, + vartime_precomputed_00_pct_dynamic, + vartime_precomputed_20_pct_dynamic, + vartime_precomputed_50_pct_dynamic, } } @@ -198,7 +218,7 @@ mod ristretto_benches { ); } - criterion_group!{ + criterion_group! { name = ristretto_benches; config = Criterion::default(); targets = @@ -219,7 +239,7 @@ mod montgomery_benches { }); } - criterion_group!{ + criterion_group! { name = montgomery_benches; config = Criterion::default(); targets = montgomery_ladder, @@ -251,7 +271,7 @@ mod scalar_benches { ); } - criterion_group!{ + criterion_group! { name = scalar_benches; config = Criterion::default(); targets = From 47967b49f085e108d0cf6bd42846276086e0a617 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Thu, 14 Feb 2019 07:59:45 -0800 Subject: [PATCH 12/12] Use rerandomized inputs for variable-time benchmarks. This avoids potentially misleading benchmark results where the memory cost of precomputation becomes "free" as re-running the benchmark loop lifts exactly the required table entries into the highest-level caches. --- benches/dalek_benchmarks.rs | 113 ++++++++++++++++++++++++------------ 1 file changed, 76 insertions(+), 37 deletions(-) diff --git a/benches/dalek_benchmarks.rs b/benches/dalek_benchmarks.rs index ad1e0e6..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; @@ -51,11 +53,13 @@ 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, + ); }); } @@ -75,25 +79,39 @@ 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) { - let mut rng = OsRng::new().unwrap(); - let scalars: Vec = (0..n).map(|_| Scalar::random(&mut rng)).collect(); - let points: Vec = scalars - .iter() - .map(|s| s * &constants::ED25519_BASEPOINT_TABLE) - .collect(); - (scalars, points) + (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 (scalars, points) = construct(size); - 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, ); @@ -103,8 +121,16 @@ mod multiscalar_benches { c.bench_function_over_inputs( "Variable-time variable-base multiscalar multiplication", |b, &&size| { - let (scalars, points) = construct(size); - 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, ); @@ -116,14 +142,17 @@ mod multiscalar_benches { move |b, &&total_size| { let static_size = total_size; - let (static_scalars, static_points) = construct(static_size); - - use curve25519_dalek::edwards::VartimeEdwardsPrecomputation; - use curve25519_dalek::traits::VartimePrecomputedMultiscalarMul; - + let static_points = construct_points(static_size); let precomp = VartimeEdwardsPrecomputation::new(&static_points); - - b.iter(|| precomp.vartime_multiscalar_mul(&static_scalars)); + // 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, ); @@ -140,21 +169,31 @@ mod multiscalar_benches { let dynamic_size = ((total_size as f64) * dynamic_fraction) as usize; let static_size = total_size - dynamic_size; - let (static_scalars, static_points) = construct(static_size); - let (dynamic_scalars, dynamic_points) = construct(dynamic_size); - - use curve25519_dalek::edwards::VartimeEdwardsPrecomputation; - use curve25519_dalek::traits::VartimePrecomputedMultiscalarMul; - + let static_points = construct_points(static_size); + let dynamic_points = construct_points(dynamic_size); let precomp = VartimeEdwardsPrecomputation::new(&static_points); - - b.iter(|| { - precomp.vartime_mixed_multiscalar_mul( - &static_scalars, - &dynamic_scalars, - &dynamic_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, );