diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 83f9698..15683e6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -91,6 +91,14 @@ jobs: command: clippy args: --all-targets -- -D warnings + - name: Run cargo doc + uses: actions-rs/cargo@v1 + env: + RUSTDOCFLAGS: -D warnings + with: + command: doc + args: --no-deps --document-private-items + format: name: cargo fmt diff --git a/src/errors.rs b/src/errors.rs index 13f15bc..c22c2a2 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -6,14 +6,13 @@ // of this source tree. //! A list of error types which are produced during an execution of the protocol -use core::fmt::Debug; #[cfg(feature = "std")] use std::error::Error; use displaydoc::Display; /// Represents an error in the manipulation of internal cryptographic data -#[derive(Clone, Display, Eq, Hash, PartialEq)] +#[derive(Clone, Debug, Display, Eq, Hash, PartialEq)] pub enum InternalError { /// Could not parse byte sequence for key InvalidByteSequence, @@ -38,24 +37,6 @@ pub enum InternalError { ZeroScalarError, } -impl Debug for InternalError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::InvalidByteSequence => f.debug_tuple("InvalidByteSequence").finish(), - Self::PointError => f.debug_tuple("PointError").finish(), - Self::HashToCurveError => f.debug_tuple("HashToCurveError").finish(), - Self::SerializationError => f.debug_tuple("SerializationError").finish(), - Self::IncompatibleModeError => f.debug_tuple("IncompatibleModeError").finish(), - Self::MismatchedLengthsForCompositeInputs => f - .debug_tuple("MismatchedLengthsForCompositeInputs") - .finish(), - Self::ProofVerificationError => f.debug_tuple("ProofVerificationError").finish(), - Self::SizeError => f.debug_tuple("SizeError").finish(), - Self::ZeroScalarError => f.debug_tuple("ZeroScalarError").finish(), - } - } -} - #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl Error for InternalError {} diff --git a/src/group/expand.rs b/src/group/expand.rs index fe1f2ad..33562a0 100644 --- a/src/group/expand.rs +++ b/src/group/expand.rs @@ -9,7 +9,7 @@ use crate::errors::InternalError; use crate::serialization::i2osp; use alloc::vec::Vec; use digest::{BlockInput, Digest}; -use generic_array::typenum::Unsigned; +use generic_array::typenum::{Unsigned, U1, U2}; // Computes ceil(x / y) fn div_ceil(x: usize, y: usize) -> usize { @@ -32,23 +32,27 @@ pub fn expand_message_xmd( dst: &[u8], len_in_bytes: usize, ) -> Result, InternalError> { - let b_in_bytes = ::OutputSize::USIZE; - let r_in_bytes = ::BlockSize::USIZE; - - let ell = div_ceil(len_in_bytes, b_in_bytes); + let ell = div_ceil(len_in_bytes, ::OutputSize::USIZE); if ell > 255 { return Err(InternalError::HashToCurveError); } - let dst_prime = [dst, &i2osp(dst.len(), 1)?].concat(); - let z_pad = i2osp(0, r_in_bytes)?; - let l_i_b_str = i2osp(len_in_bytes, 2)?; - let msg_prime = [&z_pad, msg, &l_i_b_str, &i2osp(0, 1)?, &dst_prime].concat(); + let dst_prime = [dst, &i2osp::(dst.len())?].concat(); + let z_pad = i2osp::<::BlockSize>(0)?; + let l_i_b_str = i2osp::(len_in_bytes)?; + let msg_prime = [ + &z_pad, + msg, + &l_i_b_str, + i2osp::(0)?.as_slice(), + &dst_prime, + ] + .concat(); let mut b: Vec> = alloc::vec![H::digest(&msg_prime).to_vec()]; // b[0] let mut h = H::new(); h.update(&b[0]); - h.update(&i2osp(1, 1)?); + h.update(&i2osp::(1)?); h.update(&dst_prime); b.push(h.finalize_reset().to_vec()); // b[1] @@ -57,7 +61,7 @@ pub fn expand_message_xmd( for i in 2..(ell + 1) { h.update(xor(&b[0], &b[i - 1])?); - h.update(&i2osp(i, 1)?); + h.update(&i2osp::(i)?); h.update(&dst_prime); b.push(h.finalize_reset().to_vec()); // b[i] uniform_bytes.extend_from_slice(&b[i]); diff --git a/src/group/mod.rs b/src/group/mod.rs index 5390760..ddfe366 100644 --- a/src/group/mod.rs +++ b/src/group/mod.rs @@ -59,10 +59,10 @@ pub trait Group: /// Return a scalar from its fixed-length bytes representation. If the scalar /// is zero, then return an error. - fn from_scalar_slice( - scalar_bits: &GenericArray, + fn from_scalar_slice<'a>( + scalar_bits: impl Into<&'a GenericArray>, ) -> Result { - let scalar = Self::from_scalar_slice_unchecked(scalar_bits)?; + let scalar = Self::from_scalar_slice_unchecked(scalar_bits.into())?; if Self::ct_equal_scalar(&scalar, &Self::scalar_zero()) { return Err(InternalError::ZeroScalarError); } @@ -88,10 +88,10 @@ pub trait Group: /// Return an element from its fixed-length bytes representation. If the element /// is the identity element, return an error. - fn from_element_slice( - element_bits: &GenericArray, + fn from_element_slice<'a>( + element_bits: impl Into<&'a GenericArray>, ) -> Result { - let elem = Self::from_element_slice_unchecked(element_bits)?; + let elem = Self::from_element_slice_unchecked(element_bits.into())?; if Self::ct_equal(&elem, &::identity()) { // found the identity element diff --git a/src/impls.rs b/src/impls.rs index 1580798..a04311e 100644 --- a/src/impls.rs +++ b/src/impls.rs @@ -5,139 +5,122 @@ // License, Version 2.0 found in the LICENSE-APACHE file in the root directory // of this source tree. -macro_rules! impl_debug_eq_hash_for { - (struct $name:ident$(<$($gen:ident$(: $bound1:tt$( + $bound2:tt)*)?),+$(,)?>)?, [$field1:ident$(, $field2:ident)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => { - impl$(<$($gen$(: $bound1$( + $bound2)*)?),+>)? core::fmt::Debug for $name$(<$($gen),+>)? - $(where $($type: core::fmt::Debug,)+)? +/// Implement multiple similar traits at the same time. Additionally used to +/// find `#[bind]` markers to build `while` constraint. +macro_rules! impl_with_bounds { + ( + $name:ident$(<$($gen:ident$(: $bound1:tt $(+ $bound2:tt)*)?),+>)? + // only collect types marked with `#bind` + // `|` prevents error about a possibly empty token + // `@` prevents ambiguity between `$_2` and `$trait1` + // `#` prevents ambiguity between marker traits and `$_2` + $(|$(@#bind: $type:ty|,)? $(@#pd: $_1:ty|,)? $(@$_2:ty|,)?)+ + $trait1:path => { $($fn1:item)? }, + $($trait2:path => { $($fn2:item)? },)* + ) => { + impl$(<$($gen$(: $bound1 $(+ $bound2)*)?),+>)? $trait1 for $name$(<$($gen),+>)? + where + $($($type: $trait1,)?)+ { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("$name") - .field("$field1", &self.$field1) - $(.field("$field2", &self.$field2))* - .finish() - } + $($fn1)? } - impl$(<$($gen$(: $bound1$( + $bound2)*)?),+>)? Eq for $name$(<$($gen),+>)? - $(where $($type: Eq,)+)? - {} - - impl$(<$($gen$(: $bound1$( + $bound2)*)?),+>)? PartialEq for $name$(<$($gen),+>)? - $(where $($type: PartialEq,)+)? - { - fn eq(&self, other: &Self) -> bool { - PartialEq::eq(&self.$field1, &other.$field1) - $(&& PartialEq::eq(&self.$field2, &other.$field2))* - } - } - - impl$(<$($gen$(: $bound1$( + $bound2)*)?),+>)? core::hash::Hash for $name$(<$($gen),+>)? - $(where $($type: core::hash::Hash,)+)? - { - fn hash<_H: core::hash::Hasher>(&self, state: &mut _H) { - core::hash::Hash::hash(&self.$field1, state); - $(core::hash::Hash::hash(&self.$field2, state);)* - } - } - }; - (tuple $name:ident$(<$($gen:ident$(: $bound1:tt$( + $bound2:tt)*)?),+$(,)?>)?, [$field1:tt$(, $field2:tt)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => { - impl$(<$($gen$(: $bound1$(+ $bound2)*)?),+>)? core::fmt::Debug for $name$(<$($gen),+>)? - $(where $($type: core::fmt::Debug,)+)? - { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_tuple("$name") - .field(&self.$field1) - $(.field(&self.$field2))* - .finish() - } - } - - impl$(<$($gen$(: $bound1$(+ $bound2)*)?),+>)? Eq for $name$(<$($gen),+>)? - $(where $($type: Eq,)+)? - {} - - impl$(<$($gen$(: $bound1$(+ $bound2)*)?),+>)? PartialEq for $name$(<$($gen),+>)? - $(where $($type: PartialEq,)+)? - { - fn eq(&self, other: &Self) -> bool { - PartialEq::eq(&self.$field1, &other.$field1) - $(&& PartialEq::eq(&self.$field2, &other.$field2))* - } - } - - impl$(<$($gen$(: $bound1$(+ $bound2)*)?),+>)? core::hash::Hash for $name$(<$($gen),+>)? - $(where $($type: core::hash::Hash,)+)? - { - fn hash(&self, state: &mut H) { - core::hash::Hash::hash(&self.$field1, state); - $(core::hash::Hash::hash(&self.$field2, state);)* - } - } + impl_with_bounds!( + $name$(<$($gen$(: $bound1 $(+ $bound2)*)?),+>)? + $(|$(@#bind: $type|,)? $(@#pd: $_1|,)? $(@$_2|,)?)+ + $($trait2 => { $($fn2)? },)* + ); }; + // signature triggered when all traits are exhausted + ( + $name:ident$(<$($gen:ident$(: $bound1:tt$( + $bound2:tt)*)?),+>)? + $(|$(@#bind: $type:ty|,)? $(@#pd: $_1:ty|,)? $(@$_2:ty|,)?)+ + ) => { }; } -macro_rules! impl_clone_for { - (struct $name:ident$(<$($gen:ident$(: $bound1:tt$( + $bound2:tt)*)?),+$(,)?>)?, [$field1:ident$(, $field2:ident)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => { - impl$(<$($gen$(: $bound1$(+ $bound2)*)?),+>)? Clone for $name$(<$($gen),+>)? - $(where $($type: Clone,)+)? - { - fn clone(&self) -> Self { - Self { - $field1: self.$field1.clone(), - $($field2: self.$field2.clone(),)* - } - } - } +/// Skips attempt to call [`zeroize()`](zeroize::Zeroize::zeroize) on +/// [`PhantomData`](core::marker::PhantomData). +macro_rules! impl_internal_zeroize { + ($self_:ident, #pd $field:ident) => {}; + ($self_:ident, #bind $field:ident) => { + $self_.$field.zeroize(); }; - (tuple $name:ident$(<$($gen:ident$(: $bound1:tt$( + $bound2:tt)*)?),+$(,)?>)?, [$field1:tt$(, $field2:tt)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => { - impl$(<$($gen$(: $bound1$(+ $bound2)*)?),+>)? Clone for $name$(<$($gen),+>)? - $(where $($type: Clone,)+)? - { - fn clone(&self) -> Self { - Self( - self.$field1.clone(), - $(self.$field2.clone(),)* - ) - } - } - }; -} - -macro_rules! impl_zeroize_field_skip_pd { - ($self_:ident, $field:ident, PH) => {}; ($self_:ident, $field:ident) => { $self_.$field.zeroize(); }; } -macro_rules! impl_zeroize_on_drop_for { - (struct $name:ident$(<$($gen:ident$(: $bound1:tt$( + $bound2:tt)*)?),+$(,)?>)?, [$(#[$pd1:ident] )?$field1:ident$(, $(#[$pd2:ident] )?$field2:ident)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => { - impl$(<$($gen$(: $bound1$(+ $bound2)*)?),+>)? zeroize::Zeroize for $name$(<$($gen),+>)? +macro_rules! impl_traits_for { + ( + // include documentation, Rust can't connect documentation from outside + // a macro to a `struct` generated by a macro + $(#[doc = $doc:literal])* + $vis:vis struct $name:ident$(<$($gen:ident$(: $bound1:tt $(+ $bound2:tt)*)?),+$(,)?>)? { + $(#[$attr1:ident])? $vis1:vis $field1:ident: $type1:ty$(, + $(#[$attr2:ident])? $vis2:vis $field2:ident: $type2:ty)*$(,)? + } + ) => { + // build `struct` itself + $(#[doc = $doc])* + $vis struct $name$(<$($gen$(: $bound1 $(+$bound2)*)?),+>)? { + $vis1 $field1: $type1, + $($vis2 $field2: $type2),* + } + + // implement traits that require specific `where` constraints with the + // help of `#[bind]` + impl_with_bounds!( + $name$(<$($gen$(: $bound1 $(+ $bound2)*)?),+>)? + |@$(#$attr1:)? $type1|, $(|@$(#$attr2:)? $type2|,)* + core::fmt::Debug => { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("$name") + .field("$field1", &self.$field1) + $(.field("$field2", &self.$field2))* + .finish() + } + }, + Eq => { }, + PartialEq => { + fn eq(&self, other: &Self) -> bool { + PartialEq::eq(&self.$field1, &other.$field1) + $(&& PartialEq::eq(&self.$field2, &other.$field2))* + } + }, + core::hash::Hash => { + fn hash<_H: core::hash::Hasher>(&self, state: &mut _H) { + core::hash::Hash::hash(&self.$field1, state); + $(core::hash::Hash::hash(&self.$field2, state);)* + } + }, + Clone => { + fn clone(&self) -> Self { + Self { + $field1: self.$field1.clone(), + $($field2: self.$field2.clone(),)* + } + } + }, + ); + + impl$(<$($gen$(: $bound1 $(+ $bound2)*)?),+>)? zeroize::Zeroize for $name$(<$($gen),+>)? { fn zeroize(&mut self) { - impl_zeroize_field_skip_pd!(self, $field1$(, $pd1)?); - $(impl_zeroize_field_skip_pd!(self, $field2$(, $pd2)?);)* + impl_internal_zeroize!(self, $(#$attr1)? $field1); + $(impl_internal_zeroize!(self, $(#$attr2)? $field2);)* } } - impl$(<$($gen$(: $bound1$(+ $bound2)*)?),+>)? Drop for $name$(<$($gen),+>)? + impl$(<$($gen$(: $bound1 $(+ $bound2)*)?),+>)? Drop for $name$(<$($gen),+>)? { fn drop(&mut self) { - #[allow(unused_imports)] - use zeroize::Zeroize; - impl_zeroize_field_skip_pd!(self, $field1$(, $pd1)?); - $(impl_zeroize_field_skip_pd!(self, $field2$(, $pd2)?);)* + zeroize::Zeroize::zeroize(self); } } - }; -} -/// Inner macro used for deriving `serde`'s `Serialize` and `Deserialize` traits. -macro_rules! impl_serialize_and_deserialize_for { - ($name:ident$(<$($gen:ident$(: $bound1:tt$( + $bound2:tt)*)?),+$(,)?>)?) => { #[cfg(feature = "serialize")] #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] - impl$(<$($gen$(: $bound1$(+ $bound2)*)?),+>)? serde::Serialize for $name$(<$($gen),+>)? { + impl$(<$($gen$(: $bound1 $(+ $bound2)*)?),+>)? serde::Serialize for $name$(<$($gen),+>)? { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -152,62 +135,21 @@ macro_rules! impl_serialize_and_deserialize_for { #[cfg(feature = "serialize")] #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] - impl<'de$(, $($gen$(: $bound1$(+ $bound2)*)?),+)?> serde::Deserialize<'de> for $name$(<$($gen),+>)? { + impl<'de, $($($gen$(: $bound1 $(+ $bound2)*)?),+)?> serde::Deserialize<'de> for $name$(<$($gen),+>)? { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { + use serde::de::Error; + if deserializer.is_human_readable() { let s = <&str>::deserialize(deserializer)?; - $name$(::<$($gen),+>)?::deserialize(&base64::decode(s).map_err(serde::de::Error::custom)?) - .map_err(serde::de::Error::custom) + Self::deserialize(&base64::decode(s).map_err(Error::custom)?) } else { - struct ByteVisitor$(<$($gen$(: $bound1$(+ $bound2)*)?),+> ( - #[allow(unused_parens)] - core::marker::PhantomData<($($gen),+)>, - ))?; - impl<'de$(, $($gen$(: $bound1$(+ $bound2)*)?),+)?> serde::de::Visitor<'de> for ByteVisitor$(<$($gen),+>)? { - type Value = $name$(<$($gen),+>)?; - fn expecting( - &self, - formatter: &mut core::fmt::Formatter, - ) -> core::fmt::Result { - formatter.write_str(core::concat!( - "the byte representation of a ", - core::stringify!($name) - )) - } - - fn visit_bytes(self, value: &[u8]) -> Result - where - E: serde::de::Error, - { - $name$(::<$($gen),+>)?::deserialize(value).map_err(|_| { - serde::de::Error::invalid_value( - serde::de::Unexpected::Bytes(value), - &core::concat!( - "invalid byte sequence for ", - core::stringify!($name) - ), - ) - }) - } - } - deserializer.deserialize_bytes(ByteVisitor$(::<$($gen),+> ( - core::marker::PhantomData, - ))?) + Self::deserialize(<&[u8]>::deserialize(deserializer)?) } + .map_err(Error::custom) } } }; } - -// Convenience macro for implementing all of the above traits -macro_rules! impl_traits_for { - (struct $name:ident$(<$($gen:ident$(: $bound1:tt$( + $bound2:tt)*)?),+$(,)?>)?, [$(#[$pd1:ident] )?$field1:ident$(, $(#[$pd2:ident] )?$field2:ident)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => { - impl_debug_eq_hash_for!(struct $name$(<$($gen$(: $bound1$(+ $bound2)*)?),+>)?, [$field1$(, $field2)*], $([$($type),+])?); - impl_clone_for!(struct $name$(<$($gen$(: $bound1$(+ $bound2)*)?),+>)?, [$field1$(, $field2)*], $([$($type),+])?); - impl_zeroize_on_drop_for!(struct $name$(<$($gen$(: $bound1$(+ $bound2)*)?),+>)?, [$(#[$pd1] )?$field1$(, $(#[$pd2] )?$field2)*], $([$($type),+])?); - impl_serialize_and_deserialize_for!($name$(<$($gen$(: $bound1$(+ $bound2)*)?),+>)?); - } -} diff --git a/src/lib.rs b/src/lib.rs index 2772018..077c375 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -117,9 +117,7 @@ //! //! In the final step, the client takes as input the message from //! [NonVerifiableServer::evaluate] (an [EvaluationElement]), and runs -//! [NonVerifiableClient::finalize] to produce a -//! [NonVerifiableClientFinalizeResult], which consists of an -//! output for the protocol. +//! [NonVerifiableClient::finalize] to produce an output for the protocol. //! //! ``` //! # type Group = curve25519_dalek::ristretto::RistrettoPoint; @@ -243,9 +241,7 @@ //! In the final step, the client takes as input the message from //! [VerifiableServer::evaluate] (an [EvaluationElement]), //! the proof, and the server's public key, and runs -//! [VerifiableClient::finalize] to produce a -//! [VerifiableClientFinalizeResult], which consists of an -//! output for the protocol. +//! [VerifiableClient::finalize] to produce an output for the protocol. //! //! ``` //! # type Group = curve25519_dalek::ristretto::RistrettoPoint; @@ -435,10 +431,9 @@ extern crate alloc; #[macro_use] mod impls; -#[macro_use] -mod serialization; pub mod errors; pub mod group; +mod serialization; mod voprf; #[cfg(test)] diff --git a/src/serialization.rs b/src/serialization.rs index dd4b859..cd98a1d 100644 --- a/src/serialization.rs +++ b/src/serialization.rs @@ -19,7 +19,7 @@ use crate::{ use alloc::vec::Vec; use core::marker::PhantomData; use digest::{BlockInput, Digest}; -use generic_array::{typenum::Unsigned, GenericArray}; +use generic_array::{typenum::Unsigned, ArrayLength, GenericArray}; ////////////////////////////////////////////////////////// // Serialization and Deserialization for High-Level API // @@ -29,7 +29,7 @@ use generic_array::{typenum::Unsigned, GenericArray}; impl NonVerifiableClient { /// Serialization into bytes pub fn serialize(&self) -> Vec { - [G::scalar_as_bytes(self.blind).to_vec(), self.data.clone()].concat() + [G::scalar_as_bytes(self.blind).as_slice(), &self.data].concat() } /// Deserialization from bytes @@ -39,7 +39,7 @@ impl NonVerifiableClient { return Err(InternalError::SizeError); } - let blind = G::from_scalar_slice(GenericArray::from_slice(&input[..scalar_len]))?; + let blind = G::from_scalar_slice(&input[..scalar_len])?; let data = input[scalar_len..].to_vec(); Ok(Self { @@ -54,9 +54,9 @@ impl VerifiableClient { /// Serialization into bytes pub fn serialize(&self) -> Vec { [ - G::scalar_as_bytes(self.blind).to_vec(), - self.blinded_element.to_arr().to_vec(), - self.data.clone(), + G::scalar_as_bytes(self.blind).as_slice(), + &self.blinded_element.to_arr(), + &self.data, ] .concat() } @@ -69,10 +69,8 @@ impl VerifiableClient { return Err(InternalError::SizeError); } - let blind = G::from_scalar_slice(GenericArray::from_slice(&input[..scalar_len]))?; - let blinded_element = G::from_element_slice(GenericArray::from_slice( - &input[scalar_len..scalar_len + elem_len], - ))?; + let blind = G::from_scalar_slice(&input[..scalar_len])?; + let blinded_element = G::from_element_slice(&input[scalar_len..scalar_len + elem_len])?; let data = input[scalar_len + elem_len..].to_vec(); Ok(Self { @@ -97,7 +95,7 @@ impl NonVerifiableServer { return Err(InternalError::SizeError); } - let sk = G::from_scalar_slice(GenericArray::from_slice(input))?; + let sk = G::from_scalar_slice(input)?; Ok(Self { sk, @@ -109,11 +107,7 @@ impl NonVerifiableServer { impl VerifiableServer { /// Serialization into bytes pub fn serialize(&self) -> Vec { - [ - G::scalar_as_bytes(self.sk).to_vec(), - self.pk.to_arr().to_vec(), - ] - .concat() + [G::scalar_as_bytes(self.sk).as_slice(), &self.pk.to_arr()].concat() } /// Deserialization from bytes @@ -124,8 +118,8 @@ impl VerifiableServer { return Err(InternalError::SizeError); } - let sk = G::from_scalar_slice(GenericArray::from_slice(&input[..scalar_len]))?; - let pk = G::from_element_slice(GenericArray::from_slice(&input[scalar_len..]))?; + let sk = G::from_scalar_slice(&input[..scalar_len])?; + let pk = G::from_element_slice(&input[scalar_len..])?; Ok(Self { sk, @@ -152,8 +146,8 @@ impl Proof { return Err(InternalError::SizeError); } Ok(Proof { - c_scalar: G::from_scalar_slice(GenericArray::from_slice(&input[..scalar_len]))?, - s_scalar: G::from_scalar_slice(GenericArray::from_slice(&input[scalar_len..]))?, + c_scalar: G::from_scalar_slice(&input[..scalar_len])?, + s_scalar: G::from_scalar_slice(&input[scalar_len..])?, hash: PhantomData, }) } @@ -168,7 +162,7 @@ impl BlindedElement { /// Deserialization from bytes pub fn deserialize(input: &[u8]) -> Result { Ok(Self { - value: G::from_element_slice(GenericArray::from_slice(input))?, + value: G::from_element_slice(input)?, hash: PhantomData, }) } @@ -183,7 +177,7 @@ impl EvaluationElement { /// Deserialization from bytes pub fn deserialize(input: &[u8]) -> Result { Ok(Self { - value: G::from_element_slice(GenericArray::from_slice(input))?, + value: G::from_element_slice(input)?, hash: PhantomData, }) } @@ -195,46 +189,48 @@ impl EvaluationElement { ////////////////////// // Corresponds to the I2OSP() function from RFC8017 -pub(crate) fn i2osp(input: usize, length: usize) -> Result, InternalError> { - let sizeof_usize = core::mem::size_of::(); +pub(crate) fn i2osp>( + input: usize, +) -> Result, InternalError> { + const SIZEOF_USIZE: usize = core::mem::size_of::(); // Check if input >= 256^length - if (sizeof_usize as u32 - input.leading_zeros() / 8) > length as u32 { + if (SIZEOF_USIZE as u32 - input.leading_zeros() / 8) > L::U32 { return Err(InternalError::SerializationError); } - if length <= sizeof_usize { - return Ok((&input.to_be_bytes()[sizeof_usize - length..]).to_vec()); + if L::USIZE <= SIZEOF_USIZE { + return Ok(GenericArray::clone_from_slice( + &input.to_be_bytes()[SIZEOF_USIZE - L::USIZE..], + )); } - let mut output = alloc::vec![0u8; length]; - output.splice( - length - sizeof_usize..length, - input.to_be_bytes().iter().cloned(), - ); + let mut output = GenericArray::default(); + output[L::USIZE - SIZEOF_USIZE..L::USIZE].copy_from_slice(&input.to_be_bytes()); Ok(output) } // Computes I2OSP(len(input), max_bytes) || input -pub(crate) fn serialize(input: &[u8], max_bytes: usize) -> Result, InternalError> { - Ok([&i2osp(input.len(), max_bytes)?, input].concat()) +pub(crate) fn serialize>(input: &[u8]) -> Result, InternalError> { + Ok([&i2osp::(input.len())?, input].concat()) } #[cfg(test)] mod unit_tests { use super::*; + use generic_array::typenum::{U1, U2}; // Test the error condition for I2OSP #[test] fn test_i2osp_err_check() { - assert!(i2osp(0, 1).is_ok()); + assert!(i2osp::(0).is_ok()); - assert!(i2osp(255, 1).is_ok()); - assert!(i2osp(256, 1).is_err()); - assert!(i2osp(257, 1).is_err()); + assert!(i2osp::(255).is_ok()); + assert!(i2osp::(256).is_err()); + assert!(i2osp::(257).is_err()); - assert!(i2osp(256 * 256 - 1, 2).is_ok()); - assert!(i2osp(256 * 256, 2).is_err()); - assert!(i2osp(256 * 256 + 1, 2).is_err()); + assert!(i2osp::(256 * 256 - 1).is_ok()); + assert!(i2osp::(256 * 256).is_err()); + assert!(i2osp::(256 * 256 + 1).is_err()); } } diff --git a/src/voprf.rs b/src/voprf.rs index 42bd562..a53ca88 100644 --- a/src/voprf.rs +++ b/src/voprf.rs @@ -16,7 +16,10 @@ use alloc::vec; use alloc::vec::Vec; use core::marker::PhantomData; use digest::{BlockInput, Digest}; -use generic_array::{typenum::Unsigned, GenericArray}; +use generic_array::{ + typenum::{Unsigned, U1, U2}, + GenericArray, +}; use rand::{CryptoRng, RngCore}; /////////////// @@ -46,99 +49,94 @@ enum Mode { // ====================== // //////////////////////////// -/// A client which engages with a [NonVerifiableServer] -/// in base mode, meaning that the OPRF outputs are not -/// verifiable. -pub struct NonVerifiableClient { - pub(crate) blind: ::Scalar, - pub(crate) data: Vec, - pub(crate) hash: PhantomData, +impl_traits_for! { + /// A client which engages with a [NonVerifiableServer] + /// in base mode, meaning that the OPRF outputs are not + /// verifiable. + pub struct NonVerifiableClient { + #[bind] + pub(crate) blind: ::Scalar, + pub(crate) data: Vec, + #[pd] + pub(crate) hash: PhantomData, + } } -impl_traits_for!( - struct NonVerifiableClient, - [blind, data, #[PH] hash], - [::Scalar], -); -/// A client which engages with a [VerifiableServer] -/// in verifiable mode, meaning that the OPRF outputs -/// can be checked against a server public key. -pub struct VerifiableClient { - pub(crate) blind: ::Scalar, - pub(crate) blinded_element: G, - pub(crate) data: alloc::vec::Vec, - pub(crate) hash: PhantomData, +impl_traits_for! { + /// A client which engages with a [VerifiableServer] + /// in verifiable mode, meaning that the OPRF outputs + /// can be checked against a server public key. + pub struct VerifiableClient { + #[bind] + pub(crate) blind: ::Scalar, + #[bind] + pub(crate) blinded_element: G, + pub(crate) data: alloc::vec::Vec, + #[pd] + pub(crate) hash: PhantomData, + } } -impl_traits_for!( - struct VerifiableClient, - [blind, blinded_element, data, #[PH] hash], - [::Scalar, G], -); -/// A server which engages with a [NonVerifiableClient] -/// in base mode, meaning that the OPRF outputs are not -/// verifiable. -pub struct NonVerifiableServer { - pub(crate) sk: ::Scalar, - pub(crate) hash: PhantomData, +impl_traits_for! { + /// A server which engages with a [NonVerifiableClient] + /// in base mode, meaning that the OPRF outputs are not + /// verifiable. + pub struct NonVerifiableServer { + #[bind] + pub(crate) sk: ::Scalar, + #[pd] + pub(crate) hash: PhantomData, + } } -impl_traits_for!( - struct NonVerifiableServer, - [sk, #[PH] hash], - [::Scalar], -); -/// A server which engages with a [VerifiableClient] -/// in verifiable mode, meaning that the OPRF outputs -/// can be checked against a server public key. -pub struct VerifiableServer { - pub(crate) sk: ::Scalar, - pub(crate) pk: G, - pub(crate) hash: PhantomData, +impl_traits_for! { + /// A server which engages with a [VerifiableClient] + /// in verifiable mode, meaning that the OPRF outputs + /// can be checked against a server public key. + pub struct VerifiableServer { + #[bind] + pub(crate) sk: ::Scalar, + #[bind] + pub(crate) pk: G, + #[pd] + pub(crate) hash: PhantomData, + } } -impl_traits_for!( - struct VerifiableServer, - [sk, pk, #[PH] hash], - [::Scalar, G], -); -/// A proof produced by a [VerifiableServer] that -/// the OPRF output matches against a server public key. -pub struct Proof { - pub(crate) c_scalar: ::Scalar, - pub(crate) s_scalar: ::Scalar, - pub(crate) hash: PhantomData, +impl_traits_for! { + /// A proof produced by a [VerifiableServer] that + /// the OPRF output matches against a server public key. + pub struct Proof { + #[bind] + pub(crate) c_scalar: ::Scalar, + pub(crate) s_scalar: ::Scalar, + #[pd] + pub(crate) hash: PhantomData, + } } -impl_traits_for!( - struct Proof, - [c_scalar, s_scalar, #[PH] hash], - [::Scalar], -); -/// The first client message sent from a client (either verifiable or not) -/// to a server (either verifiable or not). -pub struct BlindedElement { - pub(crate) value: G, - pub(crate) hash: PhantomData, +impl_traits_for! { + /// The first client message sent from a client (either verifiable or not) + /// to a server (either verifiable or not). + pub struct BlindedElement { + #[bind] + pub(crate) value: G, + #[pd] + pub(crate) hash: PhantomData, + } } -impl_traits_for!( - struct BlindedElement, - [value, #[PH] hash], - [G], -); -/// The server's response to the [BlindedElement] message from -/// a client (either verifiable or not) -/// to a server (either verifiable or not). -pub struct EvaluationElement { - pub(crate) value: G, - pub(crate) hash: PhantomData, +impl_traits_for! { + /// The server's response to the [BlindedElement] message from + /// a client (either verifiable or not) + /// to a server (either verifiable or not). + pub struct EvaluationElement { + #[bind] + pub(crate) value: G, + #[pd] + pub(crate) hash: PhantomData, + } } -impl_traits_for!( - struct EvaluationElement, - [value, #[PH] hash], - [G], -); ///////////////////////// // API Implementations // @@ -197,6 +195,15 @@ impl NonVerifiableClient { pub fn get_blind(&self) -> ::Scalar { self.blind } + + #[cfg(test)] + /// Only used for testing zeroize + pub fn as_ptrs(&self) -> Vec> { + vec![ + self.data.clone(), + ::scalar_as_bytes(self.blind).to_vec(), + ] + } } impl VerifiableClient { @@ -294,6 +301,16 @@ impl VerifiableClient { pub fn get_blind(&self) -> ::Scalar { self.blind } + + #[cfg(test)] + /// Only used for testing zeroize + pub fn as_ptrs(&self) -> Vec> { + vec![ + self.data.clone(), + ::scalar_as_bytes(self.blind).to_vec(), + self.blinded_element.to_arr().to_vec(), + ] + } } impl NonVerifiableServer { @@ -343,7 +360,7 @@ impl NonVerifiableServer { let context = [ STR_CONTEXT, &get_context_string::(Mode::Base)?, - &serialize(&metadata.0, 2)?, + &serialize::(&metadata.0)?, ] .concat(); let dst = [STR_HASH_TO_SCALAR, &get_context_string::(Mode::Base)?].concat(); @@ -357,6 +374,12 @@ impl NonVerifiableServer { }, }) } + + #[cfg(test)] + /// Only used for testing zeroize + pub fn as_ptrs(&self) -> Vec> { + vec![::scalar_as_bytes(self.sk).to_vec()] + } } impl VerifiableServer { @@ -429,7 +452,7 @@ impl VerifiableServer { let context = [ STR_CONTEXT, &get_context_string::(Mode::Verifiable)?, - &serialize(&metadata.0, 2)?, + &serialize::(&metadata.0)?, ] .concat(); let dst = [ @@ -462,6 +485,15 @@ impl VerifiableServer { pub fn get_public_key(&self) -> G { self.pk } + + #[cfg(test)] + /// Only used for testing zeroize + pub fn as_ptrs(&self) -> Vec> { + vec![ + ::scalar_as_bytes(self.sk).to_vec(), + self.pk.to_arr().to_vec(), + ] + } } ///////////////////////// @@ -553,6 +585,35 @@ struct BatchItems { blinded_element: BlindedElement, } +/// Convenience test functions for [BlindedElement], [EvaluationElement], and [Proof] + +impl BlindedElement { + #[cfg(test)] + /// Only used for testing zeroize + pub fn as_ptrs(&self) -> Vec> { + vec![self.value.to_arr().to_vec()] + } +} + +impl EvaluationElement { + #[cfg(test)] + /// Only used for testing zeroize + pub fn as_ptrs(&self) -> Vec> { + vec![self.value.to_arr().to_vec()] + } +} + +impl Proof { + #[cfg(test)] + /// Only used for testing zeroize + pub fn as_ptrs(&self) -> Vec> { + vec![ + ::scalar_as_bytes(self.c_scalar).to_vec(), + ::scalar_as_bytes(self.s_scalar).to_vec(), + ] + } +} + // Inner function for blind. Returns the blind scalar and the blinded element fn blind( input: &[u8], @@ -576,7 +637,7 @@ fn verifiable_unblind( let context = [ STR_CONTEXT, &get_context_string::(Mode::Verifiable)?, - &serialize(info, 2)?, + &serialize::(info)?, ] .concat(); @@ -628,12 +689,12 @@ fn generate_proof( let challenge_dst = [STR_CHALLENGE, &get_context_string::(Mode::Verifiable)?].concat(); let h2_input = [ - serialize(&b.to_arr().to_vec(), 2)?, - serialize(&m.to_arr().to_vec(), 2)?, - serialize(&z.to_arr().to_vec(), 2)?, - serialize(&t2.to_arr().to_vec(), 2)?, - serialize(&t3.to_arr().to_vec(), 2)?, - serialize(&challenge_dst, 2)?, + serialize::(&b.to_arr().to_vec())?, + serialize::(&m.to_arr().to_vec())?, + serialize::(&z.to_arr().to_vec())?, + serialize::(&t2.to_arr().to_vec())?, + serialize::(&t3.to_arr().to_vec())?, + serialize::(&challenge_dst)?, ] .concat(); @@ -667,12 +728,12 @@ fn verify_proof( let challenge_dst = [STR_CHALLENGE, &get_context_string::(Mode::Verifiable)?].concat(); let h2_input = [ - serialize(&b.to_arr().to_vec(), 2)?, - serialize(&m.to_arr().to_vec(), 2)?, - serialize(&z.to_arr().to_vec(), 2)?, - serialize(&t2.to_arr().to_vec(), 2)?, - serialize(&t3.to_arr().to_vec(), 2)?, - serialize(&challenge_dst, 2)?, + serialize::(&b.to_arr().to_vec())?, + serialize::(&m.to_arr().to_vec())?, + serialize::(&z.to_arr().to_vec())?, + serialize::(&t2.to_arr().to_vec())?, + serialize::(&t3.to_arr().to_vec())?, + serialize::(&challenge_dst)?, ] .concat(); @@ -702,10 +763,10 @@ fn finalize_after_unblind( for (input, unblinded_element) in inputs_and_unblinded_elements { outputs.push(::digest( &[ - serialize(input, 2)?, - serialize(info, 2)?, - serialize(&unblinded_element.to_arr().to_vec(), 2)?, - serialize(&finalize_dst, 2)?, + serialize::(input)?, + serialize::(info)?, + serialize::(&unblinded_element.to_arr().to_vec())?, + serialize::(&finalize_dst)?, ] .concat(), )); @@ -728,8 +789,8 @@ fn compute_composites( let composite_dst = [STR_COMPOSITE, &get_context_string::(Mode::Verifiable)?].concat(); let h1_input = [ - serialize(&b.to_arr().to_vec(), 2)?, - serialize(&seed_dst, 2)?, + serialize::(&b.to_arr().to_vec())?, + serialize::(&seed_dst)?, ] .concat(); let seed = ::digest(&h1_input); @@ -739,11 +800,11 @@ fn compute_composites( for i in 0..c_slice.len() { let h2_input = [ - serialize(&seed, 2)?, - i2osp(i, 2)?, - serialize(&c_slice[i].value.to_arr().to_vec(), 2)?, - serialize(&d_slice[i].value.to_arr().to_vec(), 2)?, - serialize(&composite_dst, 2)?, + serialize::(&seed)?, + i2osp::(i)?.to_vec(), + serialize::(&c_slice[i].value.to_arr().to_vec())?, + serialize::(&d_slice[i].value.to_arr().to_vec())?, + serialize::(&composite_dst)?, ] .concat(); let dst = [ @@ -772,8 +833,8 @@ fn compute_composites( fn get_context_string(mode: Mode) -> Result, InternalError> { Ok([ STR_VOPRF, - &i2osp(mode as usize, 1)?, - &i2osp(G::SUITE_ID, 2)?, + &i2osp::(mode as usize)?, + &i2osp::(G::SUITE_ID)?, ] .concat()) } @@ -789,6 +850,7 @@ mod tests { use crate::group::Group; use generic_array::GenericArray; use rand::rngs::OsRng; + use zeroize::Zeroize; fn prf( input: &[u8], @@ -802,7 +864,7 @@ mod tests { let context = [ STR_CONTEXT, &get_context_string::(mode).unwrap(), - &serialize(info, 2).unwrap(), + &serialize::(info).unwrap(), ] .concat(); let dst = [STR_HASH_TO_SCALAR, &get_context_string::(mode).unwrap()].concat(); @@ -982,6 +1044,98 @@ mod tests { assert_eq!(client_finalize_result, res2); } + fn zeroize_base_client() { + let input = b"input"; + let mut rng = OsRng; + let client_blind_result = NonVerifiableClient::::blind(&input[..], &mut rng).unwrap(); + + let mut state = client_blind_result.state; + Zeroize::zeroize(&mut state); + for bytes in state.as_ptrs() { + assert!(bytes.iter().all(|&x| x == 0)); + } + + let mut message = client_blind_result.message; + Zeroize::zeroize(&mut message); + for bytes in message.as_ptrs() { + assert!(bytes.iter().all(|&x| x == 0)); + } + } + + fn zeroize_verifiable_client() { + let input = b"input"; + let mut rng = OsRng; + let client_blind_result = VerifiableClient::::blind(&input[..], &mut rng).unwrap(); + + let mut state = client_blind_result.state; + Zeroize::zeroize(&mut state); + for bytes in state.as_ptrs() { + assert!(bytes.iter().all(|&x| x == 0)); + } + + let mut message = client_blind_result.message; + Zeroize::zeroize(&mut message); + for bytes in message.as_ptrs() { + assert!(bytes.iter().all(|&x| x == 0)); + } + } + + fn zeroize_base_server() { + let input = b"input"; + let info = b"info"; + let mut rng = OsRng; + let client_blind_result = NonVerifiableClient::::blind(&input[..], &mut rng).unwrap(); + let server = NonVerifiableServer::::new(&mut rng).unwrap(); + let server_result = server + .evaluate(client_blind_result.message, &Metadata(info.to_vec())) + .unwrap(); + + let mut state = server; + Zeroize::zeroize(&mut state); + for bytes in state.as_ptrs() { + assert!(bytes.iter().all(|&x| x == 0)); + } + + let mut message = server_result.message; + Zeroize::zeroize(&mut message); + for bytes in message.as_ptrs() { + assert!(bytes.iter().all(|&x| x == 0)); + } + } + + fn zeroize_verifiable_server() { + let input = b"input"; + let info = b"info"; + let mut rng = OsRng; + let client_blind_result = VerifiableClient::::blind(&input[..], &mut rng).unwrap(); + let server = VerifiableServer::::new(&mut rng).unwrap(); + let server_result = server + .evaluate( + &mut rng, + client_blind_result.message, + &Metadata(info.to_vec()), + ) + .unwrap(); + + let mut state = server; + Zeroize::zeroize(&mut state); + for bytes in state.as_ptrs() { + assert!(bytes.iter().all(|&x| x == 0)); + } + + let mut message = server_result.message; + Zeroize::zeroize(&mut message); + for bytes in message.as_ptrs() { + assert!(bytes.iter().all(|&x| x == 0)); + } + + let mut proof = server_result.proof; + Zeroize::zeroize(&mut proof); + for bytes in proof.as_ptrs() { + assert!(bytes.iter().all(|&x| x == 0)); + } + } + #[test] fn test_functionality() -> Result<(), InternalError> { use curve25519_dalek::ristretto::RistrettoPoint; @@ -994,6 +1148,11 @@ mod tests { verifiable_bad_public_key::(); verifiable_batch_bad_public_key::(); + zeroize_base_client::(); + zeroize_base_server::(); + zeroize_verifiable_client::(); + zeroize_verifiable_server::(); + #[cfg(feature = "p256")] { use p256_::ProjectivePoint; @@ -1005,6 +1164,11 @@ mod tests { verifiable_batch_retrieval::(); verifiable_bad_public_key::(); verifiable_batch_bad_public_key::(); + + zeroize_base_client::(); + zeroize_base_server::(); + zeroize_verifiable_client::(); + zeroize_verifiable_server::(); } Ok(())