From b5305b4e3068dd3007f81202f3241ac15f2397f5 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 21 Nov 2017 12:27:32 -0800 Subject: [PATCH] Connect multiscalar_mult to the AVX2 backend --- src/backend/avx2/edwards.rs | 46 ++++++----- src/edwards.rs | 152 ++++++++++++++++++++---------------- 2 files changed, 112 insertions(+), 86 deletions(-) diff --git a/src/backend/avx2/edwards.rs b/src/backend/avx2/edwards.rs index cdb47ba..93ba438 100644 --- a/src/backend/avx2/edwards.rs +++ b/src/backend/avx2/edwards.rs @@ -391,20 +391,25 @@ impl EdwardsBasepointTable { /// error to call this function with two vectors of different lengths. /// /// XXX need to clear memory +/// +/// XXX this takes `edwards::ExtendedPoints` because we have to alloc scratch space here anyways, +/// and we need some space to store the converted points, so we may as well do the conversion here. +/// maybe there's a better way to avoid code duplication... #[cfg(any(feature = "alloc", feature = "std"))] -pub fn multiscalar_mult<'a, 'b, I, J>(scalars: I, points: J) -> ExtendedPoint +pub fn multiscalar_mult<'a, 'b, I, J>(scalars: I, points: J) -> edwards::ExtendedPoint where I: IntoIterator, - J: IntoIterator + J: IntoIterator { use traits::select_precomputed_point; //assert_eq!(scalars.len(), points.len()); 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: [ExtendedPoint; 8] = [*P_i; 8]; + .map(|P| { + let P = ExtendedPoint::from(*P); + // Construct a lookup table of [P,2*P,3*P,4*P,5*P,6*P,7*P] + let mut lookup_table: [ExtendedPoint; 8] = [P; 8]; for i in 0..7 { - lookup_table[i+1] = P_i + &lookup_table[i]; + lookup_table[i+1] = &P + &lookup_table[i]; } lookup_table }).collect(); @@ -448,7 +453,7 @@ pub fn multiscalar_mult<'a, 'b, I, J>(scalars: I, points: J) -> ExtendedPoint Q = &Q + &R_i; } } - Q + Q.into() } pub mod vartime { @@ -464,10 +469,12 @@ pub mod vartime { /// error to call this function with two vectors of different lengths. /// /// XXX need to clear memory + /// + /// XXX see note on consttime multiscalar mul #[cfg(any(feature = "alloc", feature = "std"))] - pub fn multiscalar_mult<'a, 'b, I, J>(scalars: I, points: J) -> ExtendedPoint + pub fn multiscalar_mult<'a, 'b, I, J>(scalars: I, points: J) -> edwards::ExtendedPoint where I: IntoIterator, - J: IntoIterator + J: IntoIterator { //assert_eq!(scalars.len(), points.len()); @@ -475,9 +482,10 @@ pub mod vartime { .map(|c| c.non_adjacent_form()).collect(); let odd_multiples: Vec<_> = points.into_iter() .map(|P| { - // 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 P = ExtendedPoint::from(*P); + // Construct a lookup table of [P,2*P,3*P,4*P,5*P,6*P,7*P] let P2 = P.double(); - let mut lookup_table: [ExtendedPoint; 8] = [*P; 8]; + let mut lookup_table: [ExtendedPoint; 8] = [P; 8]; for i in 0..7 { lookup_table[i+1] = &P2 + &lookup_table[i]; } @@ -498,7 +506,7 @@ pub mod vartime { } } } - Q + Q.into() } } @@ -734,10 +742,10 @@ mod test { let R = &(&P1 * &s1) + &(&P2 * &s2); - let R_multiscalar = multiscalar_mult(&[s1, s2], &[P1, P2]); + let R_multiscalar = multiscalar_mult(&[s1, s2], &[P1.into(), P2.into()]); assert_eq!(edwards::ExtendedPoint::from(R).compress(), - edwards::ExtendedPoint::from(R_multiscalar).compress()); + R_multiscalar.compress()); } mod vartime { @@ -754,10 +762,10 @@ mod test { let R = &(&P1 * &s1) + &(&P2 * &s2); - let R_multiscalar = vartime::multiscalar_mult(&[s1, s2], &[P1, P2]); + let R_multiscalar = vartime::multiscalar_mult(&[s1, s2], &[P1.into(), P2.into()]); assert_eq!(edwards::ExtendedPoint::from(R).compress(), - edwards::ExtendedPoint::from(R_multiscalar).compress()); + R_multiscalar.compress()); } } } @@ -835,7 +843,7 @@ mod bench { let scalars: Vec<_> = (0..10).map(|_| Scalar::random(&mut csprng)).collect(); // Create 10 points (by doing scalar mults) let B = &constants::ED25519_BASEPOINT_POINT; - let points: Vec<_> = scalars.iter().map(|s| ExtendedPoint::from(B * &s)).collect(); + let points: Vec<_> = scalars.iter().map(|s| B * &s).collect(); b.iter(|| multiscalar_mult(&scalars, &points)); } @@ -850,7 +858,7 @@ mod bench { // Create 2 random scalars let s1 = Scalar::random(&mut csprng); let s2 = Scalar::random(&mut csprng); - let B: ExtendedPoint = constants::ED25519_BASEPOINT_POINT.into(); + let B = constants::ED25519_BASEPOINT_POINT; let P = &B * &s1; b.iter(|| vartime::multiscalar_mult(&[s1, s2], &[B, P])); @@ -863,7 +871,7 @@ mod bench { let scalars: Vec<_> = (0..10).map(|_| Scalar::random(&mut csprng)).collect(); // Create 10 points (by doing scalar mults) let B = &constants::ED25519_BASEPOINT_POINT; - let points: Vec<_> = scalars.iter().map(|s| ExtendedPoint::from(B * &s)).collect(); + let points: Vec<_> = scalars.iter().map(|s| B * &s).collect(); b.iter(|| vartime::multiscalar_mult(&scalars, &points)); } diff --git a/src/edwards.rs b/src/edwards.rs index 45354a0..3bf73a8 100644 --- a/src/edwards.rs +++ b/src/edwards.rs @@ -510,59 +510,68 @@ pub fn multiscalar_mult<'a, 'b, I, J>(scalars: I, points: J) -> ExtendedPoint where I: IntoIterator, J: IntoIterator { - //assert_eq!(scalars.len(), points.len()); + // If we built with AVX2, use the AVX2 backend. + #[cfg(all(target_feature = "avx2", feature = "avx2_backend"))] { + use backend::avx2::edwards as edwards_avx2; - 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(); - - // 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_list: Vec<_> = scalars.into_iter() - .map(|c| c.to_radix_16()).collect(); - - // 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 = ExtendedPoint::identity(); - // XXX this impl makes no effort to be cache-aware; maybe it could be improved? - for j in (0..64).rev() { - Q = Q.mult_by_pow_2(4); - let it = scalar_digits_list.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); - // Q = Q + R_i - Q = (&Q + &R_i).to_extended(); - } + edwards_avx2::multiscalar_mult(scalars, points) + } + // Otherwise, proceed as normal: + #[cfg(not(all(target_feature = "avx2", feature = "avx2_backend")))] { + //assert_eq!(scalars.len(), points.len()); + + 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(); + + // 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_list: Vec<_> = scalars.into_iter() + .map(|c| c.to_radix_16()).collect(); + + // 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 = ExtendedPoint::identity(); + // XXX this impl makes no effort to be cache-aware; maybe it could be improved? + for j in (0..64).rev() { + Q = Q.mult_by_pow_2(4); + let it = scalar_digits_list.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); + // Q = Q + R_i + Q = (&Q + &R_i).to_extended(); + } + } + Q } - Q } /// A precomputed table of multiples of a basepoint, for accelerating @@ -801,30 +810,39 @@ pub mod vartime { where I: IntoIterator, J: IntoIterator { - //assert_eq!(scalars.len(), points.len()); + // If we built with AVX2, use the AVX2 backend. + #[cfg(all(target_feature = "avx2", feature = "avx2_backend"))] { + use backend::avx2::edwards as edwards_avx2; - let nafs: Vec<_> = scalars.into_iter() - .map(|c| c.non_adjacent_form()).collect(); - let odd_multiples: Vec<_> = points.into_iter() - .map(|P| OddMultiples::create(P)).collect(); + edwards_avx2::vartime::multiscalar_mult(scalars, points) + } + // Otherwise, proceed as normal: + #[cfg(not(all(target_feature = "avx2", feature = "avx2_backend")))] { + //assert_eq!(scalars.len(), points.len()); - let mut r = ProjectivePoint::identity(); + let nafs: Vec<_> = scalars.into_iter() + .map(|c| c.non_adjacent_form()).collect(); + let odd_multiples: Vec<_> = points.into_iter() + .map(|P| OddMultiples::create(P)).collect(); - for i in (0..255).rev() { - let mut t = r.double(); + let mut r = ProjectivePoint::identity(); - for (naf, odd_multiple) in nafs.iter().zip(odd_multiples.iter()) { - if naf[i] > 0 { - t = &t.to_extended() + &odd_multiple[( naf[i]/2) as usize]; - } else if naf[i] < 0 { - t = &t.to_extended() - &odd_multiple[(-naf[i]/2) as usize]; + for i in (0..255).rev() { + let mut t = r.double(); + + for (naf, odd_multiple) in nafs.iter().zip(odd_multiples.iter()) { + if naf[i] > 0 { + t = &t.to_extended() + &odd_multiple[( naf[i]/2) as usize]; + } else if naf[i] < 0 { + t = &t.to_extended() - &odd_multiple[(-naf[i]/2) as usize]; + } } + + r = t.to_projective(); } - r = t.to_projective(); + r.to_extended() } - - r.to_extended() } /// Given a point \\(A\\) and scalars \\(a\\) and \\(b\\), compute the point