Skip to content

Commit a25867b

Browse files
committed
Write random_mod in terms of new random_bits
## Breaking changes - `Encoding::Repr` can no longer be assumed to implement `Copy`, so consumers of `Encoding::Repr` will need to explicitly call `clone`. - The numbers produced by both `random_bits` and `random_mod` will generally be different, and calling these functions will leave the RNG in a different state, than before. ## Summary This essentially applies #285 to `random_bits` as well as `random_mod`. Both functions behave the same way now, with the only difference being that `random_mod` adds rejection sampling; otherwise both will produce the same numbers over the same entropy stream. Questions of platform dependence are now easy; we do not define these algorithms in terms of machine words but in terms of bytes. Randomly sampled `Uint`s are constructed little-endian over the entropy stream. This leverages the existing work making `Uint` implement `Encoding`. It additionally needs `Encoding` on `BoxedUint` to make `RandomMod` and `RandomBits` work there; this is implemented, but requires dropping the `Copy` constraint on `Encoding::Repr`. In order to use `random_bits_core` in `random_mod_core`, we need to make it produce `R::Error`; then we call `map_err` in `RandomBits`, rather than in `random_bits_core`, to get a `RandomBitsError`. [0]: rust-lang/rust#149522
1 parent 3c57f46 commit a25867b

File tree

5 files changed

+62
-87
lines changed

5 files changed

+62
-87
lines changed

src/traits.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,6 @@ pub trait Encoding: Sized {
641641
/// Byte array representation.
642642
type Repr: AsRef<[u8]>
643643
+ AsMut<[u8]>
644-
+ Copy
645644
+ Clone
646645
+ Sized
647646
+ for<'a> TryFrom<&'a [u8], Error: core::error::Error>;

src/uint/boxed/encoding.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Const-friendly decoding operations for [`BoxedUint`].
22
33
use super::BoxedUint;
4-
use crate::{DecodeError, Limb, Word, uint::encoding};
4+
use crate::{DecodeError, Encoding, Limb, Word, uint::encoding};
55
use alloc::{boxed::Box, string::String, vec::Vec};
66
use subtle::{Choice, CtOption};
77

@@ -245,6 +245,28 @@ impl BoxedUint {
245245
}
246246
}
247247

248+
impl Encoding for BoxedUint {
249+
type Repr = Box<[u8]>;
250+
251+
fn to_be_bytes(&self) -> Self::Repr {
252+
BoxedUint::to_be_bytes(self)
253+
}
254+
255+
fn to_le_bytes(&self) -> Self::Repr {
256+
BoxedUint::to_le_bytes(self)
257+
}
258+
259+
fn from_be_bytes(bytes: Self::Repr) -> Self {
260+
BoxedUint::from_be_slice(&bytes, (bytes.len() * 8).try_into().expect("overflow"))
261+
.expect("decode error")
262+
}
263+
264+
fn from_le_bytes(bytes: Self::Repr) -> Self {
265+
BoxedUint::from_le_slice(&bytes, (bytes.len() * 8).try_into().expect("overflow"))
266+
.expect("decode error")
267+
}
268+
}
269+
248270
/// Decoder target producing a Vec<Limb>
249271
#[derive(Default)]
250272
struct VecDecodeByLimb {

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, bit_length).map_err(RandomBitsError::RandCore)?;
3232
Ok(ret)
3333
}
3434
}

src/uint/rand.rs

Lines changed: 37 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Random number generator support
22
3-
use super::{Uint, Word};
4-
use crate::{Encoding, Limb, NonZero, Random, RandomBits, RandomBitsError, RandomMod, Zero};
3+
use super::Uint;
4+
use crate::{Encoding, Limb, NonZero, Random, RandomBits, RandomBitsError, RandomMod};
55
use rand_core::{RngCore, TryRngCore};
66
use subtle::ConstantTimeLess;
77

