cryprot_ot/
base.rs

1//! Simplest-OT base OT protocol by [[CO15](https://eprint.iacr.org/2015/267)] (malicious security).
2
3use std::io;
4
5use cryprot_core::{
6    Block,
7    buf::Buf,
8    rand_compat::RngCompat,
9    random_oracle::{Hash, RandomOracle},
10};
11use cryprot_net::{Connection, ConnectionError};
12use curve25519_dalek::{RistrettoPoint, Scalar, constants::RISTRETTO_BASEPOINT_TABLE};
13use futures::{SinkExt, StreamExt};
14use rand::{Rng, SeedableRng, rngs::StdRng};
15use subtle::{Choice, ConditionallySelectable};
16use tracing::Level;
17
18use crate::{Connected, Malicious, RotReceiver, RotSender, SemiHonest, phase};
19
20pub struct SimplestOt {
21    rng: StdRng,
22    conn: Connection,
23}
24
25impl SimplestOt {
26    pub fn new(connection: Connection) -> Self {
27        Self::new_with_rng(connection, StdRng::from_os_rng())
28    }
29
30    pub fn new_with_rng(connection: Connection, rng: StdRng) -> SimplestOt {
31        Self {
32            conn: connection,
33            rng,
34        }
35    }
36}
37
38impl Connected for SimplestOt {
39    fn connection(&mut self) -> &mut Connection {
40        &mut self.conn
41    }
42}
43
44#[derive(thiserror::Error, Debug)]
45pub enum Error {
46    #[error("quic connection error")]
47    Connection(#[from] ConnectionError),
48    #[error("io communicaiton error")]
49    Io(#[from] io::Error),
50    #[error("insufficient points received. expected: {expected}, actual: {actual}")]
51    InsufficientPoints { expected: usize, actual: usize },
52    #[error("expected message but stream is closed")]
53    ClosedStream,
54    #[error("seed commitment and seed hash not equal")]
55    CommitmentHashesNotEqual,
56}
57
58impl SemiHonest for SimplestOt {}
59
60impl Malicious for SimplestOt {}
61
62impl RotSender for SimplestOt {
63    type Error = Error;
64
65    #[allow(non_snake_case)]
66    #[tracing::instrument(level = Level::DEBUG, skip_all, fields(count = ots.len()))]
67    #[tracing::instrument(target = "cryprot_metrics", level = Level::TRACE, skip_all, fields(phase = phase::BASE_OT))]
68    async fn send_into(&mut self, ots: &mut impl Buf<[Block; 2]>) -> Result<(), Self::Error> {
69        let count = ots.len();
70        let a = Scalar::random(&mut RngCompat(&mut self.rng));
71        let mut A = RISTRETTO_BASEPOINT_TABLE * &a;
72        let seed: Block = self.rng.random();
73        // commit to the seed
74        let seed_commitment = seed.ro_hash();
75        let (mut send, mut recv) = self.conn.byte_stream().await?;
76        {
77            let mut send_m1 = send.as_stream();
78            send_m1.send((A, *seed_commitment.as_bytes())).await?;
79        }
80
81        let B_points: Vec<RistrettoPoint> = {
82            let mut recv_m2 = recv.as_stream();
83            recv_m2.next().await.ok_or(Error::ClosedStream)??
84        };
85        if B_points.len() != count {
86            return Err(Error::InsufficientPoints {
87                expected: count,
88                actual: B_points.len(),
89            });
90        }
91        // decommit seed
92        {
93            let mut send_m3 = send.as_stream();
94            send_m3.send(seed).await?;
95        }
96
97        A *= a;
98        for (i, (mut B, ots)) in B_points.into_iter().zip(ots.iter_mut()).enumerate() {
99            B *= a;
100            let k0 = ro_hash_point(&B, i, seed);
101            B -= A;
102            let k1 = ro_hash_point(&B, i, seed);
103            *ots = [k0, k1];
104        }
105        Ok(())
106    }
107}
108
109impl RotReceiver for SimplestOt {
110    type Error = Error;
111
112    #[allow(non_snake_case)]
113    #[tracing::instrument(level = Level::DEBUG, skip_all, fields(count = ots.len()))]
114    #[tracing::instrument(target = "cryprot_metrics", level = Level::TRACE, skip_all, fields(phase = phase::BASE_OT))]
115    async fn receive_into(
116        &mut self,
117        ots: &mut impl Buf<Block>,
118        choices: &[Choice],
119    ) -> Result<(), Self::Error> {
120        assert_eq!(choices.len(), ots.len());
121        let (mut send, mut recv) = self.conn.byte_stream().await?;
122        let (A, commitment): (RistrettoPoint, [u8; 32]) = {
123            let mut recv_m1 = recv.as_stream();
124            recv_m1.next().await.ok_or(Error::ClosedStream)??
125        };
126
127        let (b_points, B_points): (Vec<_>, Vec<_>) = choices
128            .iter()
129            .map(|choice| {
130                let b = Scalar::random(&mut RngCompat(&mut self.rng));
131                let B_0 = RISTRETTO_BASEPOINT_TABLE * &b;
132                let B_1 = B_0 + A;
133                let B_choice = RistrettoPoint::conditional_select(&B_0, &B_1, *choice);
134                (b, B_choice)
135            })
136            .unzip();
137        {
138            let mut send_m2 = send.as_stream();
139            send_m2.send(B_points).await?;
140        }
141
142        let seed: Block = {
143            let mut recv_3 = recv.as_stream();
144            recv_3.next().await.ok_or(Error::ClosedStream)??
145        };
146        if Hash::from_bytes(commitment) != seed.ro_hash() {
147            return Err(Error::CommitmentHashesNotEqual);
148        }
149        for (i, (b, ot)) in b_points.into_iter().zip(ots.iter_mut()).enumerate() {
150            let B = A * b;
151            *ot = ro_hash_point(&B, i, seed);
152        }
153        Ok(())
154    }
155}
156
157fn ro_hash_point(point: &RistrettoPoint, tweak: usize, seed: Block) -> Block {
158    let mut ro = RandomOracle::new();
159    ro.update(point.compress().as_bytes());
160    ro.update(&tweak.to_le_bytes());
161    // TODO wouldn't it be possible to use the seed as the blake3 key?
162    ro.update(seed.as_bytes());
163    let mut out_reader = ro.finalize_xof();
164    let mut ret = Block::ZERO;
165    out_reader.fill(ret.as_mut_bytes());
166    ret
167}
168
169#[cfg(test)]
170mod tests {
171    use anyhow::Result;
172    use cryprot_net::testing::{init_tracing, local_conn};
173    use rand::{SeedableRng, rngs::StdRng};
174
175    use super::SimplestOt;
176    use crate::{RotReceiver, RotSender, random_choices};
177
178    #[tokio::test]
179    async fn base_rot() -> Result<()> {
180        let _g = init_tracing();
181        let (c1, c2) = local_conn().await?;
182        let mut rng1 = StdRng::seed_from_u64(42);
183        let rng2 = StdRng::seed_from_u64(42 * 42);
184        let count = 128;
185        let choices = random_choices(count, &mut rng1);
186
187        let mut sender = SimplestOt::new_with_rng(c1, rng1);
188        let mut receiver = SimplestOt::new_with_rng(c2, rng2);
189        let (s_ot, r_ot) = tokio::try_join!(sender.send(count), receiver.receive(&choices))?;
190
191        for ((r, s), c) in r_ot.into_iter().zip(s_ot).zip(choices) {
192            assert_eq!(r, s[c.unwrap_u8() as usize])
193        }
194        Ok(())
195    }
196}