Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b3837a789 | ||
|
|
093a15f597 | ||
|
|
14830f1436 | ||
|
|
8457e8b900 | ||
|
|
de91fafdb3 | ||
|
|
5ac52388ff | ||
|
|
fab7528a69 | ||
|
|
c93600498e | ||
|
|
6fb4cad59c | ||
|
|
2d8780476a | ||
|
|
a3db6cd9d2 | ||
|
|
a1ab892bdb | ||
|
|
ad04f224af | ||
|
|
7be67b26de |
+24
-13
@@ -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
|
||||
|
||||
@@ -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
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(¶meters.seed)?;
|
||||
let server = NonVerifiableServer::<G, H>::new_from_seed(¶meters.seed)?;
|
||||
|
||||
assert_eq!(
|
||||
¶meters.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(¶meters.seed)?;
|
||||
let server = VerifiableServer::<G, H>::new_from_seed(¶meters.seed)?;
|
||||
|
||||
assert_eq!(
|
||||
¶meters.sksm,
|
||||
&CS::Group::scalar_as_bytes(server.get_private_key()).to_vec()
|
||||
&G::scalar_as_bytes(server.get_private_key()).to_vec()
|
||||
);
|
||||
assert_eq!(¶meters.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(¶meters.input[i], &mut rng)?;
|
||||
let blind =
|
||||
G::from_scalar_slice(&GenericArray::clone_from_slice(¶meters.blind[i]))?;
|
||||
let client_result = NonVerifiableClient::<G, H>::deterministic_blind_unchecked(
|
||||
parameters.input[i].clone(),
|
||||
blind,
|
||||
)?;
|
||||
|
||||
assert_eq!(
|
||||
¶meters.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!(
|
||||
¶meters.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(¶meters.input[i], &mut rng)?;
|
||||
let blind =
|
||||
G::from_scalar_slice(&GenericArray::clone_from_slice(¶meters.blind[i]))?;
|
||||
let client_blind_result = VerifiableClient::<G, H>::deterministic_blind_unchecked(
|
||||
parameters.input[i].clone(),
|
||||
blind,
|
||||
)?;
|
||||
|
||||
assert_eq!(
|
||||
¶meters.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!(
|
||||
¶meters.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(¶meters.sksm)?;
|
||||
let server = NonVerifiableServer::<G, H>::new_with_key(¶meters.sksm)?;
|
||||
let server_result = server.evaluate(
|
||||
BlindedElement::deserialize(¶meters.blinded_element[i])?,
|
||||
&Metadata(parameters.info.clone()),
|
||||
Some(¶meters.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(¶meters.sksm)?;
|
||||
let server = VerifiableServer::<G, H>::new_with_key(¶meters.sksm)?;
|
||||
|
||||
let mut blinded_elements = vec![];
|
||||
for blinded_element_bytes in ¶meters.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(¶meters.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(
|
||||
¶meters.input[i],
|
||||
&<CS::Group as Group>::from_scalar_slice(&GenericArray::clone_from_slice(
|
||||
<G as Group>::from_scalar_slice(&GenericArray::clone_from_slice(
|
||||
¶meters.blind[i],
|
||||
))?,
|
||||
);
|
||||
|
||||
let client_finalize_result = client.finalize(
|
||||
EvaluationElement::deserialize(¶meters.evaluation_element[i])?,
|
||||
&Metadata(parameters.info.clone()),
|
||||
Some(¶meters.info),
|
||||
)?;
|
||||
|
||||
assert_eq!(
|
||||
¶meters.output[i],
|
||||
&client_finalize_result.output.to_vec()
|
||||
);
|
||||
assert_eq!(¶meters.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(
|
||||
¶meters.input[i],
|
||||
&<CS::Group as Group>::from_scalar_slice(&GenericArray::clone_from_slice(
|
||||
<G as Group>::from_scalar_slice(&GenericArray::clone_from_slice(
|
||||
¶meters.blind[i],
|
||||
))?,
|
||||
&<CS::Group as Group>::from_element_slice(&GenericArray::clone_from_slice(
|
||||
<G as Group>::from_element_slice(&GenericArray::clone_from_slice(
|
||||
¶meters.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(¶meters.proof)?,
|
||||
CS::Group::from_element_slice(GenericArray::from_slice(¶meters.pksm))?,
|
||||
&Metadata(parameters.info.clone()),
|
||||
G::from_element_slice(GenericArray::from_slice(¶meters.pksm))?,
|
||||
Some(¶meters.info),
|
||||
)?;
|
||||
|
||||
assert_eq!(
|
||||
parameters.output,
|
||||
batch_result
|
||||
.outputs
|
||||
.iter()
|
||||
.map(|arr| arr.to_vec())
|
||||
.collect::<Vec<Vec<u8>>>()
|
||||
|
||||
+180
@@ -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
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user