@@ -26,45 +26,26 @@ impl<const LIMBS: usize> Random for Uint<LIMBS> {
2626
/// `rng.fill_bytes(&mut bytes[..i]); rng.fill_bytes(&mut bytes[i..])` constructs the same `bytes`,
2727
/// as long as `i` is a multiple of `X`.
2828
/// Note that the `TryRngCore` trait does _not_ require this behaviour from `rng`.
29-
pub(crate) fn random_bits_core<R: TryRngCore + ?Sized>(
29+
pub(crate) fn random_bits_core<T, R: TryRngCore + ?Sized>(
3030
rng: &mut R,
31-
zeroed_limbs: &mut [Limb],
32-
bit_length: u32,
33-
) -> Result<(), RandomBitsError<R::Error>> {
34-
if bit_length == 0 {
31+
x: &mut T,
32+
n_bits: u32,
33+
) -> Result<(), R::Error>
34+
where
35+
T: Encoding,
36+
{
37+
if n_bits == 0 {
3538
return Ok(());
3639
}
3740

38-
let buffer: Word = 0;
39-
let mut buffer = buffer.to_be_bytes();
40-
41-
let nonzero_limbs = bit_length.div_ceil(Limb::BITS) as usize;
42-
let partial_limb = bit_length % Limb::BITS;
43-
let mask = Word::MAX >> ((Word::BITS - partial_limb) % Word::BITS);
44-
45-
for i in 0..nonzero_limbs - 1 {
46-
rng.try_fill_bytes(&mut buffer)
47-
.map_err(RandomBitsError::RandCore)?;
48-
zeroed_limbs[i] = Limb(Word::from_le_bytes(buffer));
49-
}
41+
let n_bytes = n_bits.div_ceil(u8::BITS) as usize;
42+
let hi_mask = u8::MAX >> ((u8::BITS - (n_bits % u8::BITS)) % u8::BITS);
5043

51-
// This algorithm should sample the same number of random bytes, regardless of the pointer width
52-
// of the target platform. To this end, special attention has to be paid to the case where
53-
// bit_length - 1 < 32 mod 64. Bit strings of that size can be represented using `2X+1` 32-bit
54-
// words or `X+1` 64-bit words. Note that 64*(X+1) - 32*(2X+1) = 32. Hence, if we sample full
55-
// words only, a 64-bit platform will sample 32 bits more than a 32-bit platform. We prevent
56-
// this by forcing both platforms to only sample 4 bytes for the last word in this case.
57-
let slice = if partial_limb > 0 && partial_limb <= 32 {
58-
// Note: we do not have to zeroize the second half of the buffer, as the mask will take
59-
// care of this in the end.
60-
&mut buffer[0..4]
61-
} else {
62-
buffer.as_mut_slice()
63-
};
64-
65-
rng.try_fill_bytes(slice)
66-
.map_err(RandomBitsError::RandCore)?;
67-
zeroed_limbs[nonzero_limbs - 1] = Limb(Word::from_le_bytes(buffer) & mask);
44+
let mut buffer = x.to_le_bytes();
45+
let slice = buffer.as_mut();
46+
rng.try_fill_bytes(&mut slice[..n_bytes])?;
47+
slice[n_bytes - 1] &= hi_mask;
48+
*x = T::from_le_bytes(buffer);
6849

6950
Ok(())
7051
}
@@ -94,72 +75,46 @@ impl<const LIMBS: usize> RandomBits for Uint<LIMBS> {
9475
bits_precision,
9576
});
9677
}
97-
let mut limbs = [Limb::ZERO; LIMBS];
98-
random_bits_core(rng, &mut limbs, bit_length)?;
99-
Ok(Self::from(limbs))
78+
let mut x = Self::ZERO;
79+
random_bits_core(rng, &mut x, bit_length).map_err(RandomBitsError::RandCore)?;
80+
Ok(x)
10081
}
10182
}
10283

10384
impl<const LIMBS: usize> RandomMod for Uint<LIMBS> {
10485
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
86+
let mut x = Self::ZERO;
87+
let Ok(()) = random_mod_core(rng, &mut x, modulus, modulus.bits_vartime());
88+
x
10889
}
10990

11091
fn try_random_mod<R: TryRngCore + ?Sized>(
11192
rng: &mut R,
11293
modulus: &NonZero<Self>,
11394
) -> Result<Self, R::Error> {
114-
let mut n = Self::ZERO;
115-
random_mod_core(rng, &mut n, modulus, modulus.bits_vartime())?;
116-
Ok(n)
95+
let mut x = Self::ZERO;
96+
random_mod_core(rng, &mut x, modulus, modulus.bits_vartime())?;
97+
Ok(x)
11798
}
11899
}
119100

