Improvements (#24)

* Remove manual `Debug` impl for `InternalError`

* Remove unnecessary `#[macro_use]`

* Remove unnecessary  `tuple` handling in macro

* Improve serialization macro impl

* Improve `impl_traits_for` macro

* Re-direct `Drop` implementation

* Improve macro readability

* Fix rustdoc warnings

* Rust 1.51 has no support for `rustdoc` lints

* Change `i2osp` output to `GenericArray` from `Vec`

* Reduce calls to `to_vec()` and simplify conversion

* Adding zeroize tests

Co-authored-by: Kevin Lewi <[email protected]>
This commit is contained in:
daxpedda
2021-10-10 17:51:12 -07:00
committed by GitHub
co-authored by Kevin Lewi
parent c93600498e
commit fab7528a69
8 changed files with 442 additions and 352 deletions
+8
View File
@@ -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
+1 -20
View File
@@ -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 {}
+15 -11
View File
@@ -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<H: BlockInput + Digest>(
dst: &[u8],
len_in_bytes: usize,
) -> Result<Vec<u8>, InternalError> {
let b_in_bytes = <H as Digest>::OutputSize::USIZE;
let r_in_bytes = <H as BlockInput>::BlockSize::USIZE;
let ell = div_ceil(len_in_bytes, b_in_bytes);
let ell = div_ceil(len_in_bytes, <H as Digest>::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::<U1>(dst.len())?].concat();
let z_pad = i2osp::<<H as BlockInput>::BlockSize>(0)?;
let l_i_b_str = i2osp::<U2>(len_in_bytes)?;
let msg_prime = [
&z_pad,
msg,
&l_i_b_str,
i2osp::<U1>(0)?.as_slice(),
&dst_prime,
]
.concat();
let mut b: Vec<Vec<u8>> = 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::<U1>(1)?);
h.update(&dst_prime);
b.push(h.finalize_reset().to_vec()); // b[1]
@@ -57,7 +61,7 @@ pub fn expand_message_xmd<H: BlockInput + Digest>(
for i in 2..(ell + 1) {
h.update(xor(&b[0], &b[i - 1])?);
h.update(&i2osp(i, 1)?);
h.update(&i2osp::<U1>(i)?);
h.update(&dst_prime);
b.push(h.finalize_reset().to_vec()); // b[i]
uniform_bytes.extend_from_slice(&b[i]);
+6 -6
View File
@@ -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<u8, Self::ScalarLen>,
fn from_scalar_slice<'a>(
scalar_bits: impl Into<&'a GenericArray<u8, Self::ScalarLen>>,
) -> Result<Self::Scalar, InternalError> {
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<u8, Self::ElemLen>,
fn from_element_slice<'a>(
element_bits: impl Into<&'a GenericArray<u8, Self::ElemLen>>,
) -> Result<Self, InternalError> {
let elem = Self::from_element_slice_unchecked(element_bits)?;
let elem = Self::from_element_slice_unchecked(element_bits.into())?;
if Self::ct_equal(&elem, &<Self as Group>::identity()) {
// found the identity element
+88 -146
View File
@@ -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,)?)+
{
$($fn1)?
}
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|,)?)+
) => { };
}
/// 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();
};
($self_:ident, $field:ident) => {
$self_.$field.zeroize();
};
}
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()
}
}
impl$(<$($gen$(: $bound1$( + $bound2)*)?),+>)? Eq for $name$(<$($gen),+>)?
$(where $($type: Eq,)+)?
{}
impl$(<$($gen$(: $bound1$( + $bound2)*)?),+>)? PartialEq for $name$(<$($gen),+>)?
$(where $($type: PartialEq,)+)?
{
},
Eq => { },
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,)+)?
{
},
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<H: core::hash::Hasher>(&self, state: &mut H) {
core::hash::Hash::hash(&self.$field1, state);
$(core::hash::Hash::hash(&self.$field2, state);)*
}
}
};
}
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,)+)?
{
},
Clone => {
fn clone(&self) -> Self {
Self {
$field1: self.$field1.clone(),
$($field2: self.$field2.clone(),)*
}
}
}
};
(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),+>)?
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<D>(deserializer: D) -> Result<Self, D::Error>
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<E>(self, value: &[u8]) -> Result<Self::Value, E>
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)*)?),+>)?);
}
}
+3 -8
View File
@@ -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)]
+37 -41
View File
@@ -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<G: Group, H: BlockInput + Digest> NonVerifiableClient<G, H> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
[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<G: Group, H: BlockInput + Digest> NonVerifiableClient<G, H> {
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<G: Group, H: BlockInput + Digest> VerifiableClient<G, H> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
[
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<G: Group, H: BlockInput + Digest> VerifiableClient<G, H> {
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<G: Group, H: BlockInput + Digest> NonVerifiableServer<G, H> {
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<G: Group, H: BlockInput + Digest> NonVerifiableServer<G, H> {
impl<G: Group, H: BlockInput + Digest> VerifiableServer<G, H> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
[
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<G: Group, H: BlockInput + Digest> VerifiableServer<G, H> {
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<G: Group, H: BlockInput + Digest> Proof<G, H> {
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<G: Group, H: BlockInput + Digest> BlindedElement<G, H> {
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
Ok(Self {
value: G::from_element_slice(GenericArray::from_slice(input))?,
value: G::from_element_slice(input)?,
hash: PhantomData,
})
}
@@ -183,7 +177,7 @@ impl<G: Group, H: BlockInput + Digest> EvaluationElement<G, H> {
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
Ok(Self {
value: G::from_element_slice(GenericArray::from_slice(input))?,
value: G::from_element_slice(input)?,
hash: PhantomData,
})
}
@@ -195,46 +189,48 @@ impl<G: Group, H: BlockInput + Digest> EvaluationElement<G, H> {
//////////////////////
// Corresponds to the I2OSP() function from RFC8017
pub(crate) fn i2osp(input: usize, length: usize) -> Result<alloc::vec::Vec<u8>, InternalError> {
let sizeof_usize = core::mem::size_of::<usize>();
pub(crate) fn i2osp<L: ArrayLength<u8>>(
input: usize,
) -> Result<GenericArray<u8, L>, InternalError> {
const SIZEOF_USIZE: usize = core::mem::size_of::<usize>();
// 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<Vec<u8>, InternalError> {
Ok([&i2osp(input.len(), max_bytes)?, input].concat())
pub(crate) fn serialize<L: ArrayLength<u8>>(input: &[u8]) -> Result<Vec<u8>, InternalError> {
Ok([&i2osp::<L>(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::<U1>(0).is_ok());
assert!(i2osp(255, 1).is_ok());
assert!(i2osp(256, 1).is_err());
assert!(i2osp(257, 1).is_err());
assert!(i2osp::<U1>(255).is_ok());
assert!(i2osp::<U1>(256).is_err());
assert!(i2osp::<U1>(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::<U2>(256 * 256 - 1).is_ok());
assert!(i2osp::<U2>(256 * 256).is_err());
assert!(i2osp::<U2>(256 * 256 + 1).is_err());
}
}
+255 -91
View File
@@ -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<G: Group, H: BlockInput + Digest> {
impl_traits_for! {
/// A client which engages with a [NonVerifiableServer]
/// in base mode, meaning that the OPRF outputs are not
/// verifiable.
pub struct NonVerifiableClient<G: Group, H: BlockInput + Digest> {
#[bind]
pub(crate) blind: <G as Group>::Scalar,
pub(crate) data: Vec<u8>,
#[pd]
pub(crate) hash: PhantomData<H>,
}
}
impl_traits_for!(
struct NonVerifiableClient<G: Group, H: BlockInput + Digest>,
[blind, data, #[PH] hash],
[<G as Group>::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<G: Group, H: BlockInput + Digest> {
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<G: Group, H: BlockInput + Digest> {
#[bind]
pub(crate) blind: <G as Group>::Scalar,
#[bind]
pub(crate) blinded_element: G,
pub(crate) data: alloc::vec::Vec<u8>,
#[pd]
pub(crate) hash: PhantomData<H>,
}
}
impl_traits_for!(
struct VerifiableClient<G: Group, H: BlockInput + Digest>,
[blind, blinded_element, data, #[PH] hash],
[<G as Group>::Scalar, G],
);
/// A server which engages with a [NonVerifiableClient]
/// in base mode, meaning that the OPRF outputs are not
/// verifiable.
pub struct NonVerifiableServer<G: Group, H: BlockInput + Digest> {
impl_traits_for! {
/// A server which engages with a [NonVerifiableClient]
/// in base mode, meaning that the OPRF outputs are not
/// verifiable.
pub struct NonVerifiableServer<G: Group, H: BlockInput + Digest> {
#[bind]
pub(crate) sk: <G as Group>::Scalar,
#[pd]
pub(crate) hash: PhantomData<H>,
}
}
impl_traits_for!(
struct NonVerifiableServer<G: Group, H: BlockInput + Digest>,
[sk, #[PH] hash],
[<G as Group>::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<G: Group, H: BlockInput + Digest> {
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<G: Group, H: BlockInput + Digest> {
#[bind]
pub(crate) sk: <G as Group>::Scalar,
#[bind]
pub(crate) pk: G,
#[pd]
pub(crate) hash: PhantomData<H>,
}
}
impl_traits_for!(
struct VerifiableServer<G: Group, H: BlockInput + Digest>,
[sk, pk, #[PH] hash],
[<G as Group>::Scalar, G],
);
/// A proof produced by a [VerifiableServer] that
/// the OPRF output matches against a server public key.
pub struct Proof<G: Group, H: BlockInput + Digest> {
impl_traits_for! {
/// A proof produced by a [VerifiableServer] that
/// the OPRF output matches against a server public key.
pub struct Proof<G: Group, H: BlockInput + Digest> {
#[bind]
pub(crate) c_scalar: <G as Group>::Scalar,
pub(crate) s_scalar: <G as Group>::Scalar,
#[pd]
pub(crate) hash: PhantomData<H>,
}
}
impl_traits_for!(
struct Proof<G: Group, H: BlockInput + Digest>,
[c_scalar, s_scalar, #[PH] hash],
[<G as Group>::Scalar],
);
/// The first client message sent from a client (either verifiable or not)
/// to a server (either verifiable or not).
pub struct BlindedElement<G: Group, H: BlockInput + Digest> {
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<G: Group, H: BlockInput + Digest> {
#[bind]
pub(crate) value: G,
#[pd]
pub(crate) hash: PhantomData<H>,
}
}
impl_traits_for!(
struct BlindedElement<G: Group, H: BlockInput + Digest>,
[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<G: Group, H: BlockInput + Digest> {
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<G: Group, H: BlockInput + Digest> {
#[bind]
pub(crate) value: G,
#[pd]
pub(crate) hash: PhantomData<H>,
}
}
impl_traits_for!(
struct EvaluationElement<G: Group, H: BlockInput + Digest>,
[value, #[PH] hash],
[G],
);
/////////////////////////
// API Implementations //
@@ -197,6 +195,15 @@ impl<G: Group, H: BlockInput + Digest> NonVerifiableClient<G, H> {
pub fn get_blind(&self) -> <G as Group>::Scalar {
self.blind
}
#[cfg(test)]
/// Only used for testing zeroize
pub fn as_ptrs(&self) -> Vec<Vec<u8>> {
vec![
self.data.clone(),
<G as Group>::scalar_as_bytes(self.blind).to_vec(),
]
}
}
impl<G: Group, H: BlockInput + Digest> VerifiableClient<G, H> {
@@ -294,6 +301,16 @@ impl<G: Group, H: BlockInput + Digest> VerifiableClient<G, H> {
pub fn get_blind(&self) -> <G as Group>::Scalar {
self.blind
}
#[cfg(test)]
/// Only used for testing zeroize
pub fn as_ptrs(&self) -> Vec<Vec<u8>> {
vec![
self.data.clone(),
<G as Group>::scalar_as_bytes(self.blind).to_vec(),
self.blinded_element.to_arr().to_vec(),
]
}
}
impl<G: Group, H: BlockInput + Digest> NonVerifiableServer<G, H> {
@@ -343,7 +360,7 @@ impl<G: Group, H: BlockInput + Digest> NonVerifiableServer<G, H> {
let context = [
STR_CONTEXT,
&get_context_string::<G>(Mode::Base)?,
&serialize(&metadata.0, 2)?,
&serialize::<U2>(&metadata.0)?,
]
.concat();
let dst = [STR_HASH_TO_SCALAR, &get_context_string::<G>(Mode::Base)?].concat();
@@ -357,6 +374,12 @@ impl<G: Group, H: BlockInput + Digest> NonVerifiableServer<G, H> {
},
})
}
#[cfg(test)]
/// Only used for testing zeroize
pub fn as_ptrs(&self) -> Vec<Vec<u8>> {
vec![<G as Group>::scalar_as_bytes(self.sk).to_vec()]
}
}
impl<G: Group, H: BlockInput + Digest> VerifiableServer<G, H> {
@@ -429,7 +452,7 @@ impl<G: Group, H: BlockInput + Digest> VerifiableServer<G, H> {
let context = [
STR_CONTEXT,
&get_context_string::<G>(Mode::Verifiable)?,
&serialize(&metadata.0, 2)?,
&serialize::<U2>(&metadata.0)?,
]
.concat();
let dst = [
@@ -462,6 +485,15 @@ impl<G: Group, H: BlockInput + Digest> VerifiableServer<G, H> {
pub fn get_public_key(&self) -> G {
self.pk
}
#[cfg(test)]
/// Only used for testing zeroize
pub fn as_ptrs(&self) -> Vec<Vec<u8>> {
vec![
<G as Group>::scalar_as_bytes(self.sk).to_vec(),
self.pk.to_arr().to_vec(),
]
}
}
/////////////////////////
@@ -553,6 +585,35 @@ struct BatchItems<G: Group, H: BlockInput + Digest> {
blinded_element: BlindedElement<G, H>,
}
/// Convenience test functions for [BlindedElement], [EvaluationElement], and [Proof]
impl<G: Group, H: BlockInput + Digest> BlindedElement<G, H> {
#[cfg(test)]
/// Only used for testing zeroize
pub fn as_ptrs(&self) -> Vec<Vec<u8>> {
vec![self.value.to_arr().to_vec()]
}
}
impl<G: Group, H: BlockInput + Digest> EvaluationElement<G, H> {
#[cfg(test)]
/// Only used for testing zeroize
pub fn as_ptrs(&self) -> Vec<Vec<u8>> {
vec![self.value.to_arr().to_vec()]
}
}
impl<G: Group, H: BlockInput + Digest> Proof<G, H> {
#[cfg(test)]
/// Only used for testing zeroize
pub fn as_ptrs(&self) -> Vec<Vec<u8>> {
vec![
<G as Group>::scalar_as_bytes(self.c_scalar).to_vec(),
<G as Group>::scalar_as_bytes(self.s_scalar).to_vec(),
]
}
}
// Inner function for blind. Returns the blind scalar and the blinded element
fn blind<G: Group, H: BlockInput + Digest, R: RngCore + CryptoRng>(
input: &[u8],
@@ -576,7 +637,7 @@ fn verifiable_unblind<G: Group, H: BlockInput + Digest>(
let context = [
STR_CONTEXT,
&get_context_string::<G>(Mode::Verifiable)?,
&serialize(info, 2)?,
&serialize::<U2>(info)?,
]
.concat();
@@ -628,12 +689,12 @@ fn generate_proof<G: Group, H: BlockInput + Digest, R: RngCore + CryptoRng>(
let challenge_dst = [STR_CHALLENGE, &get_context_string::<G>(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::<U2>(&b.to_arr().to_vec())?,
serialize::<U2>(&m.to_arr().to_vec())?,
serialize::<U2>(&z.to_arr().to_vec())?,
serialize::<U2>(&t2.to_arr().to_vec())?,
serialize::<U2>(&t3.to_arr().to_vec())?,
serialize::<U2>(&challenge_dst)?,
]
.concat();
@@ -667,12 +728,12 @@ fn verify_proof<G: Group, H: BlockInput + Digest>(
let challenge_dst = [STR_CHALLENGE, &get_context_string::<G>(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::<U2>(&b.to_arr().to_vec())?,
serialize::<U2>(&m.to_arr().to_vec())?,
serialize::<U2>(&z.to_arr().to_vec())?,
serialize::<U2>(&t2.to_arr().to_vec())?,
serialize::<U2>(&t3.to_arr().to_vec())?,
serialize::<U2>(&challenge_dst)?,
]
.concat();
@@ -702,10 +763,10 @@ fn finalize_after_unblind<G: Group, H: BlockInput + Digest>(
for (input, unblinded_element) in inputs_and_unblinded_elements {
outputs.push(<H as Digest>::digest(
&[
serialize(input, 2)?,
serialize(info, 2)?,
serialize(&unblinded_element.to_arr().to_vec(), 2)?,
serialize(&finalize_dst, 2)?,
serialize::<U2>(input)?,
serialize::<U2>(info)?,
serialize::<U2>(&unblinded_element.to_arr().to_vec())?,
serialize::<U2>(&finalize_dst)?,
]
.concat(),
));
@@ -728,8 +789,8 @@ fn compute_composites<G: Group, H: BlockInput + Digest>(
let composite_dst = [STR_COMPOSITE, &get_context_string::<G>(Mode::Verifiable)?].concat();
let h1_input = [
serialize(&b.to_arr().to_vec(), 2)?,
serialize(&seed_dst, 2)?,
serialize::<U2>(&b.to_arr().to_vec())?,
serialize::<U2>(&seed_dst)?,
]
.concat();
let seed = <H as Digest>::digest(&h1_input);
@@ -739,11 +800,11 @@ fn compute_composites<G: Group, H: BlockInput + Digest>(
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::<U2>(&seed)?,
i2osp::<U2>(i)?.to_vec(),
serialize::<U2>(&c_slice[i].value.to_arr().to_vec())?,
serialize::<U2>(&d_slice[i].value.to_arr().to_vec())?,
serialize::<U2>(&composite_dst)?,
]
.concat();
let dst = [
@@ -772,8 +833,8 @@ fn compute_composites<G: Group, H: BlockInput + Digest>(
fn get_context_string<G: Group>(mode: Mode) -> Result<alloc::vec::Vec<u8>, InternalError> {
Ok([
STR_VOPRF,
&i2osp(mode as usize, 1)?,
&i2osp(G::SUITE_ID, 2)?,
&i2osp::<U1>(mode as usize)?,
&i2osp::<U2>(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<G: Group, H: BlockInput + Digest>(
input: &[u8],
@@ -802,7 +864,7 @@ mod tests {
let context = [
STR_CONTEXT,
&get_context_string::<G>(mode).unwrap(),
&serialize(info, 2).unwrap(),
&serialize::<U2>(info).unwrap(),
]
.concat();
let dst = [STR_HASH_TO_SCALAR, &get_context_string::<G>(mode).unwrap()].concat();
@@ -982,6 +1044,98 @@ mod tests {
assert_eq!(client_finalize_result, res2);
}
fn zeroize_base_client<G: Group, H: BlockInput + Digest>() {
let input = b"input";
let mut rng = OsRng;
let client_blind_result = NonVerifiableClient::<G, H>::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<G: Group, H: BlockInput + Digest>() {
let input = b"input";
let mut rng = OsRng;
let client_blind_result = VerifiableClient::<G, H>::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<G: Group, H: BlockInput + Digest>() {
let input = b"input";
let info = b"info";
let mut rng = OsRng;
let client_blind_result = NonVerifiableClient::<G, H>::blind(&input[..], &mut rng).unwrap();
let server = NonVerifiableServer::<G, H>::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<G: Group, H: BlockInput + Digest>() {
let input = b"input";
let info = b"info";
let mut rng = OsRng;
let client_blind_result = VerifiableClient::<G, H>::blind(&input[..], &mut rng).unwrap();
let server = VerifiableServer::<G, H>::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::<RistrettoPoint, Sha512>();
verifiable_batch_bad_public_key::<RistrettoPoint, Sha512>();
zeroize_base_client::<RistrettoPoint, Sha512>();
zeroize_base_server::<RistrettoPoint, Sha512>();
zeroize_verifiable_client::<RistrettoPoint, Sha512>();
zeroize_verifiable_server::<RistrettoPoint, Sha512>();
#[cfg(feature = "p256")]
{
use p256_::ProjectivePoint;
@@ -1005,6 +1164,11 @@ mod tests {
verifiable_batch_retrieval::<ProjectivePoint, Sha256>();
verifiable_bad_public_key::<ProjectivePoint, Sha256>();
verifiable_batch_bad_public_key::<ProjectivePoint, Sha256>();
zeroize_base_client::<ProjectivePoint, Sha256>();
zeroize_base_server::<ProjectivePoint, Sha256>();
zeroize_verifiable_client::<ProjectivePoint, Sha256>();
zeroize_verifiable_server::<ProjectivePoint, Sha256>();
}
Ok(())