Compare commits

14 Commits
Author SHA1 Message Date
Kevin LewiandGitHub 1b3837a789 Publishing v0.2 (#31) 2021-10-18 16:47:13 -07:00
Kevin LewiandGitHub 093a15f597 Adding deterministic_blind_unchecked functionality under danger feature (#30) 2021-10-18 13:24:15 -07:00
daxpeddaandGitHub 14830f1436 Remove allocations by serialize (#29)
* Changed `expand_message_xmd` input to `Iterator`

* Changed `hash_to_scalar` input to `Iterator`

* Fix rustfmt

* Add documentation for private helper types

* Fix documentation

* Improve `chain!()` syntax bias

* Move helper functions to `mod util`
2021-10-15 16:56:21 -07:00
Kevin LewiandGitHub 8457e8b900 Adding "danger" feature to expose underlying elements (#27)
* Adding internal feature to expose underlying elements

* Applying @daxpedda's comments and suggestions
2021-10-14 13:55:43 -07:00
daxpeddaandGitHub de91fafdb3 Group improvements (#28)
* Implement `ConstantTimeEq` for `Group` and `Group::Scalar`

* Changed `expand_message_xmd` `dst` parameter to `GenericArray<u8, D>`

* Remove allocation for `dst_prime` in `expand_message_xmd`

* Remove allocation for `msg_prime` and `b` in `expand_message_xmd`

* Remove unnecessary references in `expand_message_xmd`

* Return `GenericArray` from `expand_message_xmd`

* Oxidize loop in `expand_message_xmd`

* Remove allocation for `b` in `expand_message_xmd`

* Remove allocation for `xor`

* De-duplicate code in `expand_message_xmd`

* Remove unnecessary features

* Make ristretto optional and export all backends

* Make `rand` optional

* Fix wrong documentation

* Document `rand` feature

* Remove empty line

* Fix rustfmt

* Fix testing with other backends

* Iterate by value in `expand_message_xmd`

* Fix typo

* Rename `ristretto_x` features to `ristretto255_x`

* Remove `rand` re-export

* Improve `expand_message_xmd` loop

* Remove unnecessary `#[doc(cfg())]`

* Remove unnecessary `pub(crate)` for `mod p256`

* Fix `p256` only build

* Rename `serialize` feature to `serde`

* Fix missing `Cargo.toml` update
2021-10-14 11:09:00 -07:00
daxpeddaandGitHub 5ac52388ff Reduce allocations and some general code improvements (#26)
* Remove unnecessary paths

* Remove unnecessary conversion for `G::from_scalar_slice`

* Remove unnecessary allocations

* Return `GenericArray` from `get_context_string`

* Use `zip()` instead instead of indexing

* Changed `compute_composites` input to `Iterator`s

* Changed `generate_proof` input to `Iterator`s

* Changed `verify_proof` input to `Iterator`s

* Reduce allocations in `verifiable_unblind`

* Changed `verifiable_unblind` input to `Iterator`s

* Changed `batch_finalize` input to `Iterator`s
Removed `BatchFinalizeInput`

* Reduce allocations in `VerifiableClient::finalize`

* Changed `finalize_after_unblind` input to `Iterator`s

* Changed `batch_evaluate` input to `Iterator`s

* Reduce allocations in `evaluate`

* Changed `blind` input to `Into<Vec<u8>>`

* Remove note about `BatchFinalizeInput` from documentation

* Remove `Metadata`

* Changed `blind` input to `Vec<u8>`

* General improvements to test code

* Fix rustdoc

* Remove unnecessarily installed components
2021-10-12 15:07:47 -07:00
fab7528a69 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]>
2021-10-10 17:51:12 -07:00
daxpeddaandGitHub c93600498e Add clippy::cargo and remove dependencies (#23) 2021-10-05 17:52:08 -07:00
daxpeddaandGitHub 6fb4cad59c Remove CipherSuite (#20)
* Remove `Hash`

* Remove `CipherSuite`

* Remove single field `struct`s
2021-10-05 15:53:18 -07:00
daxpeddaandGitHub 2d8780476a Use Clippy on test code too (#22) 2021-10-05 15:19:20 -07:00
daxpeddaandGitHub a3db6cd9d2 P256 improvements (#21)
* P256 improvements

* Fix Rust 1.51 compilation
2021-10-04 13:50:29 -07:00
daxpeddaandGitHub a1ab892bdb P256 improvements (#18) 2021-09-30 14:00:58 -07:00
daxpeddaandGitHub ad04f224af Improve documentation on docs.rs (#17) 2021-09-30 14:00:32 -07:00
daxpeddaandGitHub 7be67b26de Re-export simd_backend (#19) 2021-09-30 12:34:29 -07:00
21 changed files with 1564 additions and 1296 deletions
+24 -13
View File
@@ -13,11 +13,12 @@ jobs:
fail-fast: false
matrix:
backend_feature:
- u64_backend
- u32_backend
- p256,u64_backend
- ristretto255_u64
- ristretto255_u32
- p256,ristretto255_u64
frontend_feature:
- serialize
- serde
- danger
toolchain:
- stable
- 1.51.0
@@ -32,7 +33,6 @@ jobs:
profile: minimal
toolchain: ${{ matrix.toolchain }}
override: true
components: rustfmt, clippy
- name: Run cargo test
uses: actions-rs/cargo@v1
@@ -58,16 +58,19 @@ jobs:
# for any no_std target
- thumbv6m-none-eabi
backend_feature:
- u64_backend
- u32_backend
- p256,u64_backend
-
- --features ristretto255_u64
- --features ristretto255_u32
- --features p256
frontend_feature:
- serialize
-
- --features serde
- --features danger
steps:
- uses: actions/checkout@v2
- uses: hecrj/setup-rust-action@v1
- run: rustup target add ${{ matrix.target }}
- run: cargo build --verbose --target=${{ matrix.target }} --no-default-features --features ${{ matrix.frontend_feature }} --features ${{ matrix.backend_feature }}
- run: cargo build --verbose --target=${{ matrix.target }} --no-default-features ${{ matrix.frontend_feature }} ${{ matrix.backend_feature }}
clippy:
@@ -83,13 +86,21 @@ jobs:
profile: minimal
toolchain: stable
override: true
components: rustfmt, clippy
components: clippy
- name: Run cargo clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
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 --features std,p256
format:
@@ -105,7 +116,7 @@ jobs:
profile: minimal
toolchain: stable
override: true
components: rustfmt, clippy
components: rustfmt
- name: Run cargo fmt
uses: actions-rs/cargo@v1
+6
View File
@@ -1,5 +1,11 @@
# Changelog
## 0.2.0 (October 18, 2021)
* Removed the CipherSuite interface
* Added the "danger" feature for exposing internal functions
* General improvements to the group interface
## 0.1.0 (September 29, 2021)
* Initial release
+26 -27
View File
@@ -1,56 +1,55 @@
[package]
name = "voprf"
version = "0.1.0"
version = "0.2.0"
description = "An implementation of a verifiable oblivious pseudorandom function (VOPRF)"
authors = ["Kevin Lewi <[email protected]>"]
categories = ["no-std"]
repository = "https://github.com/novifinancial/voprf/"
categories = ["no-std", "algorithms", "cryptography"]
keywords = ["oprf"]
license = "MIT"
edition = "2018"
readme = "README.md"
resolver = "2"
[features]
default = ["u64_backend", "serialize"]
default = ["ristretto255_u64", "serde"]
danger = []
ristretto255_u64 = ["curve25519-dalek/u64_backend"]
ristretto255_u32 = ["curve25519-dalek/u32_backend"]
ristretto255_fiat_u64 = ["curve25519-dalek/fiat_u64_backend"]
ristretto255_fiat_u32 = ["curve25519-dalek/fiat_u32_backend"]
ristretto255_simd = ["curve25519-dalek/simd_backend"]
p256 = ["num-bigint", "num-integer", "num-traits", "once_cell", "p256_"]
std = ["curve25519-dalek/std", "getrandom", "rand/std", "rand/std_rng", "num-bigint/std", "num-integer/std", "num-traits/std"]
u64_backend = ["curve25519-dalek/u64_backend"]
u32_backend = ["curve25519-dalek/u32_backend"]
serialize = ["serde", "base64", "generic-array/serde", "curve25519-dalek/serde"]
std = []
serde = ["serde_", "base64"]
[dependencies]
base64 = { version = "0.13", default-features = false, features = ["alloc"], optional = true }
constant_time_eq = "0.1"
curve25519-dalek = { version = "3", default-features = false }
curve25519-dalek = { version = "3", default-features = false, optional = true }
digest = "0.9"
displaydoc = { version = "0.2", default-features = false }
generic-array = "0.14"
getrandom = { version = "0.2", optional = true }
hkdf = "0.11"
hmac = "0.11"
num-bigint = { version = "0.4", default-features = false, optional = true }
num-integer = { version = "0.1", default-features = false, optional = true }
num-traits = { version = "0.2", default-features = false, optional = true }
once_cell = { version = "1", default-features = false, optional = true }
p256_ = { package = "p256", version = "0.9", default-features = false, features = ["arithmetic", "zeroize"], optional = true }
rand = { version = "0.8", default-features = false }
serde = { version = "1", default-features = false, features = ["alloc", "derive"], optional = true }
rand_core = { version = "0.6", default-features = false }
serde_ = { version = "1", package = "serde", default-features = false, optional = true }
subtle = { version = "2.3", default-features = false }
zeroize = { version = "1", features = ["zeroize_derive"] }
[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2", features = ["js"], optional = true }
zeroize = { version = "1", default-features = false }
[dev-dependencies]
base64 = "0.13"
bincode = "1"
chacha20poly1305 = "0.8"
criterion = "0.3"
generic-array = { version = "0.14", features = ["more_lengths"] }
hex = "0.4"
json = "0.12"
lazy_static = "1"
serde_json = "1"
sha2 = "0.9"
proptest = "1"
rand = "0.8"
regex = "1"
rustyline = "8"
voprf = { path = "", default-features = false, features = ["std"] }
sha2 = "0.9"
voprf = { path = "", default-features = false, features = ["std", "danger"] }
[package.metadata.docs.rs]
features = ["danger", "p256", "std"]
targets = []
rustdoc-args = ["--cfg", "docsrs"]
+1 -1
View File
@@ -16,7 +16,7 @@ Installation
Add the following line to the dependencies of your `Cargo.toml`:
```
voprf = "0.1.0"
voprf = "0.2"
```
### Minimum Supported Rust Version
-17
View File
@@ -1,17 +0,0 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under both the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
//! Defines the CipherSuite trait to specify the underlying primitives for VOPRF
/// Configures the underlying primitives used in VOPRF
pub trait CipherSuite {
/// A finite cyclic group along with a point representation that allows some
/// customization on how to hash an input to a curve point. See `group::Group`.
type Group: crate::group::Group;
/// The main hash function to use (for HKDF computations and hashing transcripts).
type Hash: crate::hash::Hash;
}
+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,23 +37,5 @@ 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")]
impl Error for InternalError {}
+64 -42
View File
@@ -6,11 +6,14 @@
// of this source tree.
use crate::errors::InternalError;
use crate::hash::Hash;
use crate::serialization::i2osp;
use alloc::vec::Vec;
use crate::util::i2osp;
use core::ops::Add;
use digest::{BlockInput, Digest};
use generic_array::typenum::Unsigned;
use generic_array::{
sequence::Concat,
typenum::{Unsigned, U1, U2},
ArrayLength, GenericArray,
};
// Computes ceil(x / y)
fn div_ceil(x: usize, y: usize) -> usize {
@@ -18,57 +21,68 @@ fn div_ceil(x: usize, y: usize) -> usize {
x / y + additive
}
fn xor(x: &[u8], y: &[u8]) -> Result<Vec<u8>, InternalError> {
if x.len() != y.len() {
return Err(InternalError::HashToCurveError);
}
Ok(x.iter().zip(y).map(|(&x1, &x2)| x1 ^ x2).collect())
fn xor<L: ArrayLength<u8>>(x: GenericArray<u8, L>, y: GenericArray<u8, L>) -> GenericArray<u8, L> {
x.into_iter().zip(y).map(|(x1, x2)| x1 ^ x2).collect()
}
/// Corresponds to the expand_message_xmd() function defined in
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt>
pub fn expand_message_xmd<H: Hash>(
msg: &[u8],
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);
pub fn expand_message_xmd<
'a,
H: BlockInput + Digest,
L: ArrayLength<u8>,
M: IntoIterator<Item = &'a [u8]>,
D: ArrayLength<u8> + Add<U1>,
>(
msg: M,
dst: GenericArray<u8, D>,
) -> Result<GenericArray<u8, L>, InternalError>
where
<D as Add<U1>>::Output: ArrayLength<u8>,
{
let digest_len = <H as Digest>::OutputSize::USIZE;
let ell = div_ceil(L::USIZE, digest_len);
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 mut b: Vec<Vec<u8>> = alloc::vec![H::digest(&msg_prime).to_vec()]; // b[0]
let dst_prime = dst.concat(i2osp::<U1>(D::USIZE)?);
let z_pad = i2osp::<<H as BlockInput>::BlockSize>(0)?;
let l_i_b_str = i2osp::<U2>(L::USIZE)?;
let mut h = H::new();
h.update(&b[0]);
h.update(&i2osp(1, 1)?);
// msg_prime = Z_pad || msg || l_i_b_str || I2OSP(0, 1) || DST_prime
h.update(z_pad);
for bytes in msg {
h.update(bytes)
}
h.update(l_i_b_str);
h.update(i2osp::<U1>(0)?);
h.update(&dst_prime);
b.push(h.finalize_reset().to_vec()); // b[1]
let mut uniform_bytes: Vec<u8> = Vec::new();
uniform_bytes.extend_from_slice(&b[1]);
// b[0]
let b_0 = h.finalize_reset();
let mut b_i = GenericArray::default();
for i in 2..(ell + 1) {
h.update(xor(&b[0], &b[i - 1])?);
h.update(&i2osp(i, 1)?);
let mut uniform_bytes = GenericArray::default();
for (i, chunk) in (1..(ell + 1)).zip(uniform_bytes.chunks_mut(digest_len)) {
h.update(xor(b_0.clone(), b_i.clone()));
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]);
b_i = h.finalize_reset();
chunk.copy_from_slice(&b_i[..digest_len.min(chunk.len())]);
}
Ok(uniform_bytes[..len_in_bytes].to_vec())
Ok(uniform_bytes)
}
#[cfg(test)]
mod tests {
use generic_array::{
typenum::{U128, U32},
GenericArray,
};
struct Params {
msg: &'static str,
@@ -177,14 +191,22 @@ mod tests {
378fba044a31f5cb44583a892f5969dcd73b3fa128816e",
},
];
let dst = "QUUX-V01-CS02-with-expander";
let dst = GenericArray::from(*b"QUUX-V01-CS02-with-expander");
for tv in test_vectors {
let uniform_bytes = super::expand_message_xmd::<sha2::Sha256>(
tv.msg.as_bytes(),
dst.as_bytes(),
tv.len_in_bytes,
)
let uniform_bytes = match tv.len_in_bytes {
32 => super::expand_message_xmd::<sha2::Sha256, U32, _, _>(
Some(tv.msg.as_bytes()),
dst,
)
.map(|bytes| bytes.to_vec()),
128 => super::expand_message_xmd::<sha2::Sha256, U128, _, _>(
Some(tv.msg.as_bytes()),
dst,
)
.map(|bytes| bytes.to_vec()),
_ => unimplemented!(),
}
.unwrap();
assert_eq!(tv.uniform_bytes, hex::encode(uniform_bytes));
}
+48 -21
View File
@@ -7,16 +7,32 @@
//! Defines the Group trait to specify the underlying prime order group
#[cfg(any(
feature = "ristretto255_u64",
feature = "ristretto255_u32",
feature = "ristretto255_fiat_u64",
feature = "ristretto255_fiat_u32",
feature = "ristretto255_simd",
feature = "p256",
))]
mod expand;
#[cfg(feature = "p256")]
pub(crate) mod p256;
mod p256;
#[cfg(any(
feature = "ristretto255_u64",
feature = "ristretto255_u32",
feature = "ristretto255_fiat_u64",
feature = "ristretto255_fiat_u32",
feature = "ristretto255_simd",
))]
mod ristretto;
use crate::errors::InternalError;
use crate::hash::Hash;
use core::ops::{Add, Mul, Sub};
use generic_array::{ArrayLength, GenericArray};
use rand::{CryptoRng, RngCore};
use digest::{BlockInput, Digest};
use generic_array::{typenum::U1, ArrayLength, GenericArray};
use rand_core::{CryptoRng, RngCore};
use subtle::ConstantTimeEq;
use zeroize::Zeroize;
/// A prime-order subgroup of a base field (EC, prime-order field ...). This
@@ -24,6 +40,7 @@ use zeroize::Zeroize;
pub trait Group:
Copy
+ Sized
+ ConstantTimeEq
+ for<'a> Mul<&'a <Self as Group>::Scalar, Output = Self>
+ for<'a> Add<&'a Self, Output = Self>
{
@@ -32,14 +49,30 @@ pub trait Group:
const SUITE_ID: usize;
/// transforms a password and domain separation tag (DST) into a curve point
fn hash_to_curve<H: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, InternalError>;
fn hash_to_curve<H: BlockInput + Digest, D: ArrayLength<u8> + Add<U1>>(
msg: &[u8],
dst: GenericArray<u8, D>,
) -> Result<Self, InternalError>
where
<D as Add<U1>>::Output: ArrayLength<u8>;
/// Hashes a slice of pseudo-random bytes to a scalar
fn hash_to_scalar<H: Hash>(input: &[u8], dst: &[u8]) -> Result<Self::Scalar, InternalError>;
fn hash_to_scalar<
'a,
H: BlockInput + Digest,
D: ArrayLength<u8> + Add<U1>,
I: IntoIterator<Item = &'a [u8]>,
>(
input: I,
dst: GenericArray<u8, D>,
) -> Result<Self::Scalar, InternalError>
where
<D as Add<U1>>::Output: ArrayLength<u8>;
/// The type of base field scalars
type Scalar: Zeroize
+ Copy
+ ConstantTimeEq
+ for<'a> Add<&'a Self::Scalar, Output = Self::Scalar>
+ for<'a> Sub<&'a Self::Scalar, Output = Self::Scalar>
+ for<'a> Mul<&'a Self::Scalar, Output = Self::Scalar>;
@@ -54,11 +87,11 @@ 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)?;
if Self::ct_equal_scalar(&scalar, &Self::scalar_zero()) {
let scalar = Self::from_scalar_slice_unchecked(scalar_bits.into())?;
if scalar.ct_eq(&Self::scalar_zero()).into() {
return Err(InternalError::ZeroScalarError);
}
Ok(scalar)
@@ -83,12 +116,12 @@ 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()) {
if Self::ct_eq(&elem, &<Self as Group>::identity()).into() {
// found the identity element
return Err(InternalError::PointError);
}
@@ -104,7 +137,7 @@ pub trait Group:
/// Returns if the group element is equal to the identity (1)
fn is_identity(&self) -> bool {
self.ct_equal(&<Self as Group>::identity())
self.ct_eq(&<Self as Group>::identity()).into()
}
/// Returns the identity group element
@@ -113,12 +146,6 @@ pub trait Group:
/// Returns the scalar representing zero
fn scalar_zero() -> Self::Scalar;
/// Compares in constant time if the group elements are equal
fn ct_equal(&self, other: &Self) -> bool;
/// Compares in constant time if the scalars are equal
fn ct_equal_scalar(s1: &Self::Scalar, s2: &Self::Scalar) -> bool;
/// Set the contents of self to the identity value
fn zeroize(&mut self) {
*self = <Self as Group>::identity();
+87 -61
View File
@@ -15,10 +15,10 @@
use super::Group;
use crate::errors::InternalError;
use crate::hash::Hash;
use core::ops::{Add, Div, Mul, Neg, Sub};
use core::ops::{Add, Div, Mul, Neg};
use core::str::FromStr;
use generic_array::typenum::{U32, U33};
use digest::{BlockInput, Digest};
use generic_array::typenum::{Unsigned, U1, U2, U32, U33, U48};
use generic_array::{ArrayLength, GenericArray};
use num_bigint::{BigInt, Sign};
use num_integer::Integer;
@@ -27,20 +27,28 @@ use once_cell::unsync::Lazy;
use p256_::elliptic_curve::group::prime::PrimeCurveAffine;
use p256_::elliptic_curve::group::GroupEncoding;
use p256_::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint};
use p256_::elliptic_curve::subtle::ConstantTimeEq;
use p256_::elliptic_curve::Field;
use p256_::{AffinePoint, EncodedPoint, ProjectivePoint};
use rand::{CryptoRng, RngCore};
use rand_core::{CryptoRng, RngCore};
use subtle::{Choice, ConditionallySelectable};
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-8.2
// `L: 48`
pub const L: usize = 48;
pub type L = U48;
#[cfg(feature = "p256")]
impl Group for ProjectivePoint {
const SUITE_ID: usize = 0x0003;
// 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<H: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, InternalError> {
fn hash_to_curve<H: BlockInput + Digest, D: ArrayLength<u8> + Add<U1>>(
msg: &[u8],
dst: GenericArray<u8, D>,
) -> Result<Self, InternalError>
where
<D as Add<U1>>::Output: ArrayLength<u8>,
{
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-8.2
// `p: 2^256 - 2^224 + 2^192 + 2^96 - 1`
const P: Lazy<BigInt> = Lazy::new(|| {
@@ -66,11 +74,12 @@ impl Group for ProjectivePoint {
// `hash_to_curve` calls `hash_to_field` with a `count` of `2`
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3
// `hash_to_field` calls `expand_message` with a `len_in_bytes` of `count * L`
let uniform_bytes = super::expand::expand_message_xmd::<H>(msg, dst, 2 * L)?;
let uniform_bytes =
super::expand::expand_message_xmd::<H, <L as Mul<U2>>::Output, _, _>(Some(msg), dst)?;
// hash to curve
let (q0x, q0y) = hash_to_curve_simple_swu(&uniform_bytes[..L], &A, &B, &P, &Z);
let (q1x, q1y) = hash_to_curve_simple_swu(&uniform_bytes[L..], &A, &B, &P, &Z);
let (q0x, q0y) = hash_to_curve_simple_swu(&uniform_bytes[..L::USIZE], &A, &B, &P, &Z);
let (q1x, q1y) = hash_to_curve_simple_swu(&uniform_bytes[L::USIZE..], &A, &B, &P, &Z);
// convert to `p256` types
let p0 = AffinePoint::from_encoded_point(&EncodedPoint::from_affine_coordinates(
@@ -88,10 +97,21 @@ impl Group for ProjectivePoint {
// Implements the `HashToScalar()` function from
// https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-07.html#section-4.3
fn hash_to_scalar<H: Hash>(input: &[u8], dst: &[u8]) -> Result<Self::Scalar, InternalError> {
fn hash_to_scalar<
'a,
H: BlockInput + Digest,
D: ArrayLength<u8> + Add<U1>,
I: IntoIterator<Item = &'a [u8]>,
>(
input: I,
dst: GenericArray<u8, D>,
) -> Result<Self::Scalar, InternalError>
where
<D as Add<U1>>::Output: ArrayLength<u8>,
{
// https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf#[{%22num%22:211,%22gen%22:0},{%22name%22:%22XYZ%22},70,700,0]
// P-256 `n` is defined as `115792089210356248762697446949407573529996955224135760342 422259061068512044369`
const N: once_cell::unsync::Lazy<BigInt> = once_cell::unsync::Lazy::new(|| {
const N: Lazy<BigInt> = Lazy::new(|| {
BigInt::from_str(
"115792089210356248762697446949407573529996955224135760342422259061068512044369",
)
@@ -100,16 +120,15 @@ impl Group for ProjectivePoint {
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3
// `HashToScalar` is `hash_to_field`
let uniform_bytes = super::expand::expand_message_xmd::<H>(input, dst, L)?;
let mut bytes = BigInt::from_bytes_be(Sign::Plus, &uniform_bytes)
let uniform_bytes = super::expand::expand_message_xmd::<H, L, _, _>(input, dst)?;
let bytes = BigInt::from_bytes_be(Sign::Plus, &uniform_bytes)
.mod_floor(&N)
.to_bytes_be()
.1;
bytes.resize(32, 0);
let mut result = GenericArray::default();
result[..bytes.len()].copy_from_slice(&bytes);
Ok(p256_::Scalar::from_bytes_reduced(GenericArray::from_slice(
&bytes,
)))
Ok(p256_::Scalar::from_bytes_reduced(&result))
}
type ElemLen = U33;
@@ -141,9 +160,11 @@ impl Group for ProjectivePoint {
}
fn to_arr(&self) -> GenericArray<u8, Self::ElemLen> {
let mut bytes = self.to_affine().to_encoded_point(true).as_bytes().to_vec();
bytes.resize(33, 0);
*GenericArray::from_slice(&bytes)
let bytes = self.to_affine().to_encoded_point(true);
let bytes = bytes.as_bytes();
let mut result = GenericArray::default();
result[..bytes.len()].copy_from_slice(bytes);
result
}
fn base_point() -> Self {
@@ -157,14 +178,6 @@ impl Group for ProjectivePoint {
fn scalar_zero() -> Self::Scalar {
Self::Scalar::zero()
}
fn ct_equal(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
fn ct_equal_scalar(s1: &Self::Scalar, s2: &Self::Scalar) -> bool {
s1.ct_eq(s2).into()
}
}
/// Corresponds to the hash_to_curve_simple_swu() function defined in
@@ -172,7 +185,7 @@ impl Group for ProjectivePoint {
///
/// `cmov`, `mod_floor` and `modpow` needs to be made constant-time, which
/// will be supported after crypto-bigint is no longer experimental. See
/// https://github.com/novifinancial/voprf/issues/13 for more context.
/// <https://github.com/novifinancial/voprf/issues/13> for more context.
#[allow(clippy::many_single_char_names)]
fn hash_to_curve_simple_swu<N: ArrayLength<u8>>(
@@ -200,11 +213,6 @@ fn hash_to_curve_simple_swu<N: ArrayLength<u8>>(
fn one(&'a self) -> FieldElement<'a> {
self.element(&BigInt::one())
}
/// See <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-4>
fn inv0(&'a self, number: &FieldElement<'a>) -> FieldElement<'a> {
number.pow_internal(&(self.0 - 2))
}
}
/// Finite field arithmetic
@@ -230,14 +238,6 @@ fn hash_to_curve_simple_swu<N: ArrayLength<u8>>(
}
}
impl<'a> Sub for &FieldElement<'a> {
type Output = FieldElement<'a>;
fn sub(self, rhs: Self) -> Self::Output {
self.f.element(&(&self.number - &rhs.number))
}
}
impl<'a> Neg for FieldElement<'a> {
type Output = FieldElement<'a>;
@@ -291,7 +291,7 @@ fn hash_to_curve_simple_swu<N: ArrayLength<u8>>(
#[allow(clippy::suspicious_arithmetic_impl)]
fn div(self, rhs: &Self) -> Self::Output {
self * rhs.f.inv0(rhs)
self * rhs.inv0()
}
}
@@ -302,7 +302,10 @@ fn hash_to_curve_simple_swu<N: ArrayLength<u8>>(
fn pow_internal(&self, exponent: &BigInt) -> Self {
let exponent = exponent.mod_floor(&(self.f.0 - 1));
self.f.element(&self.number.modpow(&exponent, self.f.0))
Self {
number: self.number.modpow(&exponent, self.f.0),
f: self.f,
}
}
/// Corresponds to the sqrt_3mod4() function defined in
@@ -320,6 +323,11 @@ fn hash_to_curve_simple_swu<N: ArrayLength<u8>>(
(&self.number % 2_usize).to_i32().unwrap()
}
/// See <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-4>
fn inv0(&self) -> Self {
self.pow_internal(&(self.f.0 - 2))
}
fn is_zero(&self) -> bool {
self.number.is_zero()
}
@@ -335,18 +343,35 @@ fn hash_to_curve_simple_swu<N: ArrayLength<u8>>(
}
fn to_bytes<N: ArrayLength<u8>>(&self) -> GenericArray<u8, N> {
let val = self.number.mod_floor(self.f.0).to_bytes_be().1;
let mut bytes = alloc::vec![0u8; 32 - val.len()];
bytes.extend_from_slice(&val);
GenericArray::clone_from_slice(&bytes)
let bytes = self.number.to_bytes_be().1;
let mut result = GenericArray::default();
result[N::USIZE - bytes.len()..].copy_from_slice(&bytes);
result
}
}
fn cmov<'a>(x: &FieldElement<'a>, y: &FieldElement<'a>, b: bool) -> FieldElement<'a> {
if b {
y.clone()
} else {
x.clone()
let f = x.f;
let x_bytes = x.number.to_bytes_le().1;
let mut x = [0; 32];
x[..x_bytes.len()].copy_from_slice(&x_bytes);
let y_bytes = y.number.to_bytes_le().1;
let mut y = [0; 32];
y[..y_bytes.len()].copy_from_slice(&y_bytes);
let mut bytes = [0; 32];
let choice = Choice::from(u8::from(b));
for ((byte, x), y) in bytes.iter_mut().zip(&x).zip(&y) {
*byte = u8::conditional_select(x, y, choice);
}
FieldElement {
f,
number: BigInt::from_bytes_le(Sign::Plus, &bytes),
}
}
@@ -370,7 +395,7 @@ fn hash_to_curve_simple_swu<N: ArrayLength<u8>>(
// 3. x1 = tv1 + tv2
let mut x1 = &tv1 + &tv2;
// 4. x1 = inv0(x1)
x1 = f.inv0(&x1);
x1 = x1.inv0();
// 5. e1 = x1 == 0
let e1 = x1.is_zero();
// 6. x1 = x1 + 1
@@ -412,6 +437,7 @@ fn hash_to_curve_simple_swu<N: ArrayLength<u8>>(
#[cfg(test)]
mod tests {
use super::*;
use generic_array::typenum::U96;
struct Params {
msg: &'static str,
@@ -512,15 +538,15 @@ mod tests {
q1y: "f6ed88a7aab56a488100e6f1174fa9810b47db13e86be999644922961206e184",
},
];
let dst = "QUUX-V01-CS02-with-P256_XMD:SHA-256_SSWU_RO_";
let dst = GenericArray::from(*b"QUUX-V01-CS02-with-P256_XMD:SHA-256_SSWU_RO_");
for tv in test_vectors {
let uniform_bytes = super::super::expand::expand_message_xmd::<sha2::Sha256>(
tv.msg.as_bytes(),
dst.as_bytes(),
96,
)
.unwrap();
let uniform_bytes =
super::super::expand::expand_message_xmd::<sha2::Sha256, U96, _, _>(
Some(tv.msg.as_bytes()),
dst,
)
.unwrap();
let u0 = BigInt::from_bytes_be(Sign::Plus, &uniform_bytes[..48]).mod_floor(&P);
let u1 = BigInt::from_bytes_be(Sign::Plus, &uniform_bytes[48..]).mod_floor(&P);
+38 -30
View File
@@ -7,26 +7,42 @@
use super::Group;
use crate::errors::InternalError;
use crate::hash::Hash;
use core::convert::TryInto;
use core::ops::Add;
use curve25519_dalek::{
constants::RISTRETTO_BASEPOINT_POINT,
ristretto::{CompressedRistretto, RistrettoPoint},
scalar::Scalar,
traits::Identity,
};
use generic_array::{typenum::U32, GenericArray};
use rand::{CryptoRng, RngCore};
use subtle::ConstantTimeEq;
use digest::{BlockInput, Digest};
use generic_array::{
typenum::{U1, U32, U64},
ArrayLength, GenericArray,
};
use rand_core::{CryptoRng, RngCore};
/// The implementation of such a subgroup for Ristretto
#[cfg(any(
feature = "ristretto255_u64",
feature = "ristretto255_u32",
feature = "ristretto255_fiat_u64",
feature = "ristretto255_fiat_u32",
feature = "ristretto255_simd",
))]
impl Group for RistrettoPoint {
const SUITE_ID: usize = 0x0001;
// 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<H: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, InternalError> {
let uniform_bytes = super::expand::expand_message_xmd::<H>(msg, dst, 64)?;
fn hash_to_curve<H: BlockInput + Digest, D: ArrayLength<u8> + Add<U1>>(
msg: &[u8],
dst: GenericArray<u8, D>,
) -> Result<Self, InternalError>
where
<D as Add<U1>>::Output: ArrayLength<u8>,
{
let uniform_bytes = super::expand::expand_message_xmd::<H, U64, _, _>(Some(msg), dst)?;
Ok(RistrettoPoint::from_uniform_bytes(
uniform_bytes
@@ -38,8 +54,19 @@ impl Group for RistrettoPoint {
// Implements the `HashToScalar()` function from
// https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-07.html#section-4.1
fn hash_to_scalar<H: Hash>(input: &[u8], dst: &[u8]) -> Result<Self::Scalar, InternalError> {
let uniform_bytes = super::expand::expand_message_xmd::<H>(input, dst, 64)?;
fn hash_to_scalar<
'a,
H: BlockInput + Digest,
D: ArrayLength<u8> + Add<U1>,
I: IntoIterator<Item = &'a [u8]>,
>(
input: I,
dst: GenericArray<u8, D>,
) -> Result<Self::Scalar, InternalError>
where
<D as Add<U1>>::Output: ArrayLength<u8>,
{
let uniform_bytes = super::expand::expand_message_xmd::<H, U64, _, _>(input, dst)?;
Ok(Scalar::from_bytes_mod_order_wide(
uniform_bytes
@@ -60,20 +87,9 @@ impl Group for RistrettoPoint {
fn random_nonzero_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
loop {
let scalar = {
#[cfg(not(test))]
{
let mut scalar_bytes = [0u8; 64];
rng.fill_bytes(&mut scalar_bytes);
Scalar::from_bytes_mod_order_wide(&scalar_bytes)
}
// Tests need an exact conversion from bytes to scalar, sampling only 32 bytes from rng
#[cfg(test)]
{
let mut scalar_bytes = [0u8; 32];
rng.fill_bytes(&mut scalar_bytes);
Scalar::from_bytes_mod_order(scalar_bytes)
}
let mut scalar_bytes = [0u8; 64];
rng.fill_bytes(&mut scalar_bytes);
Scalar::from_bytes_mod_order_wide(&scalar_bytes)
};
if scalar != Scalar::zero() {
@@ -115,12 +131,4 @@ impl Group for RistrettoPoint {
fn scalar_zero() -> Self::Scalar {
Self::Scalar::zero()
}
fn ct_equal(&self, other: &Self) -> bool {
ConstantTimeEq::ct_eq(self, other).into()
}
fn ct_equal_scalar(s1: &Self::Scalar, s2: &Self::Scalar) -> bool {
ConstantTimeEq::ct_eq(s1, s2).into()
}
}
+14 -21
View File
@@ -9,49 +9,42 @@
use crate::errors::InternalError;
use crate::group::Group;
use crate::CipherSuite;
// Test that the deserialization of a group element should throw an error
// if the identity element can be deserialized properly
#[test]
fn test_group_properties() -> Result<(), InternalError> {
use crate::tests::Ristretto255Sha512;
use curve25519_dalek::ristretto::RistrettoPoint;
test_identity_element_error::<Ristretto255Sha512>()?;
test_zero_scalar_error::<Ristretto255Sha512>()?;
test_identity_element_error::<RistrettoPoint>()?;
test_zero_scalar_error::<RistrettoPoint>()?;
#[cfg(feature = "p256")]
{
use crate::tests::P256Sha256;
use p256_::ProjectivePoint;
test_identity_element_error::<P256Sha256>()?;
test_zero_scalar_error::<P256Sha256>()?;
test_identity_element_error::<ProjectivePoint>()?;
test_zero_scalar_error::<ProjectivePoint>()?;
}
Ok(())
}
// Checks that the identity element cannot be deserialized
fn test_identity_element_error<CS: CipherSuite>() -> Result<(), InternalError> {
let identity = CS::Group::identity();
let result = CS::Group::from_element_slice(&identity.to_arr());
assert!(match result {
Err(InternalError::PointError) => true,
_ => false,
});
fn test_identity_element_error<G: Group>() -> Result<(), InternalError> {
let identity = G::identity();
let result = G::from_element_slice(&identity.to_arr());
assert!(matches!(result, Err(InternalError::PointError)));
Ok(())
}
// Checks that the zero scalar cannot be deserialized
fn test_zero_scalar_error<CS: CipherSuite>() -> Result<(), InternalError> {
let zero_scalar = CS::Group::scalar_zero();
let result = CS::Group::from_scalar_slice(&CS::Group::scalar_as_bytes(zero_scalar));
assert!(match result {
Err(InternalError::ZeroScalarError) => true,
_ => false,
});
fn test_zero_scalar_error<G: Group>() -> Result<(), InternalError> {
let zero_scalar = G::scalar_zero();
let result = G::from_scalar_slice(&G::scalar_as_bytes(zero_scalar));
assert!(matches!(result, Err(InternalError::ZeroScalarError)));
Ok(())
}
-17
View File
@@ -1,17 +0,0 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under both the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
//! A convenience trait for digest bounds used throughout the library
use digest::{BlockInput, FixedOutput, Reset, Update};
/// Trait inheriting the requirements from digest::Digest for compatibility with HKDF and HMAC
// Associated types could be simplified when they are made as defaults:
// https://github.com/rust-lang/rust/issues/29661
pub trait Hash: Update + BlockInput + FixedOutput + Reset + Default + Clone {}
impl<T: Update + BlockInput + FixedOutput + Reset + Default + Clone> Hash for T {}
+104 -154
View File
@@ -5,134 +5,124 @@
// 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$(: $bound:tt)?),+$(,)?>)?, [$field1:ident$(, $field2:ident)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => {
impl$(<$($gen$(: $bound)?),+>)? 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$(: $bound)?),+>)? Eq for $name$(<$($gen),+>)?
$(where $($type: Eq,)+)?
{}
impl$(<$($gen$(: $bound)?),+>)? 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$(: $bound)?),+>)? 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);)*
}
}
impl_with_bounds!(
$name$(<$($gen$(: $bound1 $(+ $bound2)*)?),+>)?
$(|$(@#bind: $type|,)? $(@#pd: $_1|,)? $(@$_2|,)?)+
$($trait2 => { $($fn2)? },)*
);
};
(tuple $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:tt$(, $field2:tt)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => {
impl$(<$($gen$(: $bound)?),+>)? 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()
}
}
// signature triggered when all traits are exhausted
(
$name:ident$(<$($gen:ident$(: $bound1:tt$( + $bound2:tt)*)?),+>)?
$(|$(@#bind: $type:ty|,)? $(@#pd: $_1:ty|,)? $(@$_2:ty|,)?)+
) => { };
}
impl$(<$($gen$(: $bound)?),+>)? Eq for $name$(<$($gen),+>)?
$(where $($type: Eq,)+)?
{}
impl$(<$($gen$(: $bound)?),+>)? 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$(: $bound)?),+>)? 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);)*
}
}
/// 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_clone_for {
(struct $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:ident$(, $field2:ident)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => {
impl$(<$($gen$(: $bound)?),+>)? Clone for $name$(<$($gen),+>)?
$(where $($type: Clone,)+)?
{
fn clone(&self) -> Self {
Self {
$field1: self.$field1.clone(),
$($field2: self.$field2.clone(),)*
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()
}
}
}
};
(tuple $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:tt$(, $field2:tt)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => {
impl$(<$($gen$(: $bound)?),+>)? Clone for $name$(<$($gen),+>)?
$(where $($type: Clone,)+)?
{
fn clone(&self) -> Self {
Self(
self.$field1.clone(),
$(self.$field2.clone(),)*
)
}
}
};
}
},
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(),)*
}
}
},
);
macro_rules! impl_zeroize_on_drop_for {
(struct $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:ident$(, $field2:ident)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => {
impl$(<$($gen$(: $bound)?),+>)? zeroize::Zeroize for $name$(<$($gen),+>)?
impl$(<$($gen$(: $bound1 $(+ $bound2)*)?),+>)? zeroize::Zeroize for $name$(<$($gen),+>)?
{
fn zeroize(&mut self) {
self.$field1.zeroize();
$(self.$field2.zeroize();)*
impl_internal_zeroize!(self, $(#$attr1)? $field1);
$(impl_internal_zeroize!(self, $(#$attr2)? $field2);)*
}
}
impl$(<$($gen$(: $bound)?),+>)? Drop for $name$(<$($gen),+>)?
impl$(<$($gen$(: $bound1 $(+ $bound2)*)?),+>)? Drop for $name$(<$($gen),+>)?
{
fn drop(&mut self) {
#[allow(unused_imports)]
use zeroize::Zeroize;
self.$field1.zeroize();
$(self.$field2.zeroize();)*
zeroize::Zeroize::zeroize(self);
}
}
};
}
/// Inner macro used for deriving `serde`'s `Serialize` and `Deserialize` traits.
macro_rules! impl_serialize_and_deserialize_for {
($t:ident) => {
#[cfg(feature = "serialize")]
impl<CS: CipherSuite> serde::Serialize for $t<CS> {
#[cfg(feature = "serde")]
impl$(<$($gen$(: $bound1 $(+ $bound2)*)?),+>)? serde_::Serialize for $name$(<$($gen),+>)? {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
S: serde_::Serializer,
{
if serializer.is_human_readable() {
serializer.serialize_str(&base64::encode(&self.serialize()))
@@ -142,62 +132,22 @@ macro_rules! impl_serialize_and_deserialize_for {
}
}
#[cfg(feature = "serialize")]
impl<'de, CS: CipherSuite> serde::Deserialize<'de> for $t<CS> {
#[cfg(feature = "serde")]
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>,
D: serde_::Deserializer<'de>,
{
use serde_::de::Error;
if deserializer.is_human_readable() {
let s = <&str>::deserialize(deserializer)?;
$t::<CS>::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<CS: CipherSuite> {
marker: core::marker::PhantomData<CS>,
}
impl<'de, CS: CipherSuite> serde::de::Visitor<'de> for ByteVisitor<CS> {
type Value = $t<CS>;
fn expecting(
&self,
formatter: &mut core::fmt::Formatter,
) -> core::fmt::Result {
formatter.write_str(core::concat!(
"the byte representation of a ",
core::stringify!($t)
))
}
fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
$t::<CS>::deserialize(value).map_err(|_| {
serde::de::Error::invalid_value(
serde::de::Unexpected::Bytes(value),
&core::concat!(
"invalid byte sequence for ",
core::stringify!($t)
),
)
})
}
}
deserializer.deserialize_bytes(ByteVisitor::<CS> {
marker: 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$(: $bound:tt)?),+$(,)?>)?, [$field1:ident$(, $field2:ident)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => {
impl_debug_eq_hash_for!(struct $name$(<$($gen$(: $bound)?),+>)?, [$field1$(, $field2)*], $([$($type),+])?);
impl_clone_for!(struct $name$(<$($gen$(: $bound)?),+>)?, [$field1$(, $field2)*], $([$($type),+])?);
impl_zeroize_on_drop_for!(struct $name$(<$($gen$(: $bound)?),+>)?, [$field1$(, $field2)*], $([$($type),+])?);
impl_serialize_and_deserialize_for!($name);
}
}
+91 -148
View File
@@ -24,12 +24,8 @@
//! We will use the following choices in this example:
//!
//! ```
//! use voprf::CipherSuite;
//! struct Default;
//! impl CipherSuite for Default {
//! type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! type Hash = sha2::Sha512;
//! }
//! type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! type Hash = sha2::Sha512;
//! ```
//!
//! ## Modes of Operation
@@ -56,17 +52,13 @@
//! client evaluations.
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # }
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! use voprf::NonVerifiableServer;
//! use rand::{rngs::OsRng, RngCore};
//!
//! let mut server_rng = OsRng;
//! let server = NonVerifiableServer::<Default>::new(&mut server_rng)
//! let server = NonVerifiableServer::<Group, Hash>::new(&mut server_rng)
//! .expect("Unable to construct server");
//! ```
//!
@@ -79,18 +71,14 @@
//! step of the VOPRF protocol.
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # }
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! use voprf::NonVerifiableClient;
//! use rand::{rngs::OsRng, RngCore};
//!
//! let mut client_rng = OsRng;
//! let client_blind_result = NonVerifiableClient::<Default>::blind(
//! b"input",
//! let client_blind_result = NonVerifiableClient::<Group, Hash>::blind(
//! b"input".to_vec(),
//! &mut client_rng,
//! ).expect("Unable to construct client");
//! ```
@@ -104,28 +92,23 @@
//! [EvaluationElement] to be sent to the client.
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # }
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # use voprf::NonVerifiableClient;
//! # use rand::{rngs::OsRng, RngCore};
//! #
//! # let mut client_rng = OsRng;
//! # let client_blind_result = NonVerifiableClient::<Default>::blind(
//! # b"input",
//! # let client_blind_result = NonVerifiableClient::<Group, Hash>::blind(
//! # b"input".to_vec(),
//! # &mut client_rng,
//! # ).expect("Unable to construct client");
//! # use voprf::NonVerifiableServer;
//! # let mut server_rng = OsRng;
//! # let server = NonVerifiableServer::<Default>::new(&mut server_rng)
//! # let server = NonVerifiableServer::<Group, Hash>::new(&mut server_rng)
//! # .expect("Unable to construct server");
//! use voprf::Metadata;
//! let server_evaluate_result = server.evaluate(
//! client_blind_result.message,
//! &Metadata::none(),
//! None,
//! ).expect("Unable to perform server evaluate");
//! ```
//!
@@ -133,40 +116,33 @@
//!
//! 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.
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # }
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # use voprf::NonVerifiableClient;
//! # use rand::{rngs::OsRng, RngCore};
//! #
//! # let mut client_rng = OsRng;
//! # let client_blind_result = NonVerifiableClient::<Default>::blind(
//! # b"input",
//! # let client_blind_result = NonVerifiableClient::<Group, Hash>::blind(
//! # b"input".to_vec(),
//! # &mut client_rng,
//! # ).expect("Unable to construct client");
//! # use voprf::NonVerifiableServer;
//! # let mut server_rng = OsRng;
//! # let server = NonVerifiableServer::<Default>::new(&mut server_rng)
//! # let server = NonVerifiableServer::<Group, Hash>::new(&mut server_rng)
//! # .expect("Unable to construct server");
//! # let server_evaluate_result = server.evaluate(
//! # client_blind_result.message,
//! # &Metadata::none(),
//! # None,
//! # ).expect("Unable to perform server evaluate");
//! use voprf::Metadata;
//! let client_finalize_result = client_blind_result.state.finalize(
//! server_evaluate_result.message,
//! &Metadata::none(),
//! None,
//! ).expect("Unable to perform client finalization");
//!
//! println!("VOPRF output: {:?}", client_finalize_result.output.to_vec());
//! println!("VOPRF output: {:?}", client_finalize_result.to_vec());
//! ```
//!
//! ## Verifiable Mode
@@ -189,17 +165,13 @@
//! client evaluations.
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # }
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! use voprf::VerifiableServer;
//! use rand::{rngs::OsRng, RngCore};
//!
//! let mut server_rng = OsRng;
//! let server = VerifiableServer::<Default>::new(&mut server_rng)
//! let server = VerifiableServer::<Group, Hash>::new(&mut server_rng)
//! .expect("Unable to construct server");
//!
//! // To be sent to the client
@@ -219,18 +191,14 @@
//! step of the VOPRF protocol.
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # }
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! use voprf::VerifiableClient;
//! use rand::{rngs::OsRng, RngCore};
//!
//! let mut client_rng = OsRng;
//! let client_blind_result = VerifiableClient::<Default>::blind(
//! b"input",
//! let client_blind_result = VerifiableClient::<Group, Hash>::blind(
//! b"input".to_vec(),
//! &mut client_rng,
//! ).expect("Unable to construct client");
//! ```
@@ -244,29 +212,24 @@
//! [EvaluationElement] to be sent to the client along with a proof.
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # }
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # use voprf::VerifiableClient;
//! # use rand::{rngs::OsRng, RngCore};
//! #
//! # let mut client_rng = OsRng;
//! # let client_blind_result = VerifiableClient::<Default>::blind(
//! # b"input",
//! # let client_blind_result = VerifiableClient::<Group, Hash>::blind(
//! # b"input".to_vec(),
//! # &mut client_rng,
//! # ).expect("Unable to construct client");
//! # use voprf::VerifiableServer;
//! # let mut server_rng = OsRng;
//! # let server = VerifiableServer::<Default>::new(&mut server_rng)
//! # let server = VerifiableServer::<Group, Hash>::new(&mut server_rng)
//! # .expect("Unable to construct server");
//! use voprf::Metadata;
//! let server_evaluate_result = server.evaluate(
//! &mut server_rng,
//! client_blind_result.message,
//! &Metadata::none(),
//! None,
//! ).expect("Unable to perform server evaluate");
//! ```
//!
@@ -275,43 +238,36 @@
//! 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.
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # }
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # use voprf::VerifiableClient;
//! # use rand::{rngs::OsRng, RngCore};
//! #
//! # let mut client_rng = OsRng;
//! # let client_blind_result = VerifiableClient::<Default>::blind(
//! # b"input",
//! # let client_blind_result = VerifiableClient::<Group, Hash>::blind(
//! # b"input".to_vec(),
//! # &mut client_rng,
//! # ).expect("Unable to construct client");
//! # use voprf::VerifiableServer;
//! # let mut server_rng = OsRng;
//! # let server = VerifiableServer::<Default>::new(&mut server_rng)
//! # let server = VerifiableServer::<Group, Hash>::new(&mut server_rng)
//! # .expect("Unable to construct server");
//! # let server_evaluate_result = server.evaluate(
//! # &mut server_rng,
//! # client_blind_result.message,
//! # &Metadata::none(),
//! # None,
//! # ).expect("Unable to perform server evaluate");
//! use voprf::Metadata;
//! let client_finalize_result = client_blind_result.state.finalize(
//! server_evaluate_result.message,
//! server_evaluate_result.proof,
//! server.get_public_key(),
//! &Metadata::none(),
//! None,
//! ).expect("Unable to perform client finalization");
//!
//! println!("VOPRF output: {:?}", client_finalize_result.output.to_vec());
//! println!("VOPRF output: {:?}", client_finalize_result.to_vec());
//! ```
//!
//! # Advanced Usage
@@ -333,12 +289,8 @@
//! states and messages:
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # }
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # use voprf::VerifiableClient;
//! # use rand::{rngs::OsRng, RngCore};
//! #
@@ -346,8 +298,8 @@
//! let mut client_states = vec![];
//! let mut client_messages = vec![];
//! for _ in 0..10 {
//! let client_blind_result = VerifiableClient::<Default>::blind(
//! b"input",
//! let client_blind_result = VerifiableClient::<Group, Hash>::blind(
//! b"input".to_vec(),
//! &mut client_rng,
//! ).expect("Unable to construct client");
//! client_states.push(client_blind_result.state);
@@ -361,12 +313,8 @@
//! along with a single proof:
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # }
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # use voprf::VerifiableClient;
//! # use rand::{rngs::OsRng, RngCore};
//! #
@@ -374,38 +322,32 @@
//! # let mut client_states = vec![];
//! # let mut client_messages = vec![];
//! # for _ in 0..10 {
//! # let client_blind_result = VerifiableClient::<Default>::blind(
//! # b"input",
//! # let client_blind_result = VerifiableClient::<Group, Hash>::blind(
//! # b"input".to_vec(),
//! # &mut client_rng,
//! # ).expect("Unable to construct client");
//! # client_states.push(client_blind_result.state);
//! # client_messages.push(client_blind_result.message);
//! # }
//! # use voprf::Metadata;
//! # use voprf::VerifiableServer;
//! let mut server_rng = OsRng;
//! # let server = VerifiableServer::<Default>::new(&mut server_rng)
//! # let server = VerifiableServer::<Group, Hash>::new(&mut server_rng)
//! # .expect("Unable to construct server");
//! let server_batch_evaluate_result = server.batch_evaluate(
//! &mut server_rng,
//! &client_messages,
//! &Metadata::none(),
//! None,
//! ).expect("Unable to perform server batch evaluate");
//! ```
//!
//! Then, the client calls [VerifiableClient::batch_finalize] on
//! the client states saved from the first step, along with the messages
//! returned by the server (constructing a [BatchFinalizeInput]), along with the
//! server's proof, in order to produce a vector of outputs if the proof
//! verifies correctly.
//! returned by the server, along with the server's proof, in order to produce
//! a vector of outputs if the proof verifies correctly.
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # }
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # use voprf::VerifiableClient;
//! # use rand::{rngs::OsRng, RngCore};
//! #
@@ -413,36 +355,31 @@
//! # let mut client_states = vec![];
//! # let mut client_messages = vec![];
//! # for _ in 0..10 {
//! # let client_blind_result = VerifiableClient::<Default>::blind(
//! # b"input",
//! # let client_blind_result = VerifiableClient::<Group, Hash>::blind(
//! # b"input".to_vec(),
//! # &mut client_rng,
//! # ).expect("Unable to construct client");
//! # client_states.push(client_blind_result.state);
//! # client_messages.push(client_blind_result.message);
//! # }
//! # use voprf::Metadata;
//! # use voprf::VerifiableServer;
//! use voprf::BatchFinalizeInput;
//! let mut server_rng = OsRng;
//! # let server = VerifiableServer::<Default>::new(&mut server_rng)
//! # let server = VerifiableServer::<Group, Hash>::new(&mut server_rng)
//! # .expect("Unable to construct server");
//! # let server_batch_evaluate_result = server.batch_evaluate(
//! # &mut server_rng,
//! # &client_messages,
//! # &Metadata::none(),
//! # None,
//! # ).expect("Unable to perform server batch evaluate");
//! let batch_finalize_input = BatchFinalizeInput::new(
//! client_states,
//! server_batch_evaluate_result.messages,
//! );
//! let client_batch_finalize_result = VerifiableClient::batch_finalize(
//! batch_finalize_input,
//! &client_states,
//! &server_batch_evaluate_result.messages,
//! server_batch_evaluate_result.proof,
//! server.get_public_key(),
//! &Metadata::none(),
//! None,
//! ).expect("Unable to perform client batch finalization");
//!
//! println!("VOPRF batch outputs: {:?}", client_batch_finalize_result.outputs);
//! println!("VOPRF batch outputs: {:?}", client_batch_finalize_result);
//! ```
//!
//! ## Metadata
@@ -454,35 +391,45 @@
//! This metadata can be constructed with some type of higher-level domain separation
//! to avoid cross-protocol attacks or related issues.
//!
//! The default metadata simply consists of the empty vector of bytes, but a custom
//! metadata can be specified, for example, by: `Metadata(b"custom metadata")`.
//! A custom metadata can be specified, for example, by: `Some(b"custom metadata")`.
//!
//! # Features
//!
//! - The `p256` feature enables using p256 as the underlying group for the [CipherSuite] choice.
//! - The `p256` feature enables using p256 as the underlying group for the [Group](group::Group) choice.
//! Note that this is currently an experimental feature ⚠️, and is not yet ready for production use.
//!
//! - The `serialize` feature, enabled by default, provides convenience functions for serializing and deserializing with
//! - The `serde` feature, enabled by default, provides convenience functions for serializing and deserializing with
//! [serde](https://serde.rs/).
//!
//! - The `u32_backend` and `u64_backend` features are re-exported from
//! - The `danger` feature, disabled by default, exposes functions for setting and getting
//! internal values not available in the default API. These functions are intended for use in
//! by higher-level cryptographic protocols that need access to these raw values and are able to
//! perform the necessary validations on them (such as being valid group elements).
//!
//! - The backend features are re-exported from
//! [curve25519-dalek](https://doc.dalek.rs/curve25519_dalek/index.html#backends-and-features) and allow for selecting
//! the corresponding backend for the curve arithmetic used. The `u64_backend` feature is included as the default.
//! the corresponding backend for the curve arithmetic used. The `ristretto255_u64` feature is included as the default.
//! Other features are mapped as `ristretto255_u32`, `ristretto255_fiat_u64` and `ristretto255_fiat_u32`.
//!
//! - The `ristretto255_simd` feature is re-exported from
//! [curve25519-dalek](https://doc.dalek.rs/curve25519_dalek/index.html#backends-and-features) and enables parallel formulas,
//! using either AVX2 or AVX512-IFMA. This will automatically enable the `ristretto255_u64` feature and requires Rust nightly.
#![cfg_attr(not(feature = "bench"), deny(missing_docs))]
#![deny(unsafe_code)]
#![warn(clippy::cargo, missing_docs)]
#![allow(clippy::multiple_crate_versions)]
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]
extern crate alloc;
#[macro_use]
mod impls;
#[macro_use]
mod serialization;
mod ciphersuite;
mod util;
pub mod errors;
pub mod group;
pub mod hash;
mod serialization;
mod voprf;
#[cfg(test)]
@@ -490,12 +437,8 @@ mod tests;
// Exports
pub use rand;
pub use crate::ciphersuite::CipherSuite;
pub use crate::voprf::{
BatchFinalizeInput, BlindedElement, EvaluationElement, Metadata, NonVerifiableClient,
NonVerifiableClientBlindResult, NonVerifiableClientFinalizeResult, NonVerifiableServer,
NonVerifiableServerEvaluateResult, VerifiableClient, VerifiableClientBlindResult,
VerifiableClientFinalizeResult, VerifiableServer, VerifiableServerEvaluateResult,
BlindedElement, EvaluationElement, NonVerifiableClient, NonVerifiableClientBlindResult,
NonVerifiableServer, NonVerifiableServerEvaluateResult, VerifiableClient,
VerifiableClientBlindResult, VerifiableServer, VerifiableServerEvaluateResult,
};
+62 -98
View File
@@ -9,7 +9,6 @@
//! in the VOPRF protocol
use crate::{
ciphersuite::CipherSuite,
errors::InternalError,
group::Group,
voprf::{
@@ -18,138 +17,143 @@ use crate::{
},
};
use alloc::vec::Vec;
use generic_array::{typenum::Unsigned, GenericArray};
use core::marker::PhantomData;
use digest::{BlockInput, Digest};
use generic_array::typenum::Unsigned;
//////////////////////////////////////////////////////////
// Serialization and Deserialization for High-Level API //
// ==================================================== //
//////////////////////////////////////////////////////////
impl<CS: CipherSuite> NonVerifiableClient<CS> {
impl<G: Group, H: BlockInput + Digest> NonVerifiableClient<G, H> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
[
CS::Group::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
pub fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
let scalar_len = <CS::Group as Group>::ScalarLen::USIZE;
let scalar_len = <G as Group>::ScalarLen::USIZE;
if input.len() < scalar_len {
return Err(InternalError::SizeError);
}
let blind = CS::Group::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 { blind, data })
Ok(Self {
blind,
data,
hash: PhantomData,
})
}
}
impl<CS: CipherSuite> VerifiableClient<CS> {
impl<G: Group, H: BlockInput + Digest> VerifiableClient<G, H> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
[
CS::Group::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()
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
let scalar_len = <CS::Group as Group>::ScalarLen::USIZE;
let elem_len = <CS::Group as Group>::ElemLen::USIZE;
let scalar_len = <G as Group>::ScalarLen::USIZE;
let elem_len = <G as Group>::ElemLen::USIZE;
if input.len() < scalar_len + elem_len {
return Err(InternalError::SizeError);
}
let blind = CS::Group::from_scalar_slice(GenericArray::from_slice(&input[..scalar_len]))?;
let blinded_element = CS::Group::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 {
blind,
blinded_element,
data,
hash: PhantomData,
})
}
}
impl<CS: CipherSuite> NonVerifiableServer<CS> {
impl<G: Group, H: BlockInput + Digest> NonVerifiableServer<G, H> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
CS::Group::scalar_as_bytes(self.sk).to_vec()
G::scalar_as_bytes(self.sk).to_vec()
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
let scalar_len = <CS::Group as Group>::ScalarLen::USIZE;
let scalar_len = <G as Group>::ScalarLen::USIZE;
if input.len() != scalar_len {
return Err(InternalError::SizeError);
}
let sk = CS::Group::from_scalar_slice(GenericArray::from_slice(input))?;
let sk = G::from_scalar_slice(input)?;
Ok(Self { sk })
Ok(Self {
sk,
hash: PhantomData,
})
}
}
impl<CS: CipherSuite> VerifiableServer<CS> {
impl<G: Group, H: BlockInput + Digest> VerifiableServer<G, H> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
[
CS::Group::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
pub fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
let scalar_len = <CS::Group as Group>::ScalarLen::USIZE;
let elem_len = <CS::Group as Group>::ElemLen::USIZE;
let scalar_len = <G as Group>::ScalarLen::USIZE;
let elem_len = <G as Group>::ElemLen::USIZE;
if input.len() != scalar_len + elem_len {
return Err(InternalError::SizeError);
}
let sk = CS::Group::from_scalar_slice(GenericArray::from_slice(&input[..scalar_len]))?;
let pk = CS::Group::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, pk })
Ok(Self {
sk,
pk,
hash: PhantomData,
})
}
}
impl<CS: CipherSuite> Proof<CS> {
impl<G: Group, H: BlockInput + Digest> Proof<G, H> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
[
CS::Group::scalar_as_bytes(self.c_scalar),
CS::Group::scalar_as_bytes(self.s_scalar),
G::scalar_as_bytes(self.c_scalar),
G::scalar_as_bytes(self.s_scalar),
]
.concat()
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
let scalar_len = <CS::Group as Group>::ScalarLen::USIZE;
if input.len() < scalar_len + scalar_len {
let scalar_len = <G as Group>::ScalarLen::USIZE;
if input.len() != scalar_len + scalar_len {
return Err(InternalError::SizeError);
}
Ok(Proof {
c_scalar: CS::Group::from_scalar_slice(GenericArray::from_slice(&input[..scalar_len]))?,
s_scalar: CS::Group::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,
})
}
}
impl<CS: CipherSuite> BlindedElement<CS> {
impl<G: Group, H: BlockInput + Digest> BlindedElement<G, H> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
self.value.to_arr().to_vec()
@@ -157,13 +161,18 @@ impl<CS: CipherSuite> BlindedElement<CS> {
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
let elem_len = <G as Group>::ElemLen::USIZE;
if input.len() != elem_len {
return Err(InternalError::SizeError);
}
Ok(Self {
value: CS::Group::from_element_slice(GenericArray::from_slice(input))?,
value: G::from_element_slice(input)?,
hash: PhantomData,
})
}
}
impl<CS: CipherSuite> EvaluationElement<CS> {
impl<G: Group, H: BlockInput + Digest> EvaluationElement<G, H> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
self.value.to_arr().to_vec()
@@ -171,58 +180,13 @@ impl<CS: CipherSuite> EvaluationElement<CS> {
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
let elem_len = <G as Group>::ElemLen::USIZE;
if input.len() != elem_len {
return Err(InternalError::SizeError);
}
Ok(Self {
value: CS::Group::from_element_slice(GenericArray::from_slice(input))?,
value: G::from_element_slice(input)?,
hash: PhantomData,
})
}
}
//////////////////////
// Helper Functions //
// ================ //
//////////////////////
// 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>();
// Check if input >= 256^length
if (sizeof_usize as u32 - input.leading_zeros() / 8) > length as u32 {
return Err(InternalError::SerializationError);
}
if length <= sizeof_usize {
return Ok((&input.to_be_bytes()[sizeof_usize - length..]).to_vec());
}
let mut output = alloc::vec![0u8; length];
output.splice(
length - sizeof_usize..length,
input.to_be_bytes().iter().cloned(),
);
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())
}
#[cfg(test)]
mod unit_tests {
use super::*;
// Test the error condition for I2OSP
#[test]
fn test_i2osp_err_check() {
assert!(i2osp(0, 1).is_ok());
assert!(i2osp(255, 1).is_ok());
assert!(i2osp(256, 1).is_err());
assert!(i2osp(257, 1).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());
}
}
+1 -1
View File
@@ -7,7 +7,7 @@
use alloc::vec::Vec;
use core::cmp::min;
use rand::{CryptoRng, Error, RngCore};
use rand_core::{CryptoRng, Error, RngCore};
/// A simple implementation of `RngCore` for testing purposes.
///
-15
View File
@@ -9,18 +9,3 @@ mod mock_rng;
mod parser;
mod voprf_test_vectors;
mod voprf_vectors;
/// Ciphersuite definitions for tests
pub(crate) struct Ristretto255Sha512;
impl crate::CipherSuite for Ristretto255Sha512 {
type Group = curve25519_dalek::ristretto::RistrettoPoint;
type Hash = sha2::Sha512;
}
#[cfg(feature = "p256")]
pub(crate) struct P256Sha256;
#[cfg(feature = "p256")]
impl crate::CipherSuite for P256Sha256 {
type Group = p256_::ProjectivePoint;
type Hash = sha2::Sha256;
}
+3 -3
View File
@@ -85,7 +85,7 @@ fn parse_params(input: &str) -> String {
// If line contains =, then
if line.contains('=') {
// Clear out any existing string and flush to params
if param.len() > 0 {
if !param.is_empty() {
param += "\"";
params.push(param);
}
@@ -97,11 +97,11 @@ fn parse_params(input: &str) -> String {
param = format!(" \"{}\": \"{}", key, val);
} else {
let s = line.trim().to_string();
if s.contains("~") || s.contains("#") {
if s.contains('~') || s.contains('#') {
// Ignore comment lines
continue;
}
if s.len() > 0 {
if !s.is_empty() {
param += &s;
}
}
+77 -77
View File
@@ -6,17 +6,17 @@
// of this source tree.
use crate::{
ciphersuite::CipherSuite,
errors::InternalError,
group::Group,
tests::{mock_rng::CycleRng, parser::*},
voprf::{
BatchFinalizeInput, BlindedElement, EvaluationElement, Metadata, NonVerifiableClient,
NonVerifiableServer, Proof, VerifiableClient, VerifiableServer,
BlindedElement, EvaluationElement, NonVerifiableClient, NonVerifiableServer, Proof,
VerifiableClient, VerifiableServer,
},
};
use alloc::string::ToString;
use alloc::vec::Vec;
use digest::{BlockInput, Digest};
use generic_array::GenericArray;
use json::JsonValue;
@@ -40,14 +40,14 @@ fn populate_test_vectors(values: &JsonValue) -> VOPRFTestVectorParameters {
seed: decode(values, "seed"),
sksm: decode(values, "skSm"),
pksm: decode(values, "pkSm"),
input: decode_vec(&values, "Input"),
input: decode_vec(values, "Input"),
info: decode(values, "Info"),
blind: decode_vec(&values, "Blind"),
blinded_element: decode_vec(&values, "BlindedElement"),
evaluation_element: decode_vec(&values, "EvaluationElement"),
blind: decode_vec(values, "Blind"),
blinded_element: decode_vec(values, "BlindedElement"),
evaluation_element: decode_vec(values, "EvaluationElement"),
proof: decode(values, "Proof"),
proof_random_scalar: decode(values, "ProofRandomScalar"),
output: decode_vec(&values, "Output"),
output: decode_vec(values, "Output"),
}
}
@@ -55,7 +55,7 @@ fn decode(values: &JsonValue, key: &str) -> Vec<u8> {
values[key]
.as_str()
.and_then(|s| hex::decode(&s.to_string()).ok())
.unwrap_or(vec![])
.unwrap_or_default()
}
fn decode_vec(values: &JsonValue, key: &str) -> Vec<Vec<u8>> {
@@ -85,7 +85,8 @@ fn test_vectors() -> Result<(), InternalError> {
let rfc = json::parse(rfc_to_json(super::voprf_vectors::VECTORS).as_str())
.expect("Could not parse json");
use crate::tests::Ristretto255Sha512;
use curve25519_dalek::ristretto::RistrettoPoint;
use sha2::Sha512;
let ristretto_base_tvs = json_to_test_vectors!(
rfc,
@@ -99,19 +100,20 @@ fn test_vectors() -> Result<(), InternalError> {
String::from("Verifiable")
);
test_base_seed_to_key::<Ristretto255Sha512>(&ristretto_base_tvs)?;
test_base_blind::<Ristretto255Sha512>(&ristretto_base_tvs)?;
test_base_evaluate::<Ristretto255Sha512>(&ristretto_base_tvs)?;
test_base_finalize::<Ristretto255Sha512>(&ristretto_base_tvs)?;
test_base_seed_to_key::<RistrettoPoint, Sha512>(&ristretto_base_tvs)?;
test_base_blind::<RistrettoPoint, Sha512>(&ristretto_base_tvs)?;
test_base_evaluate::<RistrettoPoint, Sha512>(&ristretto_base_tvs)?;
test_base_finalize::<RistrettoPoint, Sha512>(&ristretto_base_tvs)?;
test_verifiable_seed_to_key::<Ristretto255Sha512>(&ristretto_verifiable_tvs)?;
test_verifiable_blind::<Ristretto255Sha512>(&ristretto_verifiable_tvs)?;
test_verifiable_evaluate::<Ristretto255Sha512>(&ristretto_verifiable_tvs)?;
test_verifiable_finalize::<Ristretto255Sha512>(&ristretto_verifiable_tvs)?;
test_verifiable_seed_to_key::<RistrettoPoint, Sha512>(&ristretto_verifiable_tvs)?;
test_verifiable_blind::<RistrettoPoint, Sha512>(&ristretto_verifiable_tvs)?;
test_verifiable_evaluate::<RistrettoPoint, Sha512>(&ristretto_verifiable_tvs)?;
test_verifiable_finalize::<RistrettoPoint, Sha512>(&ristretto_verifiable_tvs)?;
#[cfg(feature = "p256")]
{
use crate::tests::P256Sha256;
use p256_::ProjectivePoint;
use sha2::Sha256;
let p256_base_tvs =
json_to_test_vectors!(rfc, String::from("P-256, SHA-256"), String::from("Base"));
@@ -122,43 +124,43 @@ fn test_vectors() -> Result<(), InternalError> {
String::from("Verifiable")
);
test_base_seed_to_key::<P256Sha256>(&p256_base_tvs)?;
test_base_blind::<P256Sha256>(&p256_base_tvs)?;
test_base_evaluate::<P256Sha256>(&p256_base_tvs)?;
test_base_finalize::<P256Sha256>(&p256_base_tvs)?;
test_base_seed_to_key::<ProjectivePoint, Sha256>(&p256_base_tvs)?;
test_base_blind::<ProjectivePoint, Sha256>(&p256_base_tvs)?;
test_base_evaluate::<ProjectivePoint, Sha256>(&p256_base_tvs)?;
test_base_finalize::<ProjectivePoint, Sha256>(&p256_base_tvs)?;
test_verifiable_seed_to_key::<P256Sha256>(&p256_verifiable_tvs)?;
test_verifiable_blind::<P256Sha256>(&p256_verifiable_tvs)?;
test_verifiable_evaluate::<P256Sha256>(&p256_verifiable_tvs)?;
test_verifiable_finalize::<P256Sha256>(&p256_verifiable_tvs)?;
test_verifiable_seed_to_key::<ProjectivePoint, Sha256>(&p256_verifiable_tvs)?;
test_verifiable_blind::<ProjectivePoint, Sha256>(&p256_verifiable_tvs)?;
test_verifiable_evaluate::<ProjectivePoint, Sha256>(&p256_verifiable_tvs)?;
test_verifiable_finalize::<ProjectivePoint, Sha256>(&p256_verifiable_tvs)?;
}
Ok(())
}
fn test_base_seed_to_key<CS: CipherSuite>(
fn test_base_seed_to_key<G: Group, H: BlockInput + Digest>(
tvs: &[VOPRFTestVectorParameters],
) -> Result<(), InternalError> {
for parameters in tvs {
let server = NonVerifiableServer::<CS>::new_from_seed(&parameters.seed)?;
let server = NonVerifiableServer::<G, H>::new_from_seed(&parameters.seed)?;
assert_eq!(
&parameters.sksm,
&CS::Group::scalar_as_bytes(server.get_private_key()).to_vec()
&G::scalar_as_bytes(server.get_private_key()).to_vec()
);
}
Ok(())
}
fn test_verifiable_seed_to_key<CS: CipherSuite>(
fn test_verifiable_seed_to_key<G: Group, H: BlockInput + Digest>(
tvs: &[VOPRFTestVectorParameters],
) -> Result<(), InternalError> {
for parameters in tvs {
let server = VerifiableServer::<CS>::new_from_seed(&parameters.seed)?;
let server = VerifiableServer::<G, H>::new_from_seed(&parameters.seed)?;
assert_eq!(
&parameters.sksm,
&CS::Group::scalar_as_bytes(server.get_private_key()).to_vec()
&G::scalar_as_bytes(server.get_private_key()).to_vec()
);
assert_eq!(&parameters.pksm, &server.get_public_key().to_arr().to_vec());
}
@@ -166,17 +168,21 @@ fn test_verifiable_seed_to_key<CS: CipherSuite>(
}
// Tests input -> blind, blinded_element
fn test_base_blind<CS: CipherSuite>(
fn test_base_blind<G: Group, H: BlockInput + Digest>(
tvs: &[VOPRFTestVectorParameters],
) -> Result<(), InternalError> {
for parameters in tvs {
for i in 0..parameters.input.len() {
let mut rng = CycleRng::new(parameters.blind[i].to_vec());
let client_result = NonVerifiableClient::<CS>::blind(&parameters.input[i], &mut rng)?;
let blind =
G::from_scalar_slice(&GenericArray::clone_from_slice(&parameters.blind[i]))?;
let client_result = NonVerifiableClient::<G, H>::deterministic_blind_unchecked(
parameters.input[i].clone(),
blind,
)?;
assert_eq!(
&parameters.blind[i],
&CS::Group::scalar_as_bytes(client_result.state.get_blind()).to_vec()
&G::scalar_as_bytes(client_result.state.get_blind()).to_vec()
);
assert_eq!(
&parameters.blinded_element[i],
@@ -188,18 +194,21 @@ fn test_base_blind<CS: CipherSuite>(
}
// Tests input -> blind, blinded_element
fn test_verifiable_blind<CS: CipherSuite>(
fn test_verifiable_blind<G: Group, H: BlockInput + Digest>(
tvs: &[VOPRFTestVectorParameters],
) -> Result<(), InternalError> {
for parameters in tvs {
for i in 0..parameters.input.len() {
let mut rng = CycleRng::new(parameters.blind[i].to_vec());
let client_blind_result =
VerifiableClient::<CS>::blind(&parameters.input[i], &mut rng)?;
let blind =
G::from_scalar_slice(&GenericArray::clone_from_slice(&parameters.blind[i]))?;
let client_blind_result = VerifiableClient::<G, H>::deterministic_blind_unchecked(
parameters.input[i].clone(),
blind,
)?;
assert_eq!(
&parameters.blind[i],
&CS::Group::scalar_as_bytes(client_blind_result.state.get_blind()).to_vec()
&G::scalar_as_bytes(client_blind_result.state.get_blind()).to_vec()
);
assert_eq!(
&parameters.blinded_element[i],
@@ -211,15 +220,15 @@ fn test_verifiable_blind<CS: CipherSuite>(
}
// Tests sksm, blinded_element -> evaluation_element
fn test_base_evaluate<CS: CipherSuite>(
fn test_base_evaluate<G: Group, H: BlockInput + Digest>(
tvs: &[VOPRFTestVectorParameters],
) -> Result<(), InternalError> {
for parameters in tvs {
for i in 0..parameters.input.len() {
let server = NonVerifiableServer::<CS>::new_with_key(&parameters.sksm)?;
let server = NonVerifiableServer::<G, H>::new_with_key(&parameters.sksm)?;
let server_result = server.evaluate(
BlindedElement::deserialize(&parameters.blinded_element[i])?,
&Metadata(parameters.info.clone()),
Some(&parameters.info),
)?;
assert_eq!(
@@ -231,23 +240,20 @@ fn test_base_evaluate<CS: CipherSuite>(
Ok(())
}
fn test_verifiable_evaluate<CS: CipherSuite>(
fn test_verifiable_evaluate<G: Group, H: BlockInput + Digest>(
tvs: &[VOPRFTestVectorParameters],
) -> Result<(), InternalError> {
for parameters in tvs {
let mut rng = CycleRng::new(parameters.proof_random_scalar.clone());
let server = VerifiableServer::<CS>::new_with_key(&parameters.sksm)?;
let server = VerifiableServer::<G, H>::new_with_key(&parameters.sksm)?;
let mut blinded_elements = vec![];
for blinded_element_bytes in &parameters.blinded_element {
blinded_elements.push(BlindedElement::deserialize(&blinded_element_bytes)?);
blinded_elements.push(BlindedElement::deserialize(blinded_element_bytes)?);
}
let batch_evaluate_result = server.batch_evaluate(
&mut rng,
&blinded_elements,
&Metadata(parameters.info.clone()),
)?;
let batch_evaluate_result =
server.batch_evaluate(&mut rng, &blinded_elements, Some(&parameters.info))?;
for i in 0..parameters.evaluation_element.len() {
assert_eq!(
@@ -262,70 +268,64 @@ fn test_verifiable_evaluate<CS: CipherSuite>(
}
// Tests input, blind, evaluation_element -> output
fn test_base_finalize<CS: CipherSuite>(
fn test_base_finalize<G: Group, H: BlockInput + Digest>(
tvs: &[VOPRFTestVectorParameters],
) -> Result<(), InternalError> {
for parameters in tvs {
for i in 0..parameters.input.len() {
let client = NonVerifiableClient::<CS>::from_data_and_blind(
let client = NonVerifiableClient::<G, H>::from_data_and_blind(
&parameters.input[i],
&<CS::Group as Group>::from_scalar_slice(&GenericArray::clone_from_slice(
<G as Group>::from_scalar_slice(&GenericArray::clone_from_slice(
&parameters.blind[i],
))?,
);
let client_finalize_result = client.finalize(
EvaluationElement::deserialize(&parameters.evaluation_element[i])?,
&Metadata(parameters.info.clone()),
Some(&parameters.info),
)?;
assert_eq!(
&parameters.output[i],
&client_finalize_result.output.to_vec()
);
assert_eq!(&parameters.output[i], &client_finalize_result.to_vec());
}
}
Ok(())
}
fn test_verifiable_finalize<CS: CipherSuite>(
fn test_verifiable_finalize<G: Group, H: BlockInput + Digest>(
tvs: &[VOPRFTestVectorParameters],
) -> Result<(), InternalError> {
for parameters in tvs {
let mut clients = vec![];
for i in 0..parameters.input.len() {
let client = VerifiableClient::<CS>::from_data_and_blind(
let client = VerifiableClient::<G, H>::from_data_and_blind_and_element(
&parameters.input[i],
&<CS::Group as Group>::from_scalar_slice(&GenericArray::clone_from_slice(
<G as Group>::from_scalar_slice(&GenericArray::clone_from_slice(
&parameters.blind[i],
))?,
&<CS::Group as Group>::from_element_slice(&GenericArray::clone_from_slice(
<G as Group>::from_element_slice(&GenericArray::clone_from_slice(
&parameters.blinded_element[i],
))?,
);
clients.push(client.clone());
}
let batch_finalize_input = BatchFinalizeInput::new(
clients,
parameters
.evaluation_element
.iter()
.map(|x| EvaluationElement::deserialize(x).unwrap())
.collect(),
);
let messages: Vec<_> = parameters
.evaluation_element
.iter()
.map(|x| EvaluationElement::deserialize(x).unwrap())
.collect();
let batch_result = VerifiableClient::batch_finalize(
batch_finalize_input,
&clients,
&messages,
Proof::deserialize(&parameters.proof)?,
CS::Group::from_element_slice(GenericArray::from_slice(&parameters.pksm))?,
&Metadata(parameters.info.clone()),
G::from_element_slice(GenericArray::from_slice(&parameters.pksm))?,
Some(&parameters.info),
)?;
assert_eq!(
parameters.output,
batch_result
.outputs
.iter()
.map(|arr| arr.to_vec())
.collect::<Vec<Vec<u8>>>()
+180
View File
@@ -0,0 +1,180 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under both the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
//! Helper functions
use crate::errors::InternalError;
use core::array::IntoIter;
use generic_array::{typenum::U0, ArrayLength, GenericArray};
// Corresponds to the I2OSP() function from RFC8017
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) > L::U32 {
return Err(InternalError::SerializationError);
}
if L::USIZE <= SIZEOF_USIZE {
return Ok(GenericArray::clone_from_slice(
&input.to_be_bytes()[SIZEOF_USIZE - L::USIZE..],
));
}
let mut output = GenericArray::default();
output[L::USIZE - SIZEOF_USIZE..L::USIZE].copy_from_slice(&input.to_be_bytes());
Ok(output)
}
/// Simplifies handling of [`serialize()`] output and implements [`Iterator`].
pub(crate) struct Serialized<'a, L1: ArrayLength<u8>, L2: ArrayLength<u8>> {
octet: GenericArray<u8, L1>,
input: Input<'a, L2>,
}
enum Input<'a, L: ArrayLength<u8>> {
Owned(GenericArray<u8, L>),
Borrowed(&'a [u8]),
}
impl<'a, L1: ArrayLength<u8>, L2: ArrayLength<u8>> IntoIterator for &'a Serialized<'a, L1, L2> {
type Item = &'a [u8];
type IntoIter = IntoIter<&'a [u8], 2>;
fn into_iter(self) -> Self::IntoIter {
IntoIter::new([
&self.octet,
match self.input {
Input::Owned(ref bytes) => bytes,
Input::Borrowed(bytes) => bytes,
},
])
}
}
// Computes I2OSP(len(input), max_bytes) || input
pub(crate) fn serialize<L: ArrayLength<u8>>(
input: &[u8],
) -> Result<Serialized<L, U0>, InternalError> {
Ok(Serialized {
octet: i2osp::<L>(input.len())?,
input: Input::Borrowed(input),
})
}
// Variation of `serialize` that takes an owned `input`
pub(crate) fn serialize_owned<L1: ArrayLength<u8>, L2: ArrayLength<u8>>(
input: GenericArray<u8, L2>,
) -> Result<Serialized<'static, L1, L2>, InternalError> {
Ok(Serialized {
octet: i2osp::<L1>(input.len())?,
input: Input::Owned(input),
})
}
macro_rules! chain_name {
($var:ident, $mod:ident) => {
$mod
};
($var:ident) => {
$var
};
}
macro_rules! chain_skip {
($var:ident, $feed:expr) => {
$feed
};
($var:ident) => {
&$var
};
}
/// The purpose of this macro is to simplify [`concat`](alloc::slice::Concat::concat)ing
/// slices into an [`Iterator`] to avoid allocation
macro_rules! chain {
(
$var:ident,
$item1:expr $(=> |$mod1:ident| $feed1:expr)?,
$($item2:expr $(=> |$mod2:ident| $feed2:expr)?),+$(,)?
) => {
let chain_name!(__temp$(, $mod1)?) = $item1;
let $var = (chain_skip!(__temp$(, $feed1)?)).into_iter();
$(
let chain_name!(__temp$(, $mod2)?) = $item2;
let $var = $var.chain(chain_skip!(__temp$(, $feed2)?));
)+
};
}
#[cfg(test)]
mod unit_tests {
use super::*;
use crate::voprf::{
BlindedElement, EvaluationElement, NonVerifiableClient, NonVerifiableServer, Proof,
VerifiableClient, VerifiableServer,
};
use curve25519_dalek::ristretto::RistrettoPoint;
use generic_array::typenum::{U1, U2};
use proptest::{collection::vec, prelude::*};
use sha2::Sha512;
// Test the error condition for I2OSP
#[test]
fn test_i2osp_err_check() {
assert!(i2osp::<U1>(0).is_ok());
assert!(i2osp::<U1>(255).is_ok());
assert!(i2osp::<U1>(256).is_err());
assert!(i2osp::<U1>(257).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());
}
proptest! {
#[test]
fn test_nocrash_nonverifiable_client(bytes in vec(any::<u8>(), 0..200)) {
NonVerifiableClient::<RistrettoPoint, Sha512>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_verifiable_client(bytes in vec(any::<u8>(), 0..200)) {
VerifiableClient::<RistrettoPoint, Sha512>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_nonverifiable_server(bytes in vec(any::<u8>(), 0..200)) {
NonVerifiableServer::<RistrettoPoint, Sha512>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_verifiable_server(bytes in vec(any::<u8>(), 0..200)) {
VerifiableServer::<RistrettoPoint, Sha512>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_blinded_element(bytes in vec(any::<u8>(), 0..200)) {
BlindedElement::<RistrettoPoint, Sha512>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_evaluation_element(bytes in vec(any::<u8>(), 0..200)) {
EvaluationElement::<RistrettoPoint, Sha512>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_proof(bytes in vec(any::<u8>(), 0..200)) {
Proof::<RistrettoPoint, Sha512>::deserialize(&bytes[..]).map_or(true, |_| true);
}
}
}
+737 -530
View File
File diff suppressed because it is too large Load Diff