Skip to main content

voprf_ng/
poprf.rs

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