Skip to content

Commit 4a252a3

Browse files
authored
Some fixes for modular inversion (#159)
- Break long lines in comments and tests and fix some hardcoded bit sizes - Fix the hardcoded `Limb` size in `inv_odd_mod()` - was set to 64 (did not cause errors, just made the inversion twice as slow on 32-bit targets) - Added `inv_odd_mod_bounded()` for cases of argument/modulus known to be small - Removed `inv_odd_mod_option()` - we do not provide such interface for other constant functions Additionally: - Introduced a `CtChoice` newtype for constant-time const fns - Replaced some multiplications by `Word::MAX` with negations - Normalized constant-time comparisons API in `Limb` and `Uint`: removed `ct_cmp()` (we never need it in constant-time context, `ct_gt()`/`ct_lt()`/`ct_eq()` are enough), matched const fns with `subtle` trait methods, matched methods between `Limb` and `Uint` - Removed `SignedWord` and `SignedWideWord` - `Uint` objects are taken by reference where previously taken by value.
1 parent 64d2404 commit 4a252a3

37 files changed

+513
-419
lines changed

benches/bench.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,12 @@ fn bench_modpow<'a, M: Measurement>(group: &mut BenchmarkGroup<'a, M>) {
8989

9090
let params = moduli
9191
.iter()
92-
.map(|modulus| DynResidueParams::new(*modulus))
92+
.map(|modulus| DynResidueParams::new(modulus))
9393
.collect::<Vec<_>>();
9494
let xs_m = xs
9595
.iter()
9696
.zip(params.iter())
97-
.map(|(x, p)| DynResidue::new(*x, *p))
97+
.map(|(x, p)| DynResidue::new(x, *p))
9898
.collect::<Vec<_>>();
9999

100100
group.bench_function("modpow, 4^4", |b| {

src/ct_choice.rs

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
use subtle::Choice;
2+
3+
use crate::Word;
4+
5+
/// A boolean value returned by constant-time `const fn`s.
6+
// TODO: should be replaced by `subtle::Choice` or `CtOption`
7+
// when `subtle` starts supporting const fns.
8+
#[derive(Debug, Copy, Clone)]
9+
pub struct CtChoice(Word);
10+
11+
impl CtChoice {
12+
/// The falsy vaue.
13+
pub const FALSE: Self = Self(0);
14+
15+
/// The truthy vaue.
16+
pub const TRUE: Self = Self(Word::MAX);
17+
18+
/// Returns the truthy value if `value == Word::MAX`, and the falsy value if `value == 0`.
19+
/// Panics for other values.
20+
pub(crate) const fn from_mask(value: Word) -> Self {
21+
debug_assert!(value == Self::FALSE.0 || value == Self::TRUE.0);
22+
Self(value)
23+
}
24+
25+
/// Returns the truthy value if `value == 1`, and the falsy value if `value == 0`.
26+
/// Panics for other values.
27+
pub(crate) const fn from_lsb(value: Word) -> Self {
28+
debug_assert!(value == Self::FALSE.0 || value == 1);
29+
Self(value.wrapping_neg())
30+
}
31+
32+
pub(crate) const fn not(&self) -> Self {
33+
Self(!self.0)
34+
}
35+
36+
pub(crate) const fn and(&self, other: Self) -> Self {
37+
Self(self.0 & other.0)
38+
}
39+
40+
pub(crate) const fn or(&self, other: Self) -> Self {
41+
Self(self.0 | other.0)
42+
}
43+
44+
/// Return `b` if `self` is truthy, otherwise return `a`.
45+
pub(crate) const fn select(&self, a: Word, b: Word) -> Word {
46+
a ^ (self.0 & (a ^ b))
47+
}
48+
49+
/// Return `x` if `self` is truthy, otherwise return 0.
50+
pub(crate) const fn if_true(&self, x: Word) -> Word {
51+
x & self.0
52+
}
53+
54+
pub(crate) const fn is_true_vartime(&self) -> bool {
55+
self.0 == CtChoice::TRUE.0
56+
}
57+
}
58+
59+
impl From<CtChoice> for Choice {
60+
fn from(choice: CtChoice) -> Self {
61+
Choice::from(choice.0 as u8 & 1)
62+
}
63+
}
64+
65+
impl From<CtChoice> for bool {
66+
fn from(choice: CtChoice) -> Self {
67+
choice.is_true_vartime()
68+
}
69+
}
70+
71+
#[cfg(test)]
72+
mod tests {
73+
use super::CtChoice;
74+
use crate::Word;
75+
76+
#[test]
77+
fn select() {
78+
let a: Word = 1;
79+
let b: Word = 2;
80+
assert_eq!(CtChoice::TRUE.select(a, b), b);
81+
assert_eq!(CtChoice::FALSE.select(a, b), a);
82+
}
83+
}

src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ mod nlimbs;
161161
#[cfg(feature = "generic-array")]
162162
mod array;
163163
mod checked;
164+
mod ct_choice;
164165
mod limb;
165166
mod non_zero;
166167
mod traits;
@@ -169,6 +170,7 @@ mod wrapping;
169170

170171
pub use crate::{
171172
checked::Checked,
173+
ct_choice::CtChoice,
172174
limb::{Limb, WideWord, Word},
173175
non_zero::NonZero,
174176
traits::*,
@@ -178,8 +180,6 @@ pub use crate::{
178180
};
179181
pub use subtle;
180182

181-
pub(crate) use limb::{SignedWord, WideSignedWord};
182-
183183
#[cfg(feature = "generic-array")]
184184
pub use {
185185
crate::array::{ArrayDecoding, ArrayEncoding, ByteArray},

src/limb.rs

-24
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,10 @@ compile_error!("this crate builds on 32-bit and 64-bit platforms only");
3838
#[cfg(target_pointer_width = "32")]
3939
pub type Word = u32;
4040

41-
/// Signed integer type that corresponds to [`Word`].
42-
#[cfg(target_pointer_width = "32")]
43-
pub(crate) type SignedWord = i32;
44-
4541
/// Unsigned wide integer type: double the width of [`Word`].
4642
#[cfg(target_pointer_width = "32")]
4743
pub type WideWord = u64;
4844

49-
/// Signed wide integer type: double the width of [`Limb`].
50-
#[cfg(target_pointer_width = "32")]
51-
pub(crate) type WideSignedWord = i64;
52-
5345
//
5446
// 64-bit definitions
5547
//
@@ -58,18 +50,10 @@ pub(crate) type WideSignedWord = i64;
5850
#[cfg(target_pointer_width = "64")]
5951
pub type Word = u64;
6052

61-
/// Signed integer type that corresponds to [`Word`].
62-
#[cfg(target_pointer_width = "64")]
63-
pub(crate) type SignedWord = i64;
64-
6553
/// Wide integer type: double the width of [`Word`].
6654
#[cfg(target_pointer_width = "64")]
6755
pub type WideWord = u128;
6856

69-
/// Signed wide integer type: double the width of [`SignedWord`].
70-
#[cfg(target_pointer_width = "64")]
71-
pub(crate) type WideSignedWord = i128;
72-
7357
/// Highest bit in a [`Limb`].
7458
pub(crate) const HI_BIT: usize = Limb::BITS - 1;
7559

@@ -106,14 +90,6 @@ impl Limb {
10690
/// Size of the inner integer in bytes.
10791
#[cfg(target_pointer_width = "64")]
10892
pub const BYTES: usize = 8;
109-
110-
/// Return `a` if `c`==0 or `b` if `c`==`Word::MAX`.
111-
///
112-
/// Const-friendly: we can't yet use `subtle` in `const fn` contexts.
113-
#[inline]
114-
pub(crate) const fn ct_select(a: Self, b: Self, c: Word) -> Self {
115-
Self(a.0 ^ (c & (a.0 ^ b.0)))
116-
}
11793
}
11894

11995
impl Bounded for Limb {

src/limb/cmp.rs

+19-31
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Limb comparisons
22
3-
use super::{Limb, SignedWord, WideSignedWord, Word, HI_BIT};
3+
use super::HI_BIT;
4+
use crate::{CtChoice, Limb};
45
use core::cmp::Ordering;
56
use subtle::{Choice, ConstantTimeEq, ConstantTimeGreater, ConstantTimeLess};
67

@@ -24,58 +25,45 @@ impl Limb {
2425
self.0 == other.0
2526
}
2627

27-
/// Returns all 1's if `a`!=0 or 0 if `a`==0.
28-
///
29-
/// Const-friendly: we can't yet use `subtle` in `const fn` contexts.
28+
/// Return `b` if `c` is truthy, otherwise return `a`.
3029
#[inline]
31-
pub(crate) const fn is_nonzero(self) -> Word {
32-
let inner = self.0 as SignedWord;
33-
((inner | inner.saturating_neg()) >> HI_BIT) as Word
30+
pub(crate) const fn ct_select(a: Self, b: Self, c: CtChoice) -> Self {
31+
Self(c.select(a.0, b.0))
3432
}
3533

34+
/// Returns the truthy value if `self != 0` and the falsy value otherwise.
3635
#[inline]
37-
pub(crate) const fn ct_cmp(lhs: Self, rhs: Self) -> SignedWord {
38-
let a = lhs.0 as WideSignedWord;
39-
let b = rhs.0 as WideSignedWord;
40-
let gt = ((b - a) >> Limb::BITS) & 1;
41-
let lt = ((a - b) >> Limb::BITS) & 1 & !gt;
42-
(gt as SignedWord) - (lt as SignedWord)
36+
pub(crate) const fn ct_is_nonzero(&self) -> CtChoice {
37+
let inner = self.0;
38+
CtChoice::from_lsb((inner | inner.wrapping_neg()) >> HI_BIT)
4339
}
4440

45-
/// Returns `Word::MAX` if `lhs == rhs` and `0` otherwise.
41+
/// Returns the truthy value if `lhs == rhs` and the falsy value otherwise.
4642
#[inline]
47-
pub(crate) const fn ct_eq(lhs: Self, rhs: Self) -> Word {
43+
pub(crate) const fn ct_eq(lhs: Self, rhs: Self) -> CtChoice {
4844
let x = lhs.0;
4945
let y = rhs.0;
5046

51-
// c == 0 if and only if x == y
52-
let c = x ^ y;
53-
54-
// If c == 0, then c and -c are both equal to zero;
55-
// otherwise, one or both will have its high bit set.
56-
let d = (c | c.wrapping_neg()) >> (Limb::BITS - 1);
57-
58-
// Result is the opposite of the high bit (now shifted to low).
59-
// Convert 1 to Word::MAX.
60-
(d ^ 1).wrapping_neg()
47+
// x ^ y == 0 if and only if x == y
48+
Self(x ^ y).ct_is_nonzero().not()
6149
}
6250

63-
/// Returns `Word::MAX` if `lhs < rhs` and `0` otherwise.
51+
/// Returns the truthy value if `lhs < rhs` and the falsy value otherwise.
6452
#[inline]
65-
pub(crate) const fn ct_lt(lhs: Self, rhs: Self) -> Word {
53+
pub(crate) const fn ct_lt(lhs: Self, rhs: Self) -> CtChoice {
6654
let x = lhs.0;
6755
let y = rhs.0;
6856
let bit = (((!x) & y) | (((!x) | y) & (x.wrapping_sub(y)))) >> (Limb::BITS - 1);
69-
bit.wrapping_neg()
57+
CtChoice::from_lsb(bit)
7058
}
7159

72-
/// Returns `Word::MAX` if `lhs <= rhs` and `0` otherwise.
60+
/// Returns the truthy value if `lhs <= rhs` and the falsy value otherwise.
7361
#[inline]
74-
pub(crate) const fn ct_le(lhs: Self, rhs: Self) -> Word {
62+
pub(crate) const fn ct_le(lhs: Self, rhs: Self) -> CtChoice {
7563
let x = lhs.0;
7664
let y = rhs.0;
7765
let bit = (((!x) | y) & ((x ^ y) | !(y.wrapping_sub(x)))) >> (Limb::BITS - 1);
78-
bit.wrapping_neg()
66+
CtChoice::from_lsb(bit)
7967
}
8068
}
8169

src/uint/add.rs

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! [`Uint`] addition operations.
22
3-
use crate::{Checked, CheckedAdd, Limb, Uint, Word, Wrapping, Zero};
3+
use crate::{Checked, CheckedAdd, CtChoice, Limb, Uint, Wrapping, Zero};
44
use core::ops::{Add, AddAssign};
55
use subtle::CtOption;
66

@@ -37,12 +37,16 @@ impl<const LIMBS: usize> Uint<LIMBS> {
3737
self.adc(rhs, Limb::ZERO).0
3838
}
3939

40-
/// Perform wrapping addition, returning the overflow bit as a `Word` that is either 0...0 or 1...1.
41-
pub(crate) const fn conditional_wrapping_add(&self, rhs: &Self, choice: Word) -> (Self, Word) {
42-
let actual_rhs = Uint::ct_select(Uint::ZERO, *rhs, choice);
40+
/// Perform wrapping addition, returning the truthy value as the second element of the tuple
41+
/// if an overflow has occurred.
42+
pub(crate) const fn conditional_wrapping_add(
43+
&self,
44+
rhs: &Self,
45+
choice: CtChoice,
46+
) -> (Self, CtChoice) {
47+
let actual_rhs = Uint::ct_select(&Uint::ZERO, rhs, choice);
4348
let (sum, carry) = self.adc(&actual_rhs, Limb::ZERO);
44-
45-
(sum, carry.0.wrapping_mul(Word::MAX))
49+
(sum, CtChoice::from_lsb(carry.0))
4650
}
4751
}
4852

0 commit comments

Comments
 (0)