diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b805ed8..b84ec9d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## Added + +- Add random scalar generator `UniRng` for uniformly distributed scalar generation [#129] + +## Remove + +- Remove `uni_random` scalar generation in favor of `UniRng` [#129] + ## [0.12.3] - 2023-11-01 ### Added @@ -221,6 +229,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#125]: https://github.com/dusk-network/bls12_381/issues/125 +[#129]: https://github.com/dusk-network/bls12_381/issues/129 [#117]: https://github.com/dusk-network/bls12_381/issues/117 [#109]: https://github.com/dusk-network/bls12_381/issues/109 [#108]: https://github.com/dusk-network/bls12_381/issues/108 diff --git a/Cargo.toml b/Cargo.toml index 7ff91526..c3dc5c12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,6 +100,9 @@ optional = true [dependencies.dusk-bytes] version = "0.1" + +[dependencies.rand] +version = "0.8" # End [features] diff --git a/src/scalar/dusk.rs b/src/scalar/dusk.rs index e3355a58..ad18b922 100644 --- a/src/scalar/dusk.rs +++ b/src/scalar/dusk.rs @@ -9,7 +9,7 @@ use core::convert::TryFrom; use core::hash::{Hash, Hasher}; use core::ops::{BitAnd, BitXor}; use dusk_bytes::{Error as BytesError, Serializable}; -use rand_core::{CryptoRng, RngCore}; +use rand_core::{CryptoRng, RngCore, SeedableRng}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; use super::{Scalar, MODULUS, R2}; @@ -24,6 +24,83 @@ impl PartialOrd for Scalar { } } +/// Compute a uniformly distributed random scalar. +/// +/// Because scalars take 255 bits for encoding it is difficult to generate +/// random bit-pattern that ensures to encodes a valid scalar. +/// Wrapping the values that are higher than [`MODULUS`], as done in +/// [`Self::random`], results in hitting some values more than others, and +/// zeroing out the highest two bits will eliminate some values from the +/// possible results. +/// +/// This function achieves a uniform distribution of scalars by using +/// rejection sampling: random bit-patterns are generated until a valid +/// scalar is found. +/// The scalar creation is not constant time but that shouldn't be a concern +/// since no information about the scalar can be gained by knowing the time of +/// its generation. +pub struct UniRng(R); + +impl CryptoRng for UniRng where R: CryptoRng {} + +impl RngCore for UniRng +where + R: RngCore, +{ + fn next_u32(&mut self) -> u32 { + self.0.next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.0.next_u64() + } + + // We use rejection sampling to generate a valid scalar. + fn fill_bytes(&mut self, dest: &mut [u8]) { + // Panic if the destination slice can not fit a scalar. + if dest.len() < 32 { + panic!("cannot generate random scalar with a slice < 32 bytes"); + } + + // We loop as long as it takes to generate a valid scalar. + // As long as the random number generator is implemented properly, this + // loop will terminate. + let mut scalar: Option = None; + let mut buf = [0; 32]; + while scalar == None { + self.0.fill_bytes(&mut buf); + // Since modulus has at most 255 bits, we can zero the MSB and like + // this improve our chances of hitting a valid scalar to above 50%. + buf[32 - 1] &= 0b0111_1111; + scalar = Scalar::from_bytes(&buf).into(); + } + + // Copy the generated random scalar in the first 32 bytes of the + // destination slice (scalars are stored in little endian). + dest[..32].copy_from_slice(&buf); + + // Zero the remaining bytes (if any). + if dest.len() > 32 { + dest[32..].fill(0); + } + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { + self.0.try_fill_bytes(dest) + } +} + +impl SeedableRng for UniRng +where + R: SeedableRng, +{ + type Seed = ::Seed; + + fn from_seed(seed: Self::Seed) -> Self { + Self(R::from_seed(seed)) + } +} + impl Ord for Scalar { fn cmp(&self, other: &Self) -> Ordering { for i in (0..4).rev() { @@ -262,41 +339,6 @@ impl Scalar { res } - /// Compute a uniformly distributed random scalar. - /// - /// Because scalars take 255 bits for encoding it is difficult to generate - /// random bit-pattern that ensures to encodes a valid scalar. - /// Wrapping the values that are higher than [`MODULUS`], as done in - /// [`Self::random`], results in hitting some values more than others, and - /// zeroing out the highest two bits will eliminate some values from the - /// possible results. - /// - /// This function achieves a uniform distribution of scalars by using - /// rejection sampling: random bit-patterns are generated until a valid - /// scalar is found. - /// The function is not constant time but that shouldn't be a concern since - /// no information about the scalar can be gained by knowing the time of - /// its generation. - pub fn uni_random(rng: &mut R) -> Self - where - R: RngCore + CryptoRng, - { - let mut buf = [0; 32]; - let mut scalar: Option = None; - - // We loop as long as it takes to generate a valid scalar. - // As long as the random number generator is implemented properly, this - // loop will terminate. - while scalar == None { - rng.fill_bytes(&mut buf); - // Since modulus has at most 255 bits, we can zero the MSB and like - // this improve our chances of hitting a valid scalar to above 50% - buf[32 - 1] &= 0b0111_1111; - scalar = Self::from_bytes(&buf).into(); - } - scalar.unwrap() - } - /// SHR impl #[inline] pub fn divn(&mut self, mut n: u32) { @@ -446,3 +488,27 @@ fn test_scalar_eq_and_hash() { assert_eq!(hash_r0, hash_r1); assert_ne!(hash_r0, hash_r2); } + +#[test] +fn test_uni_rng() { + use rand::rngs::StdRng; + let mut rng: UniRng = UniRng::seed_from_u64(0xbeef); + + let mut buf32 = [0u8; 32]; + let mut buf64 = [0u8; 64]; + + for _ in 0..10000 { + rng.fill_bytes(&mut buf64); + buf32.copy_from_slice(&buf64[..32]); + + let scalar1: Option = Scalar::from_bytes(&buf32).into(); + // check that the scalar generated at random is caconical + assert!(scalar1.is_some()); + let scalar1 = scalar1.unwrap(); + + // create a second scalar from the wide array + let scalar2: Scalar = Scalar::from_bytes_wide(&buf64); + // check that both scalar are the same + assert_eq!(scalar1, scalar2); + } +}