From d71b6650d68d74c7e5560628d785d4f2f53329ca Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Wed, 23 Jan 2019 14:56:31 -0700 Subject: [PATCH 01/16] Check the equality of `EdwardsPoint`s in the projective coordinates to avoid expensive compressions. --- src/edwards.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/edwards.rs b/src/edwards.rs index 102a5ea..b69c670 100644 --- a/src/edwards.rs +++ b/src/edwards.rs @@ -394,7 +394,14 @@ impl ConditionallySelectable for EdwardsPoint { impl ConstantTimeEq for EdwardsPoint { fn ct_eq(&self, other: &EdwardsPoint) -> Choice { - self.compress().ct_eq(&other.compress()) + // We would like to check that the point (X/Z, Y/Z) is equal to + // the point (X'/Z', Y'/Z') without converting into affine + // coordinates (x, y) and (x', y'), which requires two inversions. + // We have that X = xZ and X' = x'Z'. Thus, x = x' is equivalent to + // (xZ)Z' = (x'Z')Z, and similarly for the y-coordinate. + + (&self.X * &other.Z).ct_eq(&(&other.X * &self.Z)) + & (&self.Y * &other.Z).ct_eq(&(&other.Y * &self.Z)) } } From 6503378fefe26fc7a6e9ce8c05bc840547027983 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Wed, 8 May 2019 21:04:13 -0700 Subject: [PATCH 02/16] Save 2.5% on variable-base scmul by squeezing some multiplications. This saves 4 point doublings by unwrapping the first loop iteration, as well as 63 field multiplications (one per iteration) by managing curve model choice explicitly. --- .../serial/scalar_mul/variable_base.rs | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/backend/serial/scalar_mul/variable_base.rs b/src/backend/serial/scalar_mul/variable_base.rs index 588a84e..2471375 100644 --- a/src/backend/serial/scalar_mul/variable_base.rs +++ b/src/backend/serial/scalar_mul/variable_base.rs @@ -3,7 +3,7 @@ use traits::Identity; use scalar::Scalar; use edwards::EdwardsPoint; -use backend::serial::curve_models::ProjectiveNielsPoint; +use backend::serial::curve_models::{ProjectiveNielsPoint, ProjectivePoint}; use window::LookupTable; /// Perform constant-time, variable-base scalar multiplication. @@ -23,10 +23,24 @@ pub(crate) fn mul(point: &EdwardsPoint, scalar: &Scalar) -> EdwardsPoint { // s*P = P*s_0 + 16*(P*s_1 + 16*(P*s_2 + 16*( ... + P*s_63)...)) // // We sum right-to-left. - let mut Q = EdwardsPoint::identity(); - for i in (0..64).rev() { - Q = Q.mul_by_pow_2(4); - Q = (&Q + &lookup_table.select(scalar_digits[i])).to_extended(); + + // Unwrap first loop iteration to save computing 16*identity + let mut tmp2 = ProjectivePoint::identity(); + let mut tmp3 = EdwardsPoint::identity(); + let mut tmp1 = &tmp3 + &lookup_table.select(scalar_digits[63]); + // Now tmp1 = s_63*P in P1xP1 coords + for i in (0..63).rev() { + tmp2 = tmp1.to_projective(); // tmp2 = (prev) in P2 coords + tmp1 = tmp2.double(); // tmp1 = 2*(prev) in P1xP1 coords + tmp2 = tmp1.to_projective(); // tmp2 = 2*(prev) in P2 coords + tmp1 = tmp2.double(); // tmp1 = 4*(prev) in P1xP1 coords + tmp2 = tmp1.to_projective(); // tmp2 = 4*(prev) in P2 coords + tmp1 = tmp2.double(); // tmp1 = 8*(prev) in P1xP1 coords + tmp2 = tmp1.to_projective(); // tmp2 = 8*(prev) in P2 coords + tmp1 = tmp2.double(); // tmp1 = 16*(prev) in P1xP1 coords + tmp3 = tmp1.to_extended(); // tmp3 = 16*(prev) in P3 coords + tmp1 = &tmp3 + &lookup_table.select(scalar_digits[i]); + // Now tmp1 = s_i*P + 16*(prev) in P1xP1 coords } - Q + tmp1.to_extended() } From b52c2053c1474eadf8586e72ff4f622c0238b926 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Tue, 21 May 2019 12:35:58 -0700 Subject: [PATCH 03/16] new pippenger radix 6/7/8 implementation --- src/scalar.rs | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/src/scalar.rs b/src/scalar.rs index a7a8310..ae2344e 100644 --- a/src/scalar.rs +++ b/src/scalar.rs @@ -961,6 +961,80 @@ impl Scalar { output } + /// Creates a representation of a Scalar in radix 64, 128 or 256 for use with the Pippenger algorithm. + /// For lower radix, use `to_radix_16`, which is used by the Straus multi-scalar multiplication. + /// Higher radixes are not supported to save cache space. Radix 256 is near-optimal even for very + /// large inputs. + /// + /// Radix below 64 or above 256 is prohibited. + /// This method returns digits in a fixed-sized array, excess digits are zeroes. + /// The second returned value is the number of digits. + /// + /// ## Scalar representation + /// + /// Radix \\(2\^r\\), with \\(n = ceil(256/r)\\) coefficients in \\([-(2\^r)/2,(2\^r)/2)\\), + /// i.e., scalar is represented using digits \\(a\_i\\) such that + /// $$ + /// a = a\_0 + a\_1 2\^1r + \cdots + a_{n-1} 2\^{r*(n-1)}, + /// $$ + /// with \\(-2\^r/2 \leq a_i < 2\^r/2\\) for \\(0 \leq i < (n-1)\\) and \\(-2\^r/2 \leq a_{n-1} \leq 2\^r/2\\). + /// + pub(crate) fn to_pippenger_radix(&self, r: usize) -> ([i8; 43], usize) { + debug_assert!(r >= 6); + debug_assert!(r <= 8); + + let digits_count = (256 + r - 1)/r as usize; + debug_assert!(digits_count <= 43); + + use byteorder::{ByteOrder, LittleEndian}; + + // Scalar formatted as four `u64`s with carry bit packed into the highest bit. + let mut scalar64x4 = [0u64; 4]; + LittleEndian::read_u64_into(&self.bytes, &mut scalar64x4[0..4]); + + let radix: u64 = 1 << r; + let window_mask: u64 = radix - 1; + + let mut carry = 0u64; + let mut digits = [0i8; 43]; + for i in 0..digits_count { + // Construct a buffer of bits of the scalar, starting at `bit_offset`. + let bit_offset = i*r; + let u64_idx = bit_offset / 64; + let bit_idx = bit_offset % 64; + + // Read the bits from the scalar + let bit_buf: u64; + if bit_idx < 64 - r || u64_idx == 3 { + // This window's bits are contained in a single u64, + // or it's the last u64 anyway. + bit_buf = scalar64x4[u64_idx] >> bit_idx; + } else { + // Combine the current u64's bits with the bits from the next u64 + bit_buf = (scalar64x4[u64_idx] >> bit_idx) | (scalar64x4[1+u64_idx] << (64 - bit_idx)); + } + + // Read the actual coefficient value from the window + let coef = carry + (bit_buf & window_mask); // coef = [0, 2^r) + + // Recenter coefficients from [0,2^r) to [-2^r/2, 2^r/2) + carry = (coef + (radix/2) as u64) >> r; + digits[i] = ((coef as i64) - (carry << r) as i64) as i8; + } + + // Apply the resulting carry to the last digit + // Since the highest bit of the 256-bit integer is 0, + // the last coefficient would always be in the lower half _inclusive_, + // so the carry in the end can be 1 iff the word equals 2^r/2. + // Since ±2^r/2 values are valid, to avoid adding an extra word, + // we allow the last word to touch the value 2^r/2. + // XXX: make sure tests cover this case, so the carry is non-zero and this line matters. + // Maybe it never happens to be non-zero for r=6/7/8?... + digits[digits_count-1] += (carry << r) as i8; + + (digits, digits_count) + } + /// Unpack this `Scalar` to an `UnpackedScalar` for faster arithmetic. pub(crate) fn unpack(&self) -> UnpackedScalar { UnpackedScalar::from_bytes(&self.bytes) @@ -1437,4 +1511,33 @@ mod test { assert_eq!(a * b, Scalar::one()); } } + + #[test] + fn test_pippenger_radix() { + use std::iter; + // For each valid radix it tests that 1000 random-ish scalars can be restored + // from the produced representation precisely. + for r in 6..9 { + for scalar in (2..100).map(|s| Scalar::from(s as u64).invert() ).chain(iter::once(-Scalar::one())) { + let (digits, digits_count) = scalar.to_pippenger_radix(r); + + let radix = Scalar::from((1< Date: Tue, 21 May 2019 13:31:50 -0700 Subject: [PATCH 04/16] cgs --- src/backend/serial/scalar_mul/mod.rs | 3 + src/backend/serial/scalar_mul/pippenger.rs | 207 +++++++++++++++++++++ src/backend/vector/scalar_mul/mod.rs | 3 + src/backend/vector/scalar_mul/pippenger.rs | 172 +++++++++++++++++ src/scalar.rs | 32 ++-- 5 files changed, 401 insertions(+), 16 deletions(-) create mode 100644 src/backend/serial/scalar_mul/pippenger.rs create mode 100644 src/backend/vector/scalar_mul/pippenger.rs diff --git a/src/backend/serial/scalar_mul/mod.rs b/src/backend/serial/scalar_mul/mod.rs index bec874b..8d859eb 100644 --- a/src/backend/serial/scalar_mul/mod.rs +++ b/src/backend/serial/scalar_mul/mod.rs @@ -26,3 +26,6 @@ pub mod straus; #[cfg(feature = "alloc")] pub mod precomputed_straus; + +#[cfg(feature = "alloc")] +pub mod pippenger; diff --git a/src/backend/serial/scalar_mul/pippenger.rs b/src/backend/serial/scalar_mul/pippenger.rs new file mode 100644 index 0000000..82cf1bd --- /dev/null +++ b/src/backend/serial/scalar_mul/pippenger.rs @@ -0,0 +1,207 @@ +// -*- 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 +// - Henry de Valence +// - Oleg Andreev + +//! Implementation of a variant of Pippenger's algorithm. + +#![allow(non_snake_case)] + +use core::borrow::Borrow; + +use edwards::EdwardsPoint; +use scalar::Scalar; +use traits::VartimeMultiscalarMul; + +#[allow(unused_imports)] +use prelude::*; + +/// Implements a version of Pippenger's algorithm. +/// +/// The algorithm works as follows: +/// +/// Let `n` be a number of point-scalar pairs. +/// Let `w` be a window of bits (6..8, chosen based on `n`, see cost factor). +/// +/// 1. Prepare `2^(w-1) - 1` buckets with indices `[1..2^(w-1))` initialized with identity points. +/// Bucket 0 is not needed as it would contain points multiplied by 0. +/// 2. Convert scalars to a radix-`2^w` representation with signed digits in `[-2^w/2, 2^w/2]`. +/// Note: only the last digit may equal `2^w/2`. +/// 3. Starting with the last window, for each point `i=[0..n)` add it to a a bucket indexed by +/// the point's scalar's value in the window. +/// 4. Once all points in a window are sorted into buckets, add buckets by multiplying each +/// by their index. Efficient way of doing it is to start with the last bucket and compute two sums: +/// intermediate sum from the last to the first, and the full sum made of all intermediate sums. +/// 5. Shift the resulting sum of buckets by `w` bits by using `w` doublings. +/// 6. Add to the return value. +/// 7. Repeat the loop. +/// +/// Approximate cost w/o wNAF optimizations (A = addition, D = doubling): +/// +/// ```ascii +/// cost = (n*A + 2*(2^w/2)*A + w*D + A)*256/w +/// | | | | | +/// | | | | looping over 256/w windows +/// | | | adding to the result +/// sorting points | shifting the sum by w bits (to the next window, starting from last window) +/// one by one | +/// into buckets adding/subtracting all buckets +/// multiplied by their indexes +/// using a sum of intermediate sums +/// ``` +/// +/// For large `n`, dominant factor is (n*256/w) additions. +/// However, if `w` is too big and `n` is not too big, then `(2^w/2)*A` could dominate. +/// Therefore, the optimal choice of `w` grows slowly as `n` grows. +/// +pub struct Pippenger; + +#[cfg(any(feature = "alloc", feature = "std"))] +impl VartimeMultiscalarMul for Pippenger { + type Point = EdwardsPoint; + + fn optional_multiscalar_mul(scalars: I, points: J) -> Option + where + I: IntoIterator, + I::Item: Borrow, + J: IntoIterator>, + { + use backend::serial::curve_models::{ProjectiveNielsPoint}; + use traits::Identity; + + let mut scalars = scalars.into_iter(); + let size = scalars.by_ref().size_hint().0; + + // Digit width in bits. As digit width grows, + // number of point additions goes down, but amount of + // buckets and bucket additions grows exponentially. + let w = if size < 500 { + 6 + } else if size < 800 { + 7 + } else { + 8 + }; + + let max_digit: usize = 1 << w; + let digits_count: usize = (256 + w - 1) / w; // == ceil(256/w) + let buckets_count: usize = max_digit / 2; // digits are signed+centered hence 2^w/2, excluding 0-th bucket + + // Collect optimized scalars and points in buffers for repeated access + // (scanning the whole set per digit position). + let scalars = scalars.into_iter() + .map(|s| s.borrow().to_pippenger_radix(w).0 ) + .collect::>(); + let points: Vec = match points + .into_iter() + .map(|p| p.map(|P| P.to_projective_niels())) + .collect::>>() { + Some(x) => x, + None => return None, + }; + + // Prepare 2^w/2 buckets. + // buckets[i] corresponds to a multiplication factor (i+1). + let mut buckets: Vec<_> = (0..buckets_count) + .map(|_| EdwardsPoint::identity()) + .collect(); + + let columns: Vec<_> = (0..digits_count).map(|digit_index| { + + // Clear the buckets when processing another digit. + for i in 0..buckets_count { + buckets[i] = EdwardsPoint::identity(); + } + + // Iterate over pairs of (point, scalar) + // and add/sub the point to the corresponding bucket. + // Note: if we add support for precomputed lookup tables, + // we'll be adding/subtractiong point premultiplied by `digits[i]` to buckets[0]. + for (digits, pt) in scalars.iter().zip(points.iter()) { + let digit = digits[digit_index]; + if digit > 0 { + let b = (digit - 1) as usize; + buckets[b] = (&buckets[b] + pt).to_extended(); + } else if digit < 0 { + let b = (-digit - 1) as usize; + buckets[b] = (&buckets[b] - pt).to_extended(); + } + } + + // Add the buckets applying the multiplication factor to each bucket. + // The most efficient way to do that is to have a single sum with two running sums: + // an intermediate sum from last bucket to the first, and a sum of intermediate sums. + // + // For example, to add buckets 1*A, 2*B, 3*C we need to add these points: + // C + // C B + // C B A Sum = C + (C+B) + (C+B+A) + let mut buckets_intermediate_sum = buckets[buckets_count - 1]; + let mut buckets_sum = buckets[buckets_count - 1]; + for i in (0..(buckets_count - 1)).rev() { + buckets_intermediate_sum += buckets[i]; + buckets_sum += buckets_intermediate_sum; + } + + buckets_sum + }) + .collect(); + // ^ Note: we collect points because if we chain .rev().fold() + // then the .map() will run in reversed order, producing incorrect digit values + // (they can only be produced in lo->hi order). + + // Add the intermediate per-digit results in hi->lo order + // so that we can minimize doublings. + Some(columns[0..(digits_count - 1)].iter().rev().fold( + columns[digits_count - 1], + |total, &p| total.mul_by_pow_2(w as u32) + p, + )) + } +} + +#[cfg(test)] +mod test { + use super::*; + use constants; + use scalar::Scalar; + + #[test] + fn test_vartime_pippenger() { + // Reuse points across different tests + let mut n = 512; + let x = Scalar::from(2128506u64).invert(); + let y = Scalar::from(4443282u64).invert(); + let points: Vec<_> = (0..n) + .map(|i| { + constants::ED25519_BASEPOINT_POINT * Scalar::from(1 + i as u64) + }) + .collect(); + let scalars: Vec<_> = (0..n) + .map(|i| x + (Scalar::from(i as u64)*y)) // fast way to make ~random but deterministic scalars + .collect(); + + let premultiplied: Vec = scalars + .iter() + .zip(points.iter()) + .map(|(sc, pt)| sc * pt) + .collect(); + + while n > 0 { + let scalars = &scalars[0..n].to_vec(); + let points = &points[0..n].to_vec(); + let control: EdwardsPoint = premultiplied[0..n].iter().sum(); + + let subject = Pippenger::vartime_multiscalar_mul(scalars.clone(), points.clone()); + + assert_eq!(subject.compress(), control.compress()); + + n = n / 2; + } + } +} diff --git a/src/backend/vector/scalar_mul/mod.rs b/src/backend/vector/scalar_mul/mod.rs index 5c8734d..bdd6c4a 100644 --- a/src/backend/vector/scalar_mul/mod.rs +++ b/src/backend/vector/scalar_mul/mod.rs @@ -17,3 +17,6 @@ pub mod straus; #[cfg(feature = "alloc")] pub mod precomputed_straus; + +#[cfg(feature = "alloc")] +pub mod pippenger; diff --git a/src/backend/vector/scalar_mul/pippenger.rs b/src/backend/vector/scalar_mul/pippenger.rs new file mode 100644 index 0000000..4ec72f6 --- /dev/null +++ b/src/backend/vector/scalar_mul/pippenger.rs @@ -0,0 +1,172 @@ +// -*- 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 +// - Henry de Valence +// - Oleg Andreev + +#![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 window::{LookupTable, NafLookupTable5}; +use traits::{Identity, MultiscalarMul, VartimeMultiscalarMul}; + +#[allow(unused_imports)] +use prelude::*; + +/// Implements a version of Pippenger's algorithm. +/// +/// See the documentation in the serial `scalar_mul::pippenger` module for details. +pub struct Pippenger; + +#[cfg(any(feature = "alloc", feature = "std"))] +impl VartimeMultiscalarMul for Pippenger { + type Point = EdwardsPoint; + + fn optional_multiscalar_mul(scalars: I, points: J) -> Option + where + I: IntoIterator, + I::Item: Borrow, + J: IntoIterator>, + { + let mut scalars = scalars.into_iter(); + let size = scalars.by_ref().size_hint().0; + let w = if size < 500 { + 6 + } else if size < 800 { + 7 + } else { + 8 + }; + + let max_digit: usize = 1 << w; + let digits_count: usize = (256 + w - 1) / w; // == ceil(256/w) + let buckets_count: usize = max_digit / 2; // digits are signed+centered hence 2^w/2, excluding 0-th bucket + + // Collect optimized scalars and points in buffers for repeated access + // (scanning the whole set per digit position). + let scalars = scalars.into_iter() + .map(|s| s.borrow().to_pippenger_radix(w).0 ) + .collect::>(); + let points: Vec = match points + .into_iter() + .map(|p| p.map(|P| CachedPoint::from(ExtendedPoint::from(P)))) + .collect::>>() { + Some(x) => x, + None => return None, + }; + + // Prepare 2^w/2 buckets. + // buckets[i] corresponds to a multiplication factor (i+1). + let mut buckets: Vec<_> = (0..buckets_count) + .map(|_| ExtendedPoint::identity()) + .collect(); + + let columns: Vec = (0..digits_count).map(|digit_index| { + + // Clear the buckets when processing another digit. + for i in 0..buckets_count { + buckets[i] = ExtendedPoint::identity(); + } + + // Iterate over pairs of (point, scalar) + // and add/sub the point to the corresponding bucket. + // Note: if we add support for precomputed lookup tables, + // we'll be adding/subtractiong point premultiplied by `digits[i]` to buckets[0]. + for (digits, pt) in scalars.iter().zip(points.iter()) { + let digit = digits[digit_index]; + if digit > 0 { + let b = (digit - 1) as usize; + buckets[b] = &buckets[b] + pt; + } else if digit < 0 { + let b = (-digit - 1) as usize; + buckets[b] = &buckets[b] - pt; + } + } + + // Add the buckets applying the multiplication factor to each bucket. + // The most efficient way to do that is to have a single sum with two running sums: + // an intermediate sum from last bucket to the first, and a sum of intermediate sums. + // + // For example, to add buckets 1*A, 2*B, 3*C we need to add these points: + // C + // C B + // C B A Sum = C + (C+B) + (C+B+A) + let mut buckets_intermediate_sum = buckets[buckets_count - 1]; + let mut buckets_sum = buckets[buckets_count - 1]; + for i in (0..(buckets_count - 1)).rev() { + buckets_intermediate_sum = &buckets_intermediate_sum + &buckets[i]; + buckets_sum = &buckets_sum + &buckets_intermediate_sum; + } + + buckets_sum + }) + .collect(); + // ^ Note: we collect points because if we chain .rev().fold() + // then the .map() will run in reversed order, producing incorrect digit values + // (they can only be produced in lo->hi order). + + // Add the intermediate per-digit results in hi->lo order + // so that we can minimize doublings. + Some( + columns[0..(digits_count - 1)] + .iter() + .rev() + .fold(columns[digits_count - 1], |total, &p| { + &total.mul_by_pow_2(w as u32) + &p + }) + .into(), + ) + } +} + +#[cfg(test)] +mod test { + use super::*; + use constants; + use scalar::Scalar; + + #[test] + fn test_vartime_pippenger() { + // Reuse points across different tests + let mut n = 512; + let x = Scalar::from(2128506u64).invert(); + let y = Scalar::from(4443282u64).invert(); + let points: Vec<_> = (0..n) + .map(|i| { + constants::ED25519_BASEPOINT_POINT * Scalar::from(1 + i as u64) + }) + .collect(); + let scalars: Vec<_> = (0..n) + .map(|i| x + (Scalar::from(i as u64)*y)) // fast way to make ~random but deterministic scalars + .collect(); + + let premultiplied: Vec = scalars + .iter() + .zip(points.iter()) + .map(|(sc, pt)| sc * pt) + .collect(); + + while n > 0 { + let scalars = &scalars[0..n].to_vec(); + let points = &points[0..n].to_vec(); + let control: EdwardsPoint = premultiplied[0..n].iter().sum(); + + let subject = Pippenger::vartime_multiscalar_mul(scalars.clone(), points.clone()); + + assert_eq!(subject.compress(), control.compress()); + + n = n / 2; + } + } +} \ No newline at end of file diff --git a/src/scalar.rs b/src/scalar.rs index ae2344e..73b6d82 100644 --- a/src/scalar.rs +++ b/src/scalar.rs @@ -972,18 +972,18 @@ impl Scalar { /// /// ## Scalar representation /// - /// Radix \\(2\^r\\), with \\(n = ceil(256/r)\\) coefficients in \\([-(2\^r)/2,(2\^r)/2)\\), + /// Radix \\(2\^w\\), with \\(n = ceil(256/w)\\) coefficients in \\([-(2\^w)/2,(2\^w)/2)\\), /// i.e., scalar is represented using digits \\(a\_i\\) such that /// $$ - /// a = a\_0 + a\_1 2\^1r + \cdots + a_{n-1} 2\^{r*(n-1)}, + /// a = a\_0 + a\_1 2\^1w + \cdots + a_{n-1} 2\^{w*(n-1)}, /// $$ - /// with \\(-2\^r/2 \leq a_i < 2\^r/2\\) for \\(0 \leq i < (n-1)\\) and \\(-2\^r/2 \leq a_{n-1} \leq 2\^r/2\\). + /// with \\(-2\^w/2 \leq a_i < 2\^w/2\\) for \\(0 \leq i < (n-1)\\) and \\(-2\^w/2 \leq a_{n-1} \leq 2\^w/2\\). /// - pub(crate) fn to_pippenger_radix(&self, r: usize) -> ([i8; 43], usize) { - debug_assert!(r >= 6); - debug_assert!(r <= 8); + pub(crate) fn to_pippenger_radix(&self, w: usize) -> ([i8; 43], usize) { + debug_assert!(w >= 6); + debug_assert!(w <= 8); - let digits_count = (256 + r - 1)/r as usize; + let digits_count = (256 + w - 1)/w as usize; debug_assert!(digits_count <= 43); use byteorder::{ByteOrder, LittleEndian}; @@ -992,20 +992,20 @@ impl Scalar { let mut scalar64x4 = [0u64; 4]; LittleEndian::read_u64_into(&self.bytes, &mut scalar64x4[0..4]); - let radix: u64 = 1 << r; + let radix: u64 = 1 << w; let window_mask: u64 = radix - 1; let mut carry = 0u64; let mut digits = [0i8; 43]; for i in 0..digits_count { // Construct a buffer of bits of the scalar, starting at `bit_offset`. - let bit_offset = i*r; + let bit_offset = i*w; let u64_idx = bit_offset / 64; let bit_idx = bit_offset % 64; // Read the bits from the scalar let bit_buf: u64; - if bit_idx < 64 - r || u64_idx == 3 { + if bit_idx < 64 - w || u64_idx == 3 { // This window's bits are contained in a single u64, // or it's the last u64 anyway. bit_buf = scalar64x4[u64_idx] >> bit_idx; @@ -1018,8 +1018,8 @@ impl Scalar { let coef = carry + (bit_buf & window_mask); // coef = [0, 2^r) // Recenter coefficients from [0,2^r) to [-2^r/2, 2^r/2) - carry = (coef + (radix/2) as u64) >> r; - digits[i] = ((coef as i64) - (carry << r) as i64) as i8; + carry = (coef + (radix/2) as u64) >> w; + digits[i] = ((coef as i64) - (carry << w) as i64) as i8; } // Apply the resulting carry to the last digit @@ -1030,7 +1030,7 @@ impl Scalar { // we allow the last word to touch the value 2^r/2. // XXX: make sure tests cover this case, so the carry is non-zero and this line matters. // Maybe it never happens to be non-zero for r=6/7/8?... - digits[digits_count-1] += (carry << r) as i8; + digits[digits_count-1] += (carry << w) as i8; (digits, digits_count) } @@ -1517,11 +1517,11 @@ mod test { use std::iter; // For each valid radix it tests that 1000 random-ish scalars can be restored // from the produced representation precisely. - for r in 6..9 { + for w in 6..9 { for scalar in (2..100).map(|s| Scalar::from(s as u64).invert() ).chain(iter::once(-Scalar::one())) { - let (digits, digits_count) = scalar.to_pippenger_radix(r); + let (digits, digits_count) = scalar.to_pippenger_radix(w); - let radix = Scalar::from((1< Date: Tue, 21 May 2019 13:48:29 -0700 Subject: [PATCH 05/16] fix type conversions --- src/backend/vector/scalar_mul/pippenger.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/backend/vector/scalar_mul/pippenger.rs b/src/backend/vector/scalar_mul/pippenger.rs index 4ec72f6..070273b 100644 --- a/src/backend/vector/scalar_mul/pippenger.rs +++ b/src/backend/vector/scalar_mul/pippenger.rs @@ -13,13 +13,10 @@ use core::borrow::Borrow; -use clear_on_drop::ClearOnDrop; - use backend::vector::{CachedPoint, ExtendedPoint}; use edwards::EdwardsPoint; use scalar::Scalar; -use window::{LookupTable, NafLookupTable5}; -use traits::{Identity, MultiscalarMul, VartimeMultiscalarMul}; +use traits::{Identity, VartimeMultiscalarMul}; #[allow(unused_imports)] use prelude::*; @@ -68,7 +65,7 @@ impl VartimeMultiscalarMul for Pippenger { // Prepare 2^w/2 buckets. // buckets[i] corresponds to a multiplication factor (i+1). - let mut buckets: Vec<_> = (0..buckets_count) + let mut buckets: Vec = (0..buckets_count) .map(|_| ExtendedPoint::identity()) .collect(); @@ -105,8 +102,8 @@ impl VartimeMultiscalarMul for Pippenger { let mut buckets_intermediate_sum = buckets[buckets_count - 1]; let mut buckets_sum = buckets[buckets_count - 1]; for i in (0..(buckets_count - 1)).rev() { - buckets_intermediate_sum = &buckets_intermediate_sum + &buckets[i]; - buckets_sum = &buckets_sum + &buckets_intermediate_sum; + buckets_intermediate_sum = &buckets_intermediate_sum + &CachedPoint::from(buckets[i]); + buckets_sum = &buckets_sum + &CachedPoint::from(buckets_intermediate_sum); } buckets_sum @@ -123,7 +120,7 @@ impl VartimeMultiscalarMul for Pippenger { .iter() .rev() .fold(columns[digits_count - 1], |total, &p| { - &total.mul_by_pow_2(w as u32) + &p + &total.mul_by_pow_2(w as u32) + &CachedPoint::from(p) }) .into(), ) From df745e98a2bb1f9926a7c961502e9a992d3d5641 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Tue, 21 May 2019 14:10:56 -0700 Subject: [PATCH 06/16] oops - forgot to switch on pippenger --- src/edwards.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/edwards.rs b/src/edwards.rs index 85bb11e..e43adb4 100644 --- a/src/edwards.rs +++ b/src/edwards.rs @@ -676,11 +676,15 @@ impl VartimeMultiscalarMul for EdwardsPoint { assert_eq!(s_hi, Some(s_lo)); assert_eq!(p_hi, Some(p_lo)); - // Now we know there's a single size. When we do - // size-dependent algorithm dispatch, use this as the hint. - let _size = s_lo; + // Now we know there's a single size. + // Use this as the hint to decide which algorithm to use. + let size = s_lo; - scalar_mul::straus::Straus::optional_multiscalar_mul(scalars, points) + if size < 190 { + scalar_mul::straus::Straus::optional_multiscalar_mul(scalars, points) + } else { + scalar_mul::pippenger::Pippenger::optional_multiscalar_mul(scalars, points) + } } } From 7fba2a1bccc7674794eb832720d6c9e568039192 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Wed, 22 May 2019 11:14:26 -0700 Subject: [PATCH 07/16] avoid unnecessary allocation --- src/backend/serial/scalar_mul/pippenger.rs | 23 ++++++++++----------- src/backend/vector/scalar_mul/pippenger.rs | 24 ++++++++-------------- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/src/backend/serial/scalar_mul/pippenger.rs b/src/backend/serial/scalar_mul/pippenger.rs index 82cf1bd..2425ee1 100644 --- a/src/backend/serial/scalar_mul/pippenger.rs +++ b/src/backend/serial/scalar_mul/pippenger.rs @@ -112,7 +112,7 @@ impl VartimeMultiscalarMul for Pippenger { .map(|_| EdwardsPoint::identity()) .collect(); - let columns: Vec<_> = (0..digits_count).map(|digit_index| { + let mut columns = (0..digits_count).rev().map(|digit_index| { // Clear the buckets when processing another digit. for i in 0..buckets_count { @@ -150,18 +150,17 @@ impl VartimeMultiscalarMul for Pippenger { } buckets_sum - }) - .collect(); - // ^ Note: we collect points because if we chain .rev().fold() - // then the .map() will run in reversed order, producing incorrect digit values - // (they can only be produced in lo->hi order). + }); - // Add the intermediate per-digit results in hi->lo order - // so that we can minimize doublings. - Some(columns[0..(digits_count - 1)].iter().rev().fold( - columns[digits_count - 1], - |total, &p| total.mul_by_pow_2(w as u32) + p, - )) + // Take the high column as an initial value to avoid wasting time doubling the identity element in `fold()`. + // `unwrap()` always succeeds because we know we have more than zero digits. + let hi_column = columns.next().unwrap(); + + Some( + columns.fold(hi_column, |total, p| { + total.mul_by_pow_2(w as u32) + p + }).into() + ) } } diff --git a/src/backend/vector/scalar_mul/pippenger.rs b/src/backend/vector/scalar_mul/pippenger.rs index 070273b..039ea56 100644 --- a/src/backend/vector/scalar_mul/pippenger.rs +++ b/src/backend/vector/scalar_mul/pippenger.rs @@ -69,7 +69,7 @@ impl VartimeMultiscalarMul for Pippenger { .map(|_| ExtendedPoint::identity()) .collect(); - let columns: Vec = (0..digits_count).map(|digit_index| { + let mut columns = (0..digits_count).rev().map(|digit_index| { // Clear the buckets when processing another digit. for i in 0..buckets_count { @@ -107,22 +107,16 @@ impl VartimeMultiscalarMul for Pippenger { } buckets_sum - }) - .collect(); - // ^ Note: we collect points because if we chain .rev().fold() - // then the .map() will run in reversed order, producing incorrect digit values - // (they can only be produced in lo->hi order). + }); + + // Take the high column as an initial value to avoid wasting time doubling the identity element in `fold()`. + // `unwrap()` always succeeds because we know we have more than zero digits. + let hi_column = columns.next().unwrap(); - // Add the intermediate per-digit results in hi->lo order - // so that we can minimize doublings. Some( - columns[0..(digits_count - 1)] - .iter() - .rev() - .fold(columns[digits_count - 1], |total, &p| { - &total.mul_by_pow_2(w as u32) + &CachedPoint::from(p) - }) - .into(), + columns.fold(hi_column, |total, p| { + &total.mul_by_pow_2(w as u32) + &CachedPoint::from(p) + }).into() ) } } From dfcac0d8e25895646ad9b54e804bddcfa5aa7c45 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Wed, 22 May 2019 11:38:52 -0700 Subject: [PATCH 08/16] rustfmt and copyright fixes --- src/backend/serial/scalar_mul/pippenger.rs | 35 +++++++++----------- src/backend/vector/scalar_mul/pippenger.rs | 38 +++++++++++----------- 2 files changed, 35 insertions(+), 38 deletions(-) diff --git a/src/backend/serial/scalar_mul/pippenger.rs b/src/backend/serial/scalar_mul/pippenger.rs index 2425ee1..5a3293a 100644 --- a/src/backend/serial/scalar_mul/pippenger.rs +++ b/src/backend/serial/scalar_mul/pippenger.rs @@ -1,12 +1,10 @@ // -*- mode: rust; -*- // // This file is part of curve25519-dalek. -// Copyright (c) 2016-2018 Isis Lovecruft, Henry de Valence +// Copyright (c) 2019 Oleg Andreev // See LICENSE for licensing information. // // Authors: -// - Isis Agora Lovecruft -// - Henry de Valence // - Oleg Andreev //! Implementation of a variant of Pippenger's algorithm. @@ -72,8 +70,8 @@ impl VartimeMultiscalarMul for Pippenger { I::Item: Borrow, J: IntoIterator>, { - use backend::serial::curve_models::{ProjectiveNielsPoint}; - use traits::Identity; + use backend::serial::curve_models::ProjectiveNielsPoint; + use traits::Identity; let mut scalars = scalars.into_iter(); let size = scalars.by_ref().size_hint().0; @@ -95,13 +93,15 @@ impl VartimeMultiscalarMul for Pippenger { // Collect optimized scalars and points in buffers for repeated access // (scanning the whole set per digit position). - let scalars = scalars.into_iter() - .map(|s| s.borrow().to_pippenger_radix(w).0 ) - .collect::>(); + let scalars = scalars + .into_iter() + .map(|s| s.borrow().to_pippenger_radix(w).0) + .collect::>(); let points: Vec = match points .into_iter() .map(|p| p.map(|P| P.to_projective_niels())) - .collect::>>() { + .collect::>>() + { Some(x) => x, None => return None, }; @@ -113,18 +113,17 @@ impl VartimeMultiscalarMul for Pippenger { .collect(); let mut columns = (0..digits_count).rev().map(|digit_index| { - // Clear the buckets when processing another digit. for i in 0..buckets_count { buckets[i] = EdwardsPoint::identity(); } - + // Iterate over pairs of (point, scalar) // and add/sub the point to the corresponding bucket. // Note: if we add support for precomputed lookup tables, // we'll be adding/subtractiong point premultiplied by `digits[i]` to buckets[0]. for (digits, pt) in scalars.iter().zip(points.iter()) { - let digit = digits[digit_index]; + let digit = digits[digit_index]; if digit > 0 { let b = (digit - 1) as usize; buckets[b] = (&buckets[b] + pt).to_extended(); @@ -157,9 +156,9 @@ impl VartimeMultiscalarMul for Pippenger { let hi_column = columns.next().unwrap(); Some( - columns.fold(hi_column, |total, p| { - total.mul_by_pow_2(w as u32) + p - }).into() + columns + .fold(hi_column, |total, p| total.mul_by_pow_2(w as u32) + p) + .into(), ) } } @@ -177,12 +176,10 @@ mod test { let x = Scalar::from(2128506u64).invert(); let y = Scalar::from(4443282u64).invert(); let points: Vec<_> = (0..n) - .map(|i| { - constants::ED25519_BASEPOINT_POINT * Scalar::from(1 + i as u64) - }) + .map(|i| constants::ED25519_BASEPOINT_POINT * Scalar::from(1 + i as u64)) .collect(); let scalars: Vec<_> = (0..n) - .map(|i| x + (Scalar::from(i as u64)*y)) // fast way to make ~random but deterministic scalars + .map(|i| x + (Scalar::from(i as u64) * y)) // fast way to make ~random but deterministic scalars .collect(); let premultiplied: Vec = scalars diff --git a/src/backend/vector/scalar_mul/pippenger.rs b/src/backend/vector/scalar_mul/pippenger.rs index 039ea56..1913cfa 100644 --- a/src/backend/vector/scalar_mul/pippenger.rs +++ b/src/backend/vector/scalar_mul/pippenger.rs @@ -1,12 +1,10 @@ // -*- mode: rust; -*- // // This file is part of curve25519-dalek. -// Copyright (c) 2016-2018 Isis Lovecruft, Henry de Valence +// Copyright (c) 2019 Oleg Andreev // See LICENSE for licensing information. // // Authors: -// - Isis Agora Lovecruft -// - Henry de Valence // - Oleg Andreev #![allow(non_snake_case)] @@ -52,13 +50,15 @@ impl VartimeMultiscalarMul for Pippenger { // Collect optimized scalars and points in buffers for repeated access // (scanning the whole set per digit position). - let scalars = scalars.into_iter() - .map(|s| s.borrow().to_pippenger_radix(w).0 ) - .collect::>(); + let scalars = scalars + .into_iter() + .map(|s| s.borrow().to_pippenger_radix(w).0) + .collect::>(); let points: Vec = match points .into_iter() .map(|p| p.map(|P| CachedPoint::from(ExtendedPoint::from(P)))) - .collect::>>() { + .collect::>>() + { Some(x) => x, None => return None, }; @@ -70,18 +70,17 @@ impl VartimeMultiscalarMul for Pippenger { .collect(); let mut columns = (0..digits_count).rev().map(|digit_index| { - // Clear the buckets when processing another digit. for i in 0..buckets_count { buckets[i] = ExtendedPoint::identity(); } - + // Iterate over pairs of (point, scalar) // and add/sub the point to the corresponding bucket. // Note: if we add support for precomputed lookup tables, // we'll be adding/subtractiong point premultiplied by `digits[i]` to buckets[0]. for (digits, pt) in scalars.iter().zip(points.iter()) { - let digit = digits[digit_index]; + let digit = digits[digit_index]; if digit > 0 { let b = (digit - 1) as usize; buckets[b] = &buckets[b] + pt; @@ -102,7 +101,8 @@ impl VartimeMultiscalarMul for Pippenger { let mut buckets_intermediate_sum = buckets[buckets_count - 1]; let mut buckets_sum = buckets[buckets_count - 1]; for i in (0..(buckets_count - 1)).rev() { - buckets_intermediate_sum = &buckets_intermediate_sum + &CachedPoint::from(buckets[i]); + buckets_intermediate_sum = + &buckets_intermediate_sum + &CachedPoint::from(buckets[i]); buckets_sum = &buckets_sum + &CachedPoint::from(buckets_intermediate_sum); } @@ -114,9 +114,11 @@ impl VartimeMultiscalarMul for Pippenger { let hi_column = columns.next().unwrap(); Some( - columns.fold(hi_column, |total, p| { - &total.mul_by_pow_2(w as u32) + &CachedPoint::from(p) - }).into() + columns + .fold(hi_column, |total, p| { + &total.mul_by_pow_2(w as u32) + &CachedPoint::from(p) + }) + .into(), ) } } @@ -134,12 +136,10 @@ mod test { let x = Scalar::from(2128506u64).invert(); let y = Scalar::from(4443282u64).invert(); let points: Vec<_> = (0..n) - .map(|i| { - constants::ED25519_BASEPOINT_POINT * Scalar::from(1 + i as u64) - }) + .map(|i| constants::ED25519_BASEPOINT_POINT * Scalar::from(1 + i as u64)) .collect(); let scalars: Vec<_> = (0..n) - .map(|i| x + (Scalar::from(i as u64)*y)) // fast way to make ~random but deterministic scalars + .map(|i| x + (Scalar::from(i as u64) * y)) // fast way to make ~random but deterministic scalars .collect(); let premultiplied: Vec = scalars @@ -160,4 +160,4 @@ mod test { n = n / 2; } } -} \ No newline at end of file +} From ca2926ac89532134cebfbb25e4d1430c027c2465 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Wed, 22 May 2019 11:48:30 -0700 Subject: [PATCH 09/16] use one buffer instead of two --- src/backend/serial/scalar_mul/pippenger.rs | 19 ++++++++++--------- src/backend/vector/scalar_mul/pippenger.rs | 22 ++++++++++++---------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/backend/serial/scalar_mul/pippenger.rs b/src/backend/serial/scalar_mul/pippenger.rs index 5a3293a..e477bcb 100644 --- a/src/backend/serial/scalar_mul/pippenger.rs +++ b/src/backend/serial/scalar_mul/pippenger.rs @@ -70,7 +70,6 @@ impl VartimeMultiscalarMul for Pippenger { I::Item: Borrow, J: IntoIterator>, { - use backend::serial::curve_models::ProjectiveNielsPoint; use traits::Identity; let mut scalars = scalars.into_iter(); @@ -95,14 +94,16 @@ impl VartimeMultiscalarMul for Pippenger { // (scanning the whole set per digit position). let scalars = scalars .into_iter() - .map(|s| s.borrow().to_pippenger_radix(w).0) - .collect::>(); - let points: Vec = match points + .map(|s| s.borrow().to_pippenger_radix(w).0); + + let points = points .into_iter() - .map(|p| p.map(|P| P.to_projective_niels())) - .collect::>>() - { - Some(x) => x, + .map(|p| p.map(|P| P.to_projective_niels())); + + let scalars_points = scalars.zip(points).map(|(s,maybe_p)| maybe_p.map(|p| (s,p) ) ) + .collect::>>(); + let scalars_points = match scalars_points { + Some(sp) => sp, None => return None, }; @@ -122,7 +123,7 @@ impl VartimeMultiscalarMul for Pippenger { // and add/sub the point to the corresponding bucket. // Note: if we add support for precomputed lookup tables, // we'll be adding/subtractiong point premultiplied by `digits[i]` to buckets[0]. - for (digits, pt) in scalars.iter().zip(points.iter()) { + for (digits, pt) in scalars_points.iter() { let digit = digits[digit_index]; if digit > 0 { let b = (digit - 1) as usize; diff --git a/src/backend/vector/scalar_mul/pippenger.rs b/src/backend/vector/scalar_mul/pippenger.rs index 1913cfa..80df558 100644 --- a/src/backend/vector/scalar_mul/pippenger.rs +++ b/src/backend/vector/scalar_mul/pippenger.rs @@ -48,18 +48,20 @@ impl VartimeMultiscalarMul for Pippenger { let digits_count: usize = (256 + w - 1) / w; // == ceil(256/w) let buckets_count: usize = max_digit / 2; // digits are signed+centered hence 2^w/2, excluding 0-th bucket - // Collect optimized scalars and points in buffers for repeated access - // (scanning the whole set per digit position). + // Collect optimized scalars and points in a buffer for repeated access + // (scanning the whole collection per each digit position). let scalars = scalars .into_iter() - .map(|s| s.borrow().to_pippenger_radix(w).0) - .collect::>(); - let points: Vec = match points + .map(|s| s.borrow().to_pippenger_radix(w).0); + + let points = points .into_iter() - .map(|p| p.map(|P| CachedPoint::from(ExtendedPoint::from(P)))) - .collect::>>() - { - Some(x) => x, + .map(|p| p.map(|P| CachedPoint::from(ExtendedPoint::from(P)))); + + let scalars_points = scalars.zip(points).map(|(s,maybe_p)| maybe_p.map(|p| (s,p) ) ) + .collect::>>(); + let scalars_points = match scalars_points { + Some(sp) => sp, None => return None, }; @@ -79,7 +81,7 @@ impl VartimeMultiscalarMul for Pippenger { // and add/sub the point to the corresponding bucket. // Note: if we add support for precomputed lookup tables, // we'll be adding/subtractiong point premultiplied by `digits[i]` to buckets[0]. - for (digits, pt) in scalars.iter().zip(points.iter()) { + for (digits, pt) in scalars_points.iter() { let digit = digits[digit_index]; if digit > 0 { let b = (digit - 1) as usize; From 9836d6622c08c8001cdb8f9dfbbf80606af1b4bc Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Fri, 24 May 2019 12:18:10 -0700 Subject: [PATCH 10/16] =?UTF-8?q?cleaner=20name=20per=20Henry=E2=80=99s=20?= =?UTF-8?q?suggestion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/serial/scalar_mul/pippenger.rs | 2 +- src/backend/vector/scalar_mul/pippenger.rs | 2 +- src/scalar.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/backend/serial/scalar_mul/pippenger.rs b/src/backend/serial/scalar_mul/pippenger.rs index e477bcb..29b5b99 100644 --- a/src/backend/serial/scalar_mul/pippenger.rs +++ b/src/backend/serial/scalar_mul/pippenger.rs @@ -94,7 +94,7 @@ impl VartimeMultiscalarMul for Pippenger { // (scanning the whole set per digit position). let scalars = scalars .into_iter() - .map(|s| s.borrow().to_pippenger_radix(w).0); + .map(|s| s.borrow().to_radix_2w(w).0); let points = points .into_iter() diff --git a/src/backend/vector/scalar_mul/pippenger.rs b/src/backend/vector/scalar_mul/pippenger.rs index 80df558..0053e67 100644 --- a/src/backend/vector/scalar_mul/pippenger.rs +++ b/src/backend/vector/scalar_mul/pippenger.rs @@ -52,7 +52,7 @@ impl VartimeMultiscalarMul for Pippenger { // (scanning the whole collection per each digit position). let scalars = scalars .into_iter() - .map(|s| s.borrow().to_pippenger_radix(w).0); + .map(|s| s.borrow().to_radix_2w(w).0); let points = points .into_iter() diff --git a/src/scalar.rs b/src/scalar.rs index 73b6d82..330049e 100644 --- a/src/scalar.rs +++ b/src/scalar.rs @@ -979,7 +979,7 @@ impl Scalar { /// $$ /// with \\(-2\^w/2 \leq a_i < 2\^w/2\\) for \\(0 \leq i < (n-1)\\) and \\(-2\^w/2 \leq a_{n-1} \leq 2\^w/2\\). /// - pub(crate) fn to_pippenger_radix(&self, w: usize) -> ([i8; 43], usize) { + pub(crate) fn to_radix_2w(&self, w: usize) -> ([i8; 43], usize) { debug_assert!(w >= 6); debug_assert!(w <= 8); @@ -1519,7 +1519,7 @@ mod test { // from the produced representation precisely. for w in 6..9 { for scalar in (2..100).map(|s| Scalar::from(s as u64).invert() ).chain(iter::once(-Scalar::one())) { - let (digits, digits_count) = scalar.to_pippenger_radix(w); + let (digits, digits_count) = scalar.to_radix_2w(w); let radix = Scalar::from((1< Date: Fri, 24 May 2019 18:00:34 -0500 Subject: [PATCH 11/16] Update src/backend/serial/scalar_mul/pippenger.rs Co-Authored-By: Henry de Valence --- src/backend/serial/scalar_mul/pippenger.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/serial/scalar_mul/pippenger.rs b/src/backend/serial/scalar_mul/pippenger.rs index 29b5b99..fe903e0 100644 --- a/src/backend/serial/scalar_mul/pippenger.rs +++ b/src/backend/serial/scalar_mul/pippenger.rs @@ -122,7 +122,7 @@ impl VartimeMultiscalarMul for Pippenger { // Iterate over pairs of (point, scalar) // and add/sub the point to the corresponding bucket. // Note: if we add support for precomputed lookup tables, - // we'll be adding/subtractiong point premultiplied by `digits[i]` to buckets[0]. + // we'll be adding/subtracting point premultiplied by `digits[i]` to buckets[0]. for (digits, pt) in scalars_points.iter() { let digit = digits[digit_index]; if digit > 0 { From 29dca772a9389d263aa8161d88049408bd7dc096 Mon Sep 17 00:00:00 2001 From: Fabian Drinck Date: Sun, 26 May 2019 13:00:07 +0200 Subject: [PATCH 12/16] Quench snake case warning --- src/traits.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/traits.rs b/src/traits.rs index 5297aa4..9c2acfd 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -10,6 +10,8 @@ //! Module for common traits. +#![allow(non_snake_case)] + use core::borrow::Borrow; use subtle; @@ -208,7 +210,6 @@ pub trait VartimeMultiscalarMul { /// /// assert_eq!(A1.compress(), (-A2).compress()); /// ``` - #[allow(non_snake_case)] fn vartime_multiscalar_mul(scalars: I, points: J) -> Self::Point where I: IntoIterator, From fd69503a40fb7bf753ed84af1cc185023e4976de Mon Sep 17 00:00:00 2001 From: Joey Yandle Date: Mon, 3 Jun 2019 15:29:08 -0700 Subject: [PATCH 13/16] turn off default serde features but keep it optional --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bab9c09..c545cfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,7 +49,7 @@ byteorder = { version = "^1.2.3", default-features = false, features = ["i128"] digest = { version = "0.8", default-features = false } clear_on_drop = "=0.2.3" subtle = { version = "2", default-features = false } -serde = { version = "1.0", optional = true } +serde = { version = "1.0", default-features = false, optional = true } packed_simd = { version = "0.3.0", features = ["into_bits"], optional = true } [build-dependencies] @@ -58,7 +58,7 @@ byteorder = { version = "^1.2.3", default-features = false, features = ["i128"] digest = { version = "0.8", default-features = false } clear_on_drop = "=0.2.3" subtle = { version = "2", default-features = false } -serde = { version = "1.0", optional = true } +serde = { version = "1.0", default-features = false, optional = true } packed_simd = { version = "0.3.0", features = ["into_bits"], optional = true } [features] From 5921d6d2acf0b88f71bd53fdcf6f68faddaeff74 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 4 Jun 2019 13:36:07 -0700 Subject: [PATCH 14/16] Replace std::iter with core::iter --- src/scalar.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scalar.rs b/src/scalar.rs index 330049e..00ebad8 100644 --- a/src/scalar.rs +++ b/src/scalar.rs @@ -1514,7 +1514,7 @@ mod test { #[test] fn test_pippenger_radix() { - use std::iter; + use core::iter; // For each valid radix it tests that 1000 random-ish scalars can be restored // from the produced representation precisely. for w in 6..9 { From 19dcd6205387b529df66658856f22765b631fb49 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 4 Jun 2019 13:41:43 -0700 Subject: [PATCH 15/16] Add reference to 2012/549 --- src/backend/serial/scalar_mul/pippenger.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/backend/serial/scalar_mul/pippenger.rs b/src/backend/serial/scalar_mul/pippenger.rs index fe903e0..89ea723 100644 --- a/src/backend/serial/scalar_mul/pippenger.rs +++ b/src/backend/serial/scalar_mul/pippenger.rs @@ -58,6 +58,7 @@ use prelude::*; /// However, if `w` is too big and `n` is not too big, then `(2^w/2)*A` could dominate. /// Therefore, the optimal choice of `w` grows slowly as `n` grows. /// +/// This algorithm is adapted from section 4 of https://eprint.iacr.org/2012/549.pdf. pub struct Pippenger; #[cfg(any(feature = "alloc", feature = "std"))] From 62fbd6ab63976e3a99f26b10da4e1dc91863f62d Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 4 Jun 2019 15:27:12 -0700 Subject: [PATCH 16/16] Update version to 1.2.0 --- CHANGELOG.md | 9 +++++++++ Cargo.toml | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2fa0ad..fc7a3ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ Entries are listed in reverse chronological order. +## 1.2.0 + +* New multiscalar multiplication algorithm with better performance for + large problem sizes. The backend algorithm is selected + transparently using the size hints of the input iterators, so no + changes are required for client crates to start using it. +* Equality of Edwards points is now checked in projective coordinates. +* Serde can now be used with `no_std`. + ## 1.1.4 * Fix typos in documentation comments. diff --git a/Cargo.toml b/Cargo.toml index 162f430..b2026d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "curve25519-dalek" -version = "1.1.4" +version = "1.2.0" authors = ["Isis Lovecruft ", "Henry de Valence "] readme = "README.md"