diff --git a/Cargo.toml b/Cargo.toml index b36bbe1..86eae97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,12 +34,12 @@ elliptic-curve = { version = "0.13", features = [ "voprf", ] } generic-array = "1" -rand_core = { version = "0.6", default-features = false } +rand_core = { version = "0.9", default-features = false } serde = { version = "1", default-features = false, features = [ "derive", ], optional = true } sha2 = { version = "0.10", default-features = false, optional = true } -subtle = { version = "2.3", default-features = false } +subtle = { version = "2.6", default-features = false } zeroize = { version = "1.5", default-features = false } [dev-dependencies] @@ -58,7 +58,7 @@ p521 = { version = "0.13.3", default-features = false, features = [ "voprf", ] } proptest = "1" -rand = "0.8" +rand = "0.9" regex = "1" serde_json = "1" sha2 = "0.10" diff --git a/src/common.rs b/src/common.rs index 3224791..05e322b 100644 --- a/src/common.rs +++ b/src/common.rs @@ -16,7 +16,7 @@ use digest::{Digest, Output, OutputSizeUser}; use generic_array::sequence::Concat; use generic_array::typenum::{IsLess, Unsigned, U2, U256, U9}; use generic_array::{ArrayLength, GenericArray}; -use rand_core::{CryptoRng, RngCore}; +use rand_core::{TryCryptoRng, TryRngCore}; use subtle::ConstantTimeEq; #[cfg(feature = "serde")] @@ -128,7 +128,7 @@ pub struct Proof { /// Can only fail with [`Error::Batch`]. #[allow(clippy::many_single_char_names)] -pub(crate) fn generate_proof( +pub(crate) fn generate_proof( rng: &mut R, k: ::Scalar, a: ::Elem, @@ -141,7 +141,7 @@ pub(crate) fn generate_proof( let (m, z) = compute_composites::(Some(k), b, cs, ds, mode)?; - let r = CS::Group::random_scalar(rng); + let r = CS::Group::random_scalar(rng)?; let t2 = a * &r; let t3 = m * &r; diff --git a/src/error.rs b/src/error.rs index 5289715..9f5e6a3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -28,6 +28,8 @@ pub enum Error { ProofVerification, /// The protocol has failed and can't be completed. Protocol, + /// Random number generator failure. + Rng, } /// Only used to implement [`Group`](crate::Group). diff --git a/src/group/elliptic_curve.rs b/src/group/elliptic_curve.rs index bb57083..b06028c 100644 --- a/src/group/elliptic_curve.rs +++ b/src/group/elliptic_curve.rs @@ -6,6 +6,7 @@ // of this source tree. You may select, at your option, one of the above-listed // licenses. +use core::num::NonZeroU32; use core::ops::Add; use digest::core_api::BlockSizeUser; @@ -19,7 +20,7 @@ use elliptic_curve::{ }; use generic_array::typenum::{IsLess, IsLessOrEqual, Sum, U256}; use generic_array::{ArrayLength, GenericArray}; -use rand_core::{CryptoRng, RngCore}; +use rand_core::{TryCryptoRng, TryRngCore}; use super::Group; use crate::{Error, InternalError, Result}; @@ -93,8 +94,8 @@ where .map_err(|_| Error::Deserialization) } - fn random_scalar(rng: &mut R) -> Self::Scalar { - *SecretKey::::random(rng).to_nonzero_scalar() + fn random_scalar(rng: &mut R) -> Result { + Ok(*SecretKey::::random(&mut CompatRng(rng)).to_nonzero_scalar()) } fn invert_scalar(scalar: Self::Scalar) -> Self::Scalar { @@ -123,3 +124,42 @@ where .map_err(|_| Error::Deserialization) } } + +/// Adapter allowing `rand_core 0.9` RNGs to satisfy the `elliptic_curve` 0.13 +/// requirement for `rand_core 0.6` traits. +/// +/// TODO #150: Remove this adapter when `elliptic_curve` migrates to `rand_core +/// 0.9`. +struct CompatRng<'a, R>(&'a mut R); + +impl<'a, R> elliptic_curve::rand_core::RngCore for CompatRng<'a, R> +where + R: TryRngCore, +{ + fn next_u32(&mut self) -> u32 { + self.0.try_next_u32().expect("RNG failure") + } + + fn next_u64(&mut self) -> u64 { + self.0.try_next_u64().expect("RNG failure") + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.0 + .try_fill_bytes(dest) + .expect("RNG failure while filling bytes"); + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), elliptic_curve::rand_core::Error> { + self.0.try_fill_bytes(dest).map_err(|_| compat_error())?; + Ok(()) + } +} + +impl<'a, R> elliptic_curve::rand_core::CryptoRng for CompatRng<'a, R> where R: TryCryptoRng {} + +fn compat_error() -> elliptic_curve::rand_core::Error { + let code = NonZeroU32::new(elliptic_curve::rand_core::Error::CUSTOM_START) + .expect("CUSTOM_START must be non-zero"); + elliptic_curve::rand_core::Error::from(code) +} diff --git a/src/group/mod.rs b/src/group/mod.rs index e5b3631..c089c92 100644 --- a/src/group/mod.rs +++ b/src/group/mod.rs @@ -18,7 +18,7 @@ use digest::core_api::BlockSizeUser; use digest::{FixedOutput, HashMarker}; use generic_array::typenum::{IsLess, IsLessOrEqual, Sum, U256}; use generic_array::{ArrayLength, GenericArray}; -use rand_core::{CryptoRng, RngCore}; +use rand_core::{TryCryptoRng, TryRngCore}; #[cfg(feature = "ristretto255")] pub use ristretto::Ristretto255; use subtle::{Choice, ConstantTimeEq}; @@ -100,8 +100,11 @@ where /// is not a valid point on the group or the identity element. fn deserialize_elem(element_bits: &[u8]) -> Result; - /// picks a scalar at random - fn random_scalar(rng: &mut R) -> Self::Scalar; + /// Picks a scalar at random. + /// + /// # Errors + /// [`Error::Rng`](crate::Error::Rng) if the random number generator fails. + fn random_scalar(rng: &mut R) -> Result; /// The multiplicative inverse of this scalar fn invert_scalar(scalar: Self::Scalar) -> Self::Scalar; diff --git a/src/group/ristretto.rs b/src/group/ristretto.rs index 68c7523..6239a87 100644 --- a/src/group/ristretto.rs +++ b/src/group/ristretto.rs @@ -15,7 +15,7 @@ use digest::{FixedOutput, HashMarker}; use elliptic_curve::hash2curve::{ExpandMsg, ExpandMsgXmd, Expander}; use generic_array::typenum::{IsLess, IsLessOrEqual, U256, U32, U64}; use generic_array::GenericArray; -use rand_core::{CryptoRng, RngCore}; +use rand_core::{TryCryptoRng, TryRngCore}; use subtle::ConstantTimeEq; use super::Group; @@ -94,13 +94,14 @@ impl Group for Ristretto255 { .ok_or(Error::Deserialization) } - fn random_scalar(rng: &mut R) -> Self::Scalar { + fn random_scalar(rng: &mut R) -> Result { loop { let mut scalar_bytes = [0u8; 32]; - rng.fill_bytes(&mut scalar_bytes); + rng.try_fill_bytes(&mut scalar_bytes) + .map_err(|_| Error::Rng)?; if let Ok(scalar) = Self::deserialize_scalar(&scalar_bytes) { - break scalar; + break Ok(scalar); } } } diff --git a/src/oprf.rs b/src/oprf.rs index d3e2f48..d1573a5 100644 --- a/src/oprf.rs +++ b/src/oprf.rs @@ -14,7 +14,7 @@ use derive_where::derive_where; use digest::{Digest, Output}; use generic_array::typenum::Unsigned; use generic_array::GenericArray; -use rand_core::{CryptoRng, RngCore}; +use rand_core::{TryCryptoRng, TryRngCore}; use crate::common::{ derive_key_internal, deterministic_blind_unchecked, hash_to_group, i2osp_2, @@ -73,11 +73,11 @@ impl OprfClient { /// /// # Errors /// [`Error::Input`] if the `input` is empty or longer then [`u16::MAX`]. - pub fn blind( + pub fn blind( input: &[u8], blinding_factor_rng: &mut R, ) -> Result> { - let blind = CS::Group::random_scalar(blinding_factor_rng); + let blind = CS::Group::random_scalar(blinding_factor_rng)?; Self::deterministic_blind_unchecked_inner(input, blind) } @@ -146,9 +146,9 @@ impl OprfServer { /// /// # Errors /// [`Error::Protocol`] if the protocol fails and can't be completed. - pub fn new(rng: &mut R) -> Result { + pub fn new(rng: &mut R) -> Result { let mut seed = GenericArray::<_, ::ScalarLen>::default(); - rng.fill_bytes(&mut seed); + rng.try_fill_bytes(&mut seed).map_err(|_| Error::Protocol)?; Self::new_from_seed(&seed, &[]) } @@ -268,6 +268,7 @@ mod tests { use core::ptr; use rand::rngs::OsRng; + use rand::TryRngCore; use super::*; use crate::common::{Dst, STR_HASH_TO_GROUP}; @@ -304,7 +305,7 @@ mod tests { fn base_inversion_unsalted() { let mut rng = OsRng; let mut input = [0u8; 64]; - rng.fill_bytes(&mut input); + rng.try_fill_bytes(&mut input).unwrap(); let client_blind_result = OprfClient::::blind(&input, &mut rng).unwrap(); let client_finalize_result = client_blind_result .state diff --git a/src/poprf.rs b/src/poprf.rs index fc7d789..7351408 100644 --- a/src/poprf.rs +++ b/src/poprf.rs @@ -16,7 +16,7 @@ use derive_where::derive_where; use digest::{Digest, Output, OutputSizeUser}; use generic_array::typenum::Unsigned; use generic_array::{ArrayLength, GenericArray}; -use rand_core::{CryptoRng, RngCore}; +use rand_core::{TryCryptoRng, TryRngCore}; use crate::common::{ derive_keypair, deterministic_blind_unchecked, generate_proof, hash_to_group, i2osp_2, @@ -75,11 +75,11 @@ impl PoprfClient { /// /// # Errors /// [`Error::Input`] if the `input` is empty or longer than [`u16::MAX`]. - pub fn blind( + pub fn blind( input: &[u8], blinding_factor_rng: &mut R, ) -> Result> { - let blind = CS::Group::random_scalar(blinding_factor_rng); + let blind = CS::Group::random_scalar(blinding_factor_rng)?; Self::deterministic_blind_unchecked_inner(input, blind) } @@ -189,9 +189,9 @@ impl PoprfServer { /// /// # Errors /// [`Error::Protocol`] if the protocol fails and can't be completed. - pub fn new(rng: &mut R) -> Result { + pub fn new(rng: &mut R) -> Result { let mut seed = GenericArray::<_, ::ScalarLen>::default(); - rng.fill_bytes(&mut seed); + rng.try_fill_bytes(&mut seed).map_err(|_| Error::Protocol)?; Self::new_from_seed(&seed, &[]) } @@ -235,7 +235,7 @@ impl PoprfServer { /// # Errors /// - [`Error::Info`] if the `info` is longer than `u16::MAX`. /// - [`Error::Protocol`] if the protocol fails and can't be completed. - pub fn blind_evaluate( + pub fn blind_evaluate( &self, rng: &mut R, blinded_element: &BlindedElement, @@ -273,7 +273,7 @@ impl PoprfServer { /// - [`Error::Info`] if the `info` is longer than `u16::MAX`. /// - [`Error::Protocol`] if the protocol fails and can't be completed. #[cfg(feature = "alloc")] - pub fn batch_blind_evaluate<'a, R: RngCore + CryptoRng, IE>( + pub fn batch_blind_evaluate<'a, R: TryRngCore + TryCryptoRng, IE>( &self, rng: &mut R, blinded_elements: &'a IE, @@ -346,7 +346,7 @@ impl PoprfServer { pub fn batch_blind_evaluate_finish< 'a, 'b, - R: RngCore + CryptoRng, + R: TryRngCore + TryCryptoRng, IB: Iterator> + ExactSizeIterator, IE, >( diff --git a/src/tests/mock_rng.rs b/src/tests/mock_rng.rs index eea5442..3268d41 100644 --- a/src/tests/mock_rng.rs +++ b/src/tests/mock_rng.rs @@ -9,7 +9,7 @@ use alloc::vec::Vec; use core::cmp::min; -use rand_core::{CryptoRng, Error, RngCore}; +use rand_core::{CryptoRng, RngCore}; /// A simple implementation of `RngCore` for testing purposes. /// @@ -54,12 +54,6 @@ impl RngCore for CycleRng { dest[..len].copy_from_slice(&self.v[..len]); rotate_left(&mut self.v, len); } - - #[inline] - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - self.fill_bytes(dest); - Ok(()) - } } // This is meant for testing only diff --git a/src/voprf.rs b/src/voprf.rs index d5f031e..e4c9807 100644 --- a/src/voprf.rs +++ b/src/voprf.rs @@ -16,7 +16,7 @@ use derive_where::derive_where; use digest::{Digest, Output}; use generic_array::typenum::Unsigned; use generic_array::GenericArray; -use rand_core::{CryptoRng, RngCore}; +use rand_core::{TryCryptoRng, TryRngCore}; use crate::common::{ derive_keypair, deterministic_blind_unchecked, generate_proof, hash_to_group, i2osp_2, @@ -75,11 +75,11 @@ impl VoprfClient { /// /// # Errors /// [`Error::Input`] if the `input` is empty or longer then [`u16::MAX`]. - pub fn blind( + pub fn blind( input: &[u8], blinding_factor_rng: &mut R, ) -> Result> { - let blind = CS::Group::random_scalar(blinding_factor_rng); + let blind = CS::Group::random_scalar(blinding_factor_rng)?; Self::deterministic_blind_unchecked_inner(input, blind) } @@ -196,9 +196,9 @@ impl VoprfServer { /// /// # Errors /// [`Error::Protocol`] if the protocol fails and can't be completed. - pub fn new(rng: &mut R) -> Result { + pub fn new(rng: &mut R) -> Result { let mut seed = GenericArray::<_, ::ScalarLen>::default(); - rng.fill_bytes(&mut seed); + rng.try_fill_bytes(&mut seed).map_err(|_| Error::Protocol)?; // This can't fail as the hash output is type constrained. Self::new_from_seed(&seed, &[]) } @@ -238,7 +238,7 @@ impl VoprfServer { /// Computes the second step for the multiplicative blinding version of /// DH-OPRF. This message is sent from the server (who holds the OPRF key) /// to the client. - pub fn blind_evaluate( + pub fn blind_evaluate( &self, rng: &mut R, blinded_element: &BlindedElement, @@ -271,7 +271,7 @@ impl VoprfServer { /// [`Error::Batch`] if the number of `blinded_elements` and /// `evaluation_elements` don't match or is longer then [`u16::MAX`] #[cfg(feature = "alloc")] - pub fn batch_blind_evaluate<'a, R: RngCore + CryptoRng, I>( + pub fn batch_blind_evaluate<'a, R: TryRngCore + TryCryptoRng, I>( &self, rng: &mut R, blinded_elements: &'a I, @@ -322,7 +322,7 @@ impl VoprfServer { pub fn batch_blind_evaluate_finish< 'a, 'b, - R: RngCore + CryptoRng, + R: TryRngCore + TryCryptoRng, IB: Iterator> + ExactSizeIterator, IE, >( @@ -599,7 +599,7 @@ mod tests { let num_iterations = 10; for _ in 0..num_iterations { let mut input = [0u8; 32]; - rng.fill_bytes(&mut input); + rng.try_fill_bytes(&mut input).unwrap(); let client_blind_result = VoprfClient::::blind(&input, &mut rng).unwrap(); inputs.push(input); client_states.push(client_blind_result.state); @@ -643,7 +643,7 @@ mod tests { let num_iterations = 10; for _ in 0..num_iterations { let mut input = [0u8; 32]; - rng.fill_bytes(&mut input); + rng.try_fill_bytes(&mut input).unwrap(); let client_blind_result = VoprfClient::::blind(&input, &mut rng).unwrap(); inputs.push(input); client_states.push(client_blind_result.state);