From 47a9944d3d1822222a94261623140b221931467c Mon Sep 17 00:00:00 2001 From: moana Date: Thu, 16 Nov 2023 17:30:30 +0100 Subject: [PATCH] Add `UniScalarRng` a random scalar generator Resolves: #129 --- CHANGELOG.md | 9 +++ Cargo.toml | 3 + src/lib.rs | 1 + src/scalar.rs | 2 +- src/scalar/dusk.rs | 188 ++++++++++++++++++++++++++++++++++++--------- 5 files changed, 166 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b805ed8..a58c5335 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 `UniScalarRng` for uniformly distributed scalar generation [#129] + +## Remove + +- Remove `uni_random` scalar generation in favor of `UniScalarRng` [#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/lib.rs b/src/lib.rs index 148847ec..2035bc20 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,6 +48,7 @@ mod dusk; use dusk::choice; #[cfg(all(feature = "groups", feature = "alloc"))] pub use dusk::multiscalar_mul; +pub use scalar::dusk::UniScalarRng; mod scalar; diff --git a/src/scalar.rs b/src/scalar.rs index af274031..38a7b1e4 100644 --- a/src/scalar.rs +++ b/src/scalar.rs @@ -1,7 +1,7 @@ //! This module provides an implementation of the BLS12-381 scalar field $\mathbb{F}_q$ //! where `q = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001` -mod dusk; +pub(crate) mod dusk; use core::fmt; use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; diff --git a/src/scalar/dusk.rs b/src/scalar/dusk.rs index e3355a58..6534c38a 100644 --- a/src/scalar/dusk.rs +++ b/src/scalar/dusk.rs @@ -8,8 +8,10 @@ use core::cmp::{Ord, Ordering, PartialOrd}; 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 ff::Field; +use rand_core::{CryptoRng, RngCore, SeedableRng}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; use super::{Scalar, MODULUS, R2}; @@ -24,6 +26,101 @@ impl PartialOrd for Scalar { } } +/// Random number generator for generating scalars that are uniformly +/// distributed over the entire field of scalars. +/// +/// Because scalars take 255 bits for encoding it is difficult to generate +/// random bit-pattern that ensures to encode a valid scalar. +/// Wrapping the values that are higher than [`MODULUS`], as done in +/// [`Self::random`], results in hitting some values more than others, whereas +/// 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. +/// +/// ## Example +/// +/// ``` +/// use rand::rngs::{StdRng, OsRng}; +/// use rand::SeedableRng; +/// use dusk_bls12_381::{BlsScalar, UniScalarRng}; +/// +/// // using a seedable random number generator +/// let rng: &mut UniScalarRng = &mut UniScalarRng::seed_from_u64(0x42); +/// let _scalar = BlsScalar::random(rng); +/// +/// // using randomness derived from the os +/// let mut rng = &mut UniScalarRng::::default(); +/// let _scalar = BlsScalar::random(rng); +/// ``` +#[derive(Clone, Copy, Debug, Default)] +pub struct UniScalarRng(R); + +impl CryptoRng for UniScalarRng where R: CryptoRng {} + +impl RngCore for UniScalarRng +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]) { + // There is no uniform distribution over the field of all scalars when + // the destination slice can not fit the maximum scalar. + if dest.len() < 32 { + panic!("buffer too small to generate uniformly distributed random scalar"); + } + + // 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 + // 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 UniScalarRng +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() { @@ -214,6 +311,20 @@ impl Hash for Scalar { } impl Scalar { + /// Generate a valid Scalar using the random scalar generation from the + /// `Field` trait. + /// + /// In contrast to the implementation of `random` that is part of the + /// `Field` trait this function doesn't consume the random number generator + /// but takes a mutable reference to it. Additionally we also bind the + /// random number generator to be `CryptoRng`. + pub fn random(rand: &mut T) -> Self + where + T: RngCore, + { + ::random(&mut *rand) + } + /// Checks in ct_time whether a Scalar is equal to zero. pub fn is_zero(&self) -> Choice { self.ct_eq(&Scalar::zero()) @@ -262,41 +373,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 +522,43 @@ 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: UniScalarRng = UniScalarRng::seed_from_u64(0xbeef); + + let mut buf32 = [0u8; 32]; + let mut buf64 = [0u8; 64]; + + for _ in 0..100000 { + // fill an array of 64 bytes with our random scalar generator + rng.fill_bytes(&mut buf64); + + // copy the first 32 bytes into another buffer and check that these + // bytes are the canonical encoding of a scalar + buf32.copy_from_slice(&buf64[..32]); + let scalar1: Option = Scalar::from_bytes(&buf32).into(); + assert!(scalar1.is_some()); + + // create a second scalar from the 64 bytes wide array and check that it generates + // the same scalar as generated from the 32 bytes wide array + let scalar2: Scalar = Scalar::from_bytes_wide(&buf64); + let scalar1 = scalar1.unwrap(); + assert_eq!(scalar1, scalar2); + } +} + +#[test] +fn test_random() { + use rand::rngs::StdRng; + + let mut rng: UniScalarRng = UniScalarRng::seed_from_u64(0xbeef); + let scalar1 = Scalar::random(&mut rng); + + // re-initialize the rng + let rng: UniScalarRng = UniScalarRng::seed_from_u64(0xbeef); + let scalar2 = ::random(rng); + + assert_eq!(scalar1, scalar2); +}