Skip to main content

voprf_vx/
poprf.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// Copyright (c) VexaHub and contributors.
3// Copyright (c) Meta Platforms, Inc. and affiliates.
4
5//! Contains the main POPRF API
6
7#[cfg(feature = "alloc")]
8use alloc::vec::Vec;
9use core::iter::{self, Map, Repeat, Zip};
10
11use derive_where::derive_where;
12use digest::{Digest, Output, OutputSizeUser};
13use hybrid_array::typenum::Unsigned;
14use hybrid_array::{Array, ArraySize};
15use rand_core::{TryCryptoRng, TryRng};
16
17use crate::common::{
18    BlindedElement, Dst, EvaluationElement, Mode, PreparedEvaluationElement, Proof, STR_FINALIZE,
19    STR_HASH_TO_SCALAR, STR_INFO, derive_keypair, deterministic_blind_unchecked, generate_proof,
20    hash_to_group, i2osp_2, server_evaluate_hash_input, verify_proof,
21};
22#[cfg(feature = "serde")]
23use crate::serialization::serde::{Element, Scalar};
24use crate::{CipherSuite, Error, Group, Result};
25
26////////////////////////////
27// High-level API Structs //
28// ====================== //
29////////////////////////////
30
31/// A client which engages with a [PoprfServer] in verifiable mode, meaning
32/// that the OPRF outputs can be checked against a server public key.
33#[derive_where(Clone, ZeroizeOnDrop)]
34#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::Group as Group>::Scalar, <CS::Group as Group>::Elem)]
35#[cfg_attr(
36    feature = "serde",
37    derive(serde::Deserialize, serde::Serialize),
38    serde(bound = "")
39)]
40pub struct PoprfClient<CS: CipherSuite> {
41    #[cfg_attr(feature = "serde", serde(with = "Scalar::<CS::Group>"))]
42    pub(crate) blind: <CS::Group as Group>::Scalar,
43    #[cfg_attr(feature = "serde", serde(with = "Element::<CS::Group>"))]
44    pub(crate) blinded_element: <CS::Group as Group>::Elem,
45}
46
47/// A server which engages with a [PoprfClient] in verifiable mode, meaning
48/// that the OPRF outputs can be checked against a server public key.
49#[derive_where(Clone, ZeroizeOnDrop)]
50#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::Group as Group>::Scalar, <CS::Group as Group>::Elem)]
51#[cfg_attr(
52    feature = "serde",
53    derive(serde::Deserialize, serde::Serialize),
54    serde(bound = "")
55)]
56pub struct PoprfServer<CS: CipherSuite> {
57    #[cfg_attr(feature = "serde", serde(with = "Scalar::<CS::Group>"))]
58    pub(crate) sk: <CS::Group as Group>::Scalar,
59    #[cfg_attr(feature = "serde", serde(with = "Element::<CS::Group>"))]
60    pub(crate) pk: <CS::Group as Group>::Elem,
61}
62
63/////////////////////////
64// API Implementations //
65// =================== //
66/////////////////////////
67
68impl<CS: CipherSuite> PoprfClient<CS> {
69    /// Computes the first step for the multiplicative blinding version of
70    /// DH-OPRF.
71    ///
72    /// # Errors
73    /// [`Error::Input`] if the `input` is empty or longer than [`u16::MAX`].
74    pub fn blind<R: TryRng + TryCryptoRng>(
75        input: &[u8],
76        blinding_factor_rng: &mut R,
77    ) -> Result<PoprfClientBlindResult<CS>> {
78        let blind = CS::Group::random_scalar(blinding_factor_rng)?;
79        Self::deterministic_blind_unchecked_inner(input, blind)
80    }
81
82    /// Computes the first step for the multiplicative blinding version of
83    /// DH-OPRF, taking a blinding factor scalar as input instead of sampling
84    /// from an RNG.
85    ///
86    /// # Caution
87    ///
88    /// This should be used with caution, since it does not perform any checks
89    /// on the validity of the blinding factor!
90    ///
91    /// # Errors
92    /// [`Error::Input`] if the `input` is empty or longer than [`u16::MAX`].
93    #[cfg(any(feature = "danger", test))]
94    pub fn deterministic_blind_unchecked(
95        input: &[u8],
96        blind: <CS::Group as Group>::Scalar,
97    ) -> Result<PoprfClientBlindResult<CS>> {
98        Self::deterministic_blind_unchecked_inner(input, blind)
99    }
100
101    /// Can only fail with [`Error::Input`].
102    fn deterministic_blind_unchecked_inner(
103        input: &[u8],
104        blind: <CS::Group as Group>::Scalar,
105    ) -> Result<PoprfClientBlindResult<CS>> {
106        let blinded_element = deterministic_blind_unchecked::<CS>(input, &blind, Mode::Poprf)?;
107        Ok(PoprfClientBlindResult {
108            state: Self {
109                blind,
110                blinded_element,
111            },
112            message: BlindedElement(blinded_element),
113        })
114    }
115
116    /// Computes the third step for the multiplicative blinding version of
117    /// DH-OPRF, in which the client unblinds the server's message.
118    ///
119    /// # Errors
120    /// - [`Error::Info`] if the `info` is longer than `u16::MAX`.
121    /// - [`Error::Input`] if the `input` is empty or longer than [`u16::MAX`].
122    /// - [`Error::Protocol`] if the protocol fails and can't be completed.
123    /// - [`Error::ProofVerification`] if the `proof` failed to verify.
124    pub fn finalize(
125        &self,
126        input: &[u8],
127        evaluation_element: &EvaluationElement<CS>,
128        proof: &Proof<CS>,
129        pk: <CS::Group as Group>::Elem,
130        info: Option<&[u8]>,
131    ) -> Result<Output<CS::Hash>>
132    where
133        <<CS as CipherSuite>::Hash as OutputSizeUser>::OutputSize: ArraySize,
134    {
135        let clients = core::array::from_ref(self);
136        let messages = core::array::from_ref(evaluation_element);
137
138        let mut batch_result =
139            Self::batch_finalize(iter::once(input), clients, messages, proof, pk, info)?;
140        batch_result.next().unwrap()
141    }
142
143    /// Allows for batching of the finalization of multiple [PoprfClient]
144    /// and [EvaluationElement] pairs
145    ///
146    /// # Errors
147    /// - [`Error::Info`] if the `info` is longer than `u16::MAX`.
148    /// - [`Error::Protocol`] if the protocol fails and can't be completed.
149    /// - [`Error::Batch`] if the number of `inputs`, `clients` and `messages`
150    ///   don't match or is longer than [`u16::MAX`].
151    /// - [`Error::ProofVerification`] if the `proof` failed to verify.
152    ///
153    /// The resulting messages can each fail individually with [`Error::Input`]
154    /// if the `input` is empty or longer than [`u16::MAX`].
155    pub fn batch_finalize<'a, II: 'a + Iterator<Item = &'a [u8]> + ExactSizeIterator, IC, IM>(
156        inputs: II,
157        clients: &'a IC,
158        messages: &'a IM,
159        proof: &Proof<CS>,
160        pk: <CS::Group as Group>::Elem,
161        info: Option<&'a [u8]>,
162    ) -> Result<PoprfClientBatchFinalizeResult<'a, CS, II, IC, IM>>
163    where
164        CS: 'a,
165        &'a IC: 'a + IntoIterator<Item = &'a PoprfClient<CS>>,
166        <&'a IC as IntoIterator>::IntoIter: ExactSizeIterator,
167        &'a IM: 'a + IntoIterator<Item = &'a EvaluationElement<CS>>,
168        <&'a IM as IntoIterator>::IntoIter: ExactSizeIterator,
169        <<CS as CipherSuite>::Hash as OutputSizeUser>::OutputSize: ArraySize,
170    {
171        let unblinded_elements = poprf_unblind(clients, messages, pk, proof, info)?;
172
173        finalize_after_unblind::<'a, CS, _, _>(unblinded_elements, inputs, info)
174    }
175
176    /// Only used for test functions
177    #[cfg(test)]
178    pub fn get_blind(&self) -> <CS::Group as Group>::Scalar {
179        self.blind
180    }
181}
182
183impl<CS: CipherSuite> PoprfServer<CS> {
184    /// Produces a new instance of a [PoprfServer] using a supplied RNG
185    ///
186    /// # Errors
187    /// [`Error::Protocol`] if the protocol fails and can't be completed.
188    pub fn new<R: TryRng + TryCryptoRng>(rng: &mut R) -> Result<Self> {
189        let mut seed = Array::<_, <CS::Group as Group>::ScalarLen>::default();
190        rng.try_fill_bytes(&mut seed).map_err(|_| Error::Protocol)?;
191
192        Self::new_from_seed(&seed, &[])
193    }
194
195    /// Produces a new instance of a [PoprfServer] using a supplied set of
196    /// bytes to represent the server's private key
197    ///
198    /// # Errors
199    /// [`Error::Deserialization`] if the private key is not a valid point on
200    /// the group or zero.
201    pub fn new_with_key(key: &[u8]) -> Result<Self> {
202        let sk = CS::Group::deserialize_scalar(key)?;
203        let pk = CS::Group::base_elem() * &sk;
204        Ok(Self { sk, pk })
205    }
206
207    /// Produces a new instance of a [PoprfServer] using a supplied set of
208    /// bytes which are used as a seed to derive the server's private key.
209    ///
210    /// Corresponds to DeriveKeyPair() function from the VOPRF specification.
211    ///
212    /// # Errors
213    /// - [`Error::DeriveKeyPair`] if the `input` and `seed` together are longer
214    ///   then `u16::MAX - 3`.
215    /// - [`Error::Protocol`] if the protocol fails and can't be completed.
216    pub fn new_from_seed(seed: &[u8], info: &[u8]) -> Result<Self> {
217        let (sk, pk) = derive_keypair::<CS>(seed, info, Mode::Poprf)?;
218        Ok(Self { sk, pk })
219    }
220
221    /// Only used for tests
222    #[cfg(test)]
223    pub fn get_private_key(&self) -> <CS::Group as Group>::Scalar {
224        self.sk
225    }
226
227    /// Computes the second step for the multiplicative blinding version of
228    /// DH-OPRF. This message is sent from the server (who holds the OPRF key)
229    /// to the client.
230    ///
231    /// # Errors
232    /// - [`Error::Info`] if the `info` is longer than `u16::MAX`.
233    /// - [`Error::Protocol`] if the protocol fails and can't be completed.
234    pub fn blind_evaluate<R: TryRng + TryCryptoRng>(
235        &self,
236        rng: &mut R,
237        blinded_element: &BlindedElement<CS>,
238        info: Option<&[u8]>,
239    ) -> Result<PoprfServerEvaluateResult<CS>> {
240        let PoprfServerBatchEvaluatePrepareResult {
241            mut prepared_evaluation_elements,
242            prepared_tweak,
243        } = self.batch_blind_evaluate_prepare(iter::once(blinded_element), info)?;
244
245        let prepared_evaluation_element = prepared_evaluation_elements.next().unwrap();
246        let prepared_evaluation_elements = core::array::from_ref(&prepared_evaluation_element);
247
248        let PoprfServerBatchEvaluateFinishResult {
249            mut messages,
250            proof,
251        } = Self::batch_blind_evaluate_finish(
252            rng,
253            iter::once(blinded_element),
254            prepared_evaluation_elements,
255            &prepared_tweak,
256        )
257        .unwrap();
258
259        Ok(PoprfServerEvaluateResult {
260            message: messages.next().unwrap(),
261            proof,
262        })
263    }
264
265    /// Allows for batching of the evaluation of multiple [BlindedElement]
266    /// messages from a [PoprfClient]
267    ///
268    /// # Errors
269    /// - [`Error::Info`] if the `info` is longer than `u16::MAX`.
270    /// - [`Error::Protocol`] if the protocol fails and can't be completed.
271    #[cfg(feature = "alloc")]
272    pub fn batch_blind_evaluate<'a, R: TryRng + TryCryptoRng, IE>(
273        &self,
274        rng: &mut R,
275        blinded_elements: &'a IE,
276        info: Option<&[u8]>,
277    ) -> Result<PoprfServerBatchEvaluateResult<CS>>
278    where
279        CS: 'a,
280        &'a IE: 'a + IntoIterator<Item = &'a BlindedElement<CS>>,
281        <&'a IE as IntoIterator>::IntoIter: ExactSizeIterator,
282    {
283        let PoprfServerBatchEvaluatePrepareResult {
284            prepared_evaluation_elements,
285            prepared_tweak,
286        } = self.batch_blind_evaluate_prepare(blinded_elements.into_iter(), info)?;
287
288        let prepared_evaluation_elements: Vec<_> = prepared_evaluation_elements.collect();
289
290        // This can't fail because we know the size of the inputs.
291        let PoprfServerBatchEvaluateFinishResult { messages, proof } =
292            Self::batch_blind_evaluate_finish::<_, _, Vec<_>>(
293                rng,
294                blinded_elements.into_iter(),
295                &prepared_evaluation_elements,
296                &prepared_tweak,
297            )
298            .unwrap();
299
300        let messages: Vec<_> = messages.collect();
301
302        Ok(PoprfServerBatchEvaluateResult { messages, proof })
303    }
304
305    /// Alternative version of `batch_blind_evaluate` without
306    /// memory allocation. Returned [`PreparedEvaluationElement`] have to
307    /// be [`collect`](Iterator::collect)ed and passed into
308    /// [`batch_blind_evaluate_finish`](Self::batch_blind_evaluate_finish).
309    ///
310    /// # Errors
311    /// - [`Error::Info`] if the `info` is longer than `u16::MAX`.
312    /// - [`Error::Protocol`] if the protocol fails and can't be completed.
313    pub fn batch_blind_evaluate_prepare<'a, I: Iterator<Item = &'a BlindedElement<CS>>>(
314        &self,
315        blinded_elements: I,
316        info: Option<&[u8]>,
317    ) -> Result<PoprfServerBatchEvaluatePrepareResult<CS, I>>
318    where
319        CS: 'a,
320    {
321        let tweak = compute_tweak::<CS>(self.sk, info)?;
322
323        Ok(PoprfServerBatchEvaluatePrepareResult {
324            prepared_evaluation_elements: blinded_elements.zip(iter::repeat(tweak)).map(
325                |(blinded_element, tweak)| {
326                    PreparedEvaluationElement(EvaluationElement(
327                        blinded_element.0 * &CS::Group::invert_scalar(tweak),
328                    ))
329                },
330            ),
331            prepared_tweak: PoprfPreparedTweak(tweak),
332        })
333    }
334
335    /// See [`batch_blind_evaluate_prepare`](Self::batch_blind_evaluate_prepare)
336    /// for more details.
337    ///
338    /// # Errors
339    /// [`Error::Batch`] if the number of `blinded_elements` and
340    /// `prepared_evaluation_elements` don't match or is longer then
341    /// [`u16::MAX`]
342    pub fn batch_blind_evaluate_finish<
343        'a,
344        'b,
345        R: TryRng + TryCryptoRng,
346        IB: Iterator<Item = &'a BlindedElement<CS>> + ExactSizeIterator,
347        IE,
348    >(
349        rng: &mut R,
350        blinded_elements: IB,
351        prepared_evaluation_elements: &'b IE,
352        prepared_tweak: &PoprfPreparedTweak<CS>,
353    ) -> Result<PoprfServerBatchEvaluateFinishResult<'b, CS, IE>>
354    where
355        CS: 'a,
356        &'b IE: IntoIterator<Item = &'b PreparedEvaluationElement<CS>>,
357        <&'b IE as IntoIterator>::IntoIter: ExactSizeIterator,
358    {
359        let g = CS::Group::base_elem();
360        let tweak = prepared_tweak.0;
361        let tweaked_key = g * &tweak;
362
363        let proof = generate_proof(
364            rng,
365            tweak,
366            g,
367            tweaked_key,
368            prepared_evaluation_elements
369                .into_iter()
370                .map(|element| element.0.0),
371            blinded_elements.map(|element| element.0),
372            Mode::Poprf,
373        )?;
374
375        let messages = prepared_evaluation_elements.into_iter().map(<fn(
376            &PreparedEvaluationElement<CS>,
377        ) -> _>::from(
378            |element| EvaluationElement(element.0.0),
379        ));
380
381        Ok(PoprfServerBatchEvaluateFinishResult { messages, proof })
382    }
383
384    /// Computes the output of the VOPRF on the server side
385    ///
386    /// # Errors
387    /// [`Error::Input`]  if the `input` is longer then [`u16::MAX`].
388    pub fn evaluate(
389        &self,
390        input: &[u8],
391        info: Option<&[u8]>,
392    ) -> Result<Output<<CS as CipherSuite>::Hash>> {
393        let input_element = hash_to_group::<CS>(input, Mode::Poprf)?;
394        if CS::Group::is_identity_elem(input_element).into() {
395            return Err(Error::Input);
396        };
397
398        let tweak = compute_tweak::<CS>(self.sk, info)?;
399
400        let evaluated_element = input_element * &CS::Group::invert_scalar(tweak);
401
402        let issued_element = CS::Group::serialize_elem(evaluated_element);
403
404        server_evaluate_hash_input::<CS>(input, info, issued_element)
405    }
406
407    /// Retrieves the server's public key
408    pub fn get_public_key(&self) -> <CS::Group as Group>::Elem {
409        self.pk
410    }
411}
412
413impl<CS: CipherSuite> BlindedElement<CS> {
414    /// Creates a [BlindedElement] from a raw group element.
415    ///
416    /// # Caution
417    ///
418    /// This should be used with caution, since it does not perform any checks
419    /// on the validity of the value itself!
420    #[cfg(feature = "danger")]
421    pub fn from_value_unchecked(value: <CS::Group as Group>::Elem) -> Self {
422        Self(value)
423    }
424
425    /// Exposes the internal value
426    #[cfg(feature = "danger")]
427    pub fn value(&self) -> <CS::Group as Group>::Elem {
428        self.0
429    }
430}
431
432impl<CS: CipherSuite> EvaluationElement<CS> {
433    /// Creates an [EvaluationElement] from a raw group element.
434    ///
435    /// # Caution
436    ///
437    /// This should be used with caution, since it does not perform any checks
438    /// on the validity of the value itself!
439    #[cfg(feature = "danger")]
440    pub fn from_value_unchecked(value: <CS::Group as Group>::Elem) -> Self {
441        Self(value)
442    }
443
444    /// Exposes the internal value
445    #[cfg(feature = "danger")]
446    pub fn value(&self) -> <CS::Group as Group>::Elem {
447        self.0
448    }
449}
450
451/////////////////////////
452// Convenience Structs //
453//==================== //
454/////////////////////////
455
456/// Contains the fields that are returned by a verifiable client blind
457#[derive_where(Debug; <CS::Group as Group>::Scalar, <CS::Group as Group>::Elem)]
458pub struct PoprfClientBlindResult<CS: CipherSuite> {
459    /// The state to be persisted on the client
460    pub state: PoprfClient<CS>,
461    /// The message to send to the server
462    pub message: BlindedElement<CS>,
463}
464
465/// Concrete return type for [`PoprfClient::batch_finalize`].
466pub type PoprfClientBatchFinalizeResult<'a, CS, II, IC, IM> =
467    FinalizeAfterUnblindResult<'a, CS, PoprfUnblindResult<'a, CS, IC, IM>, II>;
468
469/// Contains the fields that are returned by a verifiable server evaluate
470#[derive_where(Debug; <CS::Group as Group>::Scalar, <CS::Group as Group>::Elem)]
471pub struct PoprfServerEvaluateResult<CS: CipherSuite> {
472    /// The message to send to the client
473    pub message: EvaluationElement<CS>,
474    /// The proof for the client to verify
475    pub proof: Proof<CS>,
476}
477
478/// Contains the fields that are returned by a verifiable server batch evaluate
479#[derive_where(Debug; <CS::Group as Group>::Scalar, <CS::Group as Group>::Elem)]
480#[cfg(feature = "alloc")]
481pub struct PoprfServerBatchEvaluateResult<CS: CipherSuite> {
482    /// The messages to send to the client
483    pub messages: Vec<EvaluationElement<CS>>,
484    /// The proof for the client to verify
485    pub proof: Proof<CS>,
486}
487
488/// Concrete type of [`EvaluationElement`]s in
489/// [`PoprfServerBatchEvaluatePrepareResult`].
490pub type PoprfServerBatchEvaluatePreparedEvaluationElements<CS, I> = Map<
491    Zip<I, Repeat<<<CS as CipherSuite>::Group as Group>::Scalar>>,
492    fn(
493        (
494            &BlindedElement<CS>,
495            <<CS as CipherSuite>::Group as Group>::Scalar,
496        ),
497    ) -> PreparedEvaluationElement<CS>,
498>;
499
500/// Prepared tweak by a partially verifiable server batch evaluate prepare.
501#[derive_where(Clone, ZeroizeOnDrop)]
502#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::Group as Group>::Scalar)]
503#[cfg_attr(
504    feature = "serde",
505    derive(serde::Deserialize, serde::Serialize),
506    serde(bound = "")
507)]
508pub struct PoprfPreparedTweak<CS: CipherSuite>(
509    #[cfg_attr(feature = "serde", serde(with = "Scalar::<CS::Group>"))]
510    <CS::Group as Group>::Scalar,
511);
512
513/// Contains the fields that are returned by a partially verifiable server batch
514/// evaluate prepare
515#[derive_where(Debug; I, <CS::Group as Group>::Scalar)]
516pub struct PoprfServerBatchEvaluatePrepareResult<CS: CipherSuite, I> {
517    /// Prepared [`EvaluationElement`].
518    pub prepared_evaluation_elements: PoprfServerBatchEvaluatePreparedEvaluationElements<CS, I>,
519    /// Prepared tweak.
520    pub prepared_tweak: PoprfPreparedTweak<CS>,
521}
522
523/// Concrete type of [`EvaluationElement`]s in
524/// [`PoprfServerBatchEvaluateFinishResult`].
525pub type PoprfServerBatchEvaluateFinishedMessages<'a, CS, I> = Map<
526    <&'a I as IntoIterator>::IntoIter,
527    fn(&PreparedEvaluationElement<CS>) -> EvaluationElement<CS>,
528>;
529
530/// Contains the fields that are returned by a verifiable server batch evaluate
531/// finish.
532#[derive_where(Debug; <&'a I as IntoIterator>::IntoIter, <CS::Group as Group>::Scalar)]
533pub struct PoprfServerBatchEvaluateFinishResult<'a, CS: 'a + CipherSuite, I>
534where
535    &'a I: IntoIterator<Item = &'a PreparedEvaluationElement<CS>>,
536{
537    /// The [`EvaluationElement`]s to send to the client
538    pub messages: PoprfServerBatchEvaluateFinishedMessages<'a, CS, I>,
539    /// The proof for the client to verify
540    pub proof: Proof<CS>,
541}
542
543/////////////////////
544// Inner functions //
545// =============== //
546/////////////////////
547
548/// Inner function for POPRF blind. Computes the tweaked key from the server
549/// public key and info.
550///
551/// Can only fail with [`Error::Info`] or [`Error::Protocol`]
552fn compute_tweaked_key<CS: CipherSuite>(
553    pk: <CS::Group as Group>::Elem,
554    info: Option<&[u8]>,
555) -> Result<<CS::Group as Group>::Elem> {
556    // None for info is treated the same as empty bytes
557    let info = info.unwrap_or_default();
558
559    // framedInfo = "Info" || I2OSP(len(info), 2) || info
560    // m = G.HashToScalar(framedInfo)
561    // T = G.ScalarBaseMult(m)
562    // tweakedKey = T + pkS
563    // if tweakedKey == G.Identity():
564    //   raise InvalidInputError
565    let info_len = i2osp_2(info.len()).map_err(|_| Error::Info)?;
566    let framed_info = [STR_INFO.as_slice(), &info_len, info];
567
568    let dst = Dst::new::<CS, _>(STR_HASH_TO_SCALAR, Mode::Poprf);
569    // This can't fail, the size of the `input` is known.
570    let m = CS::Group::hash_to_scalar::<CS::Hash>(&framed_info, &dst.as_dst()).unwrap();
571
572    let t = CS::Group::base_elem() * &m;
573    let tweaked_key = t + &pk;
574
575    // Check if resulting element
576    match bool::from(CS::Group::is_identity_elem(tweaked_key)) {
577        true => Err(Error::Protocol),
578        false => Ok(tweaked_key),
579    }
580}
581
582/// Inner function for POPRF evaluate. Computes the tweak from the server
583/// private key and info.
584///
585/// Can only fail with [`Error::Info`] and [`Error::Protocol`].
586fn compute_tweak<CS: CipherSuite>(
587    sk: <CS::Group as Group>::Scalar,
588    info: Option<&[u8]>,
589) -> Result<<CS::Group as Group>::Scalar> {
590    // None for info is treated the same as empty bytes
591    let info = info.unwrap_or_default();
592
593    // framedInfo = "Info" || I2OSP(len(info), 2) || info
594    // m = G.HashToScalar(framedInfo)
595    // t = skS + m
596    // if t == 0:
597    //   raise InverseError
598    let info_len = i2osp_2(info.len()).map_err(|_| Error::Info)?;
599    let framed_info = [STR_INFO.as_slice(), &info_len, info];
600
601    let dst = Dst::new::<CS, _>(STR_HASH_TO_SCALAR, Mode::Poprf);
602    // This can't fail, the size of the `input` is known.
603    let m = CS::Group::hash_to_scalar::<CS::Hash>(&framed_info, &dst.as_dst()).unwrap();
604
605    let t = sk + &m;
606
607    // Check if resulting element is equal to zero
608    match bool::from(CS::Group::is_zero_scalar(t)) {
609        true => Err(Error::Protocol),
610        false => Ok(t),
611    }
612}
613
614type PoprfUnblindResult<'a, CS, IC, IM> = Map<
615    Zip<
616        Map<
617            <&'a IC as IntoIterator>::IntoIter,
618            fn(&PoprfClient<CS>) -> <<CS as CipherSuite>::Group as Group>::Scalar,
619        >,
620        <&'a IM as IntoIterator>::IntoIter,
621    >,
622    fn(
623        (
624            <<CS as CipherSuite>::Group as Group>::Scalar,
625            &'a EvaluationElement<CS>,
626        ),
627    ) -> <<CS as CipherSuite>::Group as Group>::Elem,
628>;
629
630/// Can only fail with [`Error::Info`], [`Error::Protocol`], [`Error::Batch] or
631/// [`Error::ProofVerification`].
632fn poprf_unblind<'a, CS: 'a + CipherSuite, IC, IM>(
633    clients: &'a IC,
634    messages: &'a IM,
635    pk: <CS::Group as Group>::Elem,
636    proof: &Proof<CS>,
637    info: Option<&[u8]>,
638) -> Result<PoprfUnblindResult<'a, CS, IC, IM>>
639where
640    &'a IC: 'a + IntoIterator<Item = &'a PoprfClient<CS>>,
641    <&'a IC as IntoIterator>::IntoIter: ExactSizeIterator,
642    &'a IM: 'a + IntoIterator<Item = &'a EvaluationElement<CS>>,
643    <&'a IM as IntoIterator>::IntoIter: ExactSizeIterator,
644{
645    let info = info.unwrap_or_default();
646    let tweaked_key = compute_tweaked_key::<CS>(pk, Some(info))?;
647
648    let g = CS::Group::base_elem();
649
650    let blinds = clients
651        .into_iter()
652        // Convert to `fn` pointer to make a return type possible.
653        .map(<fn(&PoprfClient<CS>) -> _>::from(|x| x.blind));
654    let evaluation_elements = messages.into_iter().map(|element| element.0);
655    let blinded_elements = clients.into_iter().map(|client| client.blinded_element);
656
657    verify_proof(
658        g,
659        tweaked_key,
660        evaluation_elements,
661        blinded_elements,
662        proof,
663        Mode::Poprf,
664    )?;
665
666    Ok(blinds
667        .zip(messages)
668        .map(|(blind, x)| x.0 * &CS::Group::invert_scalar(blind)))
669}
670
671type FinalizeAfterUnblindResult<'a, CS, IE, II> = Map<
672    Zip<Zip<IE, II>, Repeat<&'a [u8]>>,
673    fn(
674        ((<<CS as CipherSuite>::Group as Group>::Elem, &[u8]), &[u8]),
675    ) -> Result<Output<<CS as CipherSuite>::Hash>>,
676>;
677
678/// Can only fail with [`Error::Batch`] and returned values can only fail with
679/// [`Error::Info`] or [`Error::Input`] individually.
680fn finalize_after_unblind<
681    'a,
682    CS: CipherSuite,
683    IE: 'a + Iterator<Item = <CS::Group as Group>::Elem> + ExactSizeIterator,
684    II: 'a + Iterator<Item = &'a [u8]> + ExactSizeIterator,
685>(
686    unblinded_elements: IE,
687    inputs: II,
688    info: Option<&'a [u8]>,
689) -> Result<FinalizeAfterUnblindResult<'a, CS, IE, II>>
690where
691    <<CS as CipherSuite>::Hash as OutputSizeUser>::OutputSize: ArraySize,
692{
693    if unblinded_elements.len() != inputs.len() {
694        return Err(Error::Batch);
695    }
696
697    let info = info.unwrap_or_default();
698
699    Ok(unblinded_elements.zip(inputs).zip(iter::repeat(info)).map(
700        |((unblinded_element, input), info)| {
701            let elem_len = <CS::Group as Group>::ElemLen::U16.to_be_bytes();
702
703            // hashInput = I2OSP(len(input), 2) || input ||
704            //             I2OSP(len(info), 2) || info ||
705            //             I2OSP(len(unblindedElement), 2) || unblindedElement ||
706            //             "Finalize"
707            // return Hash(hashInput)
708            let output = CS::Hash::new()
709                .chain_update(i2osp_2(input.as_ref().len()).map_err(|_| Error::Input)?)
710                .chain_update(input.as_ref())
711                .chain_update(i2osp_2(info.as_ref().len()).map_err(|_| Error::Info)?)
712                .chain_update(info.as_ref())
713                .chain_update(elem_len)
714                .chain_update(CS::Group::serialize_elem(unblinded_element))
715                .chain_update(STR_FINALIZE)
716                .finalize();
717
718            Ok(output)
719        },
720    ))
721}
722
723///////////
724// Tests //
725// ===== //
726///////////
727
728#[cfg(test)]
729mod tests {
730    use core::ptr;
731
732    use rand::rngs::SysRng;
733
734    use super::*;
735    use crate::Group;
736    use crate::common::STR_HASH_TO_GROUP;
737
738    fn prf<CS: CipherSuite>(
739        input: &[u8],
740        key: <CS::Group as Group>::Scalar,
741        info: &[u8],
742        mode: Mode,
743    ) -> Output<CS::Hash> {
744        let t = compute_tweak::<CS>(key, Some(info)).unwrap();
745
746        let dst = Dst::new::<CS, _>(STR_HASH_TO_GROUP, mode);
747        let point = CS::Group::hash_to_curve::<CS::Hash>(&[input], &dst.as_dst()).unwrap();
748
749        // evaluatedElement = G.ScalarInverse(t) * blindedElement
750        let res = point * &CS::Group::invert_scalar(t);
751
752        finalize_after_unblind::<CS, _, _>(iter::once(res), iter::once(input), Some(info))
753            .unwrap()
754            .next()
755            .unwrap()
756            .unwrap()
757    }
758
759    fn verifiable_retrieval<CS: CipherSuite>() {
760        let input = b"input";
761        let info = b"info";
762        let mut rng = SysRng;
763        let server = PoprfServer::<CS>::new(&mut rng).unwrap();
764        let client_blind_result = PoprfClient::<CS>::blind(input, &mut rng).unwrap();
765        let server_result = server
766            .blind_evaluate(&mut rng, &client_blind_result.message, Some(info))
767            .unwrap();
768        let client_finalize_result = client_blind_result
769            .state
770            .finalize(
771                input,
772                &server_result.message,
773                &server_result.proof,
774                server.get_public_key(),
775                Some(info),
776            )
777            .unwrap();
778        let res2 = prf::<CS>(input, server.get_private_key(), info, Mode::Poprf);
779        assert_eq!(client_finalize_result, res2);
780    }
781
782    fn verifiable_bad_public_key<CS: CipherSuite>() {
783        let input = b"input";
784        let info = b"info";
785        let mut rng = SysRng;
786        let server = PoprfServer::<CS>::new(&mut rng).unwrap();
787        let client_blind_result = PoprfClient::<CS>::blind(input, &mut rng).unwrap();
788        let server_result = server
789            .blind_evaluate(&mut rng, &client_blind_result.message, Some(info))
790            .unwrap();
791        let wrong_pk = {
792            let dst = Dst::new::<CS, _>(STR_HASH_TO_GROUP, Mode::Oprf);
793            // Choose a group element that is unlikely to be the right public key
794            CS::Group::hash_to_curve::<CS::Hash>(&[b"msg"], &dst.as_dst()).unwrap()
795        };
796        let client_finalize_result = client_blind_result.state.finalize(
797            input,
798            &server_result.message,
799            &server_result.proof,
800            wrong_pk,
801            Some(info),
802        );
803        assert!(client_finalize_result.is_err());
804    }
805
806    fn verifiable_server_evaluate<CS: CipherSuite>() {
807        let input = b"input";
808        let info = Some(b"info".as_slice());
809        let mut rng = SysRng;
810        let client_blind_result = PoprfClient::<CS>::blind(input, &mut rng).unwrap();
811        let server = PoprfServer::<CS>::new(&mut rng).unwrap();
812        let server_result = server
813            .blind_evaluate(&mut rng, &client_blind_result.message, info)
814            .unwrap();
815
816        let client_finalize = client_blind_result
817            .state
818            .finalize(
819                input,
820                &server_result.message,
821                &server_result.proof,
822                server.get_public_key(),
823                info,
824            )
825            .unwrap();
826
827        // We expect the outputs from client and server to be equal given an identical
828        // input
829        let server_evaluate = server.evaluate(input, info).unwrap();
830        assert_eq!(client_finalize, server_evaluate);
831
832        // We expect the outputs from client and server to be different given different
833        // inputs
834        let wrong_input = b"wrong input";
835        let server_evaluate = server.evaluate(wrong_input, info).unwrap();
836        assert!(client_finalize != server_evaluate);
837    }
838
839    fn zeroize_verifiable_client<CS: CipherSuite>() {
840        let input = b"input";
841        let mut rng = SysRng;
842        let client_blind_result = PoprfClient::<CS>::blind(input, &mut rng).unwrap();
843
844        let mut state = client_blind_result.state;
845        unsafe { ptr::drop_in_place(&mut state) };
846        assert!(state.serialize().iter().all(|&x| x == 0));
847
848        let mut message = client_blind_result.message;
849        unsafe { ptr::drop_in_place(&mut message) };
850        assert!(message.serialize().iter().all(|&x| x == 0));
851    }
852
853    fn zeroize_verifiable_server<CS: CipherSuite>() {
854        let input = b"input";
855        let info = b"info";
856        let mut rng = SysRng;
857        let server = PoprfServer::<CS>::new(&mut rng).unwrap();
858        let client_blind_result = PoprfClient::<CS>::blind(input, &mut rng).unwrap();
859        let server_result = server
860            .blind_evaluate(&mut rng, &client_blind_result.message, Some(info))
861            .unwrap();
862
863        let mut state = server;
864        unsafe { ptr::drop_in_place(&mut state) };
865        assert!(state.serialize().iter().all(|&x| x == 0));
866
867        let mut message = server_result.message;
868        unsafe { ptr::drop_in_place(&mut message) };
869        assert!(message.serialize().iter().all(|&x| x == 0));
870
871        let mut proof = server_result.proof;
872        unsafe { ptr::drop_in_place(&mut proof) };
873        assert!(proof.serialize().iter().all(|&x| x == 0));
874    }
875
876    crate::tests::test_all_curves!(
877        verifiable_retrieval,
878        verifiable_bad_public_key,
879        verifiable_server_evaluate,
880        zeroize_verifiable_client,
881        zeroize_verifiable_server,
882    );
883}