Skip to content

Commit c29204c

Browse files
committed
Add ConstOption struct
1 parent c33b7f2 commit c29204c

File tree

16 files changed

+358
-215
lines changed

16 files changed

+358
-215
lines changed

benches/uint.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,8 @@ fn bench_inv_mod(c: &mut Criterion) {
160160
let m = U256::random(&mut OsRng) | U256::ONE;
161161
loop {
162162
let x = U256::random(&mut OsRng);
163-
let (_, is_some) = x.inv_odd_mod(&m);
164-
if is_some.into() {
163+
let inv_x = x.inv_odd_mod(&m);
164+
if inv_x.is_some().into() {
165165
break (x, m);
166166
}
167167
}
@@ -177,8 +177,8 @@ fn bench_inv_mod(c: &mut Criterion) {
177177
let m = U256::random(&mut OsRng) | U256::ONE;
178178
loop {
179179
let x = U256::random(&mut OsRng);
180-
let (_, is_some) = x.inv_odd_mod(&m);
181-
if is_some.into() {
180+
let inv_x = x.inv_odd_mod(&m);
181+
if inv_x.is_some().into() {
182182
break (x, m);
183183
}
184184
}
@@ -194,8 +194,8 @@ fn bench_inv_mod(c: &mut Criterion) {
194194
let m = U256::random(&mut OsRng);
195195
loop {
196196
let x = U256::random(&mut OsRng);
197-
let (_, is_some) = black_box(x.inv_mod(&m));
198-
if is_some.into() {
197+
let inv_x = x.inv_mod(&m);
198+
if inv_x.is_some().into() {
199199
break (x, m);
200200
}
201201
}

src/const_choice.rs

Lines changed: 121 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
use subtle::Choice;
1+
use subtle::{Choice, CtOption};
22

3-
use crate::Word;
3+
use crate::{NonZero, Uint, Word};
44

55
/// A boolean value returned by constant-time `const fn`s.
66
// TODO: should be replaced by `subtle::Choice` or `CtOption`
@@ -174,6 +174,125 @@ impl PartialEq for ConstChoice {
174174
}
175175
}
176176

177+
/// An equivalent of `subtle::CtOption` usable in a `const fn` context.
178+
#[derive(Debug, Clone)]
179+
pub struct ConstOption<T> {
180+
value: T,
181+
is_some: ConstChoice,
182+
}
183+
184+
impl<T> ConstOption<T> {
185+
#[inline]
186+
pub(crate) const fn new(value: T, is_some: ConstChoice) -> Self {
187+
Self { value, is_some }
188+
}
189+
190+
#[inline]
191+
pub(crate) const fn some(value: T) -> Self {
192+
Self {
193+
value,
194+
is_some: ConstChoice::TRUE,
195+
}
196+
}
197+
198+
#[inline]
199+
pub(crate) const fn none(dummy_value: T) -> Self {
200+
Self {
201+
value: dummy_value,
202+
is_some: ConstChoice::FALSE,
203+
}
204+
}
205+
206+
/// Returns a reference to the contents of this structure.
207+
///
208+
/// **Note:** if the second element is `None`, the first value may take any value.
209+
#[inline]
210+
pub(crate) const fn components_ref(&self) -> (&T, ConstChoice) {
211+
// Since Rust is not smart enough to tell that we would be moving the value,
212+
// and hence no destructors will be called, we have to return a reference instead.
213+
// See https://github.com/rust-lang/rust/issues/66753
214+
(&self.value, self.is_some)
215+
}
216+
217+
/// Returns a true [`ConstChoice`] if this value is `Some`.
218+
#[inline]
219+
pub const fn is_some(&self) -> ConstChoice {
220+
self.is_some
221+
}
222+
223+
/// Returns a true [`ConstChoice`] if this value is `None`.
224+
#[inline]
225+
pub const fn is_none(&self) -> ConstChoice {
226+
self.is_some.not()
227+
}
228+
229+
/// This returns the underlying value but panics if it is not `Some`.
230+
#[inline]
231+
pub fn unwrap(self) -> T {
232+
assert!(self.is_some.is_true_vartime());
233+
self.value
234+
}
235+
}
236+
237+
impl<T> From<ConstOption<T>> for CtOption<T> {
238+
#[inline]
239+
fn from(value: ConstOption<T>) -> Self {
240+
CtOption::new(value.value, value.is_some.into())
241+
}
242+
}
243+
244+
// Need specific implementations to work around the
245+
// "destructors cannot be evaluated at compile-time" error
246+
// See https://github.com/rust-lang/rust/issues/66753
247+
248+
impl<const LIMBS: usize> ConstOption<Uint<LIMBS>> {
249+
/// This returns the underlying value if it is `Some` or the provided value otherwise.
250+
#[inline]
251+
pub const fn unwrap_or(self, def: Uint<LIMBS>) -> Uint<LIMBS> {
252+
Uint::select(&def, &self.value, self.is_some)
253+
}
254+
255+
/// Returns the contained value, consuming the `self` value.
256+
///
257+
/// # Panics
258+
///
259+
/// Panics if the value is none with a custom panic message provided by
260+
/// `msg`.
261+
#[inline]
262+
pub const fn expect(self, msg: &str) -> Uint<LIMBS> {
263+
assert!(self.is_some.is_true_vartime(), "{}", msg);
264+
self.value
265+
}
266+
}
267+
268+
impl<const LIMBS: usize> ConstOption<(Uint<LIMBS>, Uint<LIMBS>)> {
269+
/// Returns the contained value, consuming the `self` value.
270+
///
271+
/// # Panics
272+
///
273+
/// Panics if the value is none with a custom panic message provided by
274+
/// `msg`.
275+
#[inline]
276+
pub const fn expect(self, msg: &str) -> (Uint<LIMBS>, Uint<LIMBS>) {
277+
assert!(self.is_some.is_true_vartime(), "{}", msg);
278+
self.value
279+
}
280+
}
281+
282+
impl<const LIMBS: usize> ConstOption<NonZero<Uint<LIMBS>>> {
283+
/// Returns the contained value, consuming the `self` value.
284+
///
285+
/// # Panics
286+
///
287+
/// Panics if the value is none with a custom panic message provided by
288+
/// `msg`.
289+
#[inline]
290+
pub const fn expect(self, msg: &str) -> NonZero<Uint<LIMBS>> {
291+
assert!(self.is_some.is_true_vartime(), "{}", msg);
292+
self.value
293+
}
294+
}
295+
177296
#[cfg(test)]
178297
mod tests {
179298
use super::ConstChoice;

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ mod wrapping;
176176

177177
pub use crate::{
178178
checked::Checked,
179-
const_choice::ConstChoice,
179+
const_choice::{ConstChoice, ConstOption},
180180
limb::{Limb, WideWord, Word},
181181
non_zero::NonZero,
182182
traits::*,

src/modular/dyn_residue.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,9 @@ impl<const LIMBS: usize> DynResidueParams<LIMBS> {
5050
let r = Uint::MAX.rem(&nz_modulus).wrapping_add(&Uint::ONE);
5151
let r2 = Uint::rem_wide(r.square_wide(), &nz_modulus);
5252

53+
let maybe_inverse = modulus.inv_mod2k_vartime(Word::BITS);
5354
// If the inverse exists, it means the modulus is odd.
54-
let (inv_mod_limb, modulus_is_odd) = modulus.inv_mod2k_vartime(Word::BITS);
55+
let (inv_mod_limb, modulus_is_odd) = maybe_inverse.components_ref();
5556
let mod_neg_inv = Limb(Word::MIN.wrapping_sub(inv_mod_limb.limbs[0].0));
5657

5758
let r3 = montgomery_reduction(&r2.square_wide(), modulus, mod_neg_inv);

src/modular/dyn_residue/inv.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use super::{DynResidue, DynResidueParams};
44
use crate::{
55
modular::{inv::inv_montgomery_form, BernsteinYangInverter},
66
traits::Invert,
7-
ConstChoice, Inverter, PrecomputeInverter, PrecomputeInverterWithAdjuster, Uint,
7+
ConstOption, Inverter, PrecomputeInverter, PrecomputeInverterWithAdjuster, Uint,
88
};
99
use core::fmt;
1010
use subtle::CtOption;
@@ -14,28 +14,28 @@ impl<const LIMBS: usize> DynResidue<LIMBS> {
1414
/// I.e. `self * self^-1 = 1`.
1515
/// If the number was invertible, the second element of the tuple is the truthy value,
1616
/// otherwise it is the falsy value (in which case the first element's value is unspecified).
17-
pub const fn invert(&self) -> (Self, ConstChoice) {
18-
let (montgomery_form, is_some) = inv_montgomery_form(
17+
pub const fn invert(&self) -> ConstOption<Self> {
18+
let maybe_inverse = inv_montgomery_form(
1919
&self.montgomery_form,
2020
&self.residue_params.modulus,
2121
&self.residue_params.r3,
2222
self.residue_params.mod_neg_inv,
2323
);
24+
let (montgomery_form, is_some) = maybe_inverse.components_ref();
2425

2526
let value = Self {
26-
montgomery_form,
27+
montgomery_form: *montgomery_form,
2728
residue_params: self.residue_params,
2829
};
2930

30-
(value, is_some)
31+
ConstOption::new(value, is_some)
3132
}
3233
}
3334

3435
impl<const LIMBS: usize> Invert for DynResidue<LIMBS> {
3536
type Output = CtOption<Self>;
3637
fn invert(&self) -> Self::Output {
37-
let (value, is_some) = self.invert();
38-
CtOption::new(value, is_some.into())
38+
self.invert().into()
3939
}
4040
}
4141

@@ -114,7 +114,7 @@ mod tests {
114114
U256::from_be_hex("77117F1273373C26C700D076B3F780074D03339F56DD0EFB60E7F58441FD3685");
115115
let x_mod = DynResidue::new(&x, params);
116116

117-
let (inv, _is_some) = x_mod.invert();
117+
let inv = x_mod.invert().unwrap();
118118
let res = x_mod * inv;
119119

120120
assert_eq!(res.retrieve(), U256::ONE);

src/modular/inv.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
use crate::{modular::reduction::montgomery_reduction, ConstChoice, Limb, Uint};
1+
use crate::{modular::reduction::montgomery_reduction, ConstOption, Limb, Uint};
22

33
pub const fn inv_montgomery_form<const LIMBS: usize>(
44
x: &Uint<LIMBS>,
55
modulus: &Uint<LIMBS>,
66
r3: &Uint<LIMBS>,
77
mod_neg_inv: Limb,
8-
) -> (Uint<LIMBS>, ConstChoice) {
9-
let (inverse, is_some) = x.inv_odd_mod(modulus);
10-
(
8+
) -> ConstOption<Uint<LIMBS>> {
9+
let maybe_inverse = x.inv_odd_mod(modulus);
10+
let (inverse, is_some) = maybe_inverse.components_ref();
11+
ConstOption::new(
1112
montgomery_reduction(&inverse.split_mul(r3), modulus, mod_neg_inv),
1213
is_some,
1314
)

src/modular/residue/inv.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use super::{Residue, ResidueParams};
44
use crate::{
55
modular::{inv::inv_montgomery_form, BernsteinYangInverter},
6-
ConstChoice, Invert, Inverter, NonZero, PrecomputeInverter, Uint,
6+
ConstChoice, ConstOption, Invert, Inverter, NonZero, PrecomputeInverter, Uint,
77
};
88
use core::{fmt, marker::PhantomData};
99
use subtle::CtOption;
@@ -13,36 +13,37 @@ impl<MOD: ResidueParams<LIMBS>, const LIMBS: usize> Residue<MOD, LIMBS> {
1313
/// I.e. `self * self^-1 = 1`.
1414
/// If the number was invertible, the second element of the tuple is the truthy value,
1515
/// otherwise it is the falsy value (in which case the first element's value is unspecified).
16-
pub const fn invert(&self) -> (Self, ConstChoice) {
17-
let (montgomery_form, is_some) = inv_montgomery_form(
16+
pub const fn invert(&self) -> ConstOption<Self> {
17+
let maybe_inverse = inv_montgomery_form(
1818
&self.montgomery_form,
1919
&MOD::MODULUS.0,
2020
&MOD::R3,
2121
MOD::MOD_NEG_INV,
2222
);
23+
let (montgomery_form, is_some) = maybe_inverse.components_ref();
2324

2425
let value = Self {
25-
montgomery_form,
26+
montgomery_form: *montgomery_form,
2627
phantom: PhantomData,
2728
};
2829

29-
(value, is_some)
30+
ConstOption::new(value, is_some)
3031
}
3132
}
3233

3334
impl<MOD: ResidueParams<LIMBS>, const LIMBS: usize> Invert for Residue<MOD, LIMBS> {
3435
type Output = CtOption<Self>;
3536
fn invert(&self) -> Self::Output {
36-
let (value, is_some) = self.invert();
37-
CtOption::new(value, is_some.into())
37+
self.invert().into()
3838
}
3939
}
4040

4141
impl<MOD: ResidueParams<LIMBS>, const LIMBS: usize> Invert for NonZero<Residue<MOD, LIMBS>> {
4242
type Output = Self;
4343
fn invert(&self) -> Self::Output {
4444
// Always succeeds for a non-zero argument
45-
let (value, _is_some) = self.as_ref().invert();
45+
let value = self.as_ref().invert().unwrap();
46+
// An inverse is necessarily non-zero
4647
NonZero::new(value).unwrap()
4748
}
4849
}
@@ -136,7 +137,7 @@ mod tests {
136137
U256::from_be_hex("77117F1273373C26C700D076B3F780074D03339F56DD0EFB60E7F58441FD3685");
137138
let x_mod = const_residue!(x, Modulus);
138139

139-
let (inv, _is_some) = x_mod.invert();
140+
let inv = x_mod.invert().unwrap();
140141
let res = x_mod * inv;
141142

142143
assert_eq!(res.retrieve(), U256::ONE);

src/modular/residue/macros.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ macro_rules! impl_modulus {
2929
}
3030

3131
// Can unwrap `NonZero::const_new()` here since `res` was asserted to be odd.
32-
$crate::NonZero::<$uint_type>::const_new(res).0
32+
$crate::NonZero::<$uint_type>::const_new(res).expect("modulus ensured non-zero")
3333
};
3434

3535
const R: $uint_type = $crate::Uint::MAX
@@ -41,7 +41,7 @@ macro_rules! impl_modulus {
4141
Self::MODULUS
4242
.as_ref()
4343
.inv_mod2k_vartime($crate::Word::BITS)
44-
.0
44+
.expect("modulus ensured odd")
4545
.as_limbs()[0]
4646
.0,
4747
),

src/non_zero.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Wrapper type for non-zero integers.
22
3-
use crate::{Bounded, ConstChoice, Constants, Encoding, Limb, Uint, Zero};
3+
use crate::{Bounded, ConstOption, Constants, Encoding, Limb, Uint, Zero};
44
use core::{
55
fmt,
66
num::{NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8},
@@ -27,16 +27,16 @@ pub struct NonZero<T: Zero>(pub(crate) T);
2727
impl NonZero<Limb> {
2828
/// Creates a new non-zero limb in a const context.
2929
/// The second return value is `FALSE` if `n` is zero, `TRUE` otherwise.
30-
pub const fn const_new(n: Limb) -> (Self, ConstChoice) {
31-
(Self(n), n.is_nonzero())
30+
pub const fn const_new(n: Limb) -> ConstOption<Self> {
31+
ConstOption::new(Self(n), n.is_nonzero())
3232
}
3333
}
3434

3535
impl<const LIMBS: usize> NonZero<Uint<LIMBS>> {
3636
/// Creates a new non-zero integer in a const context.
3737
/// The second return value is `FALSE` if `n` is zero, `TRUE` otherwise.
38-
pub const fn const_new(n: Uint<LIMBS>) -> (Self, ConstChoice) {
39-
(Self(n), n.is_nonzero())
38+
pub const fn const_new(n: Uint<LIMBS>) -> ConstOption<Self> {
39+
ConstOption::new(Self(n), n.is_nonzero())
4040
}
4141
}
4242

0 commit comments

Comments
 (0)