-
Notifications
You must be signed in to change notification settings - Fork 56
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Boxed uintlike coverage #436
Conversation
9d730ab
to
f433d38
Compare
src/uint/boxed.rs
Outdated
pub const fn const_new(n: BoxedUint) -> (Self, ConstChoice) { | ||
let is_nonzero = n.is_nonzero(); | ||
(Self(n), is_nonzero) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BoxedUint
can't be instantiated in a const fn
context because it's heap-allocated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When should a function return (Value, Choice)
instead of CtChoice(Value)
?
In boxed/sqrt.rs
the implementation of sqrt
uses a NonZero::<BoxedUint>::new(x)
whose value needs to be used whether x
is zero or not, but BoxedUint
is not ConditionallySelectable
so I can't use CtChoice
's map
and unwrap_or
. It seems like having a NonZero::<BoxedUint>::new
that returns (NonZero<BoxedUint>, Choice)
would be necessary, but the current new
's signature doesn't work this way, and such a new
is definitely not const_new
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The (hopefully temporary) workaround for that sort of thing is BoxedUint::conditional_map
.
I'm trying to find an upstream solution in subtle
but it may be necessary to define traits shaped like the ones in that PR in crypto-bigint
as a stopgap (and a CtOption
-alike type, possibly ConstCtOption
) /cc @fjarri
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I saw the ConstantTimeSelect
trait and will use ct_select
and ct_assign
where appropriate. Thank you for the implementation!
src/uint/boxed/cmp.rs
Outdated
pub(crate) fn select(a: &Self, b: &Self, c: ConstChoice) -> Self { | ||
debug_assert_eq!(a.limbs.len(), b.limbs.len()); | ||
let mut limbs = vec![Limb::ZERO; a.limbs.len()]; | ||
|
||
let mut i = 0; | ||
while i < limbs.len() { | ||
limbs[i] = Limb::select(a.limbs[i], b.limbs[i], c); | ||
i += 1; | ||
} | ||
|
||
Self { | ||
limbs: limbs.into(), | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is already impl'd as BoxedUint::conditional_select
. You can use Into
to convert from ConstChoice
to Choice
.
src/uint/boxed/cmp.rs
Outdated
/// Returns the truthy value if `self >= rhs` and the falsy value otherwise. | ||
#[inline] | ||
pub(crate) fn gt(lhs: &Self, rhs: &Self) -> ConstChoice { | ||
let (_res, borrow) = rhs.sbb(lhs, Limb::ZERO); | ||
ConstChoice::from_word_mask(borrow.0) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is already impl'd as BoxedUint::ct_gt
. Really there's not a lot of point in supporting ConstChoice
with BoxedUint
, and the least-common-denominator trait API should use Choice
instead (which provides an optimization barrier in non-const fn
contexts, which is the only place BoxedUint
can be used)
@@ -0,0 +1,295 @@ | |||
//! [`BoxedUint`] square root operations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fine to get this implemented, but in a followup PR (which I'm happy to do) it should probably get a similar macro treatment to the multiply functions so Uint
and BoxedUint
can share a common implementation.
Note to myself:
Edit: this is no longer needed; we can now use |
src/modular/boxed_residue.rs
Outdated
pub fn div_by_2(&self) -> Self { | ||
Self { | ||
montgomery_form: div_by_2(&self.montgomery_form, &self.residue_params.modulus), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a little confusing due to the name clash which makes it almost look like it's going to recurse, although it's hard to suggest something better
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about using fully qualified namespace like div_by_2::boxed::div_by_2
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, sounds good
a14399a
to
2eba0e7
Compare
Newest commits:
|
2f31a4f
to
1b65d01
Compare
let mut b = 0; | ||
let mut i = 0; | ||
while i < self.limbs.len() { | ||
b |= self.limbs[i].0; | ||
i += 1; | ||
} | ||
Limb(b).is_nonzero().into() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let mut b = 0; | |
let mut i = 0; | |
while i < self.limbs.len() { | |
b |= self.limbs[i].0; | |
i += 1; | |
} | |
Limb(b).is_nonzero().into() | |
let choice = Choice::from(1u8); | |
for limb in &self.limbs { | |
choice &= limb.ct_eq(&Limb::ZERO); | |
} | |
choice |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about just !self.is_zero()
? The logic suggested above is basically what is_zero
uses but backwards.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean, we can do that, but other instances can be replaced with !x.is_zero()
too. It makes less sense when const fn
isn't required
/// TODO: this is not really "const", but I need a way to return (value, choice) since | ||
/// BoxedUint is not [`ConditionallySelectable`] so `CtChoice::map` and such does not work | ||
pub fn const_new(n: BoxedUint) -> (Self, Choice) { | ||
let nonzero = n.is_nonzero(); | ||
(Self(n), nonzero) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I factored these into methods on e.g. in this case BoxedUint
: #484
You can use ConstCtChoice
now.
@@ -131,6 +131,40 @@ impl BoxedUint { | |||
|
|||
out.into() | |||
} | |||
|
|||
/// Create a new [`BoxedUint`] from the provided big endian hex string. | |||
pub fn from_be_hex(hex: &str, bits_precision: u32) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since these can't be constructed at compile-time, it would probably be good to make it fallible
/// Computes `self << shift`. | ||
/// Returns `None` if `shift >= self.bits_precision()`. | ||
/// | ||
/// NOTE: this operation is variable time with respect to `shift` *ONLY*. | ||
/// | ||
/// When used with a fixed `shift`, this function is constant-time with respect to `self`. | ||
#[inline(always)] | ||
pub fn shl_vartime(&self, shift: u32) -> Option<Self> { | ||
let mut result = Self::zero_with_precision(self.bits_precision()); | ||
let success = self.shl_vartime_into(&mut result, shift); | ||
success.map(|_| result) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I originally made them to accommodate the requirement from crypto-primes
, but now I realized I can use overflowing_shl
/overflowing_shr
to achieve the same thing, so I felt no need to add shl_vartime
/shr_vartime
for BoxedUint
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Err, I added shl_vartime
in #330. The reason we have *_vartime
equivalents of the sh*
methods is because they can be quite a bit faster.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh now I see the reason for all the replaced _vartime
methods previously. Yes, we should keep these.
/// Computes `self >> shift`. | ||
/// Returns `None` if `shift >= self.bits_precision()`. | ||
/// | ||
/// NOTE: this operation is variable time with respect to `shift` *ONLY*. | ||
/// | ||
/// When used with a fixed `shift`, this function is constant-time with respect to `self`. | ||
#[inline(always)] | ||
pub fn shr_vartime(&self, shift: u32) -> Option<Self> { | ||
let mut result = Self::zero_with_precision(self.bits_precision()); | ||
let success = self.shr_vartime_into(&mut result, shift); | ||
success.map(|_| result) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above.
@xuganyu96 sorry about the rebase you're gonna have to do after #485, but we'll try to get this merged ASAP once you do! |
Totally not an issue. Thank you for being so thorough with my PR! |
…lySelectable in BoxedUint
cc2efd0
to
d2d0e0f
Compare
@@ -11,7 +11,7 @@ fn bench_shifts(c: &mut Criterion) { | |||
group.bench_function("shl_vartime", |b| { | |||
b.iter_batched( | |||
|| BoxedUint::random(&mut OsRng, UINT_BITS), | |||
|x| black_box(x.shl_vartime(UINT_BITS / 2 + 10)), | |||
|x| black_box(x.overflowing_shl(UINT_BITS / 2 + 10).0), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the change here? And if you're benchmarking overflowing_shl
now, the string label should be changed too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah sorry I think I got really confused between the various shl
/shr
functions.
pub fn div_by_2(&self) -> Self { | ||
Self { | ||
montgomery_form: div_by_2::boxed::div_by_2(&self.montgomery_form, &self.params.modulus), | ||
params: self.params.clone(), // TODO: avoid clone? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There would be no need for clone if it was an inplace method, but if it creates a new number, I think cloning is unavoidable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When std
is available they're stored as an Arc
, so cloning is cheap (incrementing an atomic reference counter)
@@ -28,3 +28,23 @@ pub(crate) fn div_by_2<const LIMBS: usize>(a: &Uint<LIMBS>, modulus: &Uint<LIMBS | |||
|
|||
Uint::<LIMBS>::select(&if_even, &if_odd, is_odd) | |||
} | |||
|
|||
#[cfg(feature = "alloc")] | |||
pub(crate) mod boxed { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think putting different variants of this function in modules is a bit of an overcomplication, perhaps just different suffixes would suffice.
@@ -84,7 +140,7 @@ mod tests { | |||
fn uint_with_bits_at(positions: &[u32]) -> BoxedUint { | |||
let mut result = BoxedUint::zero_with_precision(256); | |||
for &pos in positions { | |||
result |= BoxedUint::one_with_precision(256).shl_vartime(pos).unwrap(); | |||
result |= BoxedUint::one_with_precision(256).overflowing_shl(pos).0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any specific reason for the change here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another instance of me being confused. Will restore back to its original state.
@@ -10,6 +10,28 @@ use subtle::{ | |||
Choice, ConditionallySelectable, ConstantTimeEq, ConstantTimeGreater, ConstantTimeLess, | |||
}; | |||
|
|||
impl BoxedUint { | |||
/// Returns the Ordering between `self` and `rhs` in variable time. | |||
pub fn cmp_vartime(&self, rhs: &Self) -> Ordering { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a feeling that using comparisons between limbs instead of subtraction may be faster. Put a TODO perhaps?
@@ -38,7 +52,7 @@ impl BoxedUint { | |||
let mut bd = self.bits_precision() - mb; | |||
let mut rem = self.clone(); | |||
// Will not overflow since `bd < bits_precision` | |||
let mut c = rhs.shl_vartime(bd).expect("shift within range"); | |||
let mut c = rhs.overflowing_shl(bd).0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a vartime method, no need to use a constant-time overflowing_shl
here
@@ -112,7 +135,7 @@ impl BoxedUint { | |||
let mut remainder = self.clone(); | |||
let mut quotient = Self::zero_with_precision(self.bits_precision()); | |||
// Will not overflow since `bd < bits_precision` | |||
let mut c = rhs.shl_vartime(bd).expect("shift within range"); | |||
let mut c = rhs.overflowing_shl(bd).0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above, no need for a constant-time shift here.
@@ -0,0 +1,208 @@ | |||
//! Reciprocal, shared across Uint and BoxedUint |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should reference the paper the algorithm was taken from.
use super::{div2by1, Reciprocal}; | ||
use crate::{Limb, NonZero, Word}; | ||
#[test] | ||
fn div2by1_overflow() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this test tests a function from uint::reciprocal
, perhaps it should be located there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test is actually an exact copy of the same test in uint::div_limb
. I will delete this one in boxed/div_limb.rs
.
|
||
/// Returns `u32::MAX` if `a < b` and `0` otherwise. | ||
#[inline] | ||
const fn lt(a: u32, b: u32) -> u32 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lt
and select
should really be moved to ConstChoice
- forgot to do that in #458
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added them as TODO's.
See also: #436 and entropyxyz/crypto-primes#37
This is an attempt to implement for BoxedUint the missing arithmetic needed for crypto-primes (see entropyxyz/crypto-primes#37 (comment)).
This is the continuation of #428.