From 81ccc6660382eab8427d753773ee7fa59394fdaf Mon Sep 17 00:00:00 2001 From: moana Date: Fri, 17 Nov 2023 17:58:43 +0100 Subject: [PATCH] Add `UniScalarRng` Resolves: #121 Co-authored-by: Victor Lopez --- CHANGELOG.md | 10 ++++ Cargo.toml | 3 + src/fr.rs | 2 +- src/fr/dusk.rs | 155 +++++++++++++++++++++++++++++++++++++++++++------ src/lib.rs | 1 + 5 files changed, 151 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6bedee..ff1e9e2 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] + +### Remove +- Remove dusk's `random` scalar implementation in favor of the `random` implementation that is part of the `Field` trait [#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..4e1d057 100644 --- a/src/fr/dusk.rs +++ b/src/fr/dusk.rs @@ -4,35 +4,125 @@ // // 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 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}; +/// use ff::Field; +/// +/// // using a seedable random number generator +/// let mut rng: UniScalarRng = UniScalarRng::seed_from_u64(0x42); +/// let _scalar = JubJubScalar::random(rng); +/// +/// // using randomness derived from the os +/// let mut rng = 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]) { + if dest.len() < 32 { + panic!("buffer too small to generate uniformly distributed random scalar"); + } -impl Fr { - /// Generate a valid Scalar choosen uniformly using user- - /// provided rng. - /// - /// By `rng` we mean any Rng that implements: `Rng` + `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) + // 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 Fr { /// SHR impl: shifts bits n times, equivalent to division by 2^n. #[inline] pub fn divn(&mut self, mut n: u32) { @@ -303,3 +393,30 @@ 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); + } +} 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`]