120101
/// Generic implementation of `random_mod` which can be shared with `BoxedUint`.
121102
// TODO(tarcieri): obtain `n_bits` via a trait like `Integer`
122103
pub(super) fn random_mod_core<T, R: TryRngCore + ?Sized>(
123104
rng: &mut R,
124-
n: &mut T,
105+
x: &mut T,
125106
modulus: &NonZero<T>,
126107
n_bits: u32,
127108
) -> Result<(), R::Error>
128109
where
129-
T: AsMut<[Limb]> + AsRef<[Limb]> + ConstantTimeLess + Zero,
110+
T: Encoding + ConstantTimeLess,
130111
{
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-
142112
loop {
143-
while hi_word > hi_word_modulus {
144-
hi_word = next_word()? & mask;
113+
random_bits_core(rng, x, n_bits)?;
114+
if x.ct_lt(modulus).into() {
115+
return Ok(());
145116
}
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.
157-
if n.ct_lt(modulus).into() {
158-
break;
159-
}
160-
hi_word = next_word()? & mask;
161117
}
162-
Ok(())
163118
}
164119

165120
#[cfg(test)]
@@ -269,7 +224,7 @@ mod tests {
269224

270225
let bit_length = 989;
271226
let mut val = U1024::ZERO;
272-
random_bits_core(&mut rng, val.as_mut_limbs(), bit_length).expect("safe");
227+
random_bits_core(&mut rng, &mut val, bit_length).expect("safe");
273228

274229
assert_eq!(
275230
val,
@@ -298,7 +253,7 @@ mod tests {
298253
for val in &mut vals {
299254
*val = U256::random_mod(&mut rng, &modulus);
300255
}
301-
let expected = [55, 2172, 1657, 4668, 7688];
256+
let expected = [55, 3378, 2172, 1657, 5323];
302257
for (want, got) in expected.into_iter().zip(vals.into_iter()) {
303258
// assert_eq!(got.as_words()[0], want);
304259
assert_eq!(got, U256::from_u32(want));
@@ -309,7 +264,7 @@ mod tests {
309264
let val = U256::random_mod(&mut rng, &modulus);
310265
assert_eq!(
311266
val,
312-
U256::from_be_hex("C54302F2EB1E2F69C3B919AE0D16DF2259CD1A8A9B8EA8E0862878227D4B40A3")
267+
U256::from_be_hex("E17653A37F1BCC44277FA208E6B31E08CDC4A23A7E88E660EF781C7DD2D368BA")
313268
);
314269

315270
let mut state = [0u8; 16];
@@ -318,7 +273,7 @@ mod tests {
318273
assert_eq!(
319274
state,
320275
[
321-
71, 204, 238, 147, 198, 196, 132, 164, 240, 211, 223, 12, 36, 189, 139, 48,
276+
105, 47, 30, 235, 242, 2, 67, 197, 163, 64, 75, 125, 34, 120, 40, 134,
322277
],
323278
);
324279
}
@@ -333,9 +288,8 @@ mod tests {
333288
let mut rng = get_four_sequential_rng();
334289
let mut first = U1024::ZERO;
335290
let mut second = U1024::ZERO;
336-
random_bits_core(&mut rng, first.as_mut_limbs(), bit_length).expect("safe");
337-
random_bits_core(&mut rng, second.as_mut_limbs(), U1024::BITS - bit_length)
338-
.expect("safe");
291+
random_bits_core(&mut rng, &mut first, bit_length).expect("safe");
292+
random_bits_core(&mut rng, &mut second, U1024::BITS - bit_length).expect("safe");
339293
assert_eq!(second.shl(bit_length).bitor(&first), RANDOM_OUTPUT);
340294
}
341295
}

tests/monty_form.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ where
5353
T: Unsigned + Bounded + Encoding,
5454
<T as Unsigned>::Monty: Invert<Output = CtOption<T::Monty>>,
5555
{
56-
let r = T::from_be_bytes(bytes);
56+
let r = T::from_be_bytes(bytes.clone());
5757
let rm = <T as Unsigned>::Monty::new(r.clone(), monty_params);
5858
let rm_inv = rm.invert();
5959
prop_assume!(bool::from(rm_inv.is_some()), "r={:?} is not invertible", r);

0 commit comments

Comments
 (0)