From c6acdfd5e2b74e6b2d29cc320f1c8defe979f624 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 12 Feb 2019 11:18:03 -0800 Subject: [PATCH] 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};