From f9f59cc5db02b35fe896c37f4ed939b1d4373340 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 27 Nov 2023 10:46:56 -0700 Subject: [PATCH] Add `BoxedUint::{inv_mod2k, bitor}` Support for computing inverses modulo powers of 2. The `bitor` impl was needed to test `inv_mod2k`, which internally uses a `set_bit` function that needed tested. --- src/limb/bit_or.rs | 14 ++++- src/uint/boxed.rs | 2 + src/uint/boxed/bit_or.rs | 118 ++++++++++++++++++++++++++++++++++++++ src/uint/boxed/bits.rs | 51 +++++++++++++++- src/uint/boxed/inv_mod.rs | 71 +++++++++++++++++++++++ 5 files changed, 253 insertions(+), 3 deletions(-) create mode 100644 src/uint/boxed/bit_or.rs create mode 100644 src/uint/boxed/inv_mod.rs diff --git a/src/limb/bit_or.rs b/src/limb/bit_or.rs index cafac18da..f863ac0da 100644 --- a/src/limb/bit_or.rs +++ b/src/limb/bit_or.rs @@ -1,7 +1,7 @@ //! Limb bit or operations. use super::Limb; -use core::ops::BitOr; +use core::ops::{BitOr, BitOrAssign}; impl Limb { /// Calculates `a | b`. @@ -17,3 +17,15 @@ impl BitOr for Limb { self.bitor(rhs) } } + +impl BitOrAssign for Limb { + fn bitor_assign(&mut self, other: Self) { + *self = self.bitor(other); + } +} + +impl BitOrAssign<&Limb> for Limb { + fn bitor_assign(&mut self, other: &Self) { + *self = self.bitor(*other); + } +} diff --git a/src/uint/boxed.rs b/src/uint/boxed.rs index 1ae76d76a..0f4e02148 100644 --- a/src/uint/boxed.rs +++ b/src/uint/boxed.rs @@ -3,10 +3,12 @@ mod add; mod add_mod; mod bit_and; +mod bit_or; mod bits; mod cmp; mod div; pub(crate) mod encoding; +mod inv_mod; mod modular; mod mul; mod shl; diff --git a/src/uint/boxed/bit_or.rs b/src/uint/boxed/bit_or.rs new file mode 100644 index 000000000..94655d7e6 --- /dev/null +++ b/src/uint/boxed/bit_or.rs @@ -0,0 +1,118 @@ +//! [`BoxedUint`] bitwise OR operations. + +use crate::{BoxedUint, Limb, Wrapping}; +use core::ops::{BitOr, BitOrAssign}; +use subtle::{Choice, CtOption}; + +impl BoxedUint { + /// Computes bitwise `a & b`. + #[inline(always)] + pub fn bitor(&self, rhs: &Self) -> Self { + Self::chain(self, rhs, Limb::ZERO, |a, b, z| (a.bitor(b), z)).0 + } + + /// Perform wrapping bitwise `OR`. + /// + /// There's no way wrapping could ever happen. + /// This function exists so that all operations are accounted for in the wrapping operations + pub fn wrapping_or(&self, rhs: &Self) -> Self { + self.bitor(rhs) + } + + /// Perform checked bitwise `OR`, returning a [`CtOption`] which `is_some` always + pub fn checked_or(&self, rhs: &Self) -> CtOption { + let result = self.bitor(rhs); + CtOption::new(result, Choice::from(1)) + } +} + +impl BitOr for BoxedUint { + type Output = Self; + + fn bitor(self, rhs: Self) -> BoxedUint { + self.bitor(&rhs) + } +} + +impl BitOr<&BoxedUint> for BoxedUint { + type Output = BoxedUint; + + #[allow(clippy::needless_borrow)] + fn bitor(self, rhs: &BoxedUint) -> BoxedUint { + (&self).bitor(rhs) + } +} + +impl BitOr for &BoxedUint { + type Output = BoxedUint; + + fn bitor(self, rhs: BoxedUint) -> BoxedUint { + self.bitor(&rhs) + } +} + +impl BitOr<&BoxedUint> for &BoxedUint { + type Output = BoxedUint; + + fn bitor(self, rhs: &BoxedUint) -> BoxedUint { + self.bitor(rhs) + } +} + +impl BitOrAssign for BoxedUint { + fn bitor_assign(&mut self, other: Self) { + Self::bitor_assign(self, &other) + } +} + +impl BitOrAssign<&BoxedUint> for BoxedUint { + fn bitor_assign(&mut self, other: &Self) { + for (a, b) in self.limbs.iter_mut().zip(other.limbs.iter()) { + *a |= *b; + } + } +} + +impl BitOr for Wrapping { + type Output = Self; + + fn bitor(self, rhs: Self) -> Wrapping { + Wrapping(self.0.bitor(&rhs.0)) + } +} + +impl BitOr<&Wrapping> for Wrapping { + type Output = Wrapping; + + fn bitor(self, rhs: &Wrapping) -> Wrapping { + Wrapping(self.0.bitor(&rhs.0)) + } +} + +impl BitOr> for &Wrapping { + type Output = Wrapping; + + fn bitor(self, rhs: Wrapping) -> Wrapping { + Wrapping(BoxedUint::bitor(&self.0, &rhs.0)) + } +} + +impl BitOr<&Wrapping> for &Wrapping { + type Output = Wrapping; + + fn bitor(self, rhs: &Wrapping) -> Wrapping { + Wrapping(BoxedUint::bitor(&self.0, &rhs.0)) + } +} + +impl BitOrAssign for Wrapping { + fn bitor_assign(&mut self, other: Self) { + self.0.bitor_assign(&other.0) + } +} + +impl BitOrAssign<&Wrapping> for Wrapping { + fn bitor_assign(&mut self, other: &Self) { + self.0.bitor_assign(&other.0) + } +} diff --git a/src/uint/boxed/bits.rs b/src/uint/boxed/bits.rs index c838726b5..d07f66a75 100644 --- a/src/uint/boxed/bits.rs +++ b/src/uint/boxed/bits.rs @@ -1,7 +1,7 @@ //! Bit manipulation functions. -use crate::{BoxedUint, Limb, Zero}; -use subtle::{ConditionallySelectable, ConstantTimeEq}; +use crate::{BoxedUint, Limb, Word, Zero}; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; impl BoxedUint { /// Calculate the number of bits needed to represent this number, i.e. the index of the highest @@ -28,12 +28,40 @@ impl BoxedUint { pub fn bits_precision(&self) -> usize { self.limbs.len() * Limb::BITS } + + /// Sets the bit at `index` to 0 or 1 depending on the value of `bit_value`. + pub(crate) fn set_bit(&mut self, index: usize, bit_value: Choice) { + let limb_num = index / Limb::BITS; + let index_in_limb = index % Limb::BITS; + let index_mask = 1 << index_in_limb; + + for i in 0..self.nlimbs() { + let limb = &mut self.limbs[i]; + let is_right_limb = (i as Word).ct_eq(&(limb_num as Word)); + let old_limb = *limb; + let new_limb = Limb::conditional_select( + &Limb(old_limb.0 & !index_mask), + &Limb(old_limb.0 | index_mask), + bit_value, + ); + *limb = Limb::conditional_select(&old_limb, &new_limb, is_right_limb); + } + } } #[cfg(test)] mod tests { use super::BoxedUint; use hex_literal::hex; + use subtle::Choice; + + fn uint_with_bits_at(positions: &[usize]) -> BoxedUint { + let mut result = BoxedUint::zero_with_precision(256); + for &pos in positions { + result |= BoxedUint::one_with_precision(256).shl_vartime(pos); + } + result + } #[test] fn bits() { @@ -46,4 +74,23 @@ mod tests { let n2 = BoxedUint::from_be_slice(&hex!("00000000004000000000000000000000"), 128).unwrap(); assert_eq!(87, n2.bits()); } + + #[test] + fn set_bit() { + let mut u = uint_with_bits_at(&[16, 79, 150]); + u.set_bit(127, Choice::from(1)); + assert_eq!(u, uint_with_bits_at(&[16, 79, 127, 150])); + + let mut u = uint_with_bits_at(&[16, 79, 150]); + u.set_bit(150, Choice::from(1)); + assert_eq!(u, uint_with_bits_at(&[16, 79, 150])); + + let mut u = uint_with_bits_at(&[16, 79, 150]); + u.set_bit(127, Choice::from(0)); + assert_eq!(u, uint_with_bits_at(&[16, 79, 150])); + + let mut u = uint_with_bits_at(&[16, 79, 150]); + u.set_bit(150, Choice::from(0)); + assert_eq!(u, uint_with_bits_at(&[16, 79])); + } } diff --git a/src/uint/boxed/inv_mod.rs b/src/uint/boxed/inv_mod.rs new file mode 100644 index 000000000..01e58cd38 --- /dev/null +++ b/src/uint/boxed/inv_mod.rs @@ -0,0 +1,71 @@ +//! [`BoxedUint`] modular inverse (i.e. reciprocal) operations. + +use crate::{BoxedUint, Word}; +use subtle::{Choice, ConstantTimeLess}; + +impl BoxedUint { + /// Computes 1/`self` mod `2^k`. + /// + /// Conditions: `self` < 2^k and `self` must be odd + pub fn inv_mod2k(&self, k: usize) -> Self { + // This is the same algorithm as in `inv_mod2k_vartime()`, + // but made constant-time w.r.t `k` as well. + + let mut x = Self::zero_with_precision(self.bits_precision()); // keeps `x` during iterations + let mut b = Self::one_with_precision(self.bits_precision()); // keeps `b_i` during iterations + + for i in 0..self.bits_precision() { + // Only iterations for i = 0..k need to change `x`, + // the rest are dummy ones performed for the sake of constant-timeness. + let within_range = (i as Word).ct_lt(&(k as Word)); + + // X_i = b_i mod 2 + 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).shr_vartime(1); + + // Store the X_i bit in the result (x = x | (1 << X_i)) + // Don't change the result in dummy iterations. + let x_i_choice = x_i_choice & within_range; + x.set_bit(i, x_i_choice); + } + + x + } +} + +#[cfg(test)] +mod tests { + use super::BoxedUint; + use hex_literal::hex; + + #[test] + fn inv_mod2k() { + let v = BoxedUint::from_be_slice( + &hex!("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"), + 256, + ) + .unwrap(); + let e = BoxedUint::from_be_slice( + &hex!("3642e6faeaac7c6663b93d3d6a0d489e434ddc0123db5fa627c7f6e22ddacacf"), + 256, + ) + .unwrap(); + let a = v.inv_mod2k(256); + assert_eq!(e, a); + + let v = BoxedUint::from_be_slice( + &hex!("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"), + 256, + ) + .unwrap(); + let e = BoxedUint::from_be_slice( + &hex!("261776f29b6b106c7680cf3ed83054a1af5ae537cb4613dbb4f20099aa774ec1"), + 256, + ) + .unwrap(); + let a = v.inv_mod2k(256); + assert_eq!(e, a); + } +}