Skip to content

Commit 62b90b8

Browse files
authored
BREAKING: Make random_mod platform-independent (#1010)
Replaces the fancy platform-word-based logic with a simple call to `random_bits_core` with rejection sampling. Each retry has p > 0.5 (worst case) of selecting a number inside the range Fixes #1009
1 parent f3e6291 commit 62b90b8

File tree

2 files changed

+35
-35
lines changed

2 files changed

+35
-35
lines changed

src/uint/boxed/rand.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ impl RandomBits for BoxedUint {
2828
}
2929

3030
let mut ret = BoxedUint::zero_with_precision(bits_precision);
31-
random_bits_core(rng, &mut ret.limbs, bit_length)?;
31+
random_bits_core(rng, &mut ret.limbs, bit_length).map_err(RandomBitsError::RandCore)?;
3232
Ok(ret)
3333
}
3434
}

src/uint/rand.rs

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
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, Zero};
55
use rand_core::{RngCore, TryRngCore};
66
use subtle::ConstantTimeLess;
77

@@ -30,7 +30,7 @@ pub(crate) fn random_bits_core<R: TryRngCore + ?Sized>(
3030
rng: &mut R,
3131
zeroed_limbs: &mut [Limb],
3232
bit_length: u32,
33-
) -> Result<(), RandomBitsError<R::Error>> {
33+
) -> Result<(), R::Error> {
3434
if bit_length == 0 {
3535
return Ok(());
3636
}
@@ -43,8 +43,7 @@ pub(crate) fn random_bits_core<R: TryRngCore + ?Sized>(
4343
let mask = Word::MAX >> ((Word::BITS - partial_limb) % Word::BITS);
4444

4545
for i in 0..nonzero_limbs - 1 {
46-
rng.try_fill_bytes(&mut buffer)
47-
.map_err(RandomBitsError::RandCore)?;
46+
rng.try_fill_bytes(&mut buffer)?;
4847
zeroed_limbs[i] = Limb(Word::from_le_bytes(buffer));
4948
}
5049

@@ -62,8 +61,7 @@ pub(crate) fn random_bits_core<R: TryRngCore + ?Sized>(
6261
buffer.as_mut_slice()
6362
};
6463

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

6967
Ok(())
@@ -95,7 +93,7 @@ impl<const LIMBS: usize> RandomBits for Uint<LIMBS> {
9593
});
9694
}
9795
let mut limbs = [Limb::ZERO; LIMBS];
98-
random_bits_core(rng, &mut limbs, bit_length)?;
96+
random_bits_core(rng, &mut limbs, bit_length).map_err(RandomBitsError::RandCore)?;
9997
Ok(Self::from(limbs))
10098
}
10199
}
@@ -128,43 +126,19 @@ pub(super) fn random_mod_core<T, R: TryRngCore + ?Sized>(
128126
where
129127
T: AsMut<[Limb]> + AsRef<[Limb]> + ConstantTimeLess + Zero,
130128
{
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-
142129
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());
154-
}
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.
130+
random_bits_core(rng, n.as_mut(), n_bits)?;
131+
157132
if n.ct_lt(modulus).into() {
158133
break;
159134
}
160-
hi_word = next_word()? & mask;
161135
}
162136
Ok(())
163137
}
164138

165139
#[cfg(test)]
166140
mod tests {
167-
use crate::uint::rand::random_bits_core;
141+
use crate::uint::rand::{random_bits_core, random_mod_core};
168142
use crate::{Limb, NonZero, Random, RandomBits, RandomMod, U256, U1024, Uint};
169143
use chacha20::ChaCha8Rng;
170144
use rand_core::{RngCore, SeedableRng};
@@ -288,6 +262,32 @@ mod tests {
288262
);
289263
}
290264

265+
/// Make sure random_mod output is consistent across platforms
266+
#[test]
267+
fn random_mod_platform_independence() {
268+
let mut rng = get_four_sequential_rng();
269+
270+
let modulus = NonZero::new(U256::from_u32(8192)).unwrap();
271+
let mut vals = [U256::ZERO, U256::ZERO, U256::ZERO, U256::ZERO, U256::ZERO];
272+
for val in &mut vals {
273+
random_mod_core(&mut rng, val, &modulus, modulus.bits_vartime()).unwrap();
274+
}
275+
let expected = [55, 3378, 2172, 1657, 5323];
276+
for (want, got) in expected.into_iter().zip(vals.into_iter()) {
277+
assert_eq!(got, U256::from_u32(want));
278+
}
279+
280+
let mut state = [0u8; 16];
281+
rng.fill_bytes(&mut state);
282+
283+
assert_eq!(
284+
state,
285+
[
286+
60, 146, 46, 106, 157, 83, 56, 212, 186, 104, 211, 210, 125, 28, 120, 239
287+
],
288+
);
289+
}
290+
291291
/// Test that random bytes are sampled consecutively.
292292
#[test]
293293
fn random_bits_4_bytes_sequential() {

0 commit comments

Comments
 (0)