Skip to content

Commit 65622bd

Browse files
committed
Reapply "BREAKING: Make random_mod platform-independent"
This reverts 5784b13, reapplying 62b90b8. See also: RustCrypto#1018 (comment) It turns out that the previous approach ran afoul of a likely LLVM (or possibly rustc) bug. This time, I tried removing `random_mod_core` entirely and instead writing `random_mod` and `try_random_mod` as rejection loops over `random_bits` and `try_random_bits`. Somehow, this does not (seem to) run afoul of the bug.
1 parent e9f0efd commit 65622bd

File tree

1 file changed

+48
-58
lines changed

1 file changed

+48
-58
lines changed

src/uint/rand.rs

Lines changed: 48 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
//! Random number generator support
22
33
use super::{Uint, Word};
4-
use crate::{Encoding, Limb, NonZero, Random, RandomBits, RandomBitsError, RandomMod, Zero};
4+
use crate::{Limb, NonZero, Random, RandomBits, RandomBitsError, RandomMod};
55
use rand_core::{RngCore, TryRngCore};
6-
use subtle::ConstantTimeLess;
76

87
impl<const LIMBS: usize> Random for Uint<LIMBS> {
98
fn try_random<R: TryRngCore + ?Sized>(rng: &mut R) -> Result<Self, R::Error> {
@@ -30,7 +29,7 @@ pub(crate) fn random_bits_core<R: TryRngCore + ?Sized>(
3029
rng: &mut R,
3130
zeroed_limbs: &mut [Limb],
3231
bit_length: u32,
33-
) -> Result<(), RandomBitsError<R::Error>> {
32+
) -> Result<(), R::Error> {
3433
if bit_length == 0 {
3534
return Ok(());
3635
}
@@ -43,8 +42,7 @@ pub(crate) fn random_bits_core<R: TryRngCore + ?Sized>(
4342
let mask = Word::MAX >> ((Word::BITS - partial_limb) % Word::BITS);
4443

4544
for i in 0..nonzero_limbs - 1 {
46-
rng.try_fill_bytes(&mut buffer)
47-
.map_err(RandomBitsError::RandCore)?;
45+
rng.try_fill_bytes(&mut buffer)?;
4846
zeroed_limbs[i] = Limb(Word::from_le_bytes(buffer));
4947
}
5048

@@ -62,8 +60,7 @@ pub(crate) fn random_bits_core<R: TryRngCore + ?Sized>(
6260
buffer.as_mut_slice()
6361
};
6462

65-
rng.try_fill_bytes(slice)
66-
.map_err(RandomBitsError::RandCore)?;
63+
rng.try_fill_bytes(slice)?;
6764
zeroed_limbs[nonzero_limbs - 1] = Limb(Word::from_le_bytes(buffer) & mask);
6865

6966
Ok(())
@@ -95,71 +92,38 @@ impl<const LIMBS: usize> RandomBits for Uint<LIMBS> {
9592
});
9693
}
9794
let mut limbs = [Limb::ZERO; LIMBS];
98-
random_bits_core(rng, &mut limbs, bit_length)?;
95+
random_bits_core(rng, &mut limbs, bit_length).map_err(RandomBitsError::RandCore)?;
9996
Ok(Self::from(limbs))
10097
}
10198
}
10299

