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.
BoxedUint can't be instantiated in a const fn context because it's heap-allocated
There was a problem hiding this comment.
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.
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.
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.
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.
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.
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.
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.
What about using fully qualified namespace like div_by_2::boxed::div_by_2?
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.
| 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.
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.
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.
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.
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.
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.
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.
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) | ||
| } |
|
@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.
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.
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.
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.
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.
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.
Any specific reason for the change here?
There was a problem hiding this comment.
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.
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.
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.
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.
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.
Since this test tests a function from uint::reciprocal, perhaps it should be located there.
There was a problem hiding this comment.
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.
lt and select should really be moved to ConstChoice - forgot to do that in #458
There was a problem hiding this comment.
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.