use crate::hash_to_curve::{hash_to_curve_point, hash_to_scalar};
use crate::nist::arith::Field;
use crate::nist::point::{AffinePoint, Point};
use crate::p256::{Fn as Scalar, P256};
use ic_core::traits::internal::Digest;
use ic_core::traits::RandomSource;
use ic_core::{ensure, Result, Zeroize};
use ic_hash::Sha256;
pub const ELEMENT_LEN: usize = 33;
pub const SCALAR_LEN: usize = 32;
pub const PROOF_LEN: usize = 64;
pub const OUTPUT_LEN: usize = 32;
pub const MAX_BATCH: usize = 64;
const IDENTIFIER: &[u8] = b"P256-SHA256";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mode {
Oprf,
Voprf,
Poprf,
}
impl Mode {
const fn id(self) -> u8 {
match self {
Self::Oprf => 0,
Self::Voprf => 1,
Self::Poprf => 2,
}
}
}
fn context(mode: Mode) -> [u8; 20] {
let mut c = [0u8; 20];
c[..7].copy_from_slice(b"OPRFV1-");
c[7] = mode.id();
c[8] = b'-';
c[9..].copy_from_slice(IDENTIFIER);
c
}
fn dst(prefix: &[u8], mode: Mode, out: &mut [u8; 40]) -> usize {
let ctx = context(mode);
out[..prefix.len()].copy_from_slice(prefix);
out[prefix.len()..prefix.len() + ctx.len()].copy_from_slice(&ctx);
prefix.len() + ctx.len()
}
fn hash_to_group(mode: Mode, input: &[u8]) -> Result<Point<P256>> {
let mut d = [0u8; 40];
let n = dst(b"HashToGroup-", mode, &mut d);
let p = hash_to_curve_point(&[input], &d[..n])?;
ensure!(
!bool::from(p.is_identity()),
InvalidParameter,
"oprf input hashes to the identity"
);
Ok(p)
}
fn hash_to_scalar_ctx(mode: Mode, parts: &[&[u8]]) -> Result<Scalar> {
let mut d = [0u8; 40];
let n = dst(b"HashToScalar-", mode, &mut d);
hash_to_scalar(parts, &d[..n])
}
fn serialize(p: &Point<P256>) -> Result<[u8; ELEMENT_LEN]> {
let affine = p.to_affine().ok_or(ic_core::err!(
InvalidParameter,
"oprf element is the identity"
))?;
let mut out = [0u8; ELEMENT_LEN];
let _ = affine.write_compressed(&mut out);
Ok(out)
}
fn deserialize(bytes: &[u8]) -> Result<Point<P256>> {
ensure!(
bytes.len() == ELEMENT_LEN && (bytes[0] == 0x02 || bytes[0] == 0x03),
MalformedEncoding,
"oprf element must be a 33-byte compressed point"
);
let a = AffinePoint::<P256>::from_sec1(bytes).ok_or(ic_core::err!(
MalformedEncoding,
"oprf element is not on the curve"
))?;
Ok(Point::from_affine(&a))
}
fn scalar_from(bytes: &[u8]) -> Result<Scalar> {
let mut b = [0u8; SCALAR_LEN];
ensure!(
bytes.len() == SCALAR_LEN,
InvalidLength,
"oprf scalar length"
);
b.copy_from_slice(bytes);
let s = Scalar::from_bytes(&b).ok_or(ic_core::err!(
MalformedEncoding,
"oprf scalar is not below the group order"
));
b.zeroize();
s
}
fn random_scalar<R: RandomSource + ?Sized>(rng: &mut R) -> Result<Scalar> {
let mut b = [0u8; SCALAR_LEN];
for _ in 0..64 {
rng.fill(&mut b)?;
if let Some(s) = Scalar::from_bytes(&b) {
if !bool::from(s.is_zero()) {
b.zeroize();
return Ok(s);
}
}
}
b.zeroize();
Err(ic_core::err!(EntropyFailure, "oprf: no scalar in 64 draws"))
}
fn length_prefixed(h: &mut Sha256, bytes: &[u8]) {
h.update(&(bytes.len() as u16).to_be_bytes());
h.update(bytes);
}
fn tweak(info: &[u8]) -> Result<Scalar> {
ensure!(
info.len() <= u16::MAX as usize,
InvalidLength,
"oprf info too long"
);
hash_to_scalar_ctx(
Mode::Poprf,
&[b"Info", &(info.len() as u16).to_be_bytes(), info],
)
}
fn composites(
mode: Mode,
k: Option<&Scalar>,
b: &Point<P256>,
c: &[Point<P256>],
d: &[Point<P256>],
) -> Result<(Point<P256>, Point<P256>)> {
let bm = serialize(b)?;
let mut seed_dst = [0u8; 40];
let n = dst(b"Seed-", mode, &mut seed_dst);
let mut h = Sha256::new();
length_prefixed(&mut h, &bm);
length_prefixed(&mut h, &seed_dst[..n]);
let seed = h.finalize();
let mut m = Point::<P256>::identity();
let mut z = Point::<P256>::identity();
for (i, (ci, di)) in c.iter().zip(d.iter()).enumerate() {
let (cs, ds) = (serialize(ci)?, serialize(di)?);
let weight = hash_to_scalar_ctx(
mode,
&[
&(seed.as_ref().len() as u16).to_be_bytes(),
seed.as_ref(),
&(i as u16).to_be_bytes(),
&(cs.len() as u16).to_be_bytes(),
&cs,
&(ds.len() as u16).to_be_bytes(),
&ds,
b"Composite",
],
)?;
m = ci.mul_scalar_vartime(&weight).add(&m);
if k.is_none() {
z = di.mul_scalar_vartime(&weight).add(&z);
}
}
if let Some(k) = k {
z = m.mul_scalar(k);
}
Ok((m, z))
}
fn challenge(
mode: Mode,
b: &Point<P256>,
m: &Point<P256>,
z: &Point<P256>,
t2: &Point<P256>,
t3: &Point<P256>,
) -> Result<Scalar> {
let (bm, a0, a1, a2, a3) = (
serialize(b)?,
serialize(m)?,
serialize(z)?,
serialize(t2)?,
serialize(t3)?,
);
let len = (ELEMENT_LEN as u16).to_be_bytes();
hash_to_scalar_ctx(
mode,
&[
&len,
&bm,
&len,
&a0,
&len,
&a1,
&len,
&a2,
&len,
&a3,
b"Challenge",
],
)
}
fn generate_proof<R: RandomSource + ?Sized>(
mode: Mode,
k: &Scalar,
b: &Point<P256>,
c: &[Point<P256>],
d: &[Point<P256>],
rng: &mut R,
) -> Result<[u8; PROOF_LEN]> {
let (m, z) = composites(mode, Some(k), b, c, d)?;
let r = random_scalar(rng)?;
let t2 = Point::<P256>::mul_generator(&r);
let t3 = m.mul_scalar(&r);
let ch = challenge(mode, b, &m, &z, &t2, &t3)?;
let s = r.sub(&ch.mul(k));
let mut proof = [0u8; PROOF_LEN];
proof[..32].copy_from_slice(&ch.to_bytes());
proof[32..].copy_from_slice(&s.to_bytes());
Ok(proof)
}
fn verify_proof(
mode: Mode,
b: &Point<P256>,
c: &[Point<P256>],
d: &[Point<P256>],
proof: &[u8; PROOF_LEN],
) -> Result<()> {
let (m, z) = composites(mode, None, b, c, d)?;
let ch = scalar_from(&proof[..32])?;
let s = scalar_from(&proof[32..])?;
let t2 = Point::<P256>::mul_generator(&s).add(&b.mul_scalar_vartime(&ch));
let t3 = m.mul_scalar_vartime(&s).add(&z.mul_scalar_vartime(&ch));
let expected = challenge(mode, b, &m, &z, &t2, &t3)?;
ensure!(
bool::from(expected.ct_eq(&ch)),
AuthenticationFailed,
"oprf proof"
);
Ok(())
}
fn finalize_hash(input: &[u8], info: Option<&[u8]>, n: &Point<P256>) -> Result<[u8; OUTPUT_LEN]> {
ensure!(
input.len() <= u16::MAX as usize,
InvalidLength,
"oprf input too long"
);
let unblinded = serialize(n)?;
let mut h = Sha256::new();
length_prefixed(&mut h, input);
if let Some(info) = info {
length_prefixed(&mut h, info);
}
length_prefixed(&mut h, &unblinded);
h.update(b"Finalize");
let mut out = [0u8; OUTPUT_LEN];
out.copy_from_slice(h.finalize().as_ref());
Ok(out)
}
pub struct ServerKey {
mode: Mode,
secret: [u8; SCALAR_LEN],
public: [u8; ELEMENT_LEN],
}
impl Drop for ServerKey {
fn drop(&mut self) {
self.secret.zeroize();
}
}
impl ServerKey {
fn from_scalar(mode: Mode, sk: &Scalar) -> Result<Self> {
ensure!(
!bool::from(sk.is_zero()),
InvalidParameter,
"oprf key is zero"
);
let public = serialize(&Point::<P256>::mul_generator(sk))?;
Ok(Self {
mode,
secret: sk.to_bytes(),
public,
})
}
fn scalar(&self) -> Result<Scalar> {
scalar_from(&self.secret)
}
pub fn generate<R: RandomSource + ?Sized>(mode: Mode, rng: &mut R) -> Result<Self> {
ic_core::module::operational()?;
Self::from_scalar(mode, &random_scalar(rng)?)
}
pub fn derive(mode: Mode, seed: &[u8; 32], info: &[u8]) -> Result<Self> {
ic_core::module::operational()?;
ensure!(
info.len() <= u16::MAX as usize,
InvalidLength,
"oprf info too long"
);
let mut d = [0u8; 40];
let n = dst(b"DeriveKeyPair", mode, &mut d);
for counter in 0u8..=255 {
let sk = hash_to_scalar(
&[seed, &(info.len() as u16).to_be_bytes(), info, &[counter]],
&d[..n],
)?;
if !bool::from(sk.is_zero()) {
return Self::from_scalar(mode, &sk);
}
}
Err(ic_core::err!(
InvalidParameter,
"oprf DeriveKeyPair found no key"
))
}
pub fn from_bytes(mode: Mode, secret: &[u8]) -> Result<Self> {
ic_core::module::operational()?;
Self::from_scalar(mode, &scalar_from(secret)?)
}
pub fn to_bytes(&self) -> ic_core::Zeroizing<[u8; SCALAR_LEN]> {
ic_core::Zeroizing::new(self.secret)
}
pub fn public_key(&self) -> [u8; ELEMENT_LEN] {
self.public
}
pub fn mode(&self) -> Mode {
self.mode
}
pub fn blind_evaluate<R: RandomSource + ?Sized>(
&self,
blinded: &[u8; ELEMENT_LEN],
info: &[u8],
rng: &mut R,
) -> Result<([u8; ELEMENT_LEN], Option<[u8; PROOF_LEN]>)> {
let mut evaluated = [[0u8; ELEMENT_LEN]; 1];
let proof = self.blind_evaluate_batch(&[*blinded], info, rng, &mut evaluated)?;
Ok((evaluated[0], proof))
}
pub fn blind_evaluate_batch<R: RandomSource + ?Sized>(
&self,
blinded: &[[u8; ELEMENT_LEN]],
info: &[u8],
rng: &mut R,
evaluated: &mut [[u8; ELEMENT_LEN]],
) -> Result<Option<[u8; PROOF_LEN]>> {
ic_core::module::operational()?;
ensure!(
!blinded.is_empty() && blinded.len() == evaluated.len() && blinded.len() <= MAX_BATCH,
InvalidLength,
"oprf batch: 1 to MAX_BATCH elements, one output each"
);
ensure!(
self.mode == Mode::Poprf || info.is_empty(),
InvalidParameter,
"oprf info is only for the POPRF mode"
);
let sk = self.scalar()?;
let k = if self.mode == Mode::Poprf {
let t = sk.add(&tweak(info)?);
ensure!(
!bool::from(t.is_zero()),
InvalidParameter,
"oprf: info maps to the key"
);
t.invert()
} else {
sk
};
for (b, e) in blinded.iter().zip(evaluated.iter_mut()) {
*e = serialize(&deserialize(b)?.mul_scalar(&k))?;
}
if self.mode == Mode::Oprf {
return Ok(None);
}
let c_list = decode_all(if self.mode == Mode::Poprf {
evaluated
} else {
blinded
})?;
let d_list = decode_all(if self.mode == Mode::Poprf {
blinded
} else {
evaluated
})?;
let (proof_key, b) = if self.mode == Mode::Poprf {
let t = sk.add(&tweak(info)?);
(t, Point::<P256>::mul_generator(&t))
} else {
(sk, deserialize(&self.public)?)
};
let proof = generate_proof(
self.mode,
&proof_key,
&b,
&c_list[..blinded.len()],
&d_list[..blinded.len()],
rng,
)?;
Ok(Some(proof))
}
pub fn evaluate(&self, input: &[u8], info: &[u8]) -> Result<[u8; OUTPUT_LEN]> {
ic_core::module::operational()?;
ensure!(
self.mode == Mode::Poprf || info.is_empty(),
InvalidParameter,
"oprf info is only for the POPRF mode"
);
let element = hash_to_group(self.mode, input)?;
let sk = self.scalar()?;
if self.mode == Mode::Poprf {
let t = sk.add(&tweak(info)?);
ensure!(
!bool::from(t.is_zero()),
InvalidParameter,
"oprf: info maps to the key"
);
finalize_hash(input, Some(info), &element.mul_scalar(&t.invert()))
} else {
finalize_hash(input, None, &element.mul_scalar(&sk))
}
}
}
fn decode_all(elements: &[[u8; ELEMENT_LEN]]) -> Result<[Point<P256>; MAX_BATCH]> {
ensure!(
elements.len() <= MAX_BATCH,
InvalidLength,
"oprf batch too large"
);
let mut out: [Point<P256>; MAX_BATCH] = core::array::from_fn(|_| Point::identity());
for (slot, e) in out.iter_mut().zip(elements.iter()) {
*slot = deserialize(e)?;
}
Ok(out)
}
pub struct BlindState {
mode: Mode,
blind: [u8; SCALAR_LEN],
key: Option<[u8; ELEMENT_LEN]>,
}
impl Drop for BlindState {
fn drop(&mut self) {
self.blind.zeroize();
}
}
pub fn blind<R: RandomSource + ?Sized>(
mode: Mode,
input: &[u8],
info: &[u8],
server_public_key: Option<&[u8; ELEMENT_LEN]>,
rng: &mut R,
) -> Result<(BlindState, [u8; ELEMENT_LEN])> {
ic_core::module::operational()?;
ensure!(
mode == Mode::Poprf || info.is_empty(),
InvalidParameter,
"oprf info is only for the POPRF mode"
);
let key = match mode {
Mode::Oprf => None,
Mode::Voprf | Mode::Poprf => {
let pk = server_public_key.ok_or(ic_core::err!(
InvalidParameter,
"the verifiable modes need the server's public key"
))?;
let pk_point = deserialize(pk)?;
if mode == Mode::Poprf {
let tweaked = Point::<P256>::mul_generator(&tweak(info)?).add(&pk_point);
ensure!(
!bool::from(tweaked.is_identity()),
InvalidParameter,
"oprf: info maps to the negated key"
);
Some(serialize(&tweaked)?)
} else {
Some(*pk)
}
}
};
let r = random_scalar(rng)?;
let element = hash_to_group(mode, input)?;
let blinded = serialize(&element.mul_scalar(&r))?;
Ok((
BlindState {
mode,
blind: r.to_bytes(),
key,
},
blinded,
))
}
pub fn finalize(
input: &[u8],
info: &[u8],
state: &BlindState,
blinded: &[u8; ELEMENT_LEN],
evaluated: &[u8; ELEMENT_LEN],
proof: Option<&[u8; PROOF_LEN]>,
) -> Result<[u8; OUTPUT_LEN]> {
let mut out = [[0u8; OUTPUT_LEN]; 1];
finalize_batch(
&[input],
info,
core::slice::from_ref(state),
&[*blinded],
&[*evaluated],
proof,
&mut out,
)?;
Ok(out[0])
}
pub fn finalize_batch(
inputs: &[&[u8]],
info: &[u8],
states: &[BlindState],
blinded: &[[u8; ELEMENT_LEN]],
evaluated: &[[u8; ELEMENT_LEN]],
proof: Option<&[u8; PROOF_LEN]>,
out: &mut [[u8; OUTPUT_LEN]],
) -> Result<()> {
ic_core::module::operational()?;
let n = inputs.len();
ensure!(
(1..=MAX_BATCH).contains(&n)
&& states.len() == n
&& blinded.len() == n
&& evaluated.len() == n
&& out.len() == n,
InvalidLength,
"oprf batch: 1 to MAX_BATCH of each, equal in number"
);
let mode = states[0].mode;
ensure!(
states
.iter()
.all(|s| s.mode == mode && s.key == states[0].key),
InvalidParameter,
"oprf batch mixes modes or keys"
);
ensure!(
mode == Mode::Poprf || info.is_empty(),
InvalidParameter,
"oprf info is only for the POPRF mode"
);
if mode != Mode::Oprf {
let proof = proof.ok_or(ic_core::err!(
InvalidParameter,
"the verifiable modes need the server's proof"
))?;
let key = deserialize(&states[0].key.ok_or(ic_core::err!(
InvalidParameter,
"the verifiable modes need the server's public key"
))?)?;
let blinded_points = decode_all(blinded)?;
let evaluated_points = decode_all(evaluated)?;
let (c, d) = if mode == Mode::Poprf {
(&evaluated_points[..n], &blinded_points[..n])
} else {
(&blinded_points[..n], &evaluated_points[..n])
};
verify_proof(mode, &key, c, d, proof)?;
}
for i in 0..n {
let r = scalar_from(&states[i].blind)?;
let unblinded = deserialize(&evaluated[i])?.mul_scalar(&r.invert());
out[i] = finalize_hash(inputs[i], (mode == Mode::Poprf).then_some(info), &unblinded)?;
}
Ok(())
}
pub fn validate_element(element: &[u8; ELEMENT_LEN]) -> Result<()> {
deserialize(element).map(|_| ())
}
pub fn diffie_hellman(
private: &[u8; SCALAR_LEN],
public: &[u8; ELEMENT_LEN],
) -> Result<[u8; ELEMENT_LEN]> {
ic_core::module::operational()?;
let k = scalar_from(private)?;
ensure!(
!bool::from(k.is_zero()),
InvalidParameter,
"oprf private key is zero"
);
serialize(&deserialize(public)?.mul_scalar(&k))
}
pub fn self_test() -> Result<()> {
let seed = [0xa3u8; 32];
let key = ServerKey::derive(Mode::Oprf, &seed, b"test key")?;
let mut want_sk = [0u8; 32];
ic_core::codec::hex_decode(
b"159749d750713afe245d2d39ccfaae8381c53ce92d098a9375ee70739c7ac0bf",
&mut want_sk,
)?;
ensure!(
ic_core::ct::verify(key.to_bytes().as_ref(), &want_sk),
SelfTestFailed,
"oprf"
);
let mut want = [0u8; 32];
ic_core::codec::hex_decode(
b"a0b34de5fa4c5b6da07e72af73cc507cceeb48981b97b7285fc375345fe495dd",
&mut want,
)?;
ensure!(
ic_core::ct::verify(&key.evaluate(&[0x00], b"")?, &want),
SelfTestFailed,
"oprf"
);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_self_test_passes() {
self_test().unwrap();
}
#[test]
fn the_context_string_is_rfc_9497_s() {
assert_eq!(&context(Mode::Poprf), b"OPRFV1-\x02-P256-SHA256");
}
#[test]
fn elements_are_validated() {
assert!(deserialize(&[0u8; 33]).is_err());
let mut uncompressed = [0u8; 65];
uncompressed[0] = 4;
assert!(deserialize(&uncompressed).is_err());
let mut off = [0u8; 33];
off[0] = 2;
off[32] = 7; assert!(deserialize(&off).is_err());
}
}