1use core::convert::TryFrom;
8use core::iter::Map;
9use core::ops::Add;
10
11use derive_where::derive_where;
12use digest::{Digest, Output, OutputSizeUser};
13use hybrid_array::typenum::{IsLess, U2, U9, U256, Unsigned};
14use hybrid_array::{Array, ArrayN, ArraySize};
15use rand_core::{TryCryptoRng, TryRng};
16use subtle::ConstantTimeEq;
17
18#[cfg(feature = "serde")]
19use crate::serialization::serde::{Element, Scalar};
20use crate::{CipherSuite, Error, Group, InternalError, Result};
21
22pub(crate) const STR_FINALIZE: [u8; 8] = *b"Finalize";
28pub(crate) const STR_SEED: ArrayN<u8, 5> = Array(*b"Seed-");
29pub(crate) const STR_DERIVE_KEYPAIR: ArrayN<u8, 13> = Array(*b"DeriveKeyPair");
30pub(crate) const STR_COMPOSITE: [u8; 9] = *b"Composite";
31pub(crate) const STR_CHALLENGE: [u8; 9] = *b"Challenge";
32pub(crate) const STR_INFO: [u8; 4] = *b"Info";
33pub(crate) const STR_OPRF: [u8; 7] = *b"OPRFV1-";
34pub(crate) const STR_HASH_TO_SCALAR: ArrayN<u8, 13> = Array(*b"HashToScalar-");
35pub(crate) const STR_HASH_TO_GROUP: ArrayN<u8, 12> = Array(*b"HashToGroup-");
36
37#[derive(Clone, Copy, Debug)]
40pub enum Mode {
41 Oprf,
43 Voprf,
45 Poprf,
47}
48
49impl Mode {
50 pub fn to_u8(self) -> u8 {
52 match self {
53 Mode::Oprf => 0,
54 Mode::Voprf => 1,
55 Mode::Poprf => 2,
56 }
57 }
58}
59
60#[derive_where(Clone, ZeroizeOnDrop)]
68#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::Group as Group>::Elem)]
69#[cfg_attr(
70 feature = "serde",
71 derive(serde::Deserialize, serde::Serialize),
72 serde(bound = "")
73)]
74pub struct BlindedElement<CS: CipherSuite>(
75 #[cfg_attr(feature = "serde", serde(with = "Element::<CS::Group>"))]
76 pub(crate) <CS::Group as Group>::Elem,
77);
78
79#[derive_where(Clone, ZeroizeOnDrop)]
82#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::Group as Group>::Elem)]
83#[cfg_attr(
84 feature = "serde",
85 derive(serde::Deserialize, serde::Serialize),
86 serde(bound = "")
87)]
88pub struct EvaluationElement<CS: CipherSuite>(
89 #[cfg_attr(feature = "serde", serde(with = "Element::<CS::Group>"))]
90 pub(crate) <CS::Group as Group>::Elem,
91);
92
93#[derive_where(Clone, ZeroizeOnDrop)]
96#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::Group as Group>::Elem)]
97#[cfg_attr(
98 feature = "serde",
99 derive(serde::Deserialize, serde::Serialize),
100 serde(bound = "")
101)]
102pub struct PreparedEvaluationElement<CS: CipherSuite>(pub(crate) EvaluationElement<CS>);
103
104#[derive_where(Clone, ZeroizeOnDrop)]
107#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::Group as Group>::Scalar)]
108#[cfg_attr(
109 feature = "serde",
110 derive(serde::Deserialize, serde::Serialize),
111 serde(bound = "")
112)]
113pub struct Proof<CS: CipherSuite> {
114 #[cfg_attr(feature = "serde", serde(with = "Scalar::<CS::Group>"))]
115 pub(crate) c_scalar: <CS::Group as Group>::Scalar,
116 #[cfg_attr(feature = "serde", serde(with = "Scalar::<CS::Group>"))]
117 pub(crate) s_scalar: <CS::Group as Group>::Scalar,
118}
119
120#[allow(clippy::many_single_char_names)]
127pub(crate) fn generate_proof<CS: CipherSuite, R: TryRng + TryCryptoRng>(
128 rng: &mut R,
129 k: <CS::Group as Group>::Scalar,
130 a: <CS::Group as Group>::Elem,
131 b: <CS::Group as Group>::Elem,
132 cs: impl ExactSizeIterator<Item = <CS::Group as Group>::Elem>,
133 ds: impl ExactSizeIterator<Item = <CS::Group as Group>::Elem>,
134 mode: Mode,
135) -> Result<Proof<CS>> {
136 let (m, z) = compute_composites::<CS, _, _>(Some(k), b, cs, ds, mode)?;
139
140 let r = CS::Group::random_scalar(rng)?;
141 let t2 = a * &r;
142 let t3 = m * &r;
143
144 let bm = CS::Group::serialize_elem(b);
146 let a0 = CS::Group::serialize_elem(m);
148 let a1 = CS::Group::serialize_elem(z);
150 let a2 = CS::Group::serialize_elem(t2);
152 let a3 = CS::Group::serialize_elem(t3);
154
155 let elem_len = <CS::Group as Group>::ElemLen::U16.to_be_bytes();
156
157 let h2_input = [
164 &elem_len,
165 bm.as_slice(),
166 &elem_len,
167 &a0,
168 &elem_len,
169 &a1,
170 &elem_len,
171 &a2,
172 &elem_len,
173 &a3,
174 &STR_CHALLENGE,
175 ];
176
177 let dst = Dst::new::<CS, _>(STR_HASH_TO_SCALAR, mode);
178 let c_scalar = CS::Group::hash_to_scalar::<CS::Hash>(&h2_input, &dst.as_dst()).unwrap();
180 let s_scalar = r - &(c_scalar * &k);
181
182 Ok(Proof { c_scalar, s_scalar })
183}
184
185#[allow(clippy::many_single_char_names)]
187pub(crate) fn verify_proof<CS: CipherSuite>(
188 a: <CS::Group as Group>::Elem,
189 b: <CS::Group as Group>::Elem,
190 cs: impl ExactSizeIterator<Item = <CS::Group as Group>::Elem>,
191 ds: impl ExactSizeIterator<Item = <CS::Group as Group>::Elem>,
192 proof: &Proof<CS>,
193 mode: Mode,
194) -> Result<()> {
195 let (m, z) = compute_composites::<CS, _, _>(None, b, cs, ds, mode)?;
197 let t2 = (a * &proof.s_scalar) + &(b * &proof.c_scalar);
198 let t3 = (m * &proof.s_scalar) + &(z * &proof.c_scalar);
199
200 let bm = CS::Group::serialize_elem(b);
202 let a0 = CS::Group::serialize_elem(m);
204 let a1 = CS::Group::serialize_elem(z);
206 let a2 = CS::Group::serialize_elem(t2);
208 let a3 = CS::Group::serialize_elem(t3);
210
211 let elem_len = <CS::Group as Group>::ElemLen::U16.to_be_bytes();
212
213 let h2_input = [
220 &elem_len,
221 bm.as_slice(),
222 &elem_len,
223 &a0,
224 &elem_len,
225 &a1,
226 &elem_len,
227 &a2,
228 &elem_len,
229 &a3,
230 &STR_CHALLENGE,
231 ];
232
233 let dst = Dst::new::<CS, _>(STR_HASH_TO_SCALAR, mode);
234 let c = CS::Group::hash_to_scalar::<CS::Hash>(&h2_input, &dst.as_dst()).unwrap();
236
237 match c.ct_eq(&proof.c_scalar).into() {
238 true => Ok(()),
239 false => Err(Error::ProofVerification),
240 }
241}
242
243type ComputeCompositesResult<CS> = (
244 <<CS as CipherSuite>::Group as Group>::Elem,
245 <<CS as CipherSuite>::Group as Group>::Elem,
246);
247
248fn compute_composites<
250 CS: CipherSuite,
251 IC: Iterator<Item = <CS::Group as Group>::Elem> + ExactSizeIterator,
252 ID: Iterator<Item = <CS::Group as Group>::Elem> + ExactSizeIterator,
253>(
254 k_option: Option<<CS::Group as Group>::Scalar>,
255 b: <CS::Group as Group>::Elem,
256 c_slice: IC,
257 d_slice: ID,
258 mode: Mode,
259) -> Result<ComputeCompositesResult<CS>> {
260 let elem_len = <CS::Group as Group>::ElemLen::U16.to_be_bytes();
263
264 if c_slice.len() != d_slice.len() {
265 return Err(Error::Batch);
266 }
267
268 let len = u16::try_from(c_slice.len()).map_err(|_| Error::Batch)?;
269
270 let seed_dst = Dst::new::<CS, _>(STR_SEED, mode);
272
273 let seed = CS::Hash::new()
277 .chain_update(elem_len)
278 .chain_update(CS::Group::serialize_elem(b))
279 .chain_update(seed_dst.i2osp_2())
280 .chain_update_multi(&seed_dst.as_dst())
281 .finalize();
282 let seed_len = i2osp_2_array::<<CS::Hash as OutputSizeUser>::OutputSize>();
283
284 let mut m = CS::Group::identity_elem();
285 let mut z = CS::Group::identity_elem();
286
287 for (i, (c, d)) in (0..len).zip(c_slice.zip(d_slice)) {
288 let ci = CS::Group::serialize_elem(c);
290 let di = CS::Group::serialize_elem(d);
292 let h2_input = [
297 seed_len.as_slice(),
298 &seed,
299 &i.to_be_bytes(),
300 &elem_len,
301 &ci,
302 &elem_len,
303 &di,
304 &STR_COMPOSITE,
305 ];
306
307 let dst = Dst::new::<CS, _>(STR_HASH_TO_SCALAR, mode);
308 let di = CS::Group::hash_to_scalar::<CS::Hash>(&h2_input, &dst.as_dst()).unwrap();
310 m = c * &di + &m;
311 z = match k_option {
312 Some(_) => z,
313 None => d * &di + &z,
314 };
315 }
316
317 z = match k_option {
318 Some(k) => m * &k,
319 None => z,
320 };
321
322 Ok((m, z))
323}
324
325pub(crate) fn derive_key_internal<CS: CipherSuite>(
332 seed: &[u8],
333 info: &[u8],
334 mode: Mode,
335) -> Result<<CS::Group as Group>::Scalar, Error> {
336 let dst = Dst::new::<CS, _>(STR_DERIVE_KEYPAIR, mode);
337
338 let info_len = i2osp_2(info.len()).map_err(|_| Error::DeriveKeyPair)?;
339
340 for counter in 0_u8..=u8::MAX {
341 let sk_s = CS::Group::hash_to_scalar::<CS::Hash>(
345 &[seed, &info_len, info, &counter.to_be_bytes()],
346 &dst.as_dst(),
347 )
348 .map_err(|_| Error::DeriveKeyPair)?;
349
350 if !bool::from(CS::Group::is_zero_scalar(sk_s)) {
351 return Ok(sk_s);
352 }
353 }
354
355 Err(Error::Protocol)
356}
357
358#[cfg(feature = "danger")]
365pub fn derive_key<CS: CipherSuite>(
366 seed: &[u8],
367 info: &[u8],
368 mode: Mode,
369) -> Result<<CS::Group as Group>::Scalar, Error> {
370 derive_key_internal::<CS>(seed, info, mode)
371}
372
373type DeriveKeypairResult<CS> = (
374 <<CS as CipherSuite>::Group as Group>::Scalar,
375 <<CS as CipherSuite>::Group as Group>::Elem,
376);
377
378pub(crate) fn derive_keypair<CS: CipherSuite>(
380 seed: &[u8],
381 info: &[u8],
382 mode: Mode,
383) -> Result<DeriveKeypairResult<CS>, Error> {
384 let sk_s = derive_key_internal::<CS>(seed, info, mode)?;
385 let pk_s = CS::Group::base_elem() * &sk_s;
386
387 Ok((sk_s, pk_s))
388}
389
390pub(crate) fn deterministic_blind_unchecked<CS: CipherSuite>(
396 input: &[u8],
397 blind: &<CS::Group as Group>::Scalar,
398 mode: Mode,
399) -> Result<<CS::Group as Group>::Elem> {
400 let hashed_point = hash_to_group::<CS>(input, mode)?;
401
402 if CS::Group::is_identity_elem(hashed_point).into() {
404 return Err(Error::Input);
405 }
406
407 Ok(hashed_point * blind)
408}
409
410pub(crate) fn hash_to_group<CS: CipherSuite>(
412 input: &[u8],
413 mode: Mode,
414) -> Result<<CS::Group as Group>::Elem> {
415 let dst = Dst::new::<CS, _>(STR_HASH_TO_GROUP, mode);
416 CS::Group::hash_to_curve::<CS::Hash>(&[input], &dst.as_dst()).map_err(|_| Error::Input)
417}
418
419pub(crate) fn server_evaluate_hash_input<CS: CipherSuite>(
422 input: &[u8],
423 info: Option<&[u8]>,
424 issued_element: Array<u8, <<CS as CipherSuite>::Group as Group>::ElemLen>,
425) -> Result<Output<CS::Hash>> {
426 let mut hash = CS::Hash::new()
439 .chain_update(i2osp_2(input.as_ref().len()).map_err(|_| Error::Input)?)
440 .chain_update(input.as_ref());
441 if let Some(info) = info {
442 hash = hash
443 .chain_update(i2osp_2(info.as_ref().len()).map_err(|_| Error::Input)?)
444 .chain_update(info.as_ref());
445 }
446 Ok(hash
447 .chain_update(i2osp_2(issued_element.as_slice().len()).map_err(|_| Error::Input)?)
448 .chain_update(issued_element)
449 .chain_update(STR_FINALIZE)
450 .finalize())
451}
452
453pub(crate) type FinalizeAfterUnblindResult<'a, C, I, IE> = Map<
454 IE,
455 fn((I, <<C as CipherSuite>::Group as Group>::Elem)) -> Result<Output<<C as CipherSuite>::Hash>>,
456>;
457
458pub(crate) fn finalize_after_unblind<
460 'a,
461 CS: CipherSuite,
462 I: AsRef<[u8]>,
463 IE: 'a + Iterator<Item = (I, <CS::Group as Group>::Elem)>,
464>(
465 inputs_and_unblinded_elements: IE,
466) -> FinalizeAfterUnblindResult<'a, CS, I, IE> {
467 inputs_and_unblinded_elements.map(|(input, unblinded_element)| {
468 let elem_len = <CS::Group as Group>::ElemLen::U16.to_be_bytes();
469
470 Ok(CS::Hash::new()
471 .chain_update(i2osp_2(input.as_ref().len()).map_err(|_| Error::Input)?)
472 .chain_update(input.as_ref())
473 .chain_update(elem_len)
474 .chain_update(CS::Group::serialize_elem(unblinded_element))
475 .chain_update(STR_FINALIZE)
476 .finalize())
477 })
478}
479
480pub(crate) struct Dst<L: ArraySize> {
481 dst_1: Array<u8, L>,
482 dst_2: &'static [u8],
483}
484
485impl<L: ArraySize> Dst<L> {
486 pub(crate) fn new<CS, TL>(par_1: Array<u8, TL>, mode: Mode) -> Self
487 where
488 CS: CipherSuite,
489 TL: ArraySize + Add<U9, Output = L>,
490 {
491 let par_2 = ArrayN::<u8, 7>::from(STR_OPRF)
494 .concat(ArrayN::<u8, 1>::from([mode.to_u8()]))
495 .concat(ArrayN::<u8, 1>::from([b'-']));
496
497 let dst_1 = par_1.concat(par_2);
498 let dst_2 = CS::ID;
499
500 assert!(
501 L::USIZE + dst_2.len() <= u16::MAX.into(),
502 "constructed DST longer then {}",
503 u16::MAX
504 );
505
506 Self { dst_1, dst_2 }
507 }
508
509 pub(crate) fn as_dst(&self) -> [&[u8]; 2] {
510 [&self.dst_1, self.dst_2]
511 }
512
513 pub(crate) fn i2osp_2(&self) -> [u8; 2] {
514 u16::try_from(L::USIZE + self.dst_2.len())
515 .unwrap()
516 .to_be_bytes()
517 }
518}
519
520trait DigestExt {
521 fn chain_update_multi(self, data: &[&[u8]]) -> Self;
522}
523
524impl<T> DigestExt for T
525where
526 T: Digest,
527{
528 fn chain_update_multi(mut self, datas: &[&[u8]]) -> Self {
529 for data in datas {
530 self.update(data)
531 }
532
533 self
534 }
535}
536
537pub(crate) fn i2osp_2(input: usize) -> Result<[u8; 2], InternalError> {
543 u16::try_from(input)
544 .map(|input| input.to_be_bytes())
545 .map_err(|_| InternalError::I2osp)
546}
547
548pub(crate) fn i2osp_2_array<L: ArraySize + IsLess<U256>>() -> Array<u8, U2> {
549 L::U16.to_be_bytes().into()
550}