Skip to content

Commit

Permalink
Add UniScalarRng a random scalar generator
Browse files Browse the repository at this point in the history
Resolves: #129
  • Loading branch information
moCello committed Nov 22, 2023
1 parent 99aaf01 commit 300e852
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 37 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -221,6 +229,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

<!-- Issues -->
[#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
Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ optional = true

[dependencies.dusk-bytes]
version = "0.1"

[dependencies.rand]
version = "0.8"
# End

[features]
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
2 changes: 1 addition & 1 deletion src/scalar.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down
160 changes: 124 additions & 36 deletions src/scalar/dusk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -24,6 +25,102 @@ 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<StdRng> = UniScalarRng::seed_from_u64(0x42);
/// let _scalar = BlsScalar::random(rng);
///
/// // using randomness derived from the os
/// let mut rng = &mut UniScalarRng::<OsRng>::default();
/// let _scalar = BlsScalar::random(rng);
/// ```
#[derive(Clone, Copy, Debug, Default)]
pub struct UniScalarRng<R>(R);

impl<R> CryptoRng for UniScalarRng<R> where R: CryptoRng {}

impl<R> RngCore for UniScalarRng<R>
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<Scalar> = 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<R> SeedableRng for UniScalarRng<R>
where
R: SeedableRng,
{
type Seed = <R as SeedableRng>::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() {
Expand Down Expand Up @@ -262,41 +359,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<R>(rng: &mut R) -> Self
where
R: RngCore + CryptoRng,
{
let mut buf = [0; 32];
let mut scalar: Option<Self> = 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) {
Expand Down Expand Up @@ -446,3 +508,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<StdRng> = 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> = 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);
}
}

0 comments on commit 300e852

Please sign in to comment.