Pull out constant-time straus implementation

This commit is contained in:
Henry de Valence 2018-03-26 15:32:20 -07:00
parent ac739a3edd
commit 2864a422bc
6 changed files with 219 additions and 63 deletions

View file

@ -9,3 +9,5 @@
// - Henry de Valence <hdevalence@hdevalence.ca>
pub mod variable_base;
pub mod straus;

View file

@ -0,0 +1,57 @@
// -*- mode: rust; -*-
//
// This file is part of curve25519-dalek.
// Copyright (c) 2016-2018 Isis Lovecruft, Henry de Valence
// See LICENSE for licensing information.
//
// Authors:
// - Isis Agora Lovecruft <isis@patternsinthevoid.net>
// - Henry de Valence <hdevalence@hdevalence.ca>
#![allow(non_snake_case)]
use core::borrow::Borrow;
use clear_on_drop::ClearOnDrop;
use traits::Identity;
use scalar::Scalar;
use edwards::EdwardsPoint;
use scalar_mul::window::LookupTable;
use backend::avx2::edwards::{CachedPoint, ExtendedPoint};
/// Perform constant-time, variable-base scalar multiplication.
pub(crate) fn multiscalar_mul<I, J>(scalars: I, points: J) -> EdwardsPoint
where
I: IntoIterator,
I::Item: Borrow<Scalar>,
J: IntoIterator,
J::Item: Borrow<EdwardsPoint>,
{
// Construct a lookup table of [P,2P,3P,4P,5P,6P,7P,8P]
// for each input point P
let lookup_tables: Vec<_> = points
.into_iter()
.map(|point| {
let avx2_point = ExtendedPoint::from(*point.borrow());
LookupTable::<CachedPoint>::from(avx2_point)
})
.collect();
let scalar_digits_vec: Vec<_> = scalars
.into_iter()
.map(|s| s.borrow().to_radix_16())
.collect();
// Pass ownership to a ClearOnDrop wrapper
let scalar_digits = ClearOnDrop::new(scalar_digits_vec);
let mut Q = ExtendedPoint::identity();
for j in (0..64).rev() {
Q = Q.mul_by_pow_2(4);
let it = scalar_digits.iter().zip(lookup_tables.iter());
for (s_i, lookup_table_i) in it {
// Q = Q + s_{i,j} * P_i
Q = &Q + &lookup_table_i.select(s_i[j]);
}
}
Q.into()
}

View file

@ -547,8 +547,6 @@ impl<'a, 'b> Mul<&'b EdwardsPoint> for &'a Scalar {
///
/// assert_eq!(A1.compress(), (-A2).compress());
/// ```
// XXX later when we do more fancy multiscalar mults, we can delegate
// based on the iter's size hint -- hdevalence
#[cfg(any(feature = "alloc", feature = "std"))]
pub fn multiscalar_mul<I, J>(scalars: I, points: J) -> EdwardsPoint
where I: IntoIterator,
@ -556,70 +554,20 @@ pub fn multiscalar_mul<I, J>(scalars: I, points: J) -> EdwardsPoint
J: IntoIterator,
J::Item: Borrow<EdwardsPoint>,
{
// If we built with AVX2, use the AVX2 backend.
#[cfg(all(feature="nightly", all(feature="avx2_backend", target_feature="avx2")))] {
use backend::avx2::edwards as edwards_avx2;
// XXX later when we do more fancy multiscalar mults, we can
// delegate based on the iter's size hint -- hdevalence
edwards_avx2::multiscalar_mul(scalars, points)
// If we built with AVX2, use the AVX2 backend.
#[cfg(all(feature="nightly", all(feature="avx2_backend", target_feature="avx2")))]
{
use backend::avx2::scalar_mul::straus::multiscalar_mul;
multiscalar_mul(scalars, points)
}
// Otherwise, proceed as normal:
#[cfg(not(all(feature="nightly", all(feature="avx2_backend", target_feature="avx2"))))] {
//assert_eq!(scalars.len(), points.len());
use clear_on_drop::ClearOnDrop;
let lookup_tables_vec: Vec<_> = points.into_iter()
.map(|P| LookupTable::<ProjectiveNielsPoint>::from(P.borrow()) )
.collect();
let lookup_tables = ClearOnDrop::new(lookup_tables_vec);
// Setting s_i = i-th scalar, compute
//
// s_i = s_{i,0} + s_{i,1}*16^1 + ... + s_{i,63}*16^63,
//
// with `-8 ≤ s_{i,j} < 8` for `0 ≤ j < 63` and `-8 ≤ s_{i,63} ≤ 8`.
let scalar_digits_vec: Vec<_> = scalars.into_iter()
.map(|c| c.borrow().to_radix_16())
.collect();
// This above puts the scalar digits into a heap-allocated Vec.
// To ensure that these are erased, pass ownership of the Vec into a
// ClearOnDrop wrapper.
let scalar_digits = ClearOnDrop::new(scalar_digits_vec);
// Compute s_1*P_1 + ... + s_n*P_n: since
//
// s_i*P_i = P_i*(s_{i,0} + s_{i,1}*16^1 + ... + s_{i,63}*16^63)
// s_i*P_i = P_i*s_{i,0} + P_i*s_{i,1}*16^1 + ... + P_i*s_{i,63}*16^63
// s_i*P_i = P_i*s_{i,0} + 16*(P_i*s_{i,1} + 16*( ... + 16*P_i*s_{i,63})...)
//
// we have the two-dimensional sum
//
// s_1*P_1 = P_1*s_{1,0} + 16*(P_1*s_{1,1} + 16*( ... + 16*P_1*s_{1,63})...)
// + s_2*P_2 = + P_2*s_{2,0} + 16*(P_2*s_{2,1} + 16*( ... + 16*P_2*s_{2,63})...)
// ...
// + s_n*P_n = + P_n*s_{n,0} + 16*(P_n*s_{n,1} + 16*( ... + 16*P_n*s_{n,63})...)
//
// We sum column-wise top-to-bottom, then right-to-left,
// multiplying by 16 only once per column.
//
// This provides the speedup over doing n independent scalar
// mults: we perform 63 multiplications by 16 instead of 63*n
// multiplications, saving 252*(n-1) doublings.
let mut Q = EdwardsPoint::identity();
// XXX this impl makes no effort to be cache-aware; maybe it could be improved?
for j in (0..64).rev() {
Q = Q.mul_by_pow_2(4);
let it = scalar_digits.iter().zip(lookup_tables.iter());
for (s_i, lookup_table_i) in it {
// R_i = s_{i,j} * P_i
let R_i = lookup_table_i.select(s_i[j]);
// Q = Q + R_i
Q = (&Q + &R_i).to_extended();
}
}
Q
#[cfg(not(all(feature="nightly", all(feature="avx2_backend", target_feature="avx2"))))]
{
use scalar_mul::straus::multiscalar_mul;
multiscalar_mul(scalars, points)
}
}

View file

@ -11,3 +11,5 @@
pub mod window;
pub mod variable_base;
pub mod straus;

83
src/scalar_mul/straus.rs Normal file
View file

@ -0,0 +1,83 @@
// -*- mode: rust; -*-
//
// This file is part of curve25519-dalek.
// Copyright (c) 2016-2018 Isis Lovecruft, Henry de Valence
// See LICENSE for licensing information.
//
// Authors:
// - Isis Agora Lovecruft <isis@patternsinthevoid.net>
// - Henry de Valence <hdevalence@hdevalence.ca>
#![allow(non_snake_case)]
use core::borrow::Borrow;
use clear_on_drop::ClearOnDrop;
use traits::Identity;
use scalar::Scalar;
use edwards::EdwardsPoint;
use curve_models::ProjectiveNielsPoint;
use scalar_mul::window::LookupTable;
/// Perform constant-time, variable-base scalar multiplication.
pub(crate) fn multiscalar_mul<I, J>(scalars: I, points: J) -> EdwardsPoint
where
I: IntoIterator,
I::Item: Borrow<Scalar>,
J: IntoIterator,
J::Item: Borrow<EdwardsPoint>,
{
// Construct a lookup table of [P,2P,3P,4P,5P,6P,7P,8P]
// for each input point P
let lookup_tables: Vec<_> = points
.into_iter()
.map(|point| LookupTable::<ProjectiveNielsPoint>::from(point.borrow()))
.collect();
// Setting s_i = i-th scalar, compute
//
// s_i = s_{i,0} + s_{i,1}*16^1 + ... + s_{i,63}*16^63,
//
// with `-8 ≤ s_{i,j} < 8` for `0 ≤ j < 63` and `-8 ≤ s_{i,63} ≤ 8`.
//
// This puts the scalar digits into a heap-allocated Vec.
// To ensure that these are erased, pass ownership of the Vec into a
// ClearOnDrop wrapper.
let scalar_digits_vec: Vec<_> = scalars
.into_iter()
.map(|s| s.borrow().to_radix_16())
.collect();
let scalar_digits = ClearOnDrop::new(scalar_digits_vec);
// Compute s_1*P_1 + ... + s_n*P_n: since
//
// s_i*P_i = P_i*(s_{i,0} + s_{i,1}*16^1 + ... + s_{i,63}*16^63)
// s_i*P_i = P_i*s_{i,0} + P_i*s_{i,1}*16^1 + ... + P_i*s_{i,63}*16^63
// s_i*P_i = P_i*s_{i,0} + 16*(P_i*s_{i,1} + 16*( ... + 16*P_i*s_{i,63})...)
//
// we have the two-dimensional sum
//
// s_1*P_1 = P_1*s_{1,0} + 16*(P_1*s_{1,1} + 16*( ... + 16*P_1*s_{1,63})...)
// + s_2*P_2 = + P_2*s_{2,0} + 16*(P_2*s_{2,1} + 16*( ... + 16*P_2*s_{2,63})...)
// ...
// + s_n*P_n = + P_n*s_{n,0} + 16*(P_n*s_{n,1} + 16*( ... + 16*P_n*s_{n,63})...)
//
// We sum column-wise top-to-bottom, then right-to-left,
// multiplying by 16 only once per column.
//
// This provides the speedup over doing n independent scalar
// mults: we perform 63 multiplications by 16 instead of 63*n
// multiplications, saving 252*(n-1) doublings.
let mut Q = EdwardsPoint::identity();
for j in (0..64).rev() {
Q = Q.mul_by_pow_2(4);
let it = scalar_digits.iter().zip(lookup_tables.iter());
for (s_i, lookup_table_i) in it {
// R_i = s_{i,j} * P_i
let R_i = lookup_table_i.select(s_i[j]);
// Q = Q + R_i
Q = (&Q + &R_i).to_extended();
}
}
Q
}

View file

@ -0,0 +1,64 @@
// -*- mode: rust; -*-
//
// This file is part of curve25519-dalek.
// Copyright (c) 2016-2018 Isis Lovecruft, Henry de Valence
// See LICENSE for licensing information.
//
// Authors:
// - Isis Agora Lovecruft <isis@patternsinthevoid.net>
// - Henry de Valence <hdevalence@hdevalence.ca>
#![allow(non_snake_case)]
use core::ops::{Add, Sub};
use core::borrow::Borrow;
use scalar::Scalar;
//use super::window::OddLookupTable;
use scalar_mul::window::OddLookupTable;
use traits::{Doubleable, Identity};
/// Perform variable-time, variable-base scalar multiplication.
pub(crate) fn multiscalar_mul<I, J, ExtendedPoint, CompletedPoint, ProjectivePoint, CachedPoint>(
scalars: I,
points: J,
) -> ExtendedPoint
where
I: IntoIterator,
I::Item: Borrow<Scalar>,
J: IntoIterator,
J::Item: Borrow<ExtendedPoint>,
for<'a> OddLookupTable<CachedPoint>: From<&'a ExtendedPoint>,
for<'a, 'b> &'a ExtendedPoint: Add<&'b CachedPoint, Output = CompletedPoint>,
for<'a, 'b> &'a ExtendedPoint: Sub<&'b CachedPoint, Output = CompletedPoint>,
ExtendedPoint: Identity + From<CompletedPoint> + From<ProjectivePoint>,
ProjectivePoint: Identity + Doubleable<Output = CompletedPoint> + From<CompletedPoint>,
CachedPoint: Copy,
{
let nafs: Vec<_> = scalars
.into_iter()
.map(|c| c.borrow().non_adjacent_form())
.collect();
let lookup_tables: Vec<_> = points
.into_iter()
.map(|P| OddLookupTable::from(P.borrow()))
.collect();
let mut r = ProjectivePoint::identity();
for i in (0..255).rev() {
let mut t: CompletedPoint = r.double();
for (naf, lookup_table) in nafs.iter().zip(lookup_tables.iter()) {
if naf[i] > 0 {
t = &ExtendedPoint::from(t) + &lookup_table.select(naf[i] as usize);
} else if naf[i] < 0 {
t = &ExtendedPoint::from(t) - &lookup_table.select(-naf[i] as usize);
}
}
r = ProjectivePoint::from(t);
}
ExtendedPoint::from(r)
}