Skip to content

Commit

Permalink
Add UniScalarRng
Browse files Browse the repository at this point in the history
Resolves: #121
  • Loading branch information
moCello committed Nov 22, 2023
1 parent 579806b commit 032c167
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 14 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ 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 [#121]

### Change
- Change dusk's `random` scalar generation to internally use the `Field` implementation of `random` [#121]

## [0.13.1] - 2023-10-11

### Changed
Expand Down Expand Up @@ -198,6 +205,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Initial fork from [`zkcrypto/jubjub`]

<!-- Versions -->
[#121]: https://github.com/dusk-network/jubjub/issues/121
[#115]: https://github.com/dusk-network/jubjub/issues/115
[#109]: https://github.com/dusk-network/jubjub/issues/109
[#104]: https://github.com/dusk-network/jubjub/issues/104
Expand All @@ -215,6 +224,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#31]: https://github.com/dusk-network/jubjub/issues/31
[#25]: https://github.com/dusk-network/jubjub/issues/25

<!-- Issues -->
[Unreleased]: https://github.com/dusk-network/jubjub/compare/v0.13.1...HEAD
[0.13.1]: https://github.com/dusk-network/jubjub/compare/v0.13.0...v0.13.1
[0.13.0]: https://github.com/dusk-network/jubjub/compare/v0.12.1...v0.13.0
Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ default-features = false
[dev-dependencies.blake2]
version = "0.9"

[dev-dependencies.rand]
version = "0.8"

[features]
default = ["alloc", "bits"]
alloc = ["ff/alloc", "group/alloc"]
Expand Down
2 changes: 1 addition & 1 deletion src/fr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//! $\mathbb{F}_r$ where `r =
//! 0x0e7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7`

mod dusk;
pub(crate) mod dusk;

use core::convert::TryInto;
use core::fmt;
Expand Down
163 changes: 150 additions & 13 deletions src/fr/dusk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,129 @@
//
// Copyright (c) DUSK NETWORK. All rights reserved.

use core::cmp::{Ord, Ordering, PartialOrd};
use core::convert::TryInto;
use core::ops::{Index, IndexMut};

use rand_core::RngCore;
use dusk_bls12_381::BlsScalar;
use dusk_bytes::{Error as BytesError, Serializable};
use ff::Field;
use rand_core::{CryptoRng, RngCore, SeedableRng};

use super::{Fr, MODULUS, R2};
use crate::util::sbb;

use core::cmp::{Ord, Ordering, PartialOrd};
use core::ops::{Index, IndexMut};
use dusk_bls12_381::BlsScalar;
/// Random number generator for generating scalars that are uniformly
/// distributed over the entire field of scalars.
///
/// Because scalars take 251 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_jubjub::{JubJubScalar, UniScalarRng};
///
/// // using a seedable random number generator
/// let rng: &mut UniScalarRng<StdRng> = &mut UniScalarRng::seed_from_u64(0x42);
/// let _scalar = JubJubScalar::random(rng);
///
/// // using randomness derived from the os
/// let rng = &mut UniScalarRng::<OsRng>::default();
/// let _ = JubJubScalar::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()
}

use dusk_bytes::{Error as BytesError, Serializable};
fn next_u64(&mut self) -> u64 {
self.0.next_u64()
}

use super::{Fr, MODULUS, R2};
// 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<Fr> = None;
let mut buf = [0; 32];
while scalar == None {
self.0.fill_bytes(&mut buf);
// Since modulus has at most 251 bits, we can zero the 5 MSB and
// improve our chances of hitting a valid scalar to above 50%.
buf[32 - 1] &= 0b0000_0111;
scalar = Fr::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 Fr {
/// Generate a valid Scalar choosen uniformly using user-
/// provided rng.
/// Generate a valid Scalar using the random scalar generation from the
/// `Field` trait.
///
/// By `rng` we mean any Rng that implements: `Rng` + `CryptoRng`.
/// 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<T>(rand: &mut T) -> Fr
where
T: RngCore,
{
let mut bytes = [0u8; 64];
rand.fill_bytes(&mut bytes);

Fr::from_bytes_wide(&bytes)
<Fr as Field>::random(&mut *rand)
}

/// SHR impl: shifts bits n times, equivalent to division by 2^n.
Expand Down Expand Up @@ -303,3 +399,44 @@ fn w_naf_2() {
});
assert_eq!(scalar, recomputed);
}

#[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<Fr> = Fr::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: Fr = Fr::from_bytes_wide(&buf64);
let scalar1 = scalar1.unwrap();
assert_eq!(scalar1, scalar2);
}
}

#[test]
fn test_random() {
use rand::rngs::StdRng;

let mut rng: UniScalarRng<StdRng> = UniScalarRng::seed_from_u64(0xbeef);
let scalar1 = Fr::random(&mut rng);

// re-initialize the rng
let rng: UniScalarRng<StdRng> = UniScalarRng::seed_from_u64(0xbeef);
let scalar2 = <Fr as Field>::random(rng);

assert_eq!(scalar1, scalar2);
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pub use dusk::{
dhke, GENERATOR, GENERATOR_EXTENDED, GENERATOR_NUMS,
GENERATOR_NUMS_EXTENDED,
};
pub use fr::dusk::UniScalarRng;
/// An alias for [`AffinePoint`]
pub type JubJubAffine = AffinePoint;
/// An alias for [`ExtendedPoint`]
Expand Down

0 comments on commit 032c167

Please sign in to comment.