Adding crate-level documentation (#9)

This commit is contained in:
Kevin Lewi
2021-09-20 00:17:53 -07:00
committed by GitHub
parent fa2d8ec191
commit 0445a9461c
7 changed files with 810 additions and 86 deletions
+6 -2
View File
@@ -16,6 +16,8 @@ jobs:
- u64_backend
- u32_backend
- p256,u64_backend
frontend_feature:
- serialize
toolchain:
- stable
- 1.51.0
@@ -42,7 +44,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: test
args: --no-default-features --features std --features ${{ matrix.backend_feature }}
args: --no-default-features --features ${{ matrix.frontend_feature }},std --features ${{ matrix.backend_feature }}
build-no-std:
name: Build with no-std on ${{ matrix.target }}
@@ -59,11 +61,13 @@ jobs:
- u64_backend
- u32_backend
- p256,u64_backend
frontend_feature:
- serialize
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.backend_feature }}
- run: cargo build --verbose --target=${{ matrix.target }} --no-default-features --features ${{ matrix.frontend_feature }} --features ${{ matrix.backend_feature }}
clippy:
+4 -2
View File
@@ -10,13 +10,15 @@ readme = "README.md"
resolver = "2"
[features]
default = ["u64_backend"]
default = ["u64_backend", "serialize"]
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"]
[dependencies]
base64 = { version = "0.13", default-features = false, features = ["alloc"], optional = true }
constant_time_eq = "0.1"
curve25519-dalek = { version = "3", default-features = false }
digest = "0.9"
@@ -33,7 +35,7 @@ p256_ = { package = "p256", version = "0.9", default-features = false, features
rand = { version = "0.8", default-features = false }
subtle = { version = "2.3", default-features = false }
zeroize = { version = "1", features = ["zeroize_derive"] }
serde = { version = "1", default-features = false, features = ["alloc", "derive"] }
serde = { version = "1", default-features = false, features = ["alloc", "derive"], optional = true }
[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2", features = ["js"], optional = true }
+29
View File
@@ -1,6 +1,35 @@
# voprf ![Build Status](https://github.com/novifinancial/voprf/workflows/Rust%20CI/badge.svg)
An implementation of a (verifiable) oblivious pseudorandom function (VOPRF)
A VOPRF is a verifiable oblivious pseudorandom function, a protocol between a client and a server. The regular (non-verifiable) OPRF is also supported in this implementation.
This implementation is based on the [Internet Draft for VOPRF](https://github.com/cfrg/draft-irtf-cfrg-voprf).
Documentation
-------------
The API can be found [here](https://docs.rs/voprf/) along with an example for usage.
Installation
------------
Add the following line to the dependencies of your `Cargo.toml`:
```
voprf = "0.1.0"
```
### Minimum Supported Rust Version
Rust **1.51** or higher.
Contributors
------------
The author of this code is Kevin Lewi
([@kevinlewi](https://github.com/kevinlewi)) .
To learn more about contributing to this project, [see this document](./CONTRIBUTING.md).
License
-------
+468 -3
View File
@@ -7,9 +7,462 @@
//!
//! Note: This implementation is in sync with
//! [draft-irtf-cfrg-opaque-07](https://www.ietf.org/archive/id/draft-irtf-cfrg-opaque-07.html),
//! but this specification is subject to change, until the final version published by the IETF.
//! but this specification is subject to change, until the final version
//! published by the IETF.
//!
//! # Overview
//!
//! A verifiable oblivious pseudorandom function is a protocol that is
//! evaluated between a client and a server. They must first agree on a
//! collection of primitives to be kept consistent throughout protocol
//! execution. These include:
//! - a finite cyclic group along with a point representation, and
//! - a hashing function.
//!
//! 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;
//! }
//! ```
//!
//! ## Modes of Operation
//!
//! VOPRF can be used in two modes:
//! - [Base Mode](#base-mode), which corresponds to a normal OPRF evaluation with no
//! support for the verification of the OPRF outputs
//! - [Verifiable Mode](#verifiable-mode), which corresponds to an OPRF evaluation where
//! the outputs can be verified against a server public key
//!
//! In either mode, the protocol begins with a client blinding, followed by
//! a server evaluation, and finishes with a client finalization.
//!
//! ## Base Mode
//!
//! In base mode, a [NonVerifiableClient] interacts with a
//! [NonVerifiableServer] to compute the output of the VOPRF.
//!
//! ### Server Setup
//!
//! The protocol begins with a setup phase, in which the server must run
//! [NonVerifiableServer::new()] to produce an instance of itself. This
//! instance must be persisted on the server and used for online
//! client evaluations.
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # 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)
//! .expect("Unable to construct server");
//! ```
//!
//! ### Client Blinding
//!
//! In the first step, the client chooses an input, and runs
//! [NonVerifiableClient::blind] to produce a [NonVerifiableClientBlindResult],
//! which consists of a [BlindedElement] to be sent to the server and a
//! [NonVerifiableClient] which must be persisted on the client for the final
//! step of the VOPRF protocol.
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # 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",
//! &mut client_rng,
//! ).expect("Unable to construct client");
//! ```
//!
//! ### Server Evaluation
//!
//! In the second step, the server takes as input the message from
//! [NonVerifiableClient::blind] (a [BlindedElement]), and runs
//! [NonVerifiableServer::evaluate] to produce a
//! [NonVerifiableServerEvaluateResult], which consists of an
//! [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;
//! # }
//! # use voprf::NonVerifiableClient;
//! # use rand::{rngs::OsRng, RngCore};
//! #
//! # let mut client_rng = OsRng;
//! # let client_blind_result = NonVerifiableClient::<Default>::blind(
//! # b"input",
//! # &mut client_rng,
//! # ).expect("Unable to construct client");
//! # use voprf::NonVerifiableServer;
//! # let mut server_rng = OsRng;
//! # let server = NonVerifiableServer::<Default>::new(&mut server_rng)
//! # .expect("Unable to construct server");
//! use voprf::Metadata;
//! let server_evaluate_result = server.evaluate(
//! client_blind_result.message,
//! &Metadata::none(),
//! ).expect("Unable to perform server evaluate");
//! ```
//!
//! ### Client Finalization
//!
//! 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.
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # 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",
//! # &mut client_rng,
//! # ).expect("Unable to construct client");
//! # use voprf::NonVerifiableServer;
//! # let mut server_rng = OsRng;
//! # let server = NonVerifiableServer::<Default>::new(&mut server_rng)
//! # .expect("Unable to construct server");
//! # let server_evaluate_result = server.evaluate(
//! # client_blind_result.message,
//! # &Metadata::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(),
//! ).expect("Unable to perform client finalization");
//!
//! println!("VOPRF output: {:?}", client_finalize_result.output.to_vec());
//! ```
//!
//! ## Verifiable Mode
//!
//! In verifiable mode, a [VerifiableClient] interacts with a
//! [VerifiableServer] to compute the output of the VOPRF. In order to
//! verify the server's computation, the client checks a server-generated
//! proof against the server's public key. If the proof fails to verify,
//! then the client does not receive an output.
//!
//! In batch mode, a single proof can be used for multiple VOPRF evaluations.
//! See [the batching section](#batching)
//! for more details on how to perform batch evaluations.
//!
//! ### Server Setup
//!
//! The protocol begins with a setup phase, in which the server must run
//! [VerifiableServer::new()] to produce an instance of itself. This
//! instance must be persisted on the server and used for online
//! client evaluations.
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # 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)
//! .expect("Unable to construct server");
//!
//! // To be sent to the client
//! println!("Server public key: {:?}", server.get_public_key());
//! ```
//!
//! The public key should be sent to the client, since the client will
//! need it in the final step of the protocol in order to complete
//! the evaluation of the VOPRF.
//!
//! ### Client Blinding
//!
//! In the first step, the client chooses an input, and runs
//! [VerifiableClient::blind] to produce a [VerifiableClientBlindResult],
//! which consists of a [BlindedElement] to be sent to the server and a
//! [VerifiableClient] which must be persisted on the client for the final
//! step of the VOPRF protocol.
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # 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",
//! &mut client_rng,
//! ).expect("Unable to construct client");
//! ```
//!
//! ### Server Evaluation
//!
//! In the second step, the server takes as input the message from
//! [VerifiableClient::blind] (a [BlindedElement]), and runs
//! [VerifiableServer::evaluate] to produce a
//! [VerifiableServerEvaluateResult], which consists of an
//! [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;
//! # }
//! # use voprf::VerifiableClient;
//! # use rand::{rngs::OsRng, RngCore};
//! #
//! # let mut client_rng = OsRng;
//! # let client_blind_result = VerifiableClient::<Default>::blind(
//! # b"input",
//! # &mut client_rng,
//! # ).expect("Unable to construct client");
//! # use voprf::VerifiableServer;
//! # let mut server_rng = OsRng;
//! # let server = VerifiableServer::<Default>::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(),
//! ).expect("Unable to perform server evaluate");
//! ```
//!
//! ### Client Finalization
//!
//! 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.
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # 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",
//! # &mut client_rng,
//! # ).expect("Unable to construct client");
//! # use voprf::VerifiableServer;
//! # let mut server_rng = OsRng;
//! # let server = VerifiableServer::<Default>::new(&mut server_rng)
//! # .expect("Unable to construct server");
//! # let server_evaluate_result = server.evaluate(
//! # &mut server_rng,
//! # client_blind_result.message,
//! # &Metadata::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(),
//! ).expect("Unable to perform client finalization");
//!
//! println!("VOPRF output: {:?}", client_finalize_result.output.to_vec());
//! ```
//!
//! # Advanced Usage
//!
//! There are two additional (and optional) extensions to the core VOPRF
//! protocol: support for batching of evaluations, and support for public
//! metadata.
//!
//! ## Batching
//!
//! It is sometimes desirable to generate only a single, constant-size
//! proof for an unbounded number of VOPRF evaluations (on arbitrary inputs).
//! [VerifiableClient] and [VerifiableServer] support a batch API for
//! handling this case. In the following example, we show how to use
//! the batch API to produce a single proof for 10 parallel
//! VOPRF evaluations.
//!
//! First, the client produces 10 blindings, storing their resulting
//! states and messages:
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # }
//! # use voprf::VerifiableClient;
//! # use rand::{rngs::OsRng, RngCore};
//! #
//! let mut client_rng = OsRng;
//! let mut client_states = vec![];
//! let mut client_messages = vec![];
//! for _ in 0..10 {
//! let client_blind_result = VerifiableClient::<Default>::blind(
//! b"input",
//! &mut client_rng,
//! ).expect("Unable to construct client");
//! client_states.push(client_blind_result.state);
//! client_messages.push(client_blind_result.message);
//! }
//! ```
//!
//! Next, the server calls the [VerifiableServer::batch_evaluate]
//! function on a set of client messages, to produce a corresponding
//! set of messages to be returned to the client (returned in the same order),
//! along with a single proof:
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # }
//! # use voprf::VerifiableClient;
//! # use rand::{rngs::OsRng, RngCore};
//! #
//! # let mut client_rng = OsRng;
//! # let mut client_states = vec![];
//! # let mut client_messages = vec![];
//! # for _ in 0..10 {
//! # let client_blind_result = VerifiableClient::<Default>::blind(
//! # b"input",
//! # &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)
//! # .expect("Unable to construct server");
//! let server_batch_evaluate_result = server.batch_evaluate(
//! &mut server_rng,
//! &client_messages,
//! &Metadata::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.
//!
//! ```
//! # use voprf::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type Hash = sha2::Sha512;
//! # }
//! # use voprf::VerifiableClient;
//! # use rand::{rngs::OsRng, RngCore};
//! #
//! # let mut client_rng = OsRng;
//! # let mut client_states = vec![];
//! # let mut client_messages = vec![];
//! # for _ in 0..10 {
//! # let client_blind_result = VerifiableClient::<Default>::blind(
//! # b"input",
//! # &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)
//! # .expect("Unable to construct server");
//! # let server_batch_evaluate_result = server.batch_evaluate(
//! # &mut server_rng,
//! # &client_messages,
//! # &Metadata::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,
//! server_batch_evaluate_result.proof,
//! server.get_public_key(),
//! &Metadata::none(),
//! ).expect("Unable to perform client batch finalization");
//!
//! println!("VOPRF batch outputs: {:?}", client_batch_finalize_result.outputs);
//! ```
//!
//! ## Metadata
//!
//! The optional metadata parameter included in the protocol allows clients and
//! servers (of either mode) to cryptographically bind additional data to the
//! VOPRF output. This metadata is known to both parties at the start of the protocol,
//! and is inserted under the server's evaluate step and the client's finalize step.
//! 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")`.
//!
//! # Features
//!
//! - The `serialize` 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
//! [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.
#![cfg_attr(not(feature = "bench"), deny(missing_docs))]
#![deny(unsafe_code)]
@@ -20,11 +473,23 @@ extern crate alloc;
#[macro_use]
mod serialization;
pub mod ciphersuite;
mod ciphersuite;
pub mod errors;
pub mod group;
pub mod hash;
pub mod voprf;
mod voprf;
#[cfg(test)]
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,
};
+81
View File
@@ -18,11 +18,80 @@ use crate::{
use alloc::vec::Vec;
use generic_array::{typenum::Unsigned, GenericArray};
/// 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> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if serializer.is_human_readable() {
serializer.serialize_str(&base64::encode(&self.serialize()))
} else {
serializer.serialize_bytes(&self.serialize())
}
}
}
#[cfg(feature = "serialize")]
impl<'de, CS: CipherSuite> serde::Deserialize<'de> for $t<CS> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
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)
} 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,
})
}
}
}
};
}
//////////////////////////////////////////////////////////
// Serialization and Deserialization for High-Level API //
// ==================================================== //
//////////////////////////////////////////////////////////
impl_serialize_and_deserialize_for!(NonVerifiableClient);
impl<CS: CipherSuite> NonVerifiableClient<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
@@ -47,6 +116,8 @@ impl<CS: CipherSuite> NonVerifiableClient<CS> {
}
}
impl_serialize_and_deserialize_for!(VerifiableClient);
impl<CS: CipherSuite> VerifiableClient<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
@@ -80,6 +151,8 @@ impl<CS: CipherSuite> VerifiableClient<CS> {
}
}
impl_serialize_and_deserialize_for!(NonVerifiableServer);
impl<CS: CipherSuite> NonVerifiableServer<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
@@ -99,6 +172,8 @@ impl<CS: CipherSuite> NonVerifiableServer<CS> {
}
}
impl_serialize_and_deserialize_for!(VerifiableServer);
impl<CS: CipherSuite> VerifiableServer<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
@@ -124,6 +199,8 @@ impl<CS: CipherSuite> VerifiableServer<CS> {
}
}
impl_serialize_and_deserialize_for!(Proof);
impl<CS: CipherSuite> Proof<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
@@ -147,6 +224,8 @@ impl<CS: CipherSuite> Proof<CS> {
}
}
impl_serialize_and_deserialize_for!(BlindedElement);
impl<CS: CipherSuite> BlindedElement<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
@@ -161,6 +240,8 @@ impl<CS: CipherSuite> BlindedElement<CS> {
}
}
impl_serialize_and_deserialize_for!(EvaluationElement);
impl<CS: CipherSuite> EvaluationElement<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
+43 -30
View File
@@ -9,8 +9,8 @@ use crate::{
group::Group,
tests::{mock_rng::CycleRng, parser::*},
voprf::{
BlindedElement, EvaluationElement, NonVerifiableClient, NonVerifiableServer, Proof,
VerifiableClient, VerifiableServer,
BatchFinalizeInput, BlindedElement, EvaluationElement, Metadata, NonVerifiableClient,
NonVerifiableServer, Proof, VerifiableClient, VerifiableServer,
},
};
use alloc::string::ToString;
@@ -180,14 +180,16 @@ fn test_base_blind<CS: CipherSuite>(
for parameters in tvs {
for i in 0..parameters.input.len() {
let mut rng = CycleRng::new(parameters.blind[i].to_vec());
let (client, blinded_element) =
NonVerifiableClient::<CS>::blind(&parameters.input[i], &mut rng)?;
let client_result = NonVerifiableClient::<CS>::blind(&parameters.input[i], &mut rng)?;
assert_eq!(
&parameters.blind[i],
&CS::Group::scalar_as_bytes(client.get_blind()).to_vec()
&CS::Group::scalar_as_bytes(client_result.state.get_blind()).to_vec()
);
assert_eq!(
&parameters.blinded_element[i],
&client_result.message.serialize()
);
assert_eq!(&parameters.blinded_element[i], &blinded_element.serialize());
}
}
Ok(())
@@ -200,14 +202,17 @@ fn test_verifiable_blind<CS: CipherSuite>(
for parameters in tvs {
for i in 0..parameters.input.len() {
let mut rng = CycleRng::new(parameters.blind[i].to_vec());
let (client, blinded_element) =
let client_blind_result =
VerifiableClient::<CS>::blind(&parameters.input[i], &mut rng)?;
assert_eq!(
&parameters.blind[i],
&CS::Group::scalar_as_bytes(client.get_blind()).to_vec()
&CS::Group::scalar_as_bytes(client_blind_result.state.get_blind()).to_vec()
);
assert_eq!(
&parameters.blinded_element[i],
&client_blind_result.message.serialize()
);
assert_eq!(&parameters.blinded_element[i], &blinded_element.serialize());
}
}
Ok(())
@@ -220,14 +225,14 @@ fn test_base_evaluate<CS: CipherSuite>(
for parameters in tvs {
for i in 0..parameters.input.len() {
let server = NonVerifiableServer::<CS>::new_with_key(&parameters.sksm)?;
let evaluation_element = server.evaluate(
let server_result = server.evaluate(
BlindedElement::deserialize(&parameters.blinded_element[i])?,
&parameters.info,
&Metadata(parameters.info.clone()),
)?;
assert_eq!(
&parameters.evaluation_element[i],
&evaluation_element.serialize()
&server_result.message.serialize()
);
}
}
@@ -246,17 +251,20 @@ fn test_verifiable_evaluate<CS: CipherSuite>(
blinded_elements.push(BlindedElement::deserialize(&blinded_element_bytes)?);
}
let (evaluation_elements, proof) =
server.batch_evaluate(&mut rng, &blinded_elements, &parameters.info)?;
let batch_evaluate_result = server.batch_evaluate(
&mut rng,
&blinded_elements,
&Metadata(parameters.info.clone()),
)?;
for i in 0..parameters.evaluation_element.len() {
assert_eq!(
&parameters.evaluation_element[i],
&evaluation_elements[i].serialize(),
&batch_evaluate_result.messages[i].serialize(),
);
}
assert_eq!(&parameters.proof, &proof.serialize());
assert_eq!(&parameters.proof, &batch_evaluate_result.proof.serialize());
}
Ok(())
}
@@ -274,12 +282,15 @@ fn test_base_finalize<CS: CipherSuite>(
))?,
);
let output = client.finalize(
let client_finalize_result = client.finalize(
EvaluationElement::deserialize(&parameters.evaluation_element[i])?,
&parameters.info,
&Metadata(parameters.info.clone()),
)?;
assert_eq!(&parameters.output[i], &output.to_vec());
assert_eq!(
&parameters.output[i],
&client_finalize_result.output.to_vec()
);
}
}
Ok(())
@@ -289,8 +300,6 @@ fn test_verifiable_finalize<CS: CipherSuite>(
tvs: &[VOPRFTestVectorParameters],
) -> Result<(), InternalError> {
for parameters in tvs {
let mut clients_and_evaluation_elements = vec![];
let mut clients = vec![];
for i in 0..parameters.input.len() {
let client = VerifiableClient::<CS>::from_data_and_blind(
@@ -305,22 +314,26 @@ fn test_verifiable_finalize<CS: CipherSuite>(
clients.push(client.clone());
}
for i in 0..parameters.input.len() {
let evaluation_element =
EvaluationElement::deserialize(&parameters.evaluation_element[i])?;
clients_and_evaluation_elements.push((&clients[i], evaluation_element));
}
let batch_finalize_input = BatchFinalizeInput::new(
clients,
parameters
.evaluation_element
.iter()
.map(|x| EvaluationElement::deserialize(x).unwrap())
.collect(),
);
let outputs = VerifiableClient::batch_finalize(
&clients_and_evaluation_elements,
let batch_result = VerifiableClient::batch_finalize(
batch_finalize_input,
Proof::deserialize(&parameters.proof)?,
CS::Group::from_element_slice(GenericArray::from_slice(&parameters.pksm))?,
&parameters.info,
&Metadata(parameters.info.clone()),
)?;
assert_eq!(
parameters.output,
outputs
batch_result
.outputs
.iter()
.map(|arr| arr.to_vec())
.collect::<Vec<Vec<u8>>>()
+179 -49
View File
@@ -101,15 +101,15 @@ impl<CS: CipherSuite> NonVerifiableClient<CS> {
pub fn blind<R: RngCore + CryptoRng>(
input: &[u8],
blinding_factor_rng: &mut R,
) -> Result<(Self, BlindedElement<CS>), InternalError> {
) -> Result<NonVerifiableClientBlindResult<CS>, InternalError> {
let (blind, blinded_element) = blind::<CS, _>(input, blinding_factor_rng, Mode::Base)?;
Ok((
Self {
Ok(NonVerifiableClientBlindResult {
state: Self {
data: input.to_vec(),
blind,
},
BlindedElement(blinded_element),
))
message: BlindedElement(blinded_element),
})
}
/// Computes the third step for the multiplicative blinding version of DH-OPRF, in which
@@ -117,16 +117,18 @@ impl<CS: CipherSuite> NonVerifiableClient<CS> {
pub fn finalize(
&self,
evaluation_element: EvaluationElement<CS>,
info: &[u8],
) -> Result<GenericArray<u8, <CS::Hash as Digest>::OutputSize>, InternalError> {
metadata: &Metadata,
) -> Result<NonVerifiableClientFinalizeResult<CS>, InternalError> {
let unblinded_element =
evaluation_element.0 * &<CS::Group as Group>::scalar_invert(&self.blind);
let outputs = finalize_after_unblind::<CS>(
&[(self.data.clone(), unblinded_element)],
info,
&metadata.0,
Mode::Base,
)?;
Ok(outputs[0].clone())
Ok(NonVerifiableClientFinalizeResult {
output: outputs[0].clone(),
})
}
#[cfg(test)]
@@ -150,17 +152,17 @@ impl<CS: CipherSuite> VerifiableClient<CS> {
pub fn blind<R: RngCore + CryptoRng>(
input: &[u8],
blinding_factor_rng: &mut R,
) -> Result<(Self, BlindedElement<CS>), InternalError> {
) -> Result<VerifiableClientBlindResult<CS>, InternalError> {
let (blind, blinded_element) =
blind::<CS, _>(input, blinding_factor_rng, Mode::Verifiable)?;
Ok((
Self {
Ok(VerifiableClientBlindResult {
state: Self {
data: input.to_vec(),
blind,
blinded_element,
},
BlindedElement(blinded_element),
))
message: BlindedElement(blinded_element),
})
}
/// Computes the third step for the multiplicative blinding version of DH-OPRF, in which
@@ -170,22 +172,28 @@ impl<CS: CipherSuite> VerifiableClient<CS> {
evaluation_element: EvaluationElement<CS>,
proof: Proof<CS>,
pk: CS::Group,
info: &[u8],
) -> Result<GenericArray<u8, <CS::Hash as Digest>::OutputSize>, InternalError> {
let outputs = Self::batch_finalize(&[(self, evaluation_element)], proof, pk, info)?;
Ok(outputs[0].clone())
metadata: &Metadata,
) -> Result<VerifiableClientFinalizeResult<CS>, InternalError> {
let batch_finalize_input =
BatchFinalizeInput::new(vec![self.clone()], vec![evaluation_element]);
let batch_result = Self::batch_finalize(batch_finalize_input, proof, pk, metadata)?;
Ok(VerifiableClientFinalizeResult {
output: batch_result.outputs[0].clone(),
})
}
/// Allows for batching of the finalization of multiple [VerifiableClient] and [EvaluationElement] pairs
#[allow(clippy::type_complexity)]
pub fn batch_finalize(
clients_and_evaluation_elements: &[(&VerifiableClient<CS>, EvaluationElement<CS>)],
batch_finalize_input: BatchFinalizeInput<CS>,
proof: Proof<CS>,
pk: CS::Group,
info: &[u8],
) -> Result<Vec<GenericArray<u8, <CS::Hash as Digest>::OutputSize>>, InternalError> {
let batch_items: Vec<BatchItems<CS>> = clients_and_evaluation_elements
metadata: &Metadata,
) -> Result<VerifiableClientBatchFinalizeResult<CS>, InternalError> {
let batch_items: Vec<BatchItems<CS>> = batch_finalize_input
.clients
.iter()
.zip(batch_finalize_input.messages.iter())
.map(|(client, evaluation_element)| BatchItems {
blind: client.blind,
evaluation_element: evaluation_element.clone(),
@@ -193,16 +201,22 @@ impl<CS: CipherSuite> VerifiableClient<CS> {
})
.collect();
let unblinded_elements = verifiable_unblind(&batch_items, pk, proof, info)?;
let unblinded_elements = verifiable_unblind(&batch_items, pk, proof, &metadata.0)?;
let inputs_and_unblinded_elements: Vec<(Vec<u8>, CS::Group)> =
clients_and_evaluation_elements
.iter()
.zip(unblinded_elements.iter())
.map(|((client, _), &unblinded_element)| (client.data.clone(), unblinded_element))
.collect();
let inputs_and_unblinded_elements: Vec<(Vec<u8>, CS::Group)> = batch_finalize_input
.clients
.iter()
.zip(unblinded_elements.iter())
.map(|(client, &unblinded_element)| (client.data.clone(), unblinded_element))
.collect();
finalize_after_unblind::<CS>(&inputs_and_unblinded_elements, info, Mode::Verifiable)
Ok(VerifiableClientBatchFinalizeResult {
outputs: finalize_after_unblind::<CS>(
&inputs_and_unblinded_elements,
&metadata.0,
Mode::Verifiable,
)?,
})
}
#[cfg(test)]
@@ -262,19 +276,21 @@ impl<CS: CipherSuite> NonVerifiableServer<CS> {
pub fn evaluate(
&self,
blinded_element: BlindedElement<CS>,
info: &[u8],
) -> Result<EvaluationElement<CS>, InternalError> {
metadata: &Metadata,
) -> Result<NonVerifiableServerEvaluateResult<CS>, InternalError> {
let context = [
STR_CONTEXT,
&get_context_string::<CS>(Mode::Base)?,
&serialize(info, 2)?,
&serialize(&metadata.0, 2)?,
]
.concat();
let dst = [STR_HASH_TO_SCALAR, &get_context_string::<CS>(Mode::Base)?].concat();
let m = CS::Group::hash_to_scalar::<CS::Hash>(&context, &dst)?;
let t = self.sk + &m;
let evaluation_element = blinded_element.0 * &CS::Group::scalar_invert(&t);
Ok(EvaluationElement(evaluation_element))
Ok(NonVerifiableServerEvaluateResult {
message: EvaluationElement(evaluation_element),
})
}
}
@@ -321,10 +337,13 @@ impl<CS: CipherSuite> VerifiableServer<CS> {
&self,
rng: &mut R,
blinded_element: BlindedElement<CS>,
info: &[u8],
) -> Result<(EvaluationElement<CS>, Proof<CS>), InternalError> {
let (evaluation_elements, proof) = self.batch_evaluate(rng, &[blinded_element], info)?;
Ok((evaluation_elements[0].clone(), proof))
metadata: &Metadata,
) -> Result<VerifiableServerEvaluateResult<CS>, InternalError> {
let batch_result = self.batch_evaluate(rng, &[blinded_element], metadata)?;
Ok(VerifiableServerEvaluateResult {
message: batch_result.messages[0].clone(),
proof: batch_result.proof,
})
}
/// Allows for batching of the evaluation of multiple [BlindedElement] messages from a [VerifiableClient]
@@ -332,12 +351,12 @@ impl<CS: CipherSuite> VerifiableServer<CS> {
&self,
rng: &mut R,
blinded_elements: &[BlindedElement<CS>],
info: &[u8],
) -> Result<(Vec<EvaluationElement<CS>>, Proof<CS>), InternalError> {
metadata: &Metadata,
) -> Result<VerifiableServerBatchEvaluateResult<CS>, InternalError> {
let context = [
STR_CONTEXT,
&get_context_string::<CS>(Mode::Verifiable)?,
&serialize(info, 2)?,
&serialize(&metadata.0, 2)?,
]
.concat();
let dst = [
@@ -357,7 +376,10 @@ impl<CS: CipherSuite> VerifiableServer<CS> {
let proof = generate_proof(rng, t, g, u, &evaluation_elements, blinded_elements)?;
Ok((evaluation_elements, proof))
Ok(VerifiableServerBatchEvaluateResult {
messages: evaluation_elements,
proof,
})
}
/// Retrieves the server's public key
@@ -366,6 +388,103 @@ impl<CS: CipherSuite> VerifiableServer<CS> {
}
}
/////////////////////////
// Optional Parameters //
//==================== //
/////////////////////////
/// Allows for implementations to specify an optional sequence of
/// public bytes that must be agreed-upon by the client and server
pub struct Metadata(pub Vec<u8>);
impl Default for Metadata {
fn default() -> Self {
Self(vec![])
}
}
impl Metadata {
/// Specifies no metadata (the default option)
pub fn none() -> Self {
Self::default()
}
}
/////////////////////////
// Convenience Structs //
//==================== //
/////////////////////////
/// Contains the fields that are returned by a non-verifiable client blind
pub struct NonVerifiableClientBlindResult<CS: CipherSuite> {
/// The state to be persisted on the client
pub state: NonVerifiableClient<CS>,
/// The message to send to the server
pub message: BlindedElement<CS>,
}
/// Contains the fields that are returned by a non-verifiable server evaluate
pub struct NonVerifiableServerEvaluateResult<CS: CipherSuite> {
/// The message to send to the client
pub message: EvaluationElement<CS>,
}
/// Contains the fields that are returned by a non-verifiable client finalize
pub struct NonVerifiableClientFinalizeResult<CS: CipherSuite> {
/// The output of the protocol
pub output: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
}
/// Contains the fields that are returned by a verifiable client blind
pub struct VerifiableClientBlindResult<CS: CipherSuite> {
/// The state to be persisted on the client
pub state: VerifiableClient<CS>,
/// The message to send to the server
pub message: BlindedElement<CS>,
}
/// Contains the fields that are returned by a verifiable server evaluate
pub struct VerifiableServerEvaluateResult<CS: CipherSuite> {
/// The message to send to the client
pub message: EvaluationElement<CS>,
/// The proof for the client to verify
pub proof: Proof<CS>,
}
/// Contains the fields that are returned by a verifiable server batch evaluate
pub struct VerifiableServerBatchEvaluateResult<CS: CipherSuite> {
/// The messages to send to the client
pub messages: Vec<EvaluationElement<CS>>,
/// The proof for the client to verify
pub proof: Proof<CS>,
}
/// Contains the fields that are returned by a verifiable client finalize
pub struct VerifiableClientFinalizeResult<CS: CipherSuite> {
/// The output of the protocol
pub output: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
}
/// Contains the fields that are returned by a verifiable client batch finalize
pub struct VerifiableClientBatchFinalizeResult<CS: CipherSuite> {
/// The output of the protocol
pub outputs: Vec<GenericArray<u8, <CS::Hash as Digest>::OutputSize>>,
}
/// An input to the verifiable client batch finalize function, constructed
/// by aggregating clients and server messages
pub struct BatchFinalizeInput<CS: CipherSuite> {
clients: Vec<VerifiableClient<CS>>,
messages: Vec<EvaluationElement<CS>>,
}
impl<CS: CipherSuite> BatchFinalizeInput<CS> {
/// Create a new instance from a vector of clients and a vector of messages
pub fn new(clients: Vec<VerifiableClient<CS>>, messages: Vec<EvaluationElement<CS>>) -> Self {
Self { clients, messages }
}
}
///////////////////////////////////////////////
// Inner functions and Trait Implementations //
// ========================================= //
@@ -683,7 +802,7 @@ mod tests {
let input = b"hunter2";
let info = b"info";
let mut rng = OsRng;
let (client, alpha) =
let client_blind_result =
NonVerifiableClient::<Ristretto255Sha512>::blind(&input[..], &mut rng).unwrap();
let oprf_key_bytes = arr![
u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
@@ -691,10 +810,15 @@ mod tests {
];
let server =
NonVerifiableServer::<Ristretto255Sha512>::new_with_key(&oprf_key_bytes).unwrap();
let beta = server.evaluate(alpha, info).unwrap();
let res = client.finalize(beta, info).unwrap();
let server_result = server
.evaluate(client_blind_result.message, &Metadata(info.to_vec()))
.unwrap();
let client_finalize_result = client_blind_result
.state
.finalize(server_result.message, &Metadata(info.to_vec()))
.unwrap();
let res2 = prf(&input[..], &oprf_key_bytes, info);
assert_eq!(res, res2);
assert_eq!(client_finalize_result.output, res2);
}
#[test]
@@ -703,9 +827,15 @@ mod tests {
let mut input = alloc::vec![0u8; 64];
rng.fill_bytes(&mut input);
let info = b"info";
let (client, alpha) =
let client_blind_result =
NonVerifiableClient::<Ristretto255Sha512>::blind(&input, &mut rng).unwrap();
let res = client.finalize(EvaluationElement(alpha.0), info).unwrap();
let client_finalize_result = client_blind_result
.state
.finalize(
EvaluationElement(client_blind_result.message.0),
&Metadata(info.to_vec()),
)
.unwrap();
let dst = [
STR_HASH_TO_GROUP,
@@ -720,6 +850,6 @@ mod tests {
)
.unwrap()[0];
assert_eq!(res, res2);
assert_eq!(client_finalize_result.output, res2);
}
}