diff --git a/src/modular/boxed_residue.rs b/src/modular/boxed_residue.rs index 98333e5e..c015d6a7 100644 --- a/src/modular/boxed_residue.rs +++ b/src/modular/boxed_residue.rs @@ -12,7 +12,7 @@ use super::{ reduction::{montgomery_reduction_boxed, montgomery_reduction_boxed_mut}, Retrieve, }; -use crate::{BoxedUint, Integer, Limb, NonZero, Word}; +use crate::{BoxedUint, ConstantTimeSelect, Integer, Limb, NonZero, Word}; use subtle::CtOption; #[cfg(feature = "std")] @@ -49,7 +49,7 @@ impl BoxedResidueParams { // Use a surrogate value of `1` in case a modulus of `0` is passed. // This will be rejected by the `is_odd` check above, which will fail and return `None`. - let modulus_nz = NonZero::new(BoxedUint::conditional_select( + let modulus_nz = NonZero::new(BoxedUint::ct_select( &modulus, &BoxedUint::one_with_precision(bits_precision), modulus.is_zero(), @@ -82,7 +82,7 @@ impl BoxedResidueParams { // Use a surrogate value of `1` in case a modulus of `0` is passed. // This will be rejected by the `is_odd` check above, which will fail and return `None`. - let modulus_nz = NonZero::new(BoxedUint::conditional_select( + let modulus_nz = NonZero::new(BoxedUint::ct_select( &modulus, &BoxedUint::one_with_precision(bits_precision), modulus.is_zero(), diff --git a/src/modular/boxed_residue/pow.rs b/src/modular/boxed_residue/pow.rs index e0e33b3e..977afb7c 100644 --- a/src/modular/boxed_residue/pow.rs +++ b/src/modular/boxed_residue/pow.rs @@ -1,7 +1,7 @@ //! Modular exponentiation support for [`BoxedResidue`]. use super::{mul::MontgomeryMultiplier, BoxedResidue}; -use crate::{BoxedUint, Limb, PowBoundedExp, Word}; +use crate::{BoxedUint, ConstantTimeSelect, Limb, PowBoundedExp, Word}; use alloc::vec::Vec; use subtle::{ConstantTimeEq, ConstantTimeLess}; @@ -103,7 +103,7 @@ fn pow_montgomery_form( // Constant-time lookup in the array of powers power.limbs.copy_from_slice(&powers[0].limbs); for i in 1..(1 << WINDOW) { - power.conditional_assign(&powers[i as usize], i.ct_eq(&idx)); + power.ct_assign(&powers[i as usize], i.ct_eq(&idx)); } multiplier.mul_amm_assign(&mut z, &power); diff --git a/src/traits.rs b/src/traits.rs index a06e22cc..8ade6321 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -31,6 +31,51 @@ pub trait Bounded { const BYTES: usize; } +/// Trait for types which are conditionally selectable in constant time, similar to (and blanket impl'd for) `subtle`'s +/// [`ConditionallySelectable`] trait, but without the `Copy` bound which allows it to be impl'd for heap allocated +/// types such as `BoxedUint`. +/// +/// It also provides generic implementations of conditional assignment and conditional swaps. +pub trait ConstantTimeSelect: Clone { + /// Select `a` or `b` according to `choice`. + /// + /// # Returns + /// - `a` if `choice == Choice(0)`; + /// - `b` if `choice == Choice(1)`. + fn ct_select(a: &Self, b: &Self, choice: Choice) -> Self; + + /// Conditionally assign `other` to `self`, according to `choice`. + #[inline] + fn ct_assign(&mut self, other: &Self, choice: Choice) { + *self = Self::ct_select(self, other, choice); + } + + /// Conditionally swap `self` and `other` if `choice == 1`; otherwise, reassign both unto themselves. + #[inline] + fn ct_swap(a: &mut Self, b: &mut Self, choice: Choice) { + let t: Self = a.clone(); + a.ct_assign(b, choice); + b.ct_assign(&t, choice); + } +} + +impl ConstantTimeSelect for T { + #[inline(always)] + fn ct_select(a: &Self, b: &Self, choice: Choice) -> Self { + T::conditional_select(a, b, choice) + } + + #[inline(always)] + fn ct_assign(&mut self, other: &Self, choice: Choice) { + self.conditional_assign(other, choice) + } + + #[inline(always)] + fn ct_swap(a: &mut Self, b: &mut Self, choice: Choice) { + T::conditional_swap(a, b, choice) + } +} + /// Integer trait: represents common functionality of integer types provided by this crate. pub trait Integer: 'static @@ -55,10 +100,10 @@ pub trait Integer: + CheckedMul + CheckedDiv + Clone - // + ConditionallySelectable (see dalek-cryptography/subtle#94) + ConstantTimeEq + ConstantTimeGreater + ConstantTimeLess + + ConstantTimeSelect + Debug + Default + Div, Output = Self> @@ -113,7 +158,7 @@ pub trait Integer: fn bytes_precision(&self) -> usize; /// Calculate the number of leading zeros in the binary representation of this number. - fn leading_zeros(&self) -> u32 { + fn leading_zeros(&self) -> u32 { self.bits_precision() - self.bits() } diff --git a/src/uint/boxed/ct.rs b/src/uint/boxed/ct.rs index 5b88c6f2..6c7ad258 100644 --- a/src/uint/boxed/ct.rs +++ b/src/uint/boxed/ct.rs @@ -1,17 +1,13 @@ //! Constant-time helper functions. use super::BoxedUint; -use crate::Limb; -use subtle::{Choice, ConditionallySelectable, CtOption}; +use crate::{ConstantTimeSelect, Limb}; +use subtle::{Choice, ConditionallySelectable}; -impl BoxedUint { - /// Conditionally select `a` or `b` in constant time depending on [`Choice`]. - /// - /// NOTE: can't impl `subtle`'s [`ConditionallySelectable`] trait due to its `Copy` bound, so - /// this is an inherent function instead. - /// - /// Panics if `a` and `b` don't have the same precision. - pub fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { +/// NOTE: can't impl `subtle`'s [`ConditionallySelectable`] trait due to its `Copy` bound +impl ConstantTimeSelect for BoxedUint { + #[inline] + fn ct_select(a: &Self, b: &Self, choice: Choice) -> Self { debug_assert_eq!(a.bits_precision(), b.bits_precision()); let mut limbs = vec![Limb::ZERO; a.nlimbs()].into_boxed_slice(); @@ -22,14 +18,8 @@ impl BoxedUint { Self { limbs } } - /// Conditionally assign `other` to `self`, according to `choice`. - /// - /// NOTE: can't impl `subtle`'s [`ConditionallySelectable`] trait due to its `Copy` bound, so - /// this is an inherent function instead. - /// - /// Panics if `a` and `b` don't have the same precision. #[inline] - pub fn conditional_assign(&mut self, other: &Self, choice: Choice) { + fn ct_assign(&mut self, other: &Self, choice: Choice) { debug_assert_eq!(self.bits_precision(), other.bits_precision()); for i in 0..self.nlimbs() { @@ -37,146 +27,28 @@ impl BoxedUint { } } - /// Conditionally swap `self` and `other` if `choice == 1`; otherwise, - /// reassign both unto themselves. - /// - /// NOTE: can't impl `subtle`'s [`ConditionallySelectable`] trait due to its `Copy` bound, so - /// this is an inherent function instead. - /// - /// Panics if `a` and `b` don't have the same precision. #[inline] - pub fn conditional_swap(a: &mut Self, b: &mut Self, choice: Choice) { + fn ct_swap(a: &mut Self, b: &mut Self, choice: Choice) { debug_assert_eq!(a.bits_precision(), b.bits_precision()); for i in 0..a.nlimbs() { Limb::conditional_swap(&mut a.limbs[i], &mut b.limbs[i], choice); } } - - /// Conditional `map`: workaround which provides a [`CtOption::map`]-like API. - /// - /// Ensures both functions are called regardless of whether the first returns some/none with an - /// argument whose precision matches `self`. Note this still requires branching on the - /// intermediate [`CtOption`] value and therefore isn't fully constant time, but the best we can - /// do without upstream changes to `subtle` (see dalek-cryptography/subtle#94). - /// - /// Workaround due to `Copy` in [`ConditionallySelectable`] supertrait bounds. - pub fn conditional_map(&self, condition: C, f: F) -> CtOption - where - C: Fn(&Self) -> CtOption, - F: Fn(Self) -> T, - { - let conditional_val = condition(self); - let is_some = conditional_val.is_some(); - - let placeholder = Self::zero_with_precision(self.bits_precision()); - let value = Option::::from(conditional_val).unwrap_or(placeholder); - debug_assert_eq!(self.bits_precision(), value.bits_precision()); - CtOption::new(f(value), is_some) - } - - /// Conditional `and_then`: workaround which provides a [`CtOption::and_then`]-like API. - /// - /// Ensures both functions are called regardless of whether the first returns some/none with an - /// argument whose precision matches `self`. Note this still requires branching on the - /// intermediate [`CtOption`] value and therefore isn't fully constant time, but the best we can - /// do without upstream changes to `subtle` (see dalek-cryptography/subtle#94). - /// - /// Workaround due to `Copy` in [`ConditionallySelectable`] supertrait bounds. - pub fn conditional_and_then(&self, condition: C, f: F) -> CtOption - where - C: Fn(&Self) -> CtOption, - F: Fn(Self) -> CtOption, - { - let conditional_val = condition(self); - let mut is_some = conditional_val.is_some(); - - let placeholder = Self::zero_with_precision(self.bits_precision()); - let value = Option::::from(conditional_val).unwrap_or(placeholder); - debug_assert_eq!(self.bits_precision(), value.bits_precision()); - - let conditional_val = f(value); - is_some &= conditional_val.is_some(); - - let placeholder = Self::zero_with_precision(self.bits_precision()); - let value = Option::from(conditional_val).unwrap_or(placeholder); - debug_assert_eq!(self.bits_precision(), value.bits_precision()); - - CtOption::new(value, is_some) - } } #[cfg(test)] mod tests { use super::BoxedUint; - use subtle::{Choice, CtOption}; + use crate::ConstantTimeSelect; + use subtle::Choice; #[test] fn conditional_select() { let a = BoxedUint::zero_with_precision(128); let b = BoxedUint::max(128); - assert_eq!(a, BoxedUint::conditional_select(&a, &b, Choice::from(0))); - assert_eq!(b, BoxedUint::conditional_select(&a, &b, Choice::from(1))); - } - - #[test] - fn conditional_map_some() { - let n = BoxedUint::one(); - - let ret = n - .conditional_map( - |n| CtOption::new(n.clone(), 1u8.into()), - |n| n.wrapping_add(&BoxedUint::one()), - ) - .unwrap(); - - assert_eq!(ret, BoxedUint::from(2u8)); + assert_eq!(a, BoxedUint::ct_select(&a, &b, Choice::from(0))); + assert_eq!(b, BoxedUint::ct_select(&a, &b, Choice::from(1))); } - - #[test] - fn conditional_map_none() { - let n = BoxedUint::one(); - - let ret = n.conditional_map( - |n| CtOption::new(n.clone(), 0u8.into()), - |n| n.wrapping_add(&BoxedUint::one()), - ); - - assert!(bool::from(ret.is_none())); - } - - #[test] - fn conditional_and_then_all_some() { - let n = BoxedUint::one(); - - let ret = n - .conditional_and_then( - |n| CtOption::new(n.clone(), 1u8.into()), - |n| CtOption::new(n.wrapping_add(&BoxedUint::one()), 1u8.into()), - ) - .unwrap(); - - assert_eq!(ret, BoxedUint::from(2u8)); - } - - macro_rules! conditional_and_then_none_test { - ($name:ident, $a:expr, $b:expr) => { - #[test] - fn $name() { - let n = BoxedUint::one(); - - let ret = n.conditional_and_then( - |n| CtOption::new(n.clone(), $a.into()), - |n| CtOption::new(n.wrapping_add(&BoxedUint::one()), $b.into()), - ); - - assert!(bool::from(ret.is_none())); - } - }; - } - - conditional_and_then_none_test!(conditional_and_then_none_some, 0, 1); - conditional_and_then_none_test!(conditional_and_then_some_none, 1, 0); - conditional_and_then_none_test!(conditional_and_then_none_none, 0, 0); } diff --git a/src/uint/boxed/div.rs b/src/uint/boxed/div.rs index 0ff8eb5e..1cf5ac8a 100644 --- a/src/uint/boxed/div.rs +++ b/src/uint/boxed/div.rs @@ -1,6 +1,6 @@ //! [`BoxedUint`] division operations. -use crate::{BoxedUint, CheckedDiv, Limb, NonZero, Wrapping}; +use crate::{BoxedUint, CheckedDiv, ConstantTimeSelect, Limb, NonZero, Wrapping}; use core::ops::{Div, DivAssign, Rem, RemAssign}; use subtle::{Choice, ConstantTimeEq, ConstantTimeLess, CtOption}; @@ -42,7 +42,7 @@ impl BoxedUint { loop { let (r, borrow) = rem.sbb(&c, Limb::ZERO); - rem = Self::conditional_select(&r, &rem, !borrow.ct_eq(&Limb::ZERO)); + rem = Self::ct_select(&r, &rem, !borrow.ct_eq(&Limb::ZERO)); if bd == 0 { break rem; } @@ -84,9 +84,9 @@ impl BoxedUint { loop { let (mut r, borrow) = rem.sbb(&c, Limb::ZERO); - rem.conditional_assign(&r, !(Choice::from((borrow.0 & 1) as u8) | done)); + rem.ct_assign(&r, !(Choice::from((borrow.0 & 1) as u8) | done)); r = quo.bitor(&Self::one()); - quo.conditional_assign(&r, !(Choice::from((borrow.0 & 1) as u8) | done)); + quo.ct_assign(&r, !(Choice::from((borrow.0 & 1) as u8) | done)); if i == 0 { break; } @@ -95,7 +95,7 @@ impl BoxedUint { // aren't modified further (but do the remaining iterations anyway to be constant-time) done = i.ct_lt(&mb); c.shr1_assign(); - quo.conditional_assign(&quo.shl1(), !done); + quo.ct_assign(&quo.shl1(), !done); } (quo, rem) @@ -117,9 +117,9 @@ impl BoxedUint { loop { let (mut r, borrow) = remainder.sbb(&c, Limb::ZERO); let borrow = Choice::from(borrow.0 as u8 & 1); - remainder = Self::conditional_select(&r, &remainder, borrow); + remainder = Self::ct_select(&r, &remainder, borrow); r = "ient | Self::one(); - quotient = Self::conditional_select(&r, "ient, borrow); + quotient = Self::ct_select(&r, "ient, borrow); if bd == 0 { break; } diff --git a/src/uint/boxed/inv_mod.rs b/src/uint/boxed/inv_mod.rs index fdf75fed..d997bf8d 100644 --- a/src/uint/boxed/inv_mod.rs +++ b/src/uint/boxed/inv_mod.rs @@ -1,6 +1,6 @@ //! [`BoxedUint`] modular inverse (i.e. reciprocal) operations. -use crate::{BoxedUint, Integer}; +use crate::{BoxedUint, ConstantTimeSelect, Integer}; use subtle::{Choice, ConstantTimeEq, ConstantTimeLess, CtOption}; impl BoxedUint { @@ -56,7 +56,7 @@ impl BoxedUint { let x_i = b.limbs[0].0 & 1; let x_i_choice = Choice::from(x_i as u8); // b_{i+1} = (b_i - a * X_i) / 2 - b = Self::conditional_select(&b, &b.wrapping_sub(self), x_i_choice).shr1(); + b = Self::ct_select(&b, &b.wrapping_sub(self), x_i_choice).shr1(); // Store the X_i bit in the result (x = x | (1 << X_i)) // Don't change the result in dummy iterations. @@ -115,13 +115,13 @@ impl BoxedUint { // Set `self -= b` if `self` is odd. let swap = a.conditional_sbb_assign(&b, self_odd); // Set `b += self` if `swap` is true. - b = Self::conditional_select(&b, &b.wrapping_add(&a), swap); + b = Self::ct_select(&b, &b.wrapping_add(&a), swap); // Negate `self` if `swap` is true. a = a.conditional_wrapping_neg(swap); let mut new_u = u.clone(); let mut new_v = v.clone(); - Self::conditional_swap(&mut new_u, &mut new_v, swap); + Self::ct_swap(&mut new_u, &mut new_v, swap); let cy = new_u.conditional_sbb_assign(&new_v, self_odd); let cyy = new_u.conditional_adc_assign(modulus, cy); debug_assert!(bool::from(cy.ct_eq(&cyy))); diff --git a/src/uint/boxed/neg.rs b/src/uint/boxed/neg.rs index 193b51c3..03505dfe 100644 --- a/src/uint/boxed/neg.rs +++ b/src/uint/boxed/neg.rs @@ -1,12 +1,12 @@ //! [`BoxedUint`] negation operations. -use crate::{BoxedUint, Limb, WideWord, Word, WrappingNeg}; +use crate::{BoxedUint, ConstantTimeSelect, Limb, WideWord, Word, WrappingNeg}; use subtle::Choice; impl BoxedUint { /// Negates based on `choice` by wrapping the integer. pub(crate) fn conditional_wrapping_neg(&self, choice: Choice) -> BoxedUint { - Self::conditional_select(self, &self.wrapping_neg(), choice) + Self::ct_select(self, &self.wrapping_neg(), choice) } /// Perform wrapping negation. diff --git a/src/uint/boxed/shl.rs b/src/uint/boxed/shl.rs index 081b5559..6b7ec99b 100644 --- a/src/uint/boxed/shl.rs +++ b/src/uint/boxed/shl.rs @@ -1,6 +1,6 @@ //! [`BoxedUint`] bitwise left shift operations. -use crate::{BoxedUint, Limb, WrappingShl, Zero}; +use crate::{BoxedUint, ConstantTimeSelect, Limb, WrappingShl, Zero}; use core::ops::{Shl, ShlAssign}; use subtle::{Choice, ConstantTimeLess}; @@ -49,7 +49,7 @@ impl BoxedUint { // Will not overflow by construction self.shl_vartime_into(&mut temp, 1 << i) .expect("shift within range"); - self.conditional_assign(&temp, bit); + self.ct_assign(&temp, bit); } #[cfg(feature = "zeroize")] diff --git a/src/uint/boxed/shr.rs b/src/uint/boxed/shr.rs index 01ad01b2..28397125 100644 --- a/src/uint/boxed/shr.rs +++ b/src/uint/boxed/shr.rs @@ -1,6 +1,6 @@ //! [`BoxedUint`] bitwise right shift operations. -use crate::{BoxedUint, Limb, WrappingShr, Zero}; +use crate::{BoxedUint, ConstantTimeSelect, Limb, WrappingShr, Zero}; use core::ops::{Shr, ShrAssign}; use subtle::{Choice, ConstantTimeLess}; @@ -55,7 +55,7 @@ impl BoxedUint { // Will not overflow by construction self.shr_vartime_into(&mut temp, 1 << i) .expect("shift within range"); - self.conditional_assign(&temp, bit); + self.ct_assign(&temp, bit); } #[cfg(feature = "zeroize")]