From 2480249f234a7bd2e3dc6dea898d34520f5b02f7 Mon Sep 17 00:00:00 2001 From: Arthur Gautier Date: Fri, 14 Feb 2025 21:47:16 -0800 Subject: [PATCH] provide a `try_random_mod` method --- Cargo.lock | 3 +-- Cargo.toml | 4 ++++ src/limb/rand.rs | 23 ++++++++++++++++++++++- src/traits.rs | 14 ++++++++++++++ src/uint/boxed/rand.rs | 11 ++++++++++- src/uint/rand.rs | 29 ++++++++++++++++++++--------- 6 files changed, 71 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3054ca0b..36db3e0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -565,8 +565,7 @@ dependencies = [ [[package]] name = "rand_core" version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88e0da7a2c97baa202165137c158d0a2e824ac465d13d81046727b34cb247d3" +source = "git+https://github.com/fjarri/rand.git?branch=sized#9f6f7c84e4b573bd02f3b295fec430ca0224a488" dependencies = [ "getrandom 0.3.1", "zerocopy 0.8.14", diff --git a/Cargo.toml b/Cargo.toml index ce183524..4c411b4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" } diff --git a/src/limb/rand.rs b/src/limb/rand.rs index f606df01..3ad80ae7 100644 --- a/src/limb/rand.rs +++ b/src/limb/rand.rs @@ -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 { @@ -35,4 +35,25 @@ impl RandomMod for Limb { } } } + + fn try_random_mod( + rng: &mut R, + modulus: &NonZero, + ) -> Result { + let mut bytes = ::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); + } + } + } } diff --git a/src/traits.rs b/src/traits.rs index e3dd4f39..a268375c 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -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(rng: &mut R, modulus: &NonZero) -> 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( + rng: &mut R, + modulus: &NonZero, + ) -> Result; } /// Compute `self + rhs mod p`. diff --git a/src/uint/boxed/rand.rs b/src/uint/boxed/rand.rs index 6ab0331e..0468f9d6 100644 --- a/src/uint/boxed/rand.rs +++ b/src/uint/boxed/rand.rs @@ -36,9 +36,18 @@ impl RandomBits for BoxedUint { impl RandomMod for BoxedUint { fn random_mod(rng: &mut R, modulus: &NonZero) -> Self { let mut n = BoxedUint::zero_with_precision(modulus.bits_precision()); - random_mod_core(rng, &mut n, modulus, modulus.bits()); + let Ok(()) = random_mod_core(rng, &mut n, modulus, modulus.bits()); n } + + fn try_random_mod( + rng: &mut R, + modulus: &NonZero, + ) -> Result { + let mut n = BoxedUint::zero_with_precision(modulus.bits_precision()); + random_mod_core(rng, &mut n, modulus, modulus.bits())?; + Ok(n) + } } #[cfg(test)] diff --git a/src/uint/rand.rs b/src/uint/rand.rs index 2e4e60b1..98d954ba 100644 --- a/src/uint/rand.rs +++ b/src/uint/rand.rs @@ -83,35 +83,45 @@ impl RandomBits for Uint { impl RandomMod for Uint { fn random_mod(rng: &mut R, modulus: &NonZero) -> Self { let mut n = Self::ZERO; - random_mod_core(rng, &mut n, modulus, modulus.bits_vartime()); + let Ok(()) = random_mod_core(rng, &mut n, modulus, modulus.bits_vartime()); n } + + fn try_random_mod( + rng: &mut R, + modulus: &NonZero, + ) -> Result { + 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( +pub(super) fn random_mod_core( rng: &mut R, n: &mut T, modulus: &NonZero, 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()); @@ -120,15 +130,16 @@ pub(super) fn random_mod_core( // 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)]