Skip to content

Commit

Permalink
BoxedUint random number generation (#349)
Browse files Browse the repository at this point in the history
Impls the `RandomMod` trait for `BoxedUint`, sharing an implementation
with `Uint`.

The `Random` trait can't be impl'd since we need to specify a precision
as an argument. Perhaps we need a trait like `RandomWithPrecision`.
  • Loading branch information
tarcieri authored Nov 29, 2023
1 parent ee468e3 commit f09c9ab
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 25 deletions.
3 changes: 3 additions & 0 deletions src/uint/boxed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ mod shr;
mod sub;
mod sub_mod;

#[cfg(feature = "rand_core")]
mod rand;

use crate::{Limb, NonZero, Uint, Word, Zero, U128, U64};
use alloc::{boxed::Box, vec, vec::Vec};
use core::fmt;
Expand Down
63 changes: 63 additions & 0 deletions src/uint/boxed/rand.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//! Random number generator support.
use super::BoxedUint;
use crate::{uint::rand::random_mod_core, Limb, NonZero, Random, RandomMod};
use rand_core::CryptoRngCore;

impl BoxedUint {
/// Generate a cryptographically secure random [`BoxedUint`].
pub fn random(mut rng: &mut impl CryptoRngCore, bits_precision: usize) -> Self {
let mut ret = BoxedUint::zero_with_precision(bits_precision);

for limb in &mut *ret.limbs {
*limb = Limb::random(&mut rng)
}

ret
}
}

impl RandomMod for BoxedUint {
/// Generate a cryptographically secure random [`BoxedUint`] which is less than a given
/// `modulus`.
///
/// This function uses rejection sampling, a method which produces an unbiased distribution of
/// in-range values provided the underlying CSRNG is unbiased, but runs in variable-time.
///
/// The variable-time nature of the algorithm should not pose a security issue so long as the
/// underlying random number generator is truly a CSRNG, where previous outputs are unrelated to
/// subsequent outputs and do not reveal information about the RNG's internal state.
fn random_mod(rng: &mut impl CryptoRngCore, modulus: &NonZero<Self>) -> Self {
let mut n = BoxedUint::zero_with_precision(modulus.bits_precision());
random_mod_core(rng, &mut n, modulus, modulus.bits());
n
}
}

#[cfg(test)]
mod tests {
use crate::{NonZero, RandomMod, U256};
use rand_core::SeedableRng;

#[test]
fn random_mod() {
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(1);

// Ensure `random_mod` runs in a reasonable amount of time
let modulus = NonZero::new(U256::from(42u8)).unwrap();
let res = U256::random_mod(&mut rng, &modulus);

// Check that the value is in range
assert!(res >= U256::ZERO);
assert!(res < U256::from(42u8));

// Ensure `random_mod` runs in a reasonable amount of time
// when the modulus is larger than 1 limb
let modulus = NonZero::new(U256::from(0x10000000000000001u128)).unwrap();
let res = U256::random_mod(&mut rng, &modulus);

// Check that the value is in range
assert!(res >= U256::ZERO);
assert!(res < U256::from(0x10000000000000001u128));
}
}
62 changes: 37 additions & 25 deletions src/uint/rand.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Random number generator support
use super::Uint;
use crate::{Encoding, Limb, NonZero, Random, RandomMod};
use crate::{Encoding, Limb, NonZero, Random, RandomMod, Zero};
use rand_core::CryptoRngCore;
use subtle::ConstantTimeLess;

Expand Down Expand Up @@ -32,31 +32,43 @@ impl<const LIMBS: usize> RandomMod for Uint<LIMBS> {
/// outputs and do not reveal information about the RNG's internal state.
fn random_mod(rng: &mut impl CryptoRngCore, modulus: &NonZero<Self>) -> Self {
let mut n = Self::ZERO;
random_mod_core(rng, &mut n, modulus, modulus.bits_vartime());
n
}
}

/// Generic implementation of `random_mod` which can be shared with `BoxedUint`.
// TODO(tarcieri): obtain `n_bits` via a trait like `Integer`
pub(super) fn random_mod_core<T>(
rng: &mut impl CryptoRngCore,
n: &mut T,
modulus: &NonZero<T>,
n_bits: usize,
) where
T: AsMut<[Limb]> + ConstantTimeLess + Zero,
{
let n_bytes = (n_bits + 7) / 8;
let n_limbs = (n_bits + Limb::BITS - 1) / Limb::BITS;
let hi_bytes = n_bytes - (n_limbs - 1) * Limb::BYTES;

let mut bytes = Limb::ZERO.to_le_bytes();

loop {
for i in 0..n_limbs - 1 {
rng.fill_bytes(bytes.as_mut());
// Need to deserialize from little-endian to make sure that two 32-bit limbs
// deserialized sequentially are equal to one 64-bit limb produced from the same
// byte stream.
n.as_mut()[i] = Limb::from_le_bytes(bytes);
}

// Generate the high limb which may need to only be filled partially.
bytes.as_mut().fill(0);
rng.fill_bytes(&mut (bytes.as_mut()[0..hi_bytes]));
n.as_mut()[n_limbs - 1] = Limb::from_le_bytes(bytes);

let n_bits = modulus.as_ref().bits_vartime();
let n_bytes = (n_bits + 7) / 8;
let n_limbs = (n_bits + Limb::BITS - 1) / Limb::BITS;
let hi_bytes = n_bytes - (n_limbs - 1) * Limb::BYTES;

let mut bytes = Limb::ZERO.to_le_bytes();

loop {
for i in 0..n_limbs - 1 {
rng.fill_bytes(bytes.as_mut());
// Need to deserialize from little-endian to make sure that two 32-bit limbs
// deserialized sequentially are equal to one 64-bit limb produced from the same
// byte stream.
n.limbs[i] = Limb::from_le_bytes(bytes);
}

// Generate the high limb which may need to only be filled partially.
bytes.as_mut().fill(0);
rng.fill_bytes(&mut (bytes.as_mut()[0..hi_bytes]));
n.limbs[n_limbs - 1] = Limb::from_le_bytes(bytes);

if n.ct_lt(modulus).into() {
return n;
}
if n.ct_lt(modulus).into() {
break;
}
}
}
Expand Down

0 comments on commit f09c9ab

Please sign in to comment.