// Copyright (c) Meta Platforms, Inc. and affiliates. // // This source code is dual-licensed under either the MIT license found in the // LICENSE-MIT file in the root directory of this source tree or the Apache // License, Version 2.0 found in the LICENSE-APACHE file in the root directory // of this source tree. You may select, at your option, one of the above-listed // licenses. //! Contains the main VOPRF API #[cfg(feature = "alloc")] use alloc::vec::Vec; use core::iter::{self, Map, Repeat, Zip}; use derive_where::derive_where; use digest::{Digest, Output}; use hybrid_array::Array; use hybrid_array::typenum::Unsigned; use rand_core::{TryCryptoRng, TryRng}; use crate::common::{ BlindedElement, EvaluationElement, Mode, PreparedEvaluationElement, Proof, STR_FINALIZE, derive_keypair, deterministic_blind_unchecked, generate_proof, hash_to_group, i2osp_2, server_evaluate_hash_input, verify_proof, }; #[cfg(feature = "serde")] use crate::serialization::serde::{Element, Scalar}; use crate::{CipherSuite, Error, Group, Result}; //////////////////////////// // High-level API Structs // // ====================== // //////////////////////////// /// A client which engages with a [VoprfServer] in verifiable mode, meaning /// that the OPRF outputs can be checked against a server public key. #[derive_where(Clone, ZeroizeOnDrop)] #[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; ::Scalar, ::Elem)] #[cfg_attr( feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound = "") )] pub struct VoprfClient { #[cfg_attr(feature = "serde", serde(with = "Scalar::"))] pub(crate) blind: ::Scalar, #[cfg_attr(feature = "serde", serde(with = "Element::"))] pub(crate) blinded_element: ::Elem, } /// A server which engages with a [VoprfClient] in verifiable mode, meaning /// that the OPRF outputs can be checked against a server public key. #[derive_where(Clone, ZeroizeOnDrop)] #[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; ::Scalar, ::Elem)] #[cfg_attr( feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound = "") )] pub struct VoprfServer { #[cfg_attr(feature = "serde", serde(with = "Scalar::"))] pub(crate) sk: ::Scalar, #[cfg_attr(feature = "serde", serde(with = "Element::"))] pub(crate) pk: ::Elem, } ///////////////////////// // API Implementations // // =================== // ///////////////////////// impl VoprfClient { /// Computes the first step for the multiplicative blinding version of /// DH-OPRF. /// /// # Errors /// [`Error::Input`] if the `input` is empty or longer then [`u16::MAX`]. pub fn blind( input: &[u8], blinding_factor_rng: &mut R, ) -> Result> { let blind = CS::Group::random_scalar(blinding_factor_rng)?; Self::deterministic_blind_unchecked_inner(input, blind) } /// Computes the first step for the multiplicative blinding version of /// DH-OPRF, taking a blinding factor scalar as input instead of sampling /// from an RNG. /// /// # Caution /// /// This should be used with caution, since it does not perform any checks /// on the validity of the blinding factor! /// /// # Errors /// [`Error::Input`] if the `input` is empty or longer then [`u16::MAX`]. #[cfg(any(feature = "danger", test))] pub fn deterministic_blind_unchecked( input: &[u8], blind: ::Scalar, ) -> Result> { Self::deterministic_blind_unchecked_inner(input, blind) } /// Can only fail with [`Error::Input`]. fn deterministic_blind_unchecked_inner( input: &[u8], blind: ::Scalar, ) -> Result> { let blinded_element = deterministic_blind_unchecked::(input, &blind, Mode::Voprf)?; Ok(VoprfClientBlindResult { state: Self { blind, blinded_element, }, message: BlindedElement(blinded_element), }) } /// Computes the third step for the multiplicative blinding version of /// DH-OPRF, in which the client unblinds the server's message. /// /// # Errors /// - [`Error::Input`] if the `input` is empty or longer then [`u16::MAX`]. /// - [`Error::ProofVerification`] if the `proof` failed to verify. pub fn finalize( &self, input: &[u8], evaluation_element: &EvaluationElement, proof: &Proof, pk: ::Elem, ) -> Result> { let inputs = core::array::from_ref(&input); let clients = core::array::from_ref(self); let messages = core::array::from_ref(evaluation_element); let mut batch_result = Self::batch_finalize(inputs, clients, messages, proof, pk)?; batch_result.next().unwrap() } /// Allows for batching of the finalization of multiple [VoprfClient] /// and [EvaluationElement] pairs /// /// # Errors /// - [`Error::Batch`] if the number of `clients` and `messages` don't match /// or is longer then [`u16::MAX`]. /// - [`Error::ProofVerification`] if the `proof` failed to verify. /// /// The resulting messages can each fail individually with [`Error::Input`] /// if the `input` is empty or longer then [`u16::MAX`]. pub fn batch_finalize<'a, I, II, IC, IM>( inputs: &'a II, clients: &'a IC, messages: &'a IM, proof: &Proof, pk: ::Elem, ) -> Result> where CS: 'a, I: 'a + AsRef<[u8]>, &'a II: 'a + IntoIterator, <&'a II as IntoIterator>::IntoIter: ExactSizeIterator, &'a IC: 'a + IntoIterator>, <&'a IC as IntoIterator>::IntoIter: ExactSizeIterator, &'a IM: 'a + IntoIterator>, <&'a IM as IntoIterator>::IntoIter: ExactSizeIterator, { let unblinded_elements = verifiable_unblind(clients, messages, pk, proof)?; let inputs_and_unblinded_elements = inputs.into_iter().zip(unblinded_elements); Ok(finalize_after_unblind::( inputs_and_unblinded_elements, )) } /// Only used for test functions #[cfg(test)] pub fn from_blind_and_element( blind: ::Scalar, blinded_element: ::Elem, ) -> Self { Self { blind, blinded_element, } } /// Only used for test functions #[cfg(test)] pub fn get_blind(&self) -> ::Scalar { self.blind } } impl VoprfServer { /// Produces a new instance of a [VoprfServer] using a supplied RNG /// /// # Errors /// [`Error::Protocol`] if the protocol fails and can't be completed. pub fn new(rng: &mut R) -> Result { let mut seed = Array::<_, ::ScalarLen>::default(); rng.try_fill_bytes(&mut seed).map_err(|_| Error::Protocol)?; // This can't fail as the hash output is type constrained. Self::new_from_seed(&seed, &[]) } /// Produces a new instance of a [VoprfServer] using a supplied set of /// bytes to represent the server's private key /// /// # Errors /// [`Error::Deserialization`] if the private key is not a valid point on /// the group or zero. pub fn new_with_key(key: &[u8]) -> Result { let sk = CS::Group::deserialize_scalar(key)?; let pk = CS::Group::base_elem() * &sk; Ok(Self { sk, pk }) } /// Produces a new instance of a [VoprfServer] using a supplied set of /// bytes which are used as a seed to derive the server's private key. /// /// Corresponds to DeriveKeyPair() function from the VOPRF specification. /// /// # Errors /// - [`Error::DeriveKeyPair`] if the `input` and `seed` together are longer /// then `u16::MAX - 3`. /// - [`Error::Protocol`] if the protocol fails and can't be completed. pub fn new_from_seed(seed: &[u8], info: &[u8]) -> Result { let (sk, pk) = derive_keypair::(seed, info, Mode::Voprf)?; Ok(Self { sk, pk }) } /// Only used for tests #[cfg(test)] pub fn get_private_key(&self) -> ::Scalar { self.sk } /// Computes the second step for the multiplicative blinding version of /// DH-OPRF. This message is sent from the server (who holds the OPRF key) /// to the client. pub fn blind_evaluate( &self, rng: &mut R, blinded_element: &BlindedElement, ) -> VoprfServerEvaluateResult { let mut prepared_evaluation_elements = self.batch_blind_evaluate_prepare(iter::once(blinded_element)); let prepared_evaluation_element = [prepared_evaluation_elements.next().unwrap()]; // This can't fail because we know the size of the inputs. let VoprfServerBatchEvaluateFinishResult { mut messages, proof, } = self .batch_blind_evaluate_finish( rng, iter::once(blinded_element), &prepared_evaluation_element, ) .unwrap(); let message = messages.next().unwrap(); VoprfServerEvaluateResult { message, proof } } /// Allows for batching of the evaluation of multiple [BlindedElement] /// messages from a [VoprfClient] /// /// # Errors /// [`Error::Batch`] if the number of `blinded_elements` and /// `evaluation_elements` don't match or is longer then [`u16::MAX`] #[cfg(feature = "alloc")] pub fn batch_blind_evaluate<'a, R: TryRng + TryCryptoRng, I>( &self, rng: &mut R, blinded_elements: &'a I, ) -> Result> where CS: 'a, &'a I: IntoIterator>, <&'a I as IntoIterator>::IntoIter: ExactSizeIterator, { let prepared_evaluation_elements = self .batch_blind_evaluate_prepare(blinded_elements.into_iter()) .collect(); let VoprfServerBatchEvaluateFinishResult { messages, proof } = self .batch_blind_evaluate_finish::<_, _, Vec<_>>( rng, blinded_elements.into_iter(), &prepared_evaluation_elements, )?; let messages = messages.collect(); Ok(VoprfServerBatchEvaluateResult { messages, proof }) } /// Alternative version of `batch_blind_evaluate` without memory allocation. /// Returned [`PreparedEvaluationElement`] have to be /// [`collect`](Iterator::collect)ed and passed into /// [`batch_blind_evaluate_finish`](Self::batch_blind_evaluate_finish). pub fn batch_blind_evaluate_prepare<'a, I: Iterator>>( &self, blinded_elements: I, ) -> VoprfServerBatchEvaluatePreparedEvaluationElements where CS: 'a, { blinded_elements .zip(iter::repeat(self.sk)) .map(|(blinded_element, sk)| { PreparedEvaluationElement(EvaluationElement(blinded_element.0 * &sk)) }) } /// See [`batch_blind_evaluate_prepare`](Self::batch_blind_evaluate_prepare) /// for more details. /// /// # Errors /// [`Error::Batch`] if the number of `blinded_elements` and /// `evaluation_elements` don't match or is longer then [`u16::MAX`] pub fn batch_blind_evaluate_finish< 'a, 'b, R: TryRng + TryCryptoRng, IB: Iterator> + ExactSizeIterator, IE, >( &self, rng: &mut R, blinded_elements: IB, evaluation_elements: &'b IE, ) -> Result> where CS: 'a + 'b, &'b IE: IntoIterator>, <&'b IE as IntoIterator>::IntoIter: ExactSizeIterator, { let g = CS::Group::base_elem(); let proof = generate_proof( rng, self.sk, g, self.pk, blinded_elements.map(|element| element.0), evaluation_elements.into_iter().map(|element| element.0.0), Mode::Voprf, )?; let messages = evaluation_elements.into_iter().map(, ) -> EvaluationElement>::from( |element| EvaluationElement(element.0.0), )); Ok(VoprfServerBatchEvaluateFinishResult { messages, proof }) } /// Computes the output of the POPRF on the server side /// /// # Errors /// [`Error::Input`] if the `input` is longer then [`u16::MAX`]. pub fn evaluate(&self, input: &[u8]) -> Result::Hash>> { let input_element = hash_to_group::(input, Mode::Voprf)?; if CS::Group::is_identity_elem(input_element).into() { return Err(Error::Input); }; let evaluated_element = input_element * &self.sk; let issued_element = CS::Group::serialize_elem(evaluated_element); server_evaluate_hash_input::(input, None, issued_element) } /// Retrieves the server's public key pub fn get_public_key(&self) -> ::Elem { self.pk } } ///////////////////////// // Convenience Structs // //==================== // ///////////////////////// /// Contains the fields that are returned by a verifiable client blind #[derive_where(Debug; ::Scalar, ::Elem)] pub struct VoprfClientBlindResult { /// The state to be persisted on the client pub state: VoprfClient, /// The message to send to the server pub message: BlindedElement, } /// Concrete return type for [`VoprfClient::batch_finalize`]. pub type VoprfClientBatchFinalizeResult<'a, C, I, II, IC, IM> = FinalizeAfterUnblindResult< 'a, C, I, Zip<<&'a II as IntoIterator>::IntoIter, VoprfUnblindResult<'a, C, IC, IM>>, >; /// Contains the fields that are returned by a verifiable server evaluate #[derive_where(Debug; ::Scalar, ::Elem)] pub struct VoprfServerEvaluateResult { /// 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 #[derive_where(Debug; ::Scalar, ::Elem)] #[cfg(feature = "alloc")] pub struct VoprfServerBatchEvaluateResult { /// The messages to send to the client pub messages: Vec>, /// The proof for the client to verify pub proof: Proof, } /// Concrete type of [`EvaluationElement`]s returned by /// [`VoprfServer::batch_blind_evaluate_prepare`]. pub type VoprfServerBatchEvaluatePreparedEvaluationElements = Map< Zip::Group as Group>::Scalar>>, fn( ( &BlindedElement, <::Group as Group>::Scalar, ), ) -> PreparedEvaluationElement, >; /// Concrete type of [`EvaluationElement`]s in /// [`VoprfServerBatchEvaluateFinishResult`]. pub type VoprfServerBatchEvaluateFinishedMessages<'a, CS, I> = Map< <&'a I as IntoIterator>::IntoIter, fn(&PreparedEvaluationElement) -> EvaluationElement, >; /// Contains the fields that are returned by a verifiable server batch evaluate /// finish. #[derive_where(Debug; <&'a I as IntoIterator>::IntoIter, ::Scalar)] pub struct VoprfServerBatchEvaluateFinishResult<'a, CS: 'a + CipherSuite, I> where &'a I: IntoIterator>, { /// The [`EvaluationElement`]s to send to the client pub messages: VoprfServerBatchEvaluateFinishedMessages<'a, CS, I>, /// The proof for the client to verify pub proof: Proof, } ///////////////////// // Inner functions // // =============== // ///////////////////// type VoprfUnblindResult<'a, CS, IC, IM> = Map< Zip< Map< <&'a IC as IntoIterator>::IntoIter, fn(&VoprfClient) -> <::Group as Group>::Scalar, >, <&'a IM as IntoIterator>::IntoIter, >, fn( ( <::Group as Group>::Scalar, &EvaluationElement, ), ) -> <::Group as Group>::Elem, >; /// Can only fail with [`Error::Batch] or [`Error::ProofVerification`]. fn verifiable_unblind<'a, CS: 'a + CipherSuite, IC, IM>( clients: &'a IC, messages: &'a IM, pk: ::Elem, proof: &Proof, ) -> Result> where &'a IC: 'a + IntoIterator>, <&'a IC as IntoIterator>::IntoIter: ExactSizeIterator, &'a IM: 'a + IntoIterator>, <&'a IM as IntoIterator>::IntoIter: ExactSizeIterator, { let g = CS::Group::base_elem(); let blinds = clients .into_iter() // Convert to `fn` pointer to make a return type possible. .map() -> _>::from(|x| x.blind)); let evaluation_elements = messages.into_iter().map(|element| element.0); let blinded_elements = clients.into_iter().map(|client| client.blinded_element); verify_proof( g, pk, blinded_elements, evaluation_elements, proof, Mode::Voprf, )?; Ok(blinds .zip(messages) .map(|(blind, x)| x.0 * &CS::Group::invert_scalar(blind))) } type FinalizeAfterUnblindResult<'a, C, I, IE> = Map< IE, fn((I, <::Group as Group>::Elem)) -> Result::Hash>>, >; /// Returned values can only fail with [`Error::Input`]. fn finalize_after_unblind< 'a, CS: CipherSuite, I: AsRef<[u8]>, IE: 'a + Iterator::Elem)>, >( inputs_and_unblinded_elements: IE, ) -> FinalizeAfterUnblindResult<'a, CS, I, IE> { inputs_and_unblinded_elements.map(|(input, unblinded_element)| { let elem_len = ::ElemLen::U16.to_be_bytes(); // hashInput = I2OSP(len(input), 2) || input || // I2OSP(len(unblindedElement), 2) || unblindedElement || // "Finalize" // return Hash(hashInput) Ok(CS::Hash::new() .chain_update(i2osp_2(input.as_ref().len()).map_err(|_| Error::Input)?) .chain_update(input.as_ref()) .chain_update(elem_len) .chain_update(CS::Group::serialize_elem(unblinded_element)) .chain_update(STR_FINALIZE) .finalize()) }) } /////////// // Tests // // ===== // /////////// #[cfg(test)] mod tests { use core::ptr; use ::alloc::vec; use ::alloc::vec::Vec; use rand::rngs::SysRng; use super::*; use crate::common::{Dst, STR_HASH_TO_GROUP}; use crate::{Group, Suite}; fn prf( input: &[u8], key: ::Scalar, mode: Mode, ) -> Output { let dst = Dst::new::(STR_HASH_TO_GROUP, mode); let point = CS::Group::hash_to_curve::(&[input], &dst.as_dst()).unwrap(); let res = point * &key; finalize_after_unblind::(iter::once((input, res))) .next() .unwrap() .unwrap() } fn verifiable_retrieval() { let input = b"input"; let mut rng = SysRng; let client_blind_result = VoprfClient::::blind(input, &mut rng).unwrap(); let server = VoprfServer::::new(&mut rng).unwrap(); let server_result = server.blind_evaluate(&mut rng, &client_blind_result.message); let client_finalize_result = client_blind_result .state .finalize( input, &server_result.message, &server_result.proof, server.get_public_key(), ) .unwrap(); let res2 = prf::(input, server.get_private_key(), Mode::Voprf); assert_eq!(client_finalize_result, res2); } fn verifiable_batch_retrieval() { let mut rng = SysRng; let mut inputs = vec![]; let mut client_states = vec![]; let mut client_messages = vec![]; let num_iterations = 10; for _ in 0..num_iterations { let mut input = [0u8; 32]; rng.try_fill_bytes(&mut input).unwrap(); let client_blind_result = VoprfClient::::blind(&input, &mut rng).unwrap(); inputs.push(input); client_states.push(client_blind_result.state); client_messages.push(client_blind_result.message); } let server = VoprfServer::::new(&mut rng).unwrap(); let prepared_evaluation_elements: Vec<_> = server .batch_blind_evaluate_prepare(client_messages.iter()) .collect(); let VoprfServerBatchEvaluateFinishResult { messages, proof } = server .batch_blind_evaluate_finish( &mut rng, client_messages.iter(), &prepared_evaluation_elements, ) .unwrap(); let messages: Vec<_> = messages.collect(); let client_finalize_result = VoprfClient::batch_finalize( &inputs, &client_states, &messages, &proof, server.get_public_key(), ) .unwrap() .collect::>>() .unwrap(); let mut res2 = vec![]; for input in inputs.iter().take(num_iterations) { let output = prf::(input, server.get_private_key(), Mode::Voprf); res2.push(output); } assert_eq!(client_finalize_result, res2); } fn verifiable_batch_bad_public_key() { let mut rng = SysRng; let mut inputs = vec![]; let mut client_states = vec![]; let mut client_messages = vec![]; let num_iterations = 10; for _ in 0..num_iterations { let mut input = [0u8; 32]; rng.try_fill_bytes(&mut input).unwrap(); let client_blind_result = VoprfClient::::blind(&input, &mut rng).unwrap(); inputs.push(input); client_states.push(client_blind_result.state); client_messages.push(client_blind_result.message); } let server = VoprfServer::::new(&mut rng).unwrap(); let prepared_evaluation_elements: Vec<_> = server .batch_blind_evaluate_prepare(client_messages.iter()) .collect(); let VoprfServerBatchEvaluateFinishResult { messages, proof } = server .batch_blind_evaluate_finish( &mut rng, client_messages.iter(), &prepared_evaluation_elements, ) .unwrap(); let messages: Vec<_> = messages.collect(); let wrong_pk = { let dst = Dst::new::(STR_HASH_TO_GROUP, Mode::Oprf); // Choose a group element that is unlikely to be the right public key CS::Group::hash_to_curve::(&[b"msg"], &dst.as_dst()).unwrap() }; let client_finalize_result = VoprfClient::batch_finalize(&inputs, &client_states, &messages, &proof, wrong_pk); assert!(client_finalize_result.is_err()); } fn verifiable_bad_public_key() { let input = b"input"; let mut rng = SysRng; let client_blind_result = VoprfClient::::blind(input, &mut rng).unwrap(); let server = VoprfServer::::new(&mut rng).unwrap(); let server_result = server.blind_evaluate(&mut rng, &client_blind_result.message); let wrong_pk = { let dst = Dst::new::(STR_HASH_TO_GROUP, Mode::Oprf); // Choose a group element that is unlikely to be the right public key CS::Group::hash_to_curve::(&[b"msg"], &dst.as_dst()).unwrap() }; let client_finalize_result = client_blind_result.state.finalize( input, &server_result.message, &server_result.proof, wrong_pk, ); assert!(client_finalize_result.is_err()); } fn verifiable_server_evaluate() { let input = b"input"; let mut rng = SysRng; let client_blind_result = VoprfClient::::blind(input, &mut rng).unwrap(); let server = VoprfServer::::new(&mut rng).unwrap(); let server_result = server.blind_evaluate(&mut rng, &client_blind_result.message); let client_finalize = client_blind_result .state .finalize( input, &server_result.message, &server_result.proof, server.get_public_key(), ) .unwrap(); // We expect the outputs from client and server to be equal given an identical // input let server_evaluate = server.evaluate(input).unwrap(); assert_eq!(client_finalize, server_evaluate); // We expect the outputs from client and server to be different given different // inputs let wrong_input = b"wrong input"; let server_evaluate = server.evaluate(wrong_input).unwrap(); assert!(client_finalize != server_evaluate); } fn zeroize_voprf_client() { let input = b"input"; let mut rng = SysRng; let client_blind_result = VoprfClient::::blind(input, &mut rng).unwrap(); let mut state = client_blind_result.state; unsafe { ptr::drop_in_place(&mut state) }; assert!(state.serialize().iter().all(|&x| x == 0)); let mut message = client_blind_result.message; unsafe { ptr::drop_in_place(&mut message) }; assert!(message.serialize().iter().all(|&x| x == 0)); } fn zeroize_voprf_server() { let input = b"input"; let mut rng = SysRng; let client_blind_result = VoprfClient::::blind(input, &mut rng).unwrap(); let server = VoprfServer::::new(&mut rng).unwrap(); let server_result = server.blind_evaluate(&mut rng, &client_blind_result.message); let mut state = server; unsafe { ptr::drop_in_place(&mut state) }; assert!(state.serialize().iter().all(|&x| x == 0)); let mut message = server_result.message; unsafe { ptr::drop_in_place(&mut message) }; assert!(message.serialize().iter().all(|&x| x == 0)); let mut proof = server_result.proof; unsafe { ptr::drop_in_place(&mut proof) }; assert!(proof.serialize().iter().all(|&x| x == 0)); } #[test] fn test_functionality() -> Result<()> { use p256::NistP256; use p384::NistP384; use p521::NistP521; #[cfg(feature = "ristretto255")] { use crate::Ristretto255; verifiable_retrieval::(); verifiable_batch_retrieval::(); verifiable_bad_public_key::(); verifiable_batch_bad_public_key::(); verifiable_server_evaluate::(); zeroize_voprf_client::(); zeroize_voprf_server::(); } verifiable_retrieval::>(); verifiable_batch_retrieval::>(); verifiable_bad_public_key::>(); verifiable_batch_bad_public_key::>(); verifiable_server_evaluate::>(); zeroize_voprf_client::>(); zeroize_voprf_server::>(); verifiable_retrieval::>(); verifiable_batch_retrieval::>(); verifiable_bad_public_key::>(); verifiable_batch_bad_public_key::>(); verifiable_server_evaluate::>(); zeroize_voprf_client::>(); zeroize_voprf_server::>(); verifiable_retrieval::>(); verifiable_batch_retrieval::>(); verifiable_bad_public_key::>(); verifiable_batch_bad_public_key::>(); verifiable_server_evaluate::>(); zeroize_voprf_client::>(); zeroize_voprf_server::>(); Ok(()) } }