use super::schnorr::ZkProof;
use super::secp::{self, ProjectivePoint, Scalar};
use crate::frost::hashing::sha512_256i_tagged;
use purecrypto::ct::{Choice, ConditionallySelectable};
use purecrypto::rng::RngCore;
pub const KEY_LEN: usize = 32;
const TAG_KEY: &[u8] = b"DKLS23-baseot-key-v1";
const TAG_POK: &[u8] = b"DKLS23-baseot-pok-v1";
pub type SenderKeys = (Vec<[u8; KEY_LEN]>, Vec<[u8; KEY_LEN]>);
pub struct SenderMsg1 {
pub s: ProjectivePoint,
pub pok: ZkProof,
}
pub struct ReceiverMsg1 {
pub r: Vec<ProjectivePoint>,
}
pub struct Sender {
sid: Vec<u8>,
n: usize,
y: Scalar,
s: ProjectivePoint,
}
pub struct Receiver {
sid: Vec<u8>,
n: usize,
bits: Vec<u8>,
x: Vec<Scalar>,
s: ProjectivePoint,
}
impl Sender {
pub fn new(sid: &[u8], n: usize, rng: &mut impl RngCore) -> (Sender, SenderMsg1) {
let y = secp::random_scalar(rng);
let s = secp::mul_base(&y);
let pok = ZkProof::prove(&pok_session(sid), &y, &s, rng);
(
Sender {
sid: sid.to_vec(),
n,
y,
s,
},
SenderMsg1 { s, pok },
)
}
pub fn finalize(&self, msg: &ReceiverMsg1) -> Option<SenderKeys> {
if msg.r.len() != self.n {
return None;
}
let y_s = self.s.mul(&self.y);
let neg_y_s = y_s.negate();
let mut k0 = Vec::with_capacity(self.n);
let mut k1 = Vec::with_capacity(self.n);
for (i, r) in msg.r.iter().enumerate() {
let y_r = r.mul(&self.y);
k0.push(derive_key(&self.sid, i, 0, &y_r));
k1.push(derive_key(&self.sid, i, 1, &y_r.add(&neg_y_s)));
}
Some((k0, k1))
}
pub fn n(&self) -> usize {
self.n
}
}
impl Receiver {
pub fn new(
sid: &[u8],
n: usize,
bits: &[u8],
msg: &SenderMsg1,
rng: &mut impl RngCore,
) -> Option<(Receiver, ReceiverMsg1)> {
if bits.len() * 8 < n {
return None;
}
if !msg.pok.verify(&pok_session(sid), &msg.s) {
return None;
}
let mut x = Vec::with_capacity(n);
let mut r = Vec::with_capacity(n);
for i in 0..n {
let xi = secp::random_scalar(rng);
let c = (bits[i / 8] >> (i & 7)) & 1;
let ra = secp::mul_base(&xi); let rb = ra.add(&msg.s); r.push(ProjectivePoint::conditional_select(
&rb,
&ra,
Choice::from(c),
));
x.push(xi);
}
Some((
Receiver {
sid: sid.to_vec(),
n,
bits: bits.to_vec(),
x,
s: msg.s,
},
ReceiverMsg1 { r },
))
}
pub fn finalize(&self) -> Vec<[u8; KEY_LEN]> {
(0..self.n)
.map(|i| {
let c = (self.bits[i / 8] >> (i & 7)) & 1;
let x_s = self.s.mul(&self.x[i]);
derive_key(&self.sid, i, c as usize, &x_s)
})
.collect()
}
pub fn n(&self) -> usize {
self.n
}
}
fn pok_session(sid: &[u8]) -> Vec<u8> {
[TAG_POK, sid].concat()
}
fn derive_key(sid: &[u8], instance: usize, bit: usize, p: &ProjectivePoint) -> [u8; KEY_LEN] {
let tag = [TAG_KEY, sid].concat();
let (px, py) = secp::affine_be(p);
let inst = int_be_min(instance as u64);
let b = int_be_min(bit as u64);
sha512_256i_tagged(
&tag,
&[inst.as_slice(), b.as_slice(), px.as_slice(), py.as_slice()],
)
}
fn int_be_min(n: u64) -> Vec<u8> {
let be = n.to_be_bytes();
let start = be.iter().position(|&x| x != 0).unwrap_or(be.len());
be[start..].to_vec()
}
#[cfg(test)]
mod tests {
use super::*;
use purecrypto::rng::OsRng;
#[test]
fn ot_correctness() {
let n: usize = 20;
let sid = b"test-sid".to_vec();
let mut bits = vec![0u8; n.div_ceil(8)];
OsRng.fill_bytes(&mut bits);
let (sender, m1) = Sender::new(&sid, n, &mut OsRng);
let (receiver, m2) = Receiver::new(&sid, n, &bits, &m1, &mut OsRng).unwrap();
let (k0, k1) = sender.finalize(&m2).unwrap();
let rk = receiver.finalize();
for i in 0..n {
let c = (bits[i / 8] >> (i & 7)) & 1;
let expected = if c == 1 { k1[i] } else { k0[i] };
assert_eq!(rk[i], expected, "instance {i} (bit {c})");
let other = if c == 1 { k0[i] } else { k1[i] };
assert_ne!(rk[i], other, "instance {i} non-chosen key leaked");
}
}
#[test]
fn rejects_bad_pok() {
let sid = b"sid".to_vec();
let (_s, mut m1) = Sender::new(&sid, 4, &mut OsRng);
m1.s = secp::mul_base(&secp::random_scalar(&mut OsRng));
let bits = vec![0u8; 1];
assert!(Receiver::new(&sid, 4, &bits, &m1, &mut OsRng).is_none());
}
}