Add serial implementation of precomputation.

This commit is contained in:
Henry de Valence 2019-02-12 11:18:03 -08:00
parent 5daff66079
commit c6acdfd5e2
4 changed files with 380 additions and 11 deletions

View file

@ -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(

View file

@ -23,3 +23,6 @@ pub mod vartime_double_base;
#[cfg(feature = "alloc")]
pub mod straus;
#[cfg(feature = "alloc")]
pub mod precomputed_straus;

View file

@ -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 <hdevalence@hdevalence.ca>
//! 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<LookupTable<AffineNielsPoint>>,
}
impl PrecomputedMultiscalarMul for PrecomputedStraus {
type Point = EdwardsPoint;
fn new<I>(static_points: I) -> Self
where
I: IntoIterator,
I::Item: Borrow<Self::Point>,
{
PrecomputedStraus {
static_lookup_tables: static_points
.into_iter()
.map(|point| LookupTable::<AffineNielsPoint>::from(point.borrow()))
.collect(),
}
}
fn mixed_multiscalar_mul<I, J, K>(
&self,
static_scalars: I,
dynamic_scalars: J,
dynamic_points: K,
) -> Self::Point
where
I: IntoIterator,
I::Item: Borrow<Scalar>,
J: IntoIterator,
J::Item: Borrow<Scalar>,
K: IntoIterator,
K::Item: Borrow<Self::Point>,
{
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::<ProjectiveNielsPoint>::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<NafLookupTable8<AffineNielsPoint>>,
}
impl VartimePrecomputedMultiscalarMul for VartimePrecomputedStraus {
type Point = EdwardsPoint;
fn new<I>(static_points: I) -> Self
where
I: IntoIterator,
I::Item: Borrow<Self::Point>,
{
Self {
static_lookup_tables: static_points
.into_iter()
.map(|P| NafLookupTable8::<AffineNielsPoint>::from(P.borrow()))
.collect(),
}
}
fn vartime_mixed_multiscalar_mul<I, J, K>(
&self,
static_scalars: I,
dynamic_scalars: J,
dynamic_points: K,
) -> Self::Point
where
I: IntoIterator,
I::Item: Borrow<Scalar>,
J: IntoIterator,
J::Item: Borrow<Scalar>,
K: IntoIterator,
K::Item: Borrow<Self::Point>,
{
let static_nafs = static_scalars
.into_iter()
.map(|c| c.borrow().non_adjacent_form(5))
.collect::<Vec<_>>();
let dynamic_nafs: Vec<_> = dynamic_scalars
.into_iter()
.map(|c| c.borrow().non_adjacent_form(5))
.collect::<Vec<_>>();
let dynamic_lookup_tables = dynamic_points
.into_iter()
.map(|P| NafLookupTable5::<ProjectiveNielsPoint>::from(P.borrow()))
.collect::<Vec<_>>();
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()
}
}

View file

@ -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<I>(static_points: I) -> Self
where
I: IntoIterator,
I::Item: Borrow<Self::Point>,
{
Self(scalar_mul::precomputed_straus::PrecomputedStraus::new(
static_points,
))
}
fn mixed_multiscalar_mul<I, J, K>(
&self,
static_scalars: I,
dynamic_scalars: J,
dynamic_points: K,
) -> Self::Point
where
I: IntoIterator,
I::Item: Borrow<Scalar>,
J: IntoIterator,
J::Item: Borrow<Scalar>,
K: IntoIterator,
K::Item: Borrow<Self::Point>,
{
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<I>(static_points: I) -> Self
where
I: IntoIterator,
I::Item: Borrow<Self::Point>,
{
Self(scalar_mul::precomputed_straus::VartimePrecomputedStraus::new(static_points))
}
fn vartime_mixed_multiscalar_mul<I, J, K>(
&self,
static_scalars: I,
dynamic_scalars: J,
dynamic_points: K,
) -> Self::Point
where
I: IntoIterator,
I::Item: Borrow<Scalar>,
J: IntoIterator,
J::Item: Borrow<Scalar>,
K: IntoIterator,
K::Item: Borrow<Self::Point>,
{
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::<Vec<_>>();
let dynamic_scalars = (0..128)
.map(|_| Scalar::random(&mut rng))
.collect::<Vec<_>>();
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::<Vec<_>>();
let dynamic_points = dynamic_scalars.iter().map(|s| s * B).collect::<Vec<_>>();
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::<Vec<_>>();
let dynamic_scalars = (0..128)
.map(|_| Scalar::random(&mut rng))
.collect::<Vec<_>>();
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::<Vec<_>>();
let dynamic_points = dynamic_scalars.iter().map(|s| s * B).collect::<Vec<_>>();
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};