From 389d2bc9e2da8cc4196aa064b8fd82b27f725107 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Wed, 5 Jun 2019 22:00:43 -0700 Subject: [PATCH] Ensure Pippenger works on manually-constructed extremal values. When using Scalar::from_bits to manually create unreduced Scalars (e.g., X/Ed25519 keys with specified bit patterns), it's possible to construct Scalar values that range up to 2^255-1. These shouldn't ever end up in a vartime multiscalar mul call anyways, because it doesn't handle secret data, but it is technically allowed by the type system and should be handled. When w=8, these can generate terminal carries that can't be folded into the last digit, but this can be handled by folding them into an extra digit instead. --- src/backend/serial/scalar_mul/pippenger.rs | 4 +- src/backend/vector/scalar_mul/pippenger.rs | 4 +- src/edwards.rs | 2 + src/scalar.rs | 100 +++++++++++++-------- 4 files changed, 71 insertions(+), 39 deletions(-) diff --git a/src/backend/serial/scalar_mul/pippenger.rs b/src/backend/serial/scalar_mul/pippenger.rs index 5a028fd..0cae2a1 100644 --- a/src/backend/serial/scalar_mul/pippenger.rs +++ b/src/backend/serial/scalar_mul/pippenger.rs @@ -88,14 +88,14 @@ impl VartimeMultiscalarMul for Pippenger { }; let max_digit: usize = 1 << w; - let digits_count: usize = (256 + w - 1) / w; // == ceil(256/w) + let digits_count: usize = Scalar::to_radix_2w_size_hint(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_radix_2w(w).0); + .map(|s| s.borrow().to_radix_2w(w)); let points = points .into_iter() diff --git a/src/backend/vector/scalar_mul/pippenger.rs b/src/backend/vector/scalar_mul/pippenger.rs index 21d2d37..f834a66 100644 --- a/src/backend/vector/scalar_mul/pippenger.rs +++ b/src/backend/vector/scalar_mul/pippenger.rs @@ -45,14 +45,14 @@ impl VartimeMultiscalarMul for Pippenger { }; let max_digit: usize = 1 << w; - let digits_count: usize = (256 + w - 1) / w; // == ceil(256/w) + let digits_count: usize = Scalar::to_radix_2w_size_hint(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 a buffer for repeated access // (scanning the whole collection per each digit position). let scalars = scalars .into_iter() - .map(|s| s.borrow().to_radix_2w(w).0); + .map(|s| s.borrow().to_radix_2w(w)); let points = points .into_iter() diff --git a/src/edwards.rs b/src/edwards.rs index 4dc9953..c60069c 100644 --- a/src/edwards.rs +++ b/src/edwards.rs @@ -1264,6 +1264,8 @@ mod test { // followed by some extra hardcoded ones. let xs = (0..n) .map(|_| Scalar::random(&mut rng)) + // The largest scalar allowed by the type system, 2^255-1 + .chain(iter::once(Scalar::from_bits([0xff; 32]))) .collect::>(); let check = xs.iter() .map(|xi| xi * xi) diff --git a/src/scalar.rs b/src/scalar.rs index 00ebad8..575fefe 100644 --- a/src/scalar.rs +++ b/src/scalar.rs @@ -961,6 +961,24 @@ impl Scalar { output } + /// Returns a size hint indicating how many entries of the return + /// value of `to_radix_2w` are nonzero. + pub(crate) fn to_radix_2w_size_hint(w: usize) -> usize { + debug_assert!(w >= 6); + debug_assert!(w <= 8); + + let digits_count = match w { + 6 => (256 + w - 1)/w as usize, + 7 => (256 + w - 1)/w as usize, + // See comment in to_radix_2w on handling the terminal carry. + 8 => (256 + w - 1)/w + 1 as usize, + _ => panic!("invalid radix parameter"), + }; + + debug_assert!(digits_count <= 43); + digits_count + } + /// 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 @@ -979,13 +997,10 @@ 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_radix_2w(&self, w: usize) -> ([i8; 43], usize) { + pub(crate) fn to_radix_2w(&self, w: usize) -> [i8; 43] { debug_assert!(w >= 6); debug_assert!(w <= 8); - let digits_count = (256 + w - 1)/w 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. @@ -997,6 +1012,7 @@ impl Scalar { let mut carry = 0u64; let mut digits = [0i8; 43]; + let digits_count = (256 + w - 1)/w as usize; for i in 0..digits_count { // Construct a buffer of bits of the scalar, starting at `bit_offset`. let bit_offset = i*w; @@ -1017,22 +1033,25 @@ impl Scalar { // 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) + // Recenter coefficients from [0,2^w) to [-2^w/2, 2^w/2) 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 - // 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 << w) as i8; + // When w < 8, we can fold the final carry onto the last digit d, + // because d < 2^w/2 so d + carry*2^w = d + 1*2^w < 2^(w+1) < 2^8. + // + // When w = 8, we can't fit carry*2^w into an i8. This should + // not happen anyways, because the final carry will be 0 for + // reduced scalars, but the Scalar invariant allows 255-bit scalars. + // To handle this, we expand the size_hint by 1 when w=8, + // and accumulate the final carry onto another digit. + match w { + 8 => digits[digits_count] += carry as i8, + _ => digits[digits_count-1] += (carry << w) as i8, + } - (digits, digits_count) + digits } /// Unpack this `Scalar` to an `UnpackedScalar` for faster arithmetic. @@ -1512,32 +1531,43 @@ mod test { } } + fn test_pippenger_radix_iter(scalar: Scalar, w: usize) { + let digits_count = Scalar::to_radix_2w_size_hint(w); + let digits = scalar.to_radix_2w(w); + + let radix = Scalar::from((1<