1use crate::hash_to_curve::{hash_to_curve_point, hash_to_scalar};
40use crate::nist::arith::Field;
41use crate::nist::point::{AffinePoint, Point};
42use crate::p256::{Fn as Scalar, P256};
43use ic_core::traits::internal::Digest;
44use ic_core::traits::RandomSource;
45use ic_core::{ensure, Result, Zeroize};
46use ic_hash::Sha256;
47
48pub const ELEMENT_LEN: usize = 33;
50pub const SCALAR_LEN: usize = 32;
52pub const PROOF_LEN: usize = 64;
54pub const OUTPUT_LEN: usize = 32;
56pub const MAX_BATCH: usize = 64;
60
61const IDENTIFIER: &[u8] = b"P256-SHA256";
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub enum Mode {
67 Oprf,
69 Voprf,
71 Poprf,
73}
74
75impl Mode {
76 const fn id(self) -> u8 {
77 match self {
78 Self::Oprf => 0,
79 Self::Voprf => 1,
80 Self::Poprf => 2,
81 }
82 }
83}
84
85fn context(mode: Mode) -> [u8; 20] {
87 let mut c = [0u8; 20];
88 c[..7].copy_from_slice(b"OPRFV1-");
89 c[7] = mode.id();
90 c[8] = b'-';
91 c[9..].copy_from_slice(IDENTIFIER);
92 c
93}
94
95fn dst(prefix: &[u8], mode: Mode, out: &mut [u8; 40]) -> usize {
97 let ctx = context(mode);
98 out[..prefix.len()].copy_from_slice(prefix);
99 out[prefix.len()..prefix.len() + ctx.len()].copy_from_slice(&ctx);
100 prefix.len() + ctx.len()
101}
102
103fn hash_to_group(mode: Mode, input: &[u8]) -> Result<Point<P256>> {
104 let mut d = [0u8; 40];
105 let n = dst(b"HashToGroup-", mode, &mut d);
106 let p = hash_to_curve_point(&[input], &d[..n])?;
107 ensure!(
108 !bool::from(p.is_identity()),
109 InvalidParameter,
110 "oprf input hashes to the identity"
111 );
112 Ok(p)
113}
114
115fn hash_to_scalar_ctx(mode: Mode, parts: &[&[u8]]) -> Result<Scalar> {
116 let mut d = [0u8; 40];
117 let n = dst(b"HashToScalar-", mode, &mut d);
118 hash_to_scalar(parts, &d[..n])
119}
120
121fn serialize(p: &Point<P256>) -> Result<[u8; ELEMENT_LEN]> {
122 let affine = p.to_affine().ok_or(ic_core::err!(
123 InvalidParameter,
124 "oprf element is the identity"
125 ))?;
126 let mut out = [0u8; ELEMENT_LEN];
127 let _ = affine.write_compressed(&mut out);
128 Ok(out)
129}
130
131fn deserialize(bytes: &[u8]) -> Result<Point<P256>> {
134 ensure!(
135 bytes.len() == ELEMENT_LEN && (bytes[0] == 0x02 || bytes[0] == 0x03),
136 MalformedEncoding,
137 "oprf element must be a 33-byte compressed point"
138 );
139 let a = AffinePoint::<P256>::from_sec1(bytes).ok_or(ic_core::err!(
140 MalformedEncoding,
141 "oprf element is not on the curve"
142 ))?;
143 Ok(Point::from_affine(&a))
144}
145
146fn scalar_from(bytes: &[u8]) -> Result<Scalar> {
147 let mut b = [0u8; SCALAR_LEN];
148 ensure!(
149 bytes.len() == SCALAR_LEN,
150 InvalidLength,
151 "oprf scalar length"
152 );
153 b.copy_from_slice(bytes);
154 let s = Scalar::from_bytes(&b).ok_or(ic_core::err!(
155 MalformedEncoding,
156 "oprf scalar is not below the group order"
157 ));
158 b.zeroize();
159 s
160}
161
162fn random_scalar<R: RandomSource + ?Sized>(rng: &mut R) -> Result<Scalar> {
164 let mut b = [0u8; SCALAR_LEN];
165 for _ in 0..64 {
166 rng.fill(&mut b)?;
167 if let Some(s) = Scalar::from_bytes(&b) {
168 if !bool::from(s.is_zero()) {
169 b.zeroize();
170 return Ok(s);
171 }
172 }
173 }
174 b.zeroize();
175 Err(ic_core::err!(EntropyFailure, "oprf: no scalar in 64 draws"))
176}
177
178fn length_prefixed(h: &mut Sha256, bytes: &[u8]) {
179 h.update(&(bytes.len() as u16).to_be_bytes());
180 h.update(bytes);
181}
182
183fn tweak(info: &[u8]) -> Result<Scalar> {
185 ensure!(
186 info.len() <= u16::MAX as usize,
187 InvalidLength,
188 "oprf info too long"
189 );
190 hash_to_scalar_ctx(
191 Mode::Poprf,
192 &[b"Info", &(info.len() as u16).to_be_bytes(), info],
193 )
194}
195
196fn composites(
199 mode: Mode,
200 k: Option<&Scalar>,
201 b: &Point<P256>,
202 c: &[Point<P256>],
203 d: &[Point<P256>],
204) -> Result<(Point<P256>, Point<P256>)> {
205 let bm = serialize(b)?;
206 let mut seed_dst = [0u8; 40];
207 let n = dst(b"Seed-", mode, &mut seed_dst);
208 let mut h = Sha256::new();
209 length_prefixed(&mut h, &bm);
210 length_prefixed(&mut h, &seed_dst[..n]);
211 let seed = h.finalize();
212 let mut m = Point::<P256>::identity();
213 let mut z = Point::<P256>::identity();
214 for (i, (ci, di)) in c.iter().zip(d.iter()).enumerate() {
215 let (cs, ds) = (serialize(ci)?, serialize(di)?);
216 let weight = hash_to_scalar_ctx(
217 mode,
218 &[
219 &(seed.as_ref().len() as u16).to_be_bytes(),
220 seed.as_ref(),
221 &(i as u16).to_be_bytes(),
222 &(cs.len() as u16).to_be_bytes(),
223 &cs,
224 &(ds.len() as u16).to_be_bytes(),
225 &ds,
226 b"Composite",
227 ],
228 )?;
229 m = ci.mul_scalar_vartime(&weight).add(&m);
230 if k.is_none() {
231 z = di.mul_scalar_vartime(&weight).add(&z);
232 }
233 }
234 if let Some(k) = k {
235 z = m.mul_scalar(k);
236 }
237 Ok((m, z))
238}
239
240fn challenge(
241 mode: Mode,
242 b: &Point<P256>,
243 m: &Point<P256>,
244 z: &Point<P256>,
245 t2: &Point<P256>,
246 t3: &Point<P256>,
247) -> Result<Scalar> {
248 let (bm, a0, a1, a2, a3) = (
249 serialize(b)?,
250 serialize(m)?,
251 serialize(z)?,
252 serialize(t2)?,
253 serialize(t3)?,
254 );
255 let len = (ELEMENT_LEN as u16).to_be_bytes();
256 hash_to_scalar_ctx(
257 mode,
258 &[
259 &len,
260 &bm,
261 &len,
262 &a0,
263 &len,
264 &a1,
265 &len,
266 &a2,
267 &len,
268 &a3,
269 b"Challenge",
270 ],
271 )
272}
273
274fn generate_proof<R: RandomSource + ?Sized>(
276 mode: Mode,
277 k: &Scalar,
278 b: &Point<P256>,
279 c: &[Point<P256>],
280 d: &[Point<P256>],
281 rng: &mut R,
282) -> Result<[u8; PROOF_LEN]> {
283 let (m, z) = composites(mode, Some(k), b, c, d)?;
284 let r = random_scalar(rng)?;
285 let t2 = Point::<P256>::mul_generator(&r);
286 let t3 = m.mul_scalar(&r);
287 let ch = challenge(mode, b, &m, &z, &t2, &t3)?;
288 let s = r.sub(&ch.mul(k));
289 let mut proof = [0u8; PROOF_LEN];
290 proof[..32].copy_from_slice(&ch.to_bytes());
291 proof[32..].copy_from_slice(&s.to_bytes());
292 Ok(proof)
293}
294
295fn verify_proof(
297 mode: Mode,
298 b: &Point<P256>,
299 c: &[Point<P256>],
300 d: &[Point<P256>],
301 proof: &[u8; PROOF_LEN],
302) -> Result<()> {
303 let (m, z) = composites(mode, None, b, c, d)?;
304 let ch = scalar_from(&proof[..32])?;
305 let s = scalar_from(&proof[32..])?;
306 let t2 = Point::<P256>::mul_generator(&s).add(&b.mul_scalar_vartime(&ch));
307 let t3 = m.mul_scalar_vartime(&s).add(&z.mul_scalar_vartime(&ch));
308 let expected = challenge(mode, b, &m, &z, &t2, &t3)?;
309 ensure!(
310 bool::from(expected.ct_eq(&ch)),
311 AuthenticationFailed,
312 "oprf proof"
313 );
314 Ok(())
315}
316
317fn finalize_hash(input: &[u8], info: Option<&[u8]>, n: &Point<P256>) -> Result<[u8; OUTPUT_LEN]> {
318 ensure!(
319 input.len() <= u16::MAX as usize,
320 InvalidLength,
321 "oprf input too long"
322 );
323 let unblinded = serialize(n)?;
324 let mut h = Sha256::new();
325 length_prefixed(&mut h, input);
326 if let Some(info) = info {
327 length_prefixed(&mut h, info);
328 }
329 length_prefixed(&mut h, &unblinded);
330 h.update(b"Finalize");
331 let mut out = [0u8; OUTPUT_LEN];
332 out.copy_from_slice(h.finalize().as_ref());
333 Ok(out)
334}
335
336pub struct ServerKey {
338 mode: Mode,
339 secret: [u8; SCALAR_LEN],
340 public: [u8; ELEMENT_LEN],
341}
342
343impl Drop for ServerKey {
344 fn drop(&mut self) {
345 self.secret.zeroize();
346 }
347}
348
349impl ServerKey {
350 fn from_scalar(mode: Mode, sk: &Scalar) -> Result<Self> {
351 ensure!(
352 !bool::from(sk.is_zero()),
353 InvalidParameter,
354 "oprf key is zero"
355 );
356 let public = serialize(&Point::<P256>::mul_generator(sk))?;
357 Ok(Self {
358 mode,
359 secret: sk.to_bytes(),
360 public,
361 })
362 }
363
364 fn scalar(&self) -> Result<Scalar> {
365 scalar_from(&self.secret)
366 }
367
368 pub fn generate<R: RandomSource + ?Sized>(mode: Mode, rng: &mut R) -> Result<Self> {
370 ic_core::module::operational()?;
371 Self::from_scalar(mode, &random_scalar(rng)?)
372 }
373
374 pub fn derive(mode: Mode, seed: &[u8; 32], info: &[u8]) -> Result<Self> {
377 ic_core::module::operational()?;
378 ensure!(
379 info.len() <= u16::MAX as usize,
380 InvalidLength,
381 "oprf info too long"
382 );
383 let mut d = [0u8; 40];
384 let n = dst(b"DeriveKeyPair", mode, &mut d);
385 for counter in 0u8..=255 {
386 let sk = hash_to_scalar(
387 &[seed, &(info.len() as u16).to_be_bytes(), info, &[counter]],
388 &d[..n],
389 )?;
390 if !bool::from(sk.is_zero()) {
391 return Self::from_scalar(mode, &sk);
392 }
393 }
394 Err(ic_core::err!(
395 InvalidParameter,
396 "oprf DeriveKeyPair found no key"
397 ))
398 }
399
400 pub fn from_bytes(mode: Mode, secret: &[u8]) -> Result<Self> {
402 ic_core::module::operational()?;
403 Self::from_scalar(mode, &scalar_from(secret)?)
404 }
405
406 pub fn to_bytes(&self) -> ic_core::Zeroizing<[u8; SCALAR_LEN]> {
408 ic_core::Zeroizing::new(self.secret)
409 }
410
411 pub fn public_key(&self) -> [u8; ELEMENT_LEN] {
413 self.public
414 }
415
416 pub fn mode(&self) -> Mode {
418 self.mode
419 }
420
421 pub fn blind_evaluate<R: RandomSource + ?Sized>(
423 &self,
424 blinded: &[u8; ELEMENT_LEN],
425 info: &[u8],
426 rng: &mut R,
427 ) -> Result<([u8; ELEMENT_LEN], Option<[u8; PROOF_LEN]>)> {
428 let mut evaluated = [[0u8; ELEMENT_LEN]; 1];
429 let proof = self.blind_evaluate_batch(&[*blinded], info, rng, &mut evaluated)?;
430 Ok((evaluated[0], proof))
431 }
432
433 pub fn blind_evaluate_batch<R: RandomSource + ?Sized>(
441 &self,
442 blinded: &[[u8; ELEMENT_LEN]],
443 info: &[u8],
444 rng: &mut R,
445 evaluated: &mut [[u8; ELEMENT_LEN]],
446 ) -> Result<Option<[u8; PROOF_LEN]>> {
447 ic_core::module::operational()?;
448 ensure!(
449 !blinded.is_empty() && blinded.len() == evaluated.len() && blinded.len() <= MAX_BATCH,
450 InvalidLength,
451 "oprf batch: 1 to MAX_BATCH elements, one output each"
452 );
453 ensure!(
454 self.mode == Mode::Poprf || info.is_empty(),
455 InvalidParameter,
456 "oprf info is only for the POPRF mode"
457 );
458 let sk = self.scalar()?;
459 let k = if self.mode == Mode::Poprf {
461 let t = sk.add(&tweak(info)?);
462 ensure!(
463 !bool::from(t.is_zero()),
464 InvalidParameter,
465 "oprf: info maps to the key"
466 );
467 t.invert()
468 } else {
469 sk
470 };
471 for (b, e) in blinded.iter().zip(evaluated.iter_mut()) {
472 *e = serialize(&deserialize(b)?.mul_scalar(&k))?;
473 }
474 if self.mode == Mode::Oprf {
475 return Ok(None);
476 }
477 let c_list = decode_all(if self.mode == Mode::Poprf {
480 evaluated
481 } else {
482 blinded
483 })?;
484 let d_list = decode_all(if self.mode == Mode::Poprf {
485 blinded
486 } else {
487 evaluated
488 })?;
489 let (proof_key, b) = if self.mode == Mode::Poprf {
490 let t = sk.add(&tweak(info)?);
491 (t, Point::<P256>::mul_generator(&t))
492 } else {
493 (sk, deserialize(&self.public)?)
494 };
495 let proof = generate_proof(
496 self.mode,
497 &proof_key,
498 &b,
499 &c_list[..blinded.len()],
500 &d_list[..blinded.len()],
501 rng,
502 )?;
503 Ok(Some(proof))
504 }
505
506 pub fn evaluate(&self, input: &[u8], info: &[u8]) -> Result<[u8; OUTPUT_LEN]> {
510 ic_core::module::operational()?;
511 ensure!(
512 self.mode == Mode::Poprf || info.is_empty(),
513 InvalidParameter,
514 "oprf info is only for the POPRF mode"
515 );
516 let element = hash_to_group(self.mode, input)?;
517 let sk = self.scalar()?;
518 if self.mode == Mode::Poprf {
519 let t = sk.add(&tweak(info)?);
520 ensure!(
521 !bool::from(t.is_zero()),
522 InvalidParameter,
523 "oprf: info maps to the key"
524 );
525 finalize_hash(input, Some(info), &element.mul_scalar(&t.invert()))
526 } else {
527 finalize_hash(input, None, &element.mul_scalar(&sk))
528 }
529 }
530}
531
532fn decode_all(elements: &[[u8; ELEMENT_LEN]]) -> Result<[Point<P256>; MAX_BATCH]> {
534 ensure!(
535 elements.len() <= MAX_BATCH,
536 InvalidLength,
537 "oprf batch too large"
538 );
539 let mut out: [Point<P256>; MAX_BATCH] = core::array::from_fn(|_| Point::identity());
540 for (slot, e) in out.iter_mut().zip(elements.iter()) {
541 *slot = deserialize(e)?;
542 }
543 Ok(out)
544}
545
546pub struct BlindState {
548 mode: Mode,
549 blind: [u8; SCALAR_LEN],
550 key: Option<[u8; ELEMENT_LEN]>,
552}
553
554impl Drop for BlindState {
555 fn drop(&mut self) {
556 self.blind.zeroize();
557 }
558}
559
560pub fn blind<R: RandomSource + ?Sized>(
566 mode: Mode,
567 input: &[u8],
568 info: &[u8],
569 server_public_key: Option<&[u8; ELEMENT_LEN]>,
570 rng: &mut R,
571) -> Result<(BlindState, [u8; ELEMENT_LEN])> {
572 ic_core::module::operational()?;
573 ensure!(
574 mode == Mode::Poprf || info.is_empty(),
575 InvalidParameter,
576 "oprf info is only for the POPRF mode"
577 );
578 let key = match mode {
579 Mode::Oprf => None,
580 Mode::Voprf | Mode::Poprf => {
581 let pk = server_public_key.ok_or(ic_core::err!(
582 InvalidParameter,
583 "the verifiable modes need the server's public key"
584 ))?;
585 let pk_point = deserialize(pk)?;
586 if mode == Mode::Poprf {
587 let tweaked = Point::<P256>::mul_generator(&tweak(info)?).add(&pk_point);
588 ensure!(
589 !bool::from(tweaked.is_identity()),
590 InvalidParameter,
591 "oprf: info maps to the negated key"
592 );
593 Some(serialize(&tweaked)?)
594 } else {
595 Some(*pk)
596 }
597 }
598 };
599 let r = random_scalar(rng)?;
600 let element = hash_to_group(mode, input)?;
601 let blinded = serialize(&element.mul_scalar(&r))?;
602 Ok((
603 BlindState {
604 mode,
605 blind: r.to_bytes(),
606 key,
607 },
608 blinded,
609 ))
610}
611
612pub fn finalize(
614 input: &[u8],
615 info: &[u8],
616 state: &BlindState,
617 blinded: &[u8; ELEMENT_LEN],
618 evaluated: &[u8; ELEMENT_LEN],
619 proof: Option<&[u8; PROOF_LEN]>,
620) -> Result<[u8; OUTPUT_LEN]> {
621 let mut out = [[0u8; OUTPUT_LEN]; 1];
622 finalize_batch(
623 &[input],
624 info,
625 core::slice::from_ref(state),
626 &[*blinded],
627 &[*evaluated],
628 proof,
629 &mut out,
630 )?;
631 Ok(out[0])
632}
633
634pub fn finalize_batch(
641 inputs: &[&[u8]],
642 info: &[u8],
643 states: &[BlindState],
644 blinded: &[[u8; ELEMENT_LEN]],
645 evaluated: &[[u8; ELEMENT_LEN]],
646 proof: Option<&[u8; PROOF_LEN]>,
647 out: &mut [[u8; OUTPUT_LEN]],
648) -> Result<()> {
649 ic_core::module::operational()?;
650 let n = inputs.len();
651 ensure!(
652 (1..=MAX_BATCH).contains(&n)
653 && states.len() == n
654 && blinded.len() == n
655 && evaluated.len() == n
656 && out.len() == n,
657 InvalidLength,
658 "oprf batch: 1 to MAX_BATCH of each, equal in number"
659 );
660 let mode = states[0].mode;
661 ensure!(
662 states
663 .iter()
664 .all(|s| s.mode == mode && s.key == states[0].key),
665 InvalidParameter,
666 "oprf batch mixes modes or keys"
667 );
668 ensure!(
669 mode == Mode::Poprf || info.is_empty(),
670 InvalidParameter,
671 "oprf info is only for the POPRF mode"
672 );
673 if mode != Mode::Oprf {
674 let proof = proof.ok_or(ic_core::err!(
675 InvalidParameter,
676 "the verifiable modes need the server's proof"
677 ))?;
678 let key = deserialize(&states[0].key.ok_or(ic_core::err!(
679 InvalidParameter,
680 "the verifiable modes need the server's public key"
681 ))?)?;
682 let blinded_points = decode_all(blinded)?;
683 let evaluated_points = decode_all(evaluated)?;
684 let (c, d) = if mode == Mode::Poprf {
685 (&evaluated_points[..n], &blinded_points[..n])
686 } else {
687 (&blinded_points[..n], &evaluated_points[..n])
688 };
689 verify_proof(mode, &key, c, d, proof)?;
690 }
691 for i in 0..n {
692 let r = scalar_from(&states[i].blind)?;
693 let unblinded = deserialize(&evaluated[i])?.mul_scalar(&r.invert());
694 out[i] = finalize_hash(inputs[i], (mode == Mode::Poprf).then_some(info), &unblinded)?;
695 }
696 Ok(())
697}
698
699pub fn validate_element(element: &[u8; ELEMENT_LEN]) -> Result<()> {
703 deserialize(element).map(|_| ())
704}
705
706pub fn diffie_hellman(
710 private: &[u8; SCALAR_LEN],
711 public: &[u8; ELEMENT_LEN],
712) -> Result<[u8; ELEMENT_LEN]> {
713 ic_core::module::operational()?;
714 let k = scalar_from(private)?;
715 ensure!(
716 !bool::from(k.is_zero()),
717 InvalidParameter,
718 "oprf private key is zero"
719 );
720 serialize(&deserialize(public)?.mul_scalar(&k))
721}
722
723pub fn self_test() -> Result<()> {
726 let seed = [0xa3u8; 32];
727 let key = ServerKey::derive(Mode::Oprf, &seed, b"test key")?;
728 let mut want_sk = [0u8; 32];
729 ic_core::codec::hex_decode(
730 b"159749d750713afe245d2d39ccfaae8381c53ce92d098a9375ee70739c7ac0bf",
731 &mut want_sk,
732 )?;
733 ensure!(
734 ic_core::ct::verify(key.to_bytes().as_ref(), &want_sk),
735 SelfTestFailed,
736 "oprf"
737 );
738 let mut want = [0u8; 32];
739 ic_core::codec::hex_decode(
740 b"a0b34de5fa4c5b6da07e72af73cc507cceeb48981b97b7285fc375345fe495dd",
741 &mut want,
742 )?;
743 ensure!(
744 ic_core::ct::verify(&key.evaluate(&[0x00], b"")?, &want),
745 SelfTestFailed,
746 "oprf"
747 );
748 Ok(())
749}
750
751#[cfg(test)]
752mod tests {
753 use super::*;
754
755 #[test]
756 fn the_self_test_passes() {
757 self_test().unwrap();
758 }
759
760 #[test]
761 fn the_context_string_is_rfc_9497_s() {
762 assert_eq!(&context(Mode::Poprf), b"OPRFV1-\x02-P256-SHA256");
763 }
764
765 #[test]
767 fn elements_are_validated() {
768 assert!(deserialize(&[0u8; 33]).is_err());
769 let mut uncompressed = [0u8; 65];
770 uncompressed[0] = 4;
771 assert!(deserialize(&uncompressed).is_err());
772 let mut off = [0u8; 33];
773 off[0] = 2;
774 off[32] = 7; assert!(deserialize(&off).is_err());
776 }
777}