Skip to content

Commit b02bfc7

Browse files
authored
Eliminate some timing variability in montgomery_mul (#409)
Always performs `sub_vv` on the lower half of the output, and conditionally assigns `upper` to `lower` using `subtle`. It doesn't matter if we unconditionally subtract, since the output will be overwritten in the case where it's "unnecessary" (but actually necessary for constant-time operation). There are still some data-dependent branches, however, which have been annotated with a TODO.
1 parent 9bf67d5 commit b02bfc7

File tree

1 file changed

+17
-12
lines changed
  • src/modular/boxed_residue

1 file changed

+17
-12
lines changed

src/modular/boxed_residue/mul.rs

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use core::{
1111
borrow::Borrow,
1212
ops::{Mul, MulAssign},
1313
};
14+
use subtle::{ConditionallySelectable, ConstantTimeEq};
1415

1516
#[cfg(feature = "zeroize")]
1617
use zeroize::Zeroize;
@@ -186,20 +187,22 @@ impl Drop for MontgomeryMultiplier<'_> {
186187
/// x and y are required to satisfy 0 <= z < 2**(n*_W) and then the result z is guaranteed to
187188
/// satisfy 0 <= z < 2**(n*_W), but it may not be < m.
188189
///
190+
/// Output is written into the lower (i.e. first) half of `z`.
191+
///
189192
/// Note: this was adapted from an implementation in `num-bigint`'s `monty.rs`.
190193
// TODO(tarcieri): refactor into `reduction.rs`, share impl with `(Dyn)Residue`?
191194
#[cfg(feature = "alloc")]
192-
pub(crate) fn montgomery_mul(z: &mut [Word], x: &[Word], y: &[Word], m: &[Word], k: Word) {
195+
fn montgomery_mul(z: &mut [Word], x: &[Word], y: &[Word], m: &[Word], k: Word) {
193196
// This code assumes x, y, m are all the same length (required by addMulVVW and the for loop).
194197
// It also assumes that x, y are already reduced mod m, or else the result will not be properly
195198
// reduced.
196-
let mut c: Word = 0;
197199
let n = m.len();
200+
debug_assert_eq!(z.len(), n * 2);
201+
debug_assert_eq!(x.len(), n);
202+
debug_assert_eq!(y.len(), n);
203+
debug_assert_eq!(m.len(), n);
198204

199-
let pre_cond = z.len() > n && z[n..].len() == n && x.len() == n && y.len() == n && m.len() == n;
200-
if !pre_cond {
201-
panic!("invalid inputs");
202-
}
205+
let mut c: Word = 0;
203206

204207
for i in 0..n {
205208
let c2 = add_mul_vvw(&mut z[i..n + i], x, y[i]);
@@ -208,15 +211,17 @@ pub(crate) fn montgomery_mul(z: &mut [Word], x: &[Word], y: &[Word], m: &[Word],
208211
let cx = c.wrapping_add(c2);
209212
let cy = cx.wrapping_add(c3);
210213
z[n + i] = cy;
214+
215+
// TODO(tarcieri): eliminate data-dependent branches
211216
c = (cx < c2 || cy < c3) as Word;
212217
}
213218

214-
// TODO(tarcieri): eliminate branch
215-
let (first, second) = z.split_at_mut(n);
216-
if c == 0 {
217-
first.swap_with_slice(&mut second[..]);
218-
} else {
219-
sub_vv(first, second, m);
219+
let (lower, upper) = z.split_at_mut(n);
220+
sub_vv(lower, upper, m);
221+
222+
let is_zero = c.ct_eq(&0);
223+
for (a, b) in lower.iter_mut().zip(upper.iter()) {
224+
a.conditional_assign(b, is_zero);
220225
}
221226
}
222227

0 commit comments

Comments
 (0)