diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index eba4a42..2be60ed 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -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: diff --git a/Cargo.toml b/Cargo.toml index 7320c69..8c9283c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 } diff --git a/README.md b/README.md index 5b0505a..64ab0fb 100644 --- a/README.md +++ b/README.md @@ -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 ------- diff --git a/src/lib.rs b/src/lib.rs index 063c5ba..3cb098a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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::::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::::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::::blind( +//! # b"input", +//! # &mut client_rng, +//! # ).expect("Unable to construct client"); +//! # use voprf::NonVerifiableServer; +//! # let mut server_rng = OsRng; +//! # let server = NonVerifiableServer::::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::::blind( +//! # b"input", +//! # &mut client_rng, +//! # ).expect("Unable to construct client"); +//! # use voprf::NonVerifiableServer; +//! # let mut server_rng = OsRng; +//! # let server = NonVerifiableServer::::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::::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::::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::::blind( +//! # b"input", +//! # &mut client_rng, +//! # ).expect("Unable to construct client"); +//! # use voprf::VerifiableServer; +//! # let mut server_rng = OsRng; +//! # let server = VerifiableServer::::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::::blind( +//! # b"input", +//! # &mut client_rng, +//! # ).expect("Unable to construct client"); +//! # use voprf::VerifiableServer; +//! # let mut server_rng = OsRng; +//! # let server = VerifiableServer::::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::::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::::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::::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::::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::::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, +}; diff --git a/src/serialization.rs b/src/serialization.rs index 072157b..29848f4 100644 --- a/src/serialization.rs +++ b/src/serialization.rs @@ -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 serde::Serialize for $t { + fn serialize(&self, serializer: S) -> Result + 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 { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + if deserializer.is_human_readable() { + let s = <&str>::deserialize(deserializer)?; + $t::::deserialize(&base64::decode(s).map_err(serde::de::Error::custom)?) + .map_err(serde::de::Error::custom) + } else { + struct ByteVisitor { + marker: core::marker::PhantomData, + } + impl<'de, CS: CipherSuite> serde::de::Visitor<'de> for ByteVisitor { + type Value = $t; + 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(self, value: &[u8]) -> Result + where + E: serde::de::Error, + { + $t::::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:: { + marker: core::marker::PhantomData, + }) + } + } + } + }; +} + ////////////////////////////////////////////////////////// // Serialization and Deserialization for High-Level API // // ==================================================== // ////////////////////////////////////////////////////////// +impl_serialize_and_deserialize_for!(NonVerifiableClient); + impl NonVerifiableClient { /// Serialization into bytes pub fn serialize(&self) -> Vec { @@ -47,6 +116,8 @@ impl NonVerifiableClient { } } +impl_serialize_and_deserialize_for!(VerifiableClient); + impl VerifiableClient { /// Serialization into bytes pub fn serialize(&self) -> Vec { @@ -80,6 +151,8 @@ impl VerifiableClient { } } +impl_serialize_and_deserialize_for!(NonVerifiableServer); + impl NonVerifiableServer { /// Serialization into bytes pub fn serialize(&self) -> Vec { @@ -99,6 +172,8 @@ impl NonVerifiableServer { } } +impl_serialize_and_deserialize_for!(VerifiableServer); + impl VerifiableServer { /// Serialization into bytes pub fn serialize(&self) -> Vec { @@ -124,6 +199,8 @@ impl VerifiableServer { } } +impl_serialize_and_deserialize_for!(Proof); + impl Proof { /// Serialization into bytes pub fn serialize(&self) -> Vec { @@ -147,6 +224,8 @@ impl Proof { } } +impl_serialize_and_deserialize_for!(BlindedElement); + impl BlindedElement { /// Serialization into bytes pub fn serialize(&self) -> Vec { @@ -161,6 +240,8 @@ impl BlindedElement { } } +impl_serialize_and_deserialize_for!(EvaluationElement); + impl EvaluationElement { /// Serialization into bytes pub fn serialize(&self) -> Vec { diff --git a/src/tests/voprf_test_vectors.rs b/src/tests/voprf_test_vectors.rs index 1c03fa5..0710be8 100644 --- a/src/tests/voprf_test_vectors.rs +++ b/src/tests/voprf_test_vectors.rs @@ -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( 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::::blind(¶meters.input[i], &mut rng)?; + let client_result = NonVerifiableClient::::blind(¶meters.input[i], &mut rng)?; assert_eq!( ¶meters.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!( + ¶meters.blinded_element[i], + &client_result.message.serialize() ); - assert_eq!(¶meters.blinded_element[i], &blinded_element.serialize()); } } Ok(()) @@ -200,14 +202,17 @@ fn test_verifiable_blind( 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::::blind(¶meters.input[i], &mut rng)?; assert_eq!( ¶meters.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!( + ¶meters.blinded_element[i], + &client_blind_result.message.serialize() ); - assert_eq!(¶meters.blinded_element[i], &blinded_element.serialize()); } } Ok(()) @@ -220,14 +225,14 @@ fn test_base_evaluate( for parameters in tvs { for i in 0..parameters.input.len() { let server = NonVerifiableServer::::new_with_key(¶meters.sksm)?; - let evaluation_element = server.evaluate( + let server_result = server.evaluate( BlindedElement::deserialize(¶meters.blinded_element[i])?, - ¶meters.info, + &Metadata(parameters.info.clone()), )?; assert_eq!( ¶meters.evaluation_element[i], - &evaluation_element.serialize() + &server_result.message.serialize() ); } } @@ -246,17 +251,20 @@ fn test_verifiable_evaluate( blinded_elements.push(BlindedElement::deserialize(&blinded_element_bytes)?); } - let (evaluation_elements, proof) = - server.batch_evaluate(&mut rng, &blinded_elements, ¶meters.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!( ¶meters.evaluation_element[i], - &evaluation_elements[i].serialize(), + &batch_evaluate_result.messages[i].serialize(), ); } - assert_eq!(¶meters.proof, &proof.serialize()); + assert_eq!(¶meters.proof, &batch_evaluate_result.proof.serialize()); } Ok(()) } @@ -274,12 +282,15 @@ fn test_base_finalize( ))?, ); - let output = client.finalize( + let client_finalize_result = client.finalize( EvaluationElement::deserialize(¶meters.evaluation_element[i])?, - ¶meters.info, + &Metadata(parameters.info.clone()), )?; - assert_eq!(¶meters.output[i], &output.to_vec()); + assert_eq!( + ¶meters.output[i], + &client_finalize_result.output.to_vec() + ); } } Ok(()) @@ -289,8 +300,6 @@ fn test_verifiable_finalize( 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::::from_data_and_blind( @@ -305,22 +314,26 @@ fn test_verifiable_finalize( clients.push(client.clone()); } - for i in 0..parameters.input.len() { - let evaluation_element = - EvaluationElement::deserialize(¶meters.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(¶meters.proof)?, CS::Group::from_element_slice(GenericArray::from_slice(¶meters.pksm))?, - ¶meters.info, + &Metadata(parameters.info.clone()), )?; assert_eq!( parameters.output, - outputs + batch_result + .outputs .iter() .map(|arr| arr.to_vec()) .collect::>>() diff --git a/src/voprf.rs b/src/voprf.rs index d3bb669..a897cb8 100644 --- a/src/voprf.rs +++ b/src/voprf.rs @@ -101,15 +101,15 @@ impl NonVerifiableClient { pub fn blind( input: &[u8], blinding_factor_rng: &mut R, - ) -> Result<(Self, BlindedElement), InternalError> { + ) -> Result, InternalError> { let (blind, blinded_element) = blind::(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 NonVerifiableClient { pub fn finalize( &self, evaluation_element: EvaluationElement, - info: &[u8], - ) -> Result::OutputSize>, InternalError> { + metadata: &Metadata, + ) -> Result, InternalError> { let unblinded_element = evaluation_element.0 * &::scalar_invert(&self.blind); let outputs = finalize_after_unblind::( &[(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 VerifiableClient { pub fn blind( input: &[u8], blinding_factor_rng: &mut R, - ) -> Result<(Self, BlindedElement), InternalError> { + ) -> Result, InternalError> { let (blind, blinded_element) = blind::(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 VerifiableClient { evaluation_element: EvaluationElement, proof: Proof, pk: CS::Group, - info: &[u8], - ) -> Result::OutputSize>, InternalError> { - let outputs = Self::batch_finalize(&[(self, evaluation_element)], proof, pk, info)?; - Ok(outputs[0].clone()) + metadata: &Metadata, + ) -> Result, 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, EvaluationElement)], + batch_finalize_input: BatchFinalizeInput, proof: Proof, pk: CS::Group, - info: &[u8], - ) -> Result::OutputSize>>, InternalError> { - let batch_items: Vec> = clients_and_evaluation_elements + metadata: &Metadata, + ) -> Result, InternalError> { + let batch_items: Vec> = 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 VerifiableClient { }) .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, 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, CS::Group)> = batch_finalize_input + .clients + .iter() + .zip(unblinded_elements.iter()) + .map(|(client, &unblinded_element)| (client.data.clone(), unblinded_element)) + .collect(); - finalize_after_unblind::(&inputs_and_unblinded_elements, info, Mode::Verifiable) + Ok(VerifiableClientBatchFinalizeResult { + outputs: finalize_after_unblind::( + &inputs_and_unblinded_elements, + &metadata.0, + Mode::Verifiable, + )?, + }) } #[cfg(test)] @@ -262,19 +276,21 @@ impl NonVerifiableServer { pub fn evaluate( &self, blinded_element: BlindedElement, - info: &[u8], - ) -> Result, InternalError> { + metadata: &Metadata, + ) -> Result, InternalError> { let context = [ STR_CONTEXT, &get_context_string::(Mode::Base)?, - &serialize(info, 2)?, + &serialize(&metadata.0, 2)?, ] .concat(); let dst = [STR_HASH_TO_SCALAR, &get_context_string::(Mode::Base)?].concat(); let m = CS::Group::hash_to_scalar::(&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 VerifiableServer { &self, rng: &mut R, blinded_element: BlindedElement, - info: &[u8], - ) -> Result<(EvaluationElement, Proof), InternalError> { - let (evaluation_elements, proof) = self.batch_evaluate(rng, &[blinded_element], info)?; - Ok((evaluation_elements[0].clone(), proof)) + metadata: &Metadata, + ) -> Result, 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 VerifiableServer { &self, rng: &mut R, blinded_elements: &[BlindedElement], - info: &[u8], - ) -> Result<(Vec>, Proof), InternalError> { + metadata: &Metadata, + ) -> Result, InternalError> { let context = [ STR_CONTEXT, &get_context_string::(Mode::Verifiable)?, - &serialize(info, 2)?, + &serialize(&metadata.0, 2)?, ] .concat(); let dst = [ @@ -357,7 +376,10 @@ impl VerifiableServer { 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 VerifiableServer { } } +///////////////////////// +// 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); + +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 { + /// The state to be persisted on the client + pub state: NonVerifiableClient, + /// The message to send to the server + pub message: BlindedElement, +} + +/// Contains the fields that are returned by a non-verifiable server evaluate +pub struct NonVerifiableServerEvaluateResult { + /// The message to send to the client + pub message: EvaluationElement, +} + +/// Contains the fields that are returned by a non-verifiable client finalize +pub struct NonVerifiableClientFinalizeResult { + /// The output of the protocol + pub output: GenericArray::OutputSize>, +} + +/// Contains the fields that are returned by a verifiable client blind +pub struct VerifiableClientBlindResult { + /// The state to be persisted on the client + pub state: VerifiableClient, + /// The message to send to the server + pub message: BlindedElement, +} + +/// Contains the fields that are returned by a verifiable server evaluate +pub struct VerifiableServerEvaluateResult { + /// The message to send to the client + pub message: EvaluationElement, + /// The proof for the client to verify + pub proof: Proof, +} + +/// Contains the fields that are returned by a verifiable server batch evaluate +pub struct VerifiableServerBatchEvaluateResult { + /// The messages to send to the client + pub messages: Vec>, + /// The proof for the client to verify + pub proof: Proof, +} + +/// Contains the fields that are returned by a verifiable client finalize +pub struct VerifiableClientFinalizeResult { + /// The output of the protocol + pub output: GenericArray::OutputSize>, +} + +/// Contains the fields that are returned by a verifiable client batch finalize +pub struct VerifiableClientBatchFinalizeResult { + /// The output of the protocol + pub outputs: Vec::OutputSize>>, +} + +/// An input to the verifiable client batch finalize function, constructed +/// by aggregating clients and server messages +pub struct BatchFinalizeInput { + clients: Vec>, + messages: Vec>, +} + +impl BatchFinalizeInput { + /// Create a new instance from a vector of clients and a vector of messages + pub fn new(clients: Vec>, messages: Vec>) -> 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::::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::::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::::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); } }