diff --git a/Cargo.toml b/Cargo.toml index 1be9a6a..ce1766b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ std = ["alloc"] [dependencies] curve25519-dalek = { version = "=4.0.0-pre.1", default-features = false, optional = true } -derive-where = { version = "=1.0.0-rc.2", features = ["zeroize-on-drop"] } +derive-where = { version = "=1.0.0-rc.3", features = ["zeroize-on-drop"] } digest = "0.10" displaydoc = { version = "0.2", default-features = false } elliptic-curve = { version = "=0.12.0-pre.1", features = [ diff --git a/src/util.rs b/src/common.rs similarity index 84% rename from src/util.rs rename to src/common.rs index c6b09d5..d30fced 100644 --- a/src/util.rs +++ b/src/common.rs @@ -5,7 +5,7 @@ // License, Version 2.0 found in the LICENSE-APACHE file in the root directory // of this source tree. -//! Helper functions +//! Common functionality between multiple OPRF modes. use core::convert::TryFrom; @@ -18,7 +18,6 @@ use generic_array::{ArrayLength, GenericArray}; use rand_core::{CryptoRng, RngCore}; use subtle::ConstantTimeEq; -use crate::group::{STR_HASH_TO_GROUP, STR_HASH_TO_SCALAR}; #[cfg(feature = "serde")] use crate::serialization::serde::{Element, Scalar}; use crate::{CipherSuite, Error, Group, InternalError, Result}; @@ -35,6 +34,8 @@ pub(crate) const STR_COMPOSITE: [u8; 9] = *b"Composite"; pub(crate) const STR_CHALLENGE: [u8; 9] = *b"Challenge"; pub(crate) const STR_INFO: [u8; 4] = *b"Info"; pub(crate) const STR_VOPRF: [u8; 8] = *b"VOPRF09-"; +pub(crate) const STR_HASH_TO_SCALAR: [u8; 13] = *b"HashToScalar-"; +pub(crate) const STR_HASH_TO_GROUP: [u8; 12] = *b"HashToGroup-"; /// Determines the mode of operation (either base mode or verifiable mode). This /// is only used for custom implementations for [`Group`]. @@ -195,7 +196,7 @@ where let dst = GenericArray::from(STR_HASH_TO_SCALAR).concat(create_context_string::(mode)); // This can't fail, the size of the `input` is known. - let c_scalar = CS::Group::hash_to_scalar::(&h2_input, &dst).unwrap(); + let c_scalar = CS::Group::hash_to_scalar::(&h2_input, &dst).unwrap(); let s_scalar = r - &(c_scalar * &k); Ok(Proof { c_scalar, s_scalar }) @@ -255,7 +256,7 @@ where let dst = GenericArray::from(STR_HASH_TO_SCALAR).concat(create_context_string::(mode)); // This can't fail, the size of the `input` is known. - let c = CS::Group::hash_to_scalar::(&h2_input, &dst).unwrap(); + let c = CS::Group::hash_to_scalar::(&h2_input, &dst).unwrap(); match c.ct_eq(&proof.c_scalar).into() { true => Ok(()), @@ -333,7 +334,7 @@ where let dst = GenericArray::from(STR_HASH_TO_SCALAR).concat(create_context_string::(mode)); // This can't fail, the size of the `input` is known. - let di = CS::Group::hash_to_scalar::(&h2_input, &dst).unwrap(); + let di = CS::Group::hash_to_scalar::(&h2_input, &dst).unwrap(); m = c * &di + &m; z = match k_option { Some(_) => z, @@ -354,6 +355,39 @@ where // =============== // ///////////////////// +/// Can only fail with [`Error::DeriveKeyPair`] and [`Error::Protocol`]. +pub(crate) fn derive_key( + seed: &[u8], + info: &[u8], + mode: Mode, +) -> Result<::Scalar, Error> +where + ::OutputSize: + IsLess + IsLessOrEqual<::BlockSize>, +{ + let context_string = create_context_string::(mode); + let dst = GenericArray::from(STR_DERIVE_KEYPAIR).concat(context_string); + + let info_len = i2osp_2(info.len()).map_err(|_| Error::DeriveKeyPair)?; + + for counter in 0_u8..=u8::MAX { + // deriveInput = seed || I2OSP(len(info), 2) || info + // skS = G.HashToScalar(deriveInput || I2OSP(counter, 1), DST = "DeriveKeyPair" + // || contextString) + let sk_s = CS::Group::hash_to_scalar::( + &[seed, &info_len, info, &counter.to_be_bytes()], + &dst, + ) + .map_err(|_| Error::DeriveKeyPair)?; + + if !bool::from(CS::Group::is_zero_scalar(sk_s)) { + return Ok(sk_s); + } + } + + Err(Error::Protocol) +} + type DeriveKeypairResult = ( <::Group as Group>::Scalar, <::Group as Group>::Elem, @@ -369,28 +403,10 @@ where ::OutputSize: IsLess + IsLessOrEqual<::BlockSize>, { - let context_string = create_context_string::(mode); - let dst = GenericArray::from(STR_DERIVE_KEYPAIR).concat(context_string); + let sk_s = derive_key::(seed, info, mode)?; + let pk_s = CS::Group::base_elem() * &sk_s; - let info_len = i2osp_2(info.len()).map_err(|_| Error::DeriveKeyPair)?; - - for counter in 0_u8..=u8::MAX { - // deriveInput = seed || I2OSP(len(info), 2) || info - // skS = G.HashToScalar(deriveInput || I2OSP(counter, 1), DST = "DeriveKeyPair" - // || contextString) - let sk_s = ::hash_to_scalar::( - &[seed, &info_len, info, &counter.to_be_bytes()], - &dst, - ) - .map_err(|_| Error::DeriveKeyPair)?; - - if !bool::from(CS::Group::is_zero_scalar(sk_s)) { - let pk_s = CS::Group::base_elem() * &sk_s; - return Ok((sk_s, pk_s)); - } - } - - Err(Error::Protocol) + Ok((sk_s, pk_s)) } /// Inner function for blind that assumes that the blinding factor has already @@ -408,7 +424,8 @@ where IsLess + IsLessOrEqual<::BlockSize>, { let dst = GenericArray::from(STR_HASH_TO_GROUP).concat(create_context_string::(mode)); - let hashed_point = CS::Group::hash_to_curve::(&[input], &dst).map_err(|_| Error::Input)?; + let hashed_point = + CS::Group::hash_to_curve::(&[input], &dst).map_err(|_| Error::Input)?; Ok(hashed_point * blind) } @@ -440,73 +457,3 @@ pub(crate) fn i2osp_2_array + IsLess>( ) -> GenericArray { L::U16.to_be_bytes().into() } - -#[cfg(test)] -mod unit_tests { - use proptest::collection::vec; - use proptest::prelude::*; - - use crate::{ - BlindedElement, EvaluationElement, OprfClient, OprfServer, PoprfClient, PoprfServer, Proof, - VoprfClient, VoprfServer, - }; - - macro_rules! test_deserialize { - ($item:ident, $bytes:ident) => { - #[cfg(feature = "ristretto255")] - { - let _ = $item::::deserialize(&$bytes[..]); - } - - let _ = $item::::deserialize(&$bytes[..]); - }; - } - - proptest! { - #[test] - fn test_nocrash_oprf_client(bytes in vec(any::(), 0..200)) { - test_deserialize!(OprfClient, bytes); - } - - #[test] - fn test_nocrash_voprf_client(bytes in vec(any::(), 0..200)) { - test_deserialize!(VoprfClient, bytes); - } - - #[test] - fn test_nocrash_poprf_client(bytes in vec(any::(), 0..200)) { - test_deserialize!(PoprfClient, bytes); - } - - #[test] - fn test_nocrash_oprf_server(bytes in vec(any::(), 0..200)) { - test_deserialize!(OprfServer, bytes); - } - - #[test] - fn test_nocrash_voprf_server(bytes in vec(any::(), 0..200)) { - test_deserialize!(VoprfServer, bytes); - } - - #[test] - fn test_nocrash_poprf_server(bytes in vec(any::(), 0..200)) { - test_deserialize!(PoprfServer, bytes); - } - - - #[test] - fn test_nocrash_blinded_element(bytes in vec(any::(), 0..200)) { - test_deserialize!(BlindedElement, bytes); - } - - #[test] - fn test_nocrash_evaluation_element(bytes in vec(any::(), 0..200)) { - test_deserialize!(EvaluationElement, bytes); - } - - #[test] - fn test_nocrash_proof(bytes in vec(any::(), 0..200)) { - test_deserialize!(Proof, bytes); - } - } -} diff --git a/src/group/elliptic_curve.rs b/src/group/elliptic_curve.rs index 75b642e..edbd958 100644 --- a/src/group/elliptic_curve.rs +++ b/src/group/elliptic_curve.rs @@ -6,7 +6,7 @@ // of this source tree. use digest::core_api::BlockSizeUser; -use digest::OutputSizeUser; +use digest::Digest; use elliptic_curve::group::cofactor::CofactorGroup; use elliptic_curve::hash2curve::{ExpandMsgXmd, FromOkm, GroupDigest}; use elliptic_curve::sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint}; @@ -18,12 +18,12 @@ use generic_array::GenericArray; use rand_core::{CryptoRng, RngCore}; use super::Group; -use crate::{CipherSuite, Error, InternalError, Result}; +use crate::{Error, InternalError, Result}; impl Group for C where C: GroupDigest, - ProjectivePoint: CofactorGroup, + ProjectivePoint: CofactorGroup + ToEncodedPoint, FieldSize: ModulusSize, AffinePoint: FromEncodedPoint + ToEncodedPoint, Scalar: FromOkm, @@ -38,28 +38,21 @@ where // Implements the `hash_to_curve()` function from // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3 - fn hash_to_curve( - input: &[&[u8]], - dst: &[u8], - ) -> Result + fn hash_to_curve(input: &[&[u8]], dst: &[u8]) -> Result where - ::OutputSize: - IsLess + IsLessOrEqual<::BlockSize>, + H: Digest + BlockSizeUser, + H::OutputSize: IsLess + IsLessOrEqual, { - Self::hash_from_bytes::>(input, dst) - .map_err(|_| InternalError::Input) + Self::hash_from_bytes::>(input, dst).map_err(|_| InternalError::Input) } // Implements the `HashToScalar()` function - fn hash_to_scalar( - input: &[&[u8]], - dst: &[u8], - ) -> Result + fn hash_to_scalar(input: &[&[u8]], dst: &[u8]) -> Result where - ::OutputSize: - IsLess + IsLessOrEqual<::BlockSize>, + H: Digest + BlockSizeUser, + H::OutputSize: IsLess + IsLessOrEqual, { - ::hash_to_scalar::>(input, dst) + ::hash_to_scalar::>(input, dst) .map_err(|_| InternalError::Input) } @@ -72,8 +65,7 @@ where } fn serialize_elem(elem: Self::Elem) -> GenericArray { - let point: AffinePoint = elem.into(); - let bytes = point.to_encoded_point(true); + let bytes = elem.to_encoded_point(true); let bytes = bytes.as_bytes(); let mut result = GenericArray::default(); result[..bytes.len()].copy_from_slice(bytes); diff --git a/src/group/mod.rs b/src/group/mod.rs index 7f7eebe..132b78d 100644 --- a/src/group/mod.rs +++ b/src/group/mod.rs @@ -14,7 +14,7 @@ mod ristretto; use core::ops::{Add, Mul, Sub}; use digest::core_api::BlockSizeUser; -use digest::OutputSizeUser; +use digest::Digest; use generic_array::typenum::{IsLess, IsLessOrEqual, U256}; use generic_array::{ArrayLength, GenericArray}; use rand_core::{CryptoRng, RngCore}; @@ -23,10 +23,7 @@ pub use ristretto::Ristretto255; use subtle::{Choice, ConstantTimeEq}; use zeroize::Zeroize; -use crate::{CipherSuite, InternalError, Result}; - -pub(crate) const STR_HASH_TO_SCALAR: [u8; 13] = *b"HashToScalar-"; -pub(crate) const STR_HASH_TO_GROUP: [u8; 12] = *b"HashToGroup-"; +use crate::{InternalError, Result}; /// A prime-order subgroup of a base field (EC, prime-order field ...). This /// subgroup is noted additively — as in the draft RFC — in this trait. @@ -57,26 +54,20 @@ pub trait Group { /// # Errors /// [`Error::Input`](crate::Error::Input) if the `input` is empty or longer /// then [`u16::MAX`]. - fn hash_to_curve( - input: &[&[u8]], - dst: &[u8], - ) -> Result + fn hash_to_curve(input: &[&[u8]], dst: &[u8]) -> Result where - ::OutputSize: - IsLess + IsLessOrEqual<::BlockSize>; + H: Digest + BlockSizeUser, + H::OutputSize: IsLess + IsLessOrEqual; /// Hashes a slice of pseudo-random bytes to a scalar /// /// # Errors /// [`Error::Input`](crate::Error::Input) if the `input` is empty or longer /// then [`u16::MAX`]. - fn hash_to_scalar( - input: &[&[u8]], - dst: &[u8], - ) -> Result + fn hash_to_scalar(input: &[&[u8]], dst: &[u8]) -> Result where - ::OutputSize: - IsLess + IsLessOrEqual<::BlockSize>; + H: Digest + BlockSizeUser, + H::OutputSize: IsLess + IsLessOrEqual; /// Get the base point for the group fn base_elem() -> Self::Elem; diff --git a/src/group/ristretto.rs b/src/group/ristretto.rs index 3026496..bf42d19 100644 --- a/src/group/ristretto.rs +++ b/src/group/ristretto.rs @@ -10,7 +10,7 @@ use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint}; use curve25519_dalek::scalar::Scalar; use curve25519_dalek::traits::Identity; use digest::core_api::BlockSizeUser; -use digest::OutputSizeUser; +use digest::Digest; use elliptic_curve::hash2curve::{ExpandMsg, ExpandMsgXmd, Expander}; use generic_array::typenum::{IsLess, IsLessOrEqual, U256, U32, U64}; use generic_array::GenericArray; @@ -18,7 +18,7 @@ use rand_core::{CryptoRng, RngCore}; use subtle::ConstantTimeEq; use super::Group; -use crate::{CipherSuite, Error, InternalError, Result}; +use crate::{Error, InternalError, Result}; /// [`Group`] implementation for Ristretto255. #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] @@ -48,16 +48,13 @@ impl Group for Ristretto255 { // Implements the `hash_to_ristretto255()` function from // https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt - fn hash_to_curve( - input: &[&[u8]], - dst: &[u8], - ) -> Result + fn hash_to_curve(input: &[&[u8]], dst: &[u8]) -> Result where - ::OutputSize: - IsLess + IsLessOrEqual<::BlockSize>, + H: Digest + BlockSizeUser, + H::OutputSize: IsLess + IsLessOrEqual, { let mut uniform_bytes = GenericArray::<_, U64>::default(); - ExpandMsgXmd::::expand_message(input, dst, 64) + ExpandMsgXmd::::expand_message(input, dst, 64) .map_err(|_| InternalError::Input)? .fill_bytes(&mut uniform_bytes); @@ -66,16 +63,13 @@ impl Group for Ristretto255 { // Implements the `HashToScalar()` function from // https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-07.html#section-4.1 - fn hash_to_scalar( - input: &[&[u8]], - dst: &[u8], - ) -> Result + fn hash_to_scalar(input: &[&[u8]], dst: &[u8]) -> Result where - ::OutputSize: - IsLess + IsLessOrEqual<::BlockSize>, + H: Digest + BlockSizeUser, + H::OutputSize: IsLess + IsLessOrEqual, { let mut uniform_bytes = GenericArray::<_, U64>::default(); - ExpandMsgXmd::::expand_message(input, dst, 64) + ExpandMsgXmd::::expand_message(input, dst, 64) .map_err(|_| InternalError::Input)? .fill_bytes(&mut uniform_bytes); @@ -96,6 +90,10 @@ impl Group for Ristretto255 { } fn deserialize_elem(element_bits: &[u8]) -> Result { + if element_bits.len() != 32 { + return Err(Error::Deserialization); + } + CompressedRistretto::from_slice(element_bits) .decompress() .filter(|point| point != &RistrettoPoint::identity()) @@ -104,11 +102,7 @@ impl Group for Ristretto255 { fn random_scalar(rng: &mut R) -> Self::Scalar { loop { - let scalar = { - let mut scalar_bytes = [0u8; 64]; - rng.fill_bytes(&mut scalar_bytes); - Scalar::from_bytes_mod_order_wide(&scalar_bytes) - }; + let scalar = Scalar::random(rng); if scalar != Scalar::zero() { break scalar; diff --git a/src/lib.rs b/src/lib.rs index 178ecbd..a9a3ffd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -483,12 +483,12 @@ extern crate std; extern crate serde_ as serde; mod ciphersuite; +mod common; mod error; mod group; mod oprf; mod poprf; mod serialization; -mod util; mod voprf; #[cfg(test)] @@ -497,6 +497,9 @@ mod tests; // Exports pub use crate::ciphersuite::CipherSuite; +pub use crate::common::{ + BlindedElement, EvaluationElement, Mode, PreparedEvaluationElement, Proof, +}; pub use crate::error::{Error, InternalError, Result}; pub use crate::group::Group; #[cfg(feature = "ristretto255")] @@ -513,7 +516,6 @@ pub use crate::serialization::{ BlindedElementLen, EvaluationElementLen, OprfClientLen, OprfServerLen, PoprfClientLen, PoprfServerLen, ProofLen, VoprfClientLen, VoprfServerLen, }; -pub use crate::util::{BlindedElement, EvaluationElement, Mode, PreparedEvaluationElement, Proof}; #[cfg(feature = "alloc")] pub use crate::voprf::VoprfServerBatchEvaluateResult; pub use crate::voprf::{ diff --git a/src/oprf.rs b/src/oprf.rs index dc44297..48bff0d 100644 --- a/src/oprf.rs +++ b/src/oprf.rs @@ -16,12 +16,12 @@ use generic_array::typenum::{IsLess, IsLessOrEqual, Unsigned, U256}; use generic_array::GenericArray; use rand_core::{CryptoRng, RngCore}; +use crate::common::{ + derive_key, deterministic_blind_unchecked, i2osp_2, BlindedElement, EvaluationElement, Mode, + STR_FINALIZE, +}; #[cfg(feature = "serde")] use crate::serialization::serde::Scalar; -use crate::util::{ - derive_keypair, deterministic_blind_unchecked, i2osp_2, BlindedElement, EvaluationElement, - Mode, STR_FINALIZE, -}; use crate::{CipherSuite, Error, Group, Result}; /////////////// @@ -189,7 +189,7 @@ where /// then `u16::MAX - 3`. /// - [`Error::Protocol`] if the protocol fails and can't be completed. pub fn new_from_seed(seed: &[u8], info: &[u8]) -> Result { - let (sk, _) = derive_keypair::(seed, info, Mode::Oprf)?; + let sk = derive_key::(seed, info, Mode::Oprf)?; Ok(Self { sk }) } @@ -279,8 +279,7 @@ mod tests { use rand::rngs::OsRng; use super::*; - use crate::group::STR_HASH_TO_GROUP; - use crate::util::create_context_string; + use crate::common::{create_context_string, STR_HASH_TO_GROUP}; use crate::Group; fn prf( @@ -294,7 +293,7 @@ mod tests { IsLess + IsLessOrEqual<::BlockSize>, { let dst = GenericArray::from(STR_HASH_TO_GROUP).concat(create_context_string::(mode)); - let point = CS::Group::hash_to_curve::(&[input], &dst).unwrap(); + let point = CS::Group::hash_to_curve::(&[input], &dst).unwrap(); let res = point * &key; @@ -335,7 +334,7 @@ mod tests { let dst = GenericArray::from(STR_HASH_TO_GROUP).concat(create_context_string::(Mode::Oprf)); - let point = CS::Group::hash_to_curve::(&[&input], &dst).unwrap(); + let point = CS::Group::hash_to_curve::(&[&input], &dst).unwrap(); let res2 = finalize_after_unblind::(iter::once((input.as_ref(), point)), &[]) .next() .unwrap() diff --git a/src/poprf.rs b/src/poprf.rs index b157b98..cfcd57f 100644 --- a/src/poprf.rs +++ b/src/poprf.rs @@ -19,14 +19,13 @@ use generic_array::typenum::{IsLess, IsLessOrEqual, Unsigned, U256}; use generic_array::GenericArray; use rand_core::{CryptoRng, RngCore}; -use crate::group::STR_HASH_TO_SCALAR; -#[cfg(feature = "serde")] -use crate::serialization::serde::{Element, Scalar}; -use crate::util::{ +use crate::common::{ create_context_string, derive_keypair, deterministic_blind_unchecked, generate_proof, i2osp_2, verify_proof, BlindedElement, EvaluationElement, Mode, PreparedEvaluationElement, Proof, - STR_FINALIZE, STR_INFO, + STR_FINALIZE, STR_HASH_TO_SCALAR, STR_INFO, }; +#[cfg(feature = "serde")] +use crate::serialization::serde::{Element, Scalar}; use crate::{CipherSuite, Error, Group, Result}; //////////////////////////// @@ -596,7 +595,7 @@ where let dst = GenericArray::from(STR_HASH_TO_SCALAR).concat(create_context_string::(Mode::Poprf)); // This can't fail, the size of the `input` is known. - let m = CS::Group::hash_to_scalar::(&framed_info, &dst).unwrap(); + let m = CS::Group::hash_to_scalar::(&framed_info, &dst).unwrap(); let t = CS::Group::base_elem() * &m; let tweaked_key = t + &pk; @@ -634,7 +633,7 @@ where let dst = GenericArray::from(STR_HASH_TO_SCALAR).concat(create_context_string::(Mode::Poprf)); // This can't fail, the size of the `input` is known. - let m = CS::Group::hash_to_scalar::(&framed_info, &dst).unwrap(); + let m = CS::Group::hash_to_scalar::(&framed_info, &dst).unwrap(); let t = sk + &m; @@ -772,7 +771,7 @@ mod tests { use rand::rngs::OsRng; use super::*; - use crate::group::STR_HASH_TO_GROUP; + use crate::common::STR_HASH_TO_GROUP; use crate::Group; fn prf( @@ -788,7 +787,7 @@ mod tests { let t = compute_tweak::(key, Some(info)).unwrap(); let dst = GenericArray::from(STR_HASH_TO_GROUP).concat(create_context_string::(mode)); - let point = CS::Group::hash_to_curve::(&[input], &dst).unwrap(); + let point = CS::Group::hash_to_curve::(&[input], &dst).unwrap(); // evaluatedElement = G.ScalarInverse(t) * blindedElement let res = point * &CS::Group::invert_scalar(t); @@ -844,7 +843,7 @@ mod tests { let dst = GenericArray::from(STR_HASH_TO_GROUP) .concat(create_context_string::(Mode::Oprf)); // Choose a group element that is unlikely to be the right public key - CS::Group::hash_to_curve::(&[b"msg"], &dst).unwrap() + CS::Group::hash_to_curve::(&[b"msg"], &dst).unwrap() }; let client_finalize_result = client_blind_result.state.finalize( input, diff --git a/src/serialization.rs b/src/serialization.rs index 1040e2f..6a6fa60 100644 --- a/src/serialization.rs +++ b/src/serialization.rs @@ -43,10 +43,8 @@ where /// /// # Errors /// [`Error::Deserialization`] if failed to deserialize `input`. - pub fn deserialize(input: &[u8]) -> Result { - let mut input = input.iter().copied(); - - let blind = deserialize_scalar::(&mut input)?; + pub fn deserialize(mut input: &[u8]) -> Result { + let blind = deserialize_scalar::(&mut input)?; Ok(Self { blind }) } @@ -77,11 +75,9 @@ where /// /// # Errors /// [`Error::Deserialization`] if failed to deserialize `input`. - pub fn deserialize(input: &[u8]) -> Result { - let mut input = input.iter().copied(); - - let blind = deserialize_scalar::(&mut input)?; - let blinded_element = deserialize_elem::(&mut input)?; + pub fn deserialize(mut input: &[u8]) -> Result { + let blind = deserialize_scalar::(&mut input)?; + let blinded_element = deserialize_elem::(&mut input)?; Ok(Self { blind, @@ -115,11 +111,9 @@ where /// /// # Errors /// [`Error::Deserialization`] if failed to deserialize `input`. - pub fn deserialize(input: &[u8]) -> Result { - let mut input = input.iter().copied(); - - let blind = deserialize_scalar::(&mut input)?; - let blinded_element = deserialize_elem::(&mut input)?; + pub fn deserialize(mut input: &[u8]) -> Result { + let blind = deserialize_scalar::(&mut input)?; + let blinded_element = deserialize_elem::(&mut input)?; Ok(Self { blind, @@ -145,10 +139,8 @@ where /// /// # Errors /// [`Error::Deserialization`] if failed to deserialize `input`. - pub fn deserialize(input: &[u8]) -> Result { - let mut input = input.iter().copied(); - - let sk = deserialize_scalar::(&mut input)?; + pub fn deserialize(mut input: &[u8]) -> Result { + let sk = deserialize_scalar::(&mut input)?; Ok(Self { sk }) } @@ -178,11 +170,9 @@ where /// /// # Errors /// [`Error::Deserialization`] if failed to deserialize `input`. - pub fn deserialize(input: &[u8]) -> Result { - let mut input = input.iter().copied(); - - let sk = deserialize_scalar::(&mut input)?; - let pk = deserialize_elem::(&mut input)?; + pub fn deserialize(mut input: &[u8]) -> Result { + let sk = deserialize_scalar::(&mut input)?; + let pk = deserialize_elem::(&mut input)?; Ok(Self { sk, pk }) } @@ -212,11 +202,9 @@ where /// /// # Errors /// [`Error::Deserialization`] if failed to deserialize `input`. - pub fn deserialize(input: &[u8]) -> Result { - let mut input = input.iter().copied(); - - let sk = deserialize_scalar::(&mut input)?; - let pk = deserialize_elem::(&mut input)?; + pub fn deserialize(mut input: &[u8]) -> Result { + let sk = deserialize_scalar::(&mut input)?; + let pk = deserialize_elem::(&mut input)?; Ok(Self { sk, pk }) } @@ -247,11 +235,9 @@ where /// /// # Errors /// [`Error::Deserialization`] if failed to deserialize `input`. - pub fn deserialize(input: &[u8]) -> Result { - let mut input = input.iter().copied(); - - let c_scalar = deserialize_scalar::(&mut input)?; - let s_scalar = deserialize_scalar::(&mut input)?; + pub fn deserialize(mut input: &[u8]) -> Result { + let c_scalar = deserialize_scalar::(&mut input)?; + let s_scalar = deserialize_scalar::(&mut input)?; Ok(Proof { c_scalar, s_scalar }) } @@ -274,10 +260,8 @@ where /// /// # Errors /// [`Error::Deserialization`] if failed to deserialize `input`. - pub fn deserialize(input: &[u8]) -> Result { - let mut input = input.iter().copied(); - - let value = deserialize_elem::(&mut input)?; + pub fn deserialize(mut input: &[u8]) -> Result { + let value = deserialize_elem::(&mut input)?; Ok(Self(value)) } @@ -300,27 +284,41 @@ where /// /// # Errors /// [`Error::Deserialization`] if failed to deserialize `input`. - pub fn deserialize(input: &[u8]) -> Result { - let mut input = input.iter().copied(); - - let value = deserialize_elem::(&mut input)?; + pub fn deserialize(mut input: &[u8]) -> Result { + let value = deserialize_elem::(&mut input)?; Ok(Self(value)) } } -fn deserialize_elem>(input: &mut I) -> Result { - let input = input.by_ref().take(G::ElemLen::USIZE); - GenericArray::<_, G::ElemLen>::from_exact_iter(input) - .ok_or(Error::Deserialization) - .and_then(|bytes| G::deserialize_elem(&bytes)) +fn deserialize_elem(input: &mut &[u8]) -> Result { + let input = input + .take_ext(G::ElemLen::USIZE) + .ok_or(Error::Deserialization)?; + G::deserialize_elem(input) } -fn deserialize_scalar>(input: &mut I) -> Result { - let input = input.by_ref().take(G::ScalarLen::USIZE); - GenericArray::<_, G::ScalarLen>::from_exact_iter(input) - .ok_or(Error::Deserialization) - .and_then(|bytes| G::deserialize_scalar(&bytes)) +fn deserialize_scalar(input: &mut &[u8]) -> Result { + let input = input + .take_ext(G::ScalarLen::USIZE) + .ok_or(Error::Deserialization)?; + G::deserialize_scalar(input) +} + +trait SliceExt { + fn take_ext(self: &mut &Self, take: usize) -> Option<&Self>; +} + +impl SliceExt for [T] { + fn take_ext(self: &mut &Self, take: usize) -> Option<&Self> { + if take > self.len() { + return None; + } + + let (front, back) = self.split_at(take); + *self = back; + Some(front) + } } #[cfg(feature = "serde")] @@ -372,3 +370,73 @@ pub(crate) mod serde { } } } + +#[cfg(test)] +mod test { + use proptest::collection::vec; + use proptest::prelude::*; + + use crate::{ + BlindedElement, EvaluationElement, OprfClient, OprfServer, PoprfClient, PoprfServer, Proof, + VoprfClient, VoprfServer, + }; + + macro_rules! test_deserialize { + ($item:ident, $bytes:ident) => { + #[cfg(feature = "ristretto255")] + { + let _ = $item::::deserialize(&$bytes[..]); + } + + let _ = $item::::deserialize(&$bytes[..]); + }; + } + + proptest! { + #[test] + fn test_nocrash_oprf_client(bytes in vec(any::(), 0..200)) { + test_deserialize!(OprfClient, bytes); + } + + #[test] + fn test_nocrash_voprf_client(bytes in vec(any::(), 0..200)) { + test_deserialize!(VoprfClient, bytes); + } + + #[test] + fn test_nocrash_poprf_client(bytes in vec(any::(), 0..200)) { + test_deserialize!(PoprfClient, bytes); + } + + #[test] + fn test_nocrash_oprf_server(bytes in vec(any::(), 0..200)) { + test_deserialize!(OprfServer, bytes); + } + + #[test] + fn test_nocrash_voprf_server(bytes in vec(any::(), 0..200)) { + test_deserialize!(VoprfServer, bytes); + } + + #[test] + fn test_nocrash_poprf_server(bytes in vec(any::(), 0..200)) { + test_deserialize!(PoprfServer, bytes); + } + + + #[test] + fn test_nocrash_blinded_element(bytes in vec(any::(), 0..200)) { + test_deserialize!(BlindedElement, bytes); + } + + #[test] + fn test_nocrash_evaluation_element(bytes in vec(any::(), 0..200)) { + test_deserialize!(EvaluationElement, bytes); + } + + #[test] + fn test_nocrash_proof(bytes in vec(any::(), 0..200)) { + test_deserialize!(Proof, bytes); + } + } +} diff --git a/src/tests/test_cfrg_vectors.rs b/src/tests/test_cfrg_vectors.rs index 9322e24..191ee69 100644 --- a/src/tests/test_cfrg_vectors.rs +++ b/src/tests/test_cfrg_vectors.rs @@ -5,7 +5,7 @@ // License, Version 2.0 found in the LICENSE-APACHE file in the root directory // of this source tree. -use alloc::string::{String, ToString}; +use alloc::string::String; use alloc::vec; use alloc::vec::Vec; use core::ops::Add; @@ -60,19 +60,15 @@ fn populate_test_vectors(values: &JsonValue) -> VOPRFTestVectorParameters { fn decode(values: &JsonValue, key: &str) -> Vec { values[key] .as_str() - .and_then(|s| hex::decode(&s.to_string()).ok()) + .and_then(|s| hex::decode(&s).ok()) .unwrap_or_default() } fn decode_vec(values: &JsonValue, key: &str) -> Vec> { let s = values[key].as_str().unwrap(); let res = match s.contains(',') { - true => Some( - s.split(',') - .map(|x| hex::decode(&x.to_string()).unwrap()) - .collect(), - ), - false => Some(vec![hex::decode(&s.to_string()).unwrap()]), + true => Some(s.split(',').map(|x| hex::decode(&x).unwrap()).collect()), + false => Some(vec![hex::decode(&s).unwrap()]), }; res.unwrap() } diff --git a/src/voprf.rs b/src/voprf.rs index 333ee74..5538d55 100644 --- a/src/voprf.rs +++ b/src/voprf.rs @@ -18,12 +18,12 @@ use generic_array::typenum::{IsLess, IsLessOrEqual, Unsigned, U256}; use generic_array::GenericArray; use rand_core::{CryptoRng, RngCore}; -#[cfg(feature = "serde")] -use crate::serialization::serde::{Element, Scalar}; -use crate::util::{ +use crate::common::{ derive_keypair, deterministic_blind_unchecked, generate_proof, i2osp_2, verify_proof, BlindedElement, EvaluationElement, Mode, PreparedEvaluationElement, Proof, STR_FINALIZE, }; +#[cfg(feature = "serde")] +use crate::serialization::serde::{Element, Scalar}; use crate::{CipherSuite, Error, Group, Result}; //////////////////////////// @@ -576,8 +576,7 @@ mod tests { use rand::rngs::OsRng; use super::*; - use crate::group::STR_HASH_TO_GROUP; - use crate::util::create_context_string; + use crate::common::{create_context_string, STR_HASH_TO_GROUP}; use crate::Group; fn prf( @@ -590,7 +589,7 @@ mod tests { IsLess + IsLessOrEqual<::BlockSize>, { let dst = GenericArray::from(STR_HASH_TO_GROUP).concat(create_context_string::(mode)); - let point = CS::Group::hash_to_curve::(&[input], &dst).unwrap(); + let point = CS::Group::hash_to_curve::(&[input], &dst).unwrap(); let res = point * &key; @@ -705,7 +704,7 @@ mod tests { let dst = GenericArray::from(STR_HASH_TO_GROUP) .concat(create_context_string::(Mode::Oprf)); // Choose a group element that is unlikely to be the right public key - CS::Group::hash_to_curve::(&[b"msg"], &dst).unwrap() + CS::Group::hash_to_curve::(&[b"msg"], &dst).unwrap() }; let client_finalize_result = VoprfClient::batch_finalize(&inputs, &client_states, &messages, &proof, wrong_pk); @@ -726,7 +725,7 @@ mod tests { let dst = GenericArray::from(STR_HASH_TO_GROUP) .concat(create_context_string::(Mode::Oprf)); // Choose a group element that is unlikely to be the right public key - CS::Group::hash_to_curve::(&[b"msg"], &dst).unwrap() + CS::Group::hash_to_curve::(&[b"msg"], &dst).unwrap() }; let client_finalize_result = client_blind_result.state.finalize( input,