From 720b3dacf98c04b95aafc3a1073b87c264c26764 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 Co-authored-by: Victor Lopez --- CHANGELOG.md | 9 +++ Cargo.toml | 3 + src/lib.rs | 1 + src/scalar.rs | 2 +- src/scalar/dusk.rs | 168 +++++++++++++++++++++++++++++++++++---------- 5 files changed, 146 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..4b71bb85 100644 --- a/src/scalar/dusk.rs +++ b/src/scalar/dusk.rs @@ -8,8 +8,9 @@ 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 rand_core::{CryptoRng, RngCore, SeedableRng}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; use super::{Scalar, MODULUS, R2}; @@ -24,6 +25,110 @@ 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}; +/// use ff::Field; +/// +/// // using a seedable random number generator +/// let mut rng: UniScalarRng = 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]) { + if dest.len() < 32 { + panic!("buffer too small to generate uniformly distributed random scalar"); + } + + // Loop until we find a canonical scalar. + // As long as the random number generator is implemented properly, this + // loop will terminate. + let mut scalar = [0u64; 4]; + loop { + for integer in scalar.iter_mut() { + *integer = self.0.next_u64(); + } + + // Check that the generated potential scalar is smaller than MODULUS + let bx = scalar[3] <= MODULUS.0[3]; + let b1 = bx && MODULUS.0[0] > scalar[0]; + let b2 = bx && (MODULUS.0[1] + b1 as u64) > scalar[1]; + let b3 = bx && (MODULUS.0[2] + b2 as u64) > scalar[2]; + let b4 = bx && (MODULUS.0[3] + b3 as u64) > scalar[3]; + + if b4 { + // Copy the generated random scalar in the first 32 bytes of the + // destination slice (scalars are stored in little endian). + for (i, integer) in scalar.iter().enumerate() { + let bytes = integer.to_le_bytes(); + dest[i * 8..(i + 1) * 8].copy_from_slice(&bytes); + } + + // Zero the remaining bytes (if any). + if dest.len() > 32 { + dest[32..].fill(0); + } + return; + } + } + } + + 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() { @@ -262,41 +367,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 +516,29 @@ 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); + } +}