Skip to content

Commit

Permalink
Add UniRng a random scalar generator
Browse files Browse the repository at this point in the history
Resolves: #129
  • Loading branch information
moCello committed Nov 16, 2023
1 parent 4363152 commit d9bbd4a
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 36 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 `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
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
138 changes: 102 additions & 36 deletions src/scalar/dusk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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>(R);

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

impl<R> RngCore for UniRng<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]) {
// 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<Scalar> = None;
let mut buf = [0; 32];
while scalar == None {

Check failure on line 70 in src/scalar/dusk.rs

View workflow job for this annotation

GitHub Actions / Code Analysis / Clippy Check

binary comparison to literal `Option::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<R> SeedableRng for UniRng<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 +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<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 +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<StdRng> = 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> = 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);
}
}

0 comments on commit d9bbd4a

Please sign in to comment.