Skip to content

Commit

Permalink
provide a try_random_mod method
Browse files Browse the repository at this point in the history
  • Loading branch information
baloo committed Feb 19, 2025
1 parent 3234fa1 commit 8e6435b
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 14 deletions.
3 changes: 1 addition & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ subtle = { version = "2.6", default-features = false }
der = { version = "0.8.0-rc.1", optional = true, default-features = false }
hybrid-array = { version = "0.2", optional = true }
num-traits = { version = "0.2.19", default-features = false }
rand_core = { version = "0.9", optional = true, default-features = false }
rand_core = { version = "0.9.1", optional = true, default-features = false }
rlp = { version = "0.6", optional = true, default-features = false }
serdect = { version = "0.3", optional = true, default-features = false }
zeroize = { version = "1", optional = true, default-features = false }
Expand Down Expand Up @@ -81,3 +81,7 @@ harness = false
[[bench]]
name = "int"
harness = false

[patch.crates-io]
# https://github.com/rust-random/rand/pull/1593
rand_core = { git = "https://github.com/fjarri/rand.git", branch = "sized" }
23 changes: 22 additions & 1 deletion src/limb/rand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use super::Limb;
use crate::{Encoding, NonZero, Random, RandomMod};
use rand_core::RngCore;
use rand_core::{RngCore, TryRngCore};
use subtle::ConstantTimeLess;

impl Random for Limb {
Expand Down Expand Up @@ -35,4 +35,25 @@ impl RandomMod for Limb {
}
}
}

fn try_random_mod<R: TryRngCore + ?Sized>(
rng: &mut R,
modulus: &NonZero<Self>,
) -> Result<Self, R::Error> {
let mut bytes = <Self as Encoding>::Repr::default();

let n_bits = modulus.bits() as usize;
let n_bytes = (n_bits + 7) / 8;
let mask = 0xffu8 >> (8 * n_bytes - n_bits);

loop {
rng.try_fill_bytes(&mut bytes[..n_bytes])?;
bytes[n_bytes - 1] &= mask;

let n = Limb::from_le_bytes(bytes);
if n.ct_lt(modulus).into() {
return Ok(n);
}
}
}
}
14 changes: 14 additions & 0 deletions src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,20 @@ pub trait RandomMod: Sized + Zero {
/// leak anything about the output value aside from it being less than
/// `modulus`.
fn random_mod<R: RngCore + ?Sized>(rng: &mut R, modulus: &NonZero<Self>) -> Self;

/// Generate a random number which is less than a given `modulus`.
///
/// This uses rejection sampling.
///
/// As a result, it runs in variable time that depends in part on
/// `modulus`. If the generator `rng` is cryptographically secure (for
/// example, it implements `CryptoRng`), then this is guaranteed not to
/// leak anything about the output value aside from it being less than
/// `modulus`.
fn try_random_mod<R: TryRngCore + ?Sized>(
rng: &mut R,
modulus: &NonZero<Self>,
) -> Result<Self, R::Error>;
}

/// Compute `self + rhs mod p`.
Expand Down
12 changes: 11 additions & 1 deletion src/uint/boxed/rand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,19 @@ impl RandomBits for BoxedUint {
impl RandomMod for BoxedUint {
fn random_mod<R: RngCore + ?Sized>(rng: &mut R, modulus: &NonZero<Self>) -> Self {
let mut n = BoxedUint::zero_with_precision(modulus.bits_precision());
random_mod_core(rng, &mut n, modulus, modulus.bits());
random_mod_core(rng, &mut n, modulus, modulus.bits())
.expect("RngCore used as TryRngCore failed");
n
}

fn try_random_mod<R: TryRngCore + ?Sized>(
rng: &mut R,
modulus: &NonZero<Self>,
) -> Result<Self, R::Error> {
let mut n = BoxedUint::zero_with_precision(modulus.bits_precision());
random_mod_core(rng, &mut n, modulus, modulus.bits())?;
Ok(n)
}
}

#[cfg(test)]
Expand Down
30 changes: 21 additions & 9 deletions src/uint/rand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,35 +83,46 @@ impl<const LIMBS: usize> RandomBits for Uint<LIMBS> {
impl<const LIMBS: usize> RandomMod for Uint<LIMBS> {
fn random_mod<R: RngCore + ?Sized>(rng: &mut R, modulus: &NonZero<Self>) -> Self {
let mut n = Self::ZERO;
random_mod_core(rng, &mut n, modulus, modulus.bits_vartime());
random_mod_core(rng, &mut n, modulus, modulus.bits_vartime())
.expect("RngCore used as TryRngCore failed");
n
}

fn try_random_mod<R: TryRngCore + ?Sized>(
rng: &mut R,
modulus: &NonZero<Self>,
) -> Result<Self, R::Error> {
let mut n = Self::ZERO;
random_mod_core(rng, &mut n, modulus, modulus.bits_vartime())?;
Ok(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, R: RngCore + ?Sized>(
pub(super) fn random_mod_core<T, R: TryRngCore + ?Sized>(
rng: &mut R,
n: &mut T,
modulus: &NonZero<T>,
n_bits: u32,
) where
) -> Result<(), R::Error>
where
T: AsMut<[Limb]> + AsRef<[Limb]> + ConstantTimeLess + Zero,
{
#[cfg(target_pointer_width = "64")]
let mut next_word = || rng.next_u64();
let mut next_word = || rng.try_next_u64();
#[cfg(target_pointer_width = "32")]
let mut next_word = || rng.next_u32();
let mut next_word = || rng.try_next_u32();

let n_limbs = n_bits.div_ceil(Limb::BITS) as usize;

let hi_word_modulus = modulus.as_ref().as_ref()[n_limbs - 1].0;
let mask = !0 >> hi_word_modulus.leading_zeros();
let mut hi_word = next_word() & mask;
let mut hi_word = next_word()? & mask;

loop {
while hi_word > hi_word_modulus {
hi_word = next_word() & mask;
hi_word = next_word()? & mask;
}
// Set high limb
n.as_mut()[n_limbs - 1] = Limb::from_le_bytes(hi_word.to_le_bytes());
Expand All @@ -120,15 +131,16 @@ pub(super) fn random_mod_core<T, R: RngCore + ?Sized>(
// 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(next_word().to_le_bytes());
n.as_mut()[i] = Limb::from_le_bytes(next_word()?.to_le_bytes());
}
// If the high limb is equal to the modulus' high limb, it's still possible
// that the full uint is too big so we check and repeat if it is.
if n.ct_lt(modulus).into() {
break;
}
hi_word = next_word() & mask;
hi_word = next_word()? & mask;
}
Ok(())
}

#[cfg(test)]
Expand Down

0 comments on commit 8e6435b

Please sign in to comment.