From 032c16734e96fbdb3bc511156d0e6eef6a981fe4 Mon Sep 17 00:00:00 2001 From: moana Date: Fri, 17 Nov 2023 17:58:43 +0100 Subject: [PATCH] Add `UniScalarRng` Resolves: #121 --- CHANGELOG.md | 10 +++ Cargo.toml | 3 + src/fr.rs | 2 +- src/fr/dusk.rs | 163 +++++++++++++++++++++++++++++++++++++++++++++---- src/lib.rs | 1 + 5 files changed, 165 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6bedee..78d4cb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 @@ -198,6 +205,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Initial fork from [`zkcrypto/jubjub`] + +[#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 @@ -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 + [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 diff --git a/Cargo.toml b/Cargo.toml index 69749f2..aef2553 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] diff --git a/src/fr.rs b/src/fr.rs index 6b8540d..a31938d 100644 --- a/src/fr.rs +++ b/src/fr.rs @@ -2,7 +2,7 @@ //! $\mathbb{F}_r$ where `r = //! 0x0e7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7` -mod dusk; +pub(crate) mod dusk; use core::convert::TryInto; use core::fmt; diff --git a/src/fr/dusk.rs b/src/fr/dusk.rs index df227c3..81c7cdd 100644 --- a/src/fr/dusk.rs +++ b/src/fr/dusk.rs @@ -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 = &mut UniScalarRng::seed_from_u64(0x42); +/// let _scalar = JubJubScalar::random(rng); +/// +/// // using randomness derived from the os +/// let rng = &mut UniScalarRng::::default(); +/// let _ = JubJubScalar::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() + } -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 = 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 SeedableRng for UniScalarRng +where + R: SeedableRng, +{ + type Seed = ::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(rand: &mut T) -> Fr where T: RngCore, { - let mut bytes = [0u8; 64]; - rand.fill_bytes(&mut bytes); - - Fr::from_bytes_wide(&bytes) + ::random(&mut *rand) } /// SHR impl: shifts bits n times, equivalent to division by 2^n. @@ -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 = 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::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 = UniScalarRng::seed_from_u64(0xbeef); + let scalar1 = Fr::random(&mut rng); + + // re-initialize the rng + let rng: UniScalarRng = UniScalarRng::seed_from_u64(0xbeef); + let scalar2 = ::random(rng); + + assert_eq!(scalar1, scalar2); +} diff --git a/src/lib.rs b/src/lib.rs index 867d7a5..49a41d2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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`]