103100
impl<const LIMBS: usize> RandomMod for Uint<LIMBS> {
104101
fn random_mod<R: RngCore + ?Sized>(rng: &mut R, modulus: &NonZero<Self>) -> Self {
105-
let mut n = Self::ZERO;
106-
let Ok(()) = random_mod_core(rng, &mut n, modulus, modulus.bits_vartime());
107-
n
102+
let n_bits = modulus.bits_vartime();
103+
loop {
104+
let n = Self::random_bits(rng, n_bits);
105+
if &n < modulus.as_ref() {
106+
return n;
107+
}
108+
}
108109
}
109110

110111
fn try_random_mod<R: TryRngCore + ?Sized>(
111112
rng: &mut R,
112113
modulus: &NonZero<Self>,
113114
) -> Result<Self, R::Error> {
114-
let mut n = Self::ZERO;
115-
random_mod_core(rng, &mut n, modulus, modulus.bits_vartime())?;
116-
Ok(n)
117-
}
118-
}
119-
120-
/// Generic implementation of `random_mod` which can be shared with `BoxedUint`.
121-
// TODO(tarcieri): obtain `n_bits` via a trait like `Integer`
122-
pub(super) fn random_mod_core<T, R: TryRngCore + ?Sized>(
123-
rng: &mut R,
124-
n: &mut T,
125-
modulus: &NonZero<T>,
126-
n_bits: u32,
127-
) -> Result<(), R::Error>
128-
where
129-
T: AsMut<[Limb]> + AsRef<[Limb]> + ConstantTimeLess + Zero,
130-
{
131-
#[cfg(target_pointer_width = "64")]
132-
let mut next_word = || rng.try_next_u64();
133-
#[cfg(target_pointer_width = "32")]
134-
let mut next_word = || rng.try_next_u32();
135-
136-
let n_limbs = n_bits.div_ceil(Limb::BITS) as usize;
137-
138-
let hi_word_modulus = modulus.as_ref().as_ref()[n_limbs - 1].0;
139-
let mask = !0 >> hi_word_modulus.leading_zeros();
140-
let mut hi_word = next_word()? & mask;
141-
142-
loop {
143-
while hi_word > hi_word_modulus {
144-
hi_word = next_word()? & mask;
145-
}
146-
// Set high limb
147-
n.as_mut()[n_limbs - 1] = Limb::from_le_bytes(hi_word.to_le_bytes());
148-
// Set low limbs
149-
for i in 0..n_limbs - 1 {
150-
// Need to deserialize from little-endian to make sure that two 32-bit limbs
151-
// deserialized sequentially are equal to one 64-bit limb produced from the same
152-
// byte stream.
153-
n.as_mut()[i] = Limb::from_le_bytes(next_word()?.to_le_bytes());
115+
let n_bits = modulus.bits_vartime();
116+
loop {
117+
let n = match Self::try_random_bits(rng, n_bits) {
118+
Ok(n) => n,
119+
Err(RandomBitsError::RandCore(e)) => return Err(e),
120+
_ => unreachable!(),
121+
};
122+
if &n < modulus.as_ref() {
123+
return Ok(n);
124+
}
154125
}
155-
// If the high limb is equal to the modulus' high limb, it's still possible
156-
// that the full uint is too big so we check and repeat if it is.
157-
if n.ct_lt(modulus).into() {
158-
break;
159-
}
160-
hi_word = next_word()? & mask;
161126
}
162-
Ok(())
163127
}
164128

165129
#[cfg(test)]
@@ -288,6 +252,32 @@ mod tests {
288252
);
289253
}
290254

255+
/// Make sure random_mod output is consistent across platforms
256+
#[test]
257+
fn random_mod_platform_independence() {
258+
let mut rng = get_four_sequential_rng();
259+
260+
let modulus = NonZero::new(U256::from_u32(8192)).unwrap();
261+
let mut vals = [U256::ZERO, U256::ZERO, U256::ZERO, U256::ZERO, U256::ZERO];
262+
for val in &mut vals {
263+
*val = U256::random_mod(&mut rng, &modulus);
264+
}
265+
let expected = [55, 3378, 2172, 1657, 5323];
266+
for (want, got) in expected.into_iter().zip(vals.into_iter()) {
267+
assert_eq!(got, U256::from_u32(want));
268+
}
269+
270+
let mut state = [0u8; 16];
271+
rng.fill_bytes(&mut state);
272+
273+
assert_eq!(
274+
state,
275+
[
276+
60, 146, 46, 106, 157, 83, 56, 212, 186, 104, 211, 210, 125, 28, 120, 239
277+
],
278+
);
279+
}
280+
291281
/// Test that random bytes are sampled consecutively.
292282
#[test]
293283
fn random_bits_4_bytes_sequential() {

0 commit comments

Comments
 (0)