From a3f53c9134096fbf7ec02c2656cf501abb91a956 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Wed, 6 Dec 2017 17:29:41 -0800 Subject: [PATCH] Refactor select_precomputed_point into a LookupTable struct. Originally this was for hygiene, so that we could erase points from heap-allocated memory in multiscalar_mult, but it ends up providing a cleaner API for scalar multiplication. It's kept inside curve_models for now, but it could go somewhere else if that's a better place. --- build.rs | 1 + src/curve_models/mod.rs | 2 + src/curve_models/window.rs | 122 +++++++++++++++++++++++++++++++++++++ src/edwards.rs | 85 ++++++++++++-------------- src/traits.rs | 34 ----------- 5 files changed, 163 insertions(+), 81 deletions(-) create mode 100644 src/curve_models/window.rs diff --git a/build.rs b/build.rs index 7fa55da..28044c3 100644 --- a/build.rs +++ b/build.rs @@ -69,6 +69,7 @@ use backend::u32::field::FieldElement32; use edwards::EdwardsBasepointTable; +use curve_models::window::LookupTable; use curve_models::AffineNielsPoint; /// Table containing precomputed multiples of the basepoint `B = (x,4/5)`. diff --git a/src/curve_models/mod.rs b/src/curve_models/mod.rs index fedc1ae..b514405 100644 --- a/src/curve_models/mod.rs +++ b/src/curve_models/mod.rs @@ -134,6 +134,8 @@ use edwards::ExtendedPoint; use subtle::ConditionallyAssignable; use traits::ValidityCheck; +pub mod window; + // ------------------------------------------------------------------------ // Internal point representations // ------------------------------------------------------------------------ diff --git a/src/curve_models/window.rs b/src/curve_models/window.rs new file mode 100644 index 0000000..6a0091e --- /dev/null +++ b/src/curve_models/window.rs @@ -0,0 +1,122 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-dalek. +// Copyright (c) 2016-2017 Isis Lovecruft, Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - Isis Agora Lovecruft +// - Henry de Valence + +//! Code for fixed- and sliding-window functionality + +#![allow(non_snake_case)] + +use core::fmt::Debug; + +use subtle; +use subtle::ConditionallyNegatable; +use subtle::ConditionallyAssignable; + +use traits::Identity; + +/// A lookup table of precomputed multiples of a point \\(P\\), used to +/// compute \\( xP \\) for \\( -8 \leq x \leq 8 \\). +/// +/// The computation of \\( xP \\) is done in constant time by the `select` function. +/// +/// Since `LookupTable` does not implement `Index`, it's more difficult +/// to accidentally use the table directly. Unfortunately the table is +/// only `pub(crate)` so that we can write hardcoded constants, so it's +/// still technically possible. It would be nice to prevent direct +/// access to the table. +/// +/// XXX make this generic with respect to table size +#[derive(Copy, Clone)] +pub struct LookupTable(pub(crate) [T; 8]); + +use clear_on_drop::clear::ZeroSafe; + +/// This type isn't actually zeroable (all zero bytes are not valid +/// points), but we want to be able to use `clear_on_drop` to erase slices +/// of `LookupTable`. +/// +/// Since the `ZeroSafe` trait is only used by `clear_on_drop`, the only +/// situation where this would be a problem is if code attempted to use +/// a `ClearOnDrop` to erase a `LookupTable` and then used the table +/// afterwards. +/// +/// Normally this is not a problem, since the table's storage is usually +/// dropped too. +/// +/// XXX is this a good compromise? +unsafe impl ZeroSafe for LookupTable {} + +impl LookupTable +where T: Identity + ConditionallyAssignable + ConditionallyNegatable +{ + /// Given \\(-8 \leq x \leq 8\\), return \\(xP\\) in constant time. + pub fn select(&self, x: i8) -> T { + debug_assert!(x >= -8); debug_assert!(x <= 8); + + // Compute xabs = |x| + let xmask = x >> 7; + let xabs = (x + xmask) ^ xmask; + + // Set t = 0 * P = identity + let mut t = T::identity(); + for j in 1..9 { + // Copy `points[j-1] == j*P` onto `t` in constant time if `|x| == j`. + t.conditional_assign(&self.0[j-1], + subtle::bytes_equal(xabs as u8, j as u8)); + } + // Now t == |x| * P. + + let neg_mask = (xmask & 1) as u8; + t.conditional_negate(neg_mask); + // Now t == x * P. + + t + } +} + +impl Default for LookupTable { + fn default() -> LookupTable { + LookupTable([T::default(); 8]) + } +} + +impl Debug for LookupTable { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "LookupTable({:?})", self.0) + } +} + +use edwards::ExtendedPoint; +use curve_models::ProjectiveNielsPoint; +use curve_models::AffineNielsPoint; + +impl<'a> From<&'a ExtendedPoint> for LookupTable { + fn from(P: &'a ExtendedPoint) -> Self { + let mut points = [P.to_projective_niels(); 8]; + for j in 0..7 { + points[j+1] = (P + &points[j]) + .to_extended() + .to_projective_niels(); + } + LookupTable(points) + } +} + +impl<'a> From<&'a ExtendedPoint> for LookupTable { + fn from(P: &'a ExtendedPoint) -> Self { + let mut points = [P.to_affine_niels(); 8]; + // XXX batch inversion would be good if perf mattered here + for j in 0..7 { + points[j+1] = (P + &points[j]) + .to_extended() + .to_affine_niels() + } + LookupTable(points) + } +} diff --git a/src/edwards.rs b/src/edwards.rs index bd639ae..1f00d9b 100644 --- a/src/edwards.rs +++ b/src/edwards.rs @@ -43,11 +43,11 @@ use curve_models::CompletedPoint; use curve_models::AffineNielsPoint; use curve_models::ProjectiveNielsPoint; +use curve_models::window::LookupTable; + use traits::{Identity, IsIdentity}; use traits::ValidityCheck; -use traits::select_precomputed_point; - // ------------------------------------------------------------------------ // Compressed points // ------------------------------------------------------------------------ @@ -433,12 +433,7 @@ impl<'a, 'b> Mul<&'b Scalar> for &'a ExtendedPoint { /// `EdwardsBasepointTable` is approximately 4x faster. fn mul(self, scalar: &'b Scalar) -> ExtendedPoint { // Construct a lookup table of [P,2P,3P,4P,5P,6P,7P,8P] - let P = self.to_projective_niels(); - let mut lookup_table: [ProjectiveNielsPoint; 8] = [P; 8]; - for i in 0..7 { - lookup_table[i+1] = (self + &lookup_table[i]) - .to_extended().to_projective_niels(); - } + let lookup_table = LookupTable::::from(self); // Setting s = scalar, compute // @@ -456,13 +451,12 @@ impl<'a, 'b> Mul<&'b Scalar> for &'a ExtendedPoint { // We sum right-to-left. let mut Q = ExtendedPoint::identity(); for i in (0..64).rev() { - // Q = 16*Q + // Q <-- 16*Q Q = Q.mult_by_pow_2(4); - // R = s_i * Q - let R = select_precomputed_point(scalar_digits[i], &lookup_table); - // Q = Q + R - Q = (&Q + &R).to_extended(); + // Q <-- Q + P * s_i + Q = (&Q + &lookup_table.select(scalar_digits[i])).to_extended() } + Q } } @@ -502,17 +496,14 @@ pub fn multiscalar_mult<'a, 'b, I, J>(scalars: I, points: J) -> ExtendedPoint J: IntoIterator { //assert_eq!(scalars.len(), points.len()); + + use clear_on_drop::ClearOnDrop; - let lookup_tables: Vec<_> = points.into_iter() - .map(|P_i| { - // Construct a lookup table of [P_i,2*P_i,3*P_i,4*P_i,5*P_i,6*P_i,7*P_i] - let mut lookup_table = [P_i.to_projective_niels(); 8]; - for j in 0..7 { - lookup_table[j+1] = (P_i + &lookup_table[j]) - .to_extended().to_projective_niels(); - } - lookup_table - }).collect(); + let lookup_tables_vec: Vec<_> = points.into_iter() + .map(|P| LookupTable::::from(P) ) + .collect(); + + let lookup_tables = ClearOnDrop::new(lookup_tables_vec); // Setting s_i = i-th scalar, compute // @@ -520,12 +511,12 @@ pub fn multiscalar_mult<'a, 'b, I, J>(scalars: I, points: J) -> ExtendedPoint // // 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.to_radix_16()).collect(); + .map(|c| c.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. - use clear_on_drop::ClearOnDrop; let scalar_digits = ClearOnDrop::new(scalar_digits_vec); // Compute s_1*P_1 + ... + s_n*P_n: since @@ -554,7 +545,7 @@ pub fn multiscalar_mult<'a, 'b, I, J>(scalars: I, points: J) -> ExtendedPoint 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 = select_precomputed_point(s_i[j], lookup_table_i); + let R_i = lookup_table_i.select(s_i[j]); // Q = Q + R_i Q = (&Q + &R_i).to_extended(); } @@ -569,7 +560,7 @@ pub fn multiscalar_mult<'a, 'b, I, J>(scalars: I, points: J) -> ExtendedPoint /// The basepoint tables are reasonably large (30KB), so they should /// probably be boxed. #[derive(Clone)] -pub struct EdwardsBasepointTable(pub(crate) [[AffineNielsPoint; 8]; 32]); +pub struct EdwardsBasepointTable(pub(crate) [LookupTable; 32]); impl EdwardsBasepointTable { /// The computation uses Pippeneger's algorithm, as described on @@ -578,7 +569,7 @@ impl EdwardsBasepointTable { /// $$ /// a = a\_0 + a\_1 16\^1 + \cdots + a\_{63} 16\^{63}, /// $$ - /// with \\(-8 \leq a_i < 8\\). Then + /// with \\(-8 \leq a_i < 8\\), \\(-8 \leq a\_{63} \leq 8\\). Then /// $$ /// a B = a\_0 B + a\_1 16\^1 B + \cdots + a\_{63} 16\^{63} B. /// $$ @@ -591,25 +582,28 @@ impl EdwardsBasepointTable { /// + 16(a\_1 16\^0 B +& a\_3 16\^2 B + \cdots + a\_{63} 16\^{62} B). \\\\ /// \end{aligned} /// $$ - /// We then use the `select_precomputed_point` function, which - /// takes \\(-8 \leq x < 8\\) and \\([16\^{2i} B, \ldots, 8\cdot16\^{2i} B]\\), - /// and returns \\(x \cdot 16\^{2i} \cdot B\\) in constant time. + /// For each \\(i = 0 \ldots 31\\), we create a lookup table of + /// $$ + /// [16\^{2i} B, \ldots, 8\cdot16\^{2i} B], + /// $$ + /// and use it to select \\( x \cdot 16\^{2i} \cdot B \\) in constant time. /// /// The radix-\\(16\\) representation requires that the scalar is bounded /// by \\(2\^{255}\\), which is always the case. fn basepoint_mul(&self, scalar: &Scalar) -> ExtendedPoint { let a = scalar.to_radix_16(); + let tables = &self.0; let mut P = ExtendedPoint::identity(); for i in (0..64).filter(|x| x % 2 == 1) { - P = (&P + &select_precomputed_point(a[i], &self.0[i/2])).to_extended(); + P = (&P + &tables[i/2].select(a[i])).to_extended(); } P = P.mult_by_pow_2(4); for i in (0..64).filter(|x| x % 2 == 0) { - P = (&P + &select_precomputed_point(a[i], &self.0[i/2])).to_extended(); + P = (&P + &tables[i/2].select(a[i])).to_extended(); } P @@ -640,29 +634,24 @@ impl<'a, 'b> Mul<&'a EdwardsBasepointTable> for &'b Scalar { impl EdwardsBasepointTable { /// Create a table of precomputed multiples of `basepoint`. pub fn create(basepoint: &ExtendedPoint) -> EdwardsBasepointTable { - // Create the table storage - // XXX can we skip the initialization without too much unsafety? - // stick 30K on the stack and call it a day. - let mut table = EdwardsBasepointTable([[AffineNielsPoint::identity(); 8]; 32]); + // XXX use init_with + let mut table = EdwardsBasepointTable([LookupTable::default(); 32]); let mut P = *basepoint; for i in 0..32 { // P = (16^2)^i * B - let mut jP = P.to_affine_niels(); - for j in 1..9 { - // table[i][j-1] is supposed to be j*(16^2)^i*B - table.0[i][j-1] = jP; - jP = (&P + &jP).to_extended().to_affine_niels(); - } + table.0[i] = LookupTable::from(&P); P = P.mult_by_pow_2(8); } table } /// Get the basepoint for this table as an `ExtendedPoint`. + /// + /// XXX maybe this would be better as a `From` impl pub fn basepoint(&self) -> ExtendedPoint { - // self.0[0][0] has 1*(16^2)^0*B, but as an `AffineNielsPoint` - // Add identity to convert to extended. - (&ExtendedPoint::identity() + &self.0[0][0]).to_extended() + // self.0[0].select(1) = 1*(16^2)^0*B + // but as an `AffineNielsPoint`, so add identity to convert to extended. + (&ExtendedPoint::identity() + &self.0[0].select(1)).to_extended() } } @@ -1274,7 +1263,9 @@ mod bench { #[bench] #[cfg(feature="precomputed_tables")] fn bench_select_precomputed_point(b: &mut Bencher) { - b.iter(|| select_precomputed_point(0, &constants::ED25519_BASEPOINT_TABLE.0[0])); + use test::black_box; + let table = &constants::ED25519_BASEPOINT_TABLE.0[0]; + b.iter(|| table.select(black_box(5)) ); } #[bench] diff --git a/src/traits.rs b/src/traits.rs index aaac49a..b0beeb9 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -10,11 +10,7 @@ //! Module for common traits. -use core::ops::Neg; - use subtle; -use subtle::ConditionallyAssignable; -use subtle::ConditionallyNegatable; // ------------------------------------------------------------------------ // Public Traits @@ -55,33 +51,3 @@ pub(crate) trait ValidityCheck { /// Checks whether the point is on the curve. Not CT. fn is_valid(&self) -> bool; } - -// This isn't a trait, but it is fully generic... - -/// Given precomputed points `[P, 2P, 3P, ..., 8P]`, as well as `-8 ≤ -/// x ≤ 8`, compute `x * B` in constant time, i.e., without branching -/// on x or using it as an array index. -pub(crate) fn select_precomputed_point(x: i8, points: &[T; 8]) -> T - where T: Identity + ConditionallyAssignable, for<'a> &'a T: Neg -{ - debug_assert!(x >= -8); debug_assert!(x <= 8); - - // Compute xabs = |x| - let xmask = x >> 7; - let xabs = (x + xmask) ^ xmask; - - // Set t = 0 * P = identity - let mut t = T::identity(); - for j in 1..9 { - // Copy `points[j-1] == j*P` onto `t` in constant time if `|x| == j`. - t.conditional_assign(&points[j-1], - subtle::bytes_equal(xabs as u8, j as u8)); - } - // Now t == |x| * P. - - let neg_mask = (xmask & 1) as u8; - t.conditional_negate(neg_mask); - // Now t == x * P. - - t -}