-
Couldn't load subscription status.
- Fork 70
Boxed uintlike coverage #436
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
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.
| } | ||
|
|
||
| /// 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
| 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)
| } | ||
|
|
||
| #[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.
| 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.
|
|
||
| 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?
| 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
| 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.