Skip to main content

cryprot_ot/
lib.rs

1#![warn(clippy::unwrap_used)]
2//! CryProt-OT implements several [oblivious transfer](https://en.wikipedia.org/wiki/Oblivious_transfer) protocols.
3//!
4//! - base OT: "Simplest OT" [[CO15](https://eprint.iacr.org/2015/267)]
5//!   (classical security)
6//! - post-quantum base OT: ML-KEM based OT [[MR19](https://eprint.iacr.org/2019/706)]
7//!   (post-quantum security, enable one of the `ml-kem-base-ot-{512,768,1024}`
8//!   features)
9//! - semi-honest OT extension: optimized [[IKNP03](https://www.iacr.org/archive/crypto2003/27290145/27290145.pdf)]
10//!   protocol
11//! - malicious OT extension: optimized [[KOS15](https://eprint.iacr.org/2015/546.pdf)]
12//!   protocol
13//! - silent OT extension: [[BCG+19](https://eprint.iacr.org/2019/1159)] silent OT
14//!   using [[RRT23](https://eprint.iacr.org/2023/882)] code (semi-honest and malicious
15//!   with [[YWL+20](https://dl.acm.org/doi/pdf/10.1145/3372297.3417276)]
16//!   consistency check)
17//!
18//! This library is heavily inspired by and in parts a port of the C++ [libOTe](https://github.com/osu-crypto/libOTe) library.
19//!
20//! ## ML-KEM Base OT
21//!
22//! Enable one of the `ml-kem-base-ot-{512,768,1024}` features to use
23//! ML-KEM-based OT for the base OT protocol, providing post-quantum security:
24//!
25//! This replaces the classical "Simplest OT" with an ML-KEM-based construction
26//! following FIPS 203 at <https://csrc.nist.gov/pubs/fips/203/final>, similar to libOTe's `ENABLE_MR_KYBER` option.
27//! We use the ML-KEM crate <https://crates.io/crates/ml-kem>.
28//!
29//! ## Benchmarks
30//! We continously run the benchmark suite in CI witht the results publicly
31//! available on [bencher.dev](https://bencher.dev/perf/cryprot/plots). The raw criterion output, including throughput is
32//! available in the logs of the [bench workflow](https://github.com/robinhundt/CryProt/actions/workflows/bench.yml)
33//! (latest run > benchmarks job > Run Benchmarks step).
34//!
35//! ## OT Extension Benchmarks
36//! Following are benchmark numbers for several OT protocols on a 4-core VM
37//! running on an AMD EPYC 9454P. For up to date benchmarks view the links in
38//! the benchmarks section. Each OT sender/receiver uses one worker thread and
39//! number of cores many background threads for communication (which by default
40//! is also encrypted as part of QUIC).
41//!
42//! | Benchmark                                         | Mean Throughput (million OT/s) |
43//! |--------------------------------------------------|--------------------------|
44//! | Semi-honest R-OT ext. (2^24 R-OTs)       | 51.539                   |
45//! | Malicious R-OT ext. (2^24 R-OTs)         | 33.663                   |
46//! | Semi-Honest Silent C-OT ext. (2^21 C-OTs)          | 4.2306                   |
47//! | Semi-Honest Silent R-OT ext. (2^21 R-OTs)              | 9.5426                   |
48//! | Malicious Silent R-OT ext. (2^21 R-OTs)    | 7.4180                   |
49//!
50//! Silent OT will perform faster for smaller numbers of OTs at slightly
51//! increased communication.
52//!
53//! Our OT implementations should be on par or faster than those in libOTe. In
54//! the future we want to benchmark libOTe on the same hardware for a fair
55//! comparison.
56//!
57//! **Base OT Benchmark (Simplest OT):**
58//!
59//! | Benchmark      | Mean Time (ms) |
60//! |---------------|---------------|
61//! | 128 base R-OTs   | 28.001        |
62
63use std::{fmt::Debug, future::Future};
64
65use cryprot_core::{Block, buf::Buf};
66use cryprot_net::Connection;
67use rand::{CryptoRng, Rng, distr, prelude::Distribution, rngs::StdRng};
68use subtle::Choice;
69
70pub mod adapter;
71pub mod extension;
72#[cfg(feature = "_ml-kem-base-ot")]
73pub mod mlkem_ot;
74pub mod noisy_vole;
75pub mod phase;
76pub mod silent_ot;
77pub mod simplest_ot;
78
79/// Base OT implementation used by extension protocols.
80///
81/// When one of the `ml-kem-base-ot-{512,768,1024}` features is enabled, uses
82/// [`mlkem_ot::MlKemOt`].
83#[cfg(feature = "_ml-kem-base-ot")]
84pub type BaseOt = mlkem_ot::MlKemOt;
85
86/// Base OT implementation used by extension protocols.
87///
88/// When the `ml-kem-base-ot` feature is not enabled, use
89/// [`simplest_ot::SimplestOt`].
90#[cfg(not(feature = "_ml-kem-base-ot"))]
91pub type BaseOt = simplest_ot::SimplestOt;
92
93/// Error type for base OT operations.
94#[cfg(feature = "_ml-kem-base-ot")]
95pub type BaseOtError = mlkem_ot::Error;
96
97/// Error type for base OT operations.
98#[cfg(not(feature = "_ml-kem-base-ot"))]
99pub type BaseOtError = simplest_ot::Error;
100
101/// Trait for OT receivers/senders which hold a [`Connection`].
102pub trait Connected {
103    fn connection(&mut self) -> &mut Connection;
104}
105
106impl<C: Connected> Connected for &mut C {
107    fn connection(&mut self) -> &mut Connection {
108        (*self).connection()
109    }
110}
111
112/// A random OT sender.
113pub trait RotSender: Connected + Send {
114    /// The error type returned by send operations.
115    type Error;
116
117    /// Send `count` many random OTs.
118    ///
119    /// For better performance, use [RotSender::send_into] with an existing
120    /// [`Buf`].
121    fn send(
122        &mut self,
123        count: usize,
124    ) -> impl Future<Output = Result<Vec<[Block; 2]>, Self::Error>> + Send {
125        async move {
126            let mut ots = Vec::zeroed(count);
127            self.send_into(&mut ots).await?;
128            Ok(ots)
129        }
130    }
131
132    /// Store OTs in the provided [`Buf`]fer.
133    ///
134    /// Note that implementations might temporarily take ownership of the
135    /// [`Buf`] pointed to by `ots`. If the future returned by this method is
136    /// dropped befire completion, `ots` might point at an empty `Buf`.
137    ///
138    /// For large number of OTs, using
139    /// [`HugePageMemory`](`cryprot_core::alloc::HugePageMemory`) can
140    /// significantly improve performance on Linux systems.
141    fn send_into(
142        &mut self,
143        ots: &mut impl Buf<[Block; 2]>,
144    ) -> impl Future<Output = Result<(), Self::Error>> + Send;
145}
146
147/// A random OT receiver.
148pub trait RotReceiver: Connected + Send {
149    /// The error type returned by receive operations.
150    type Error;
151
152    /// Receive `choices.len()` many random OTs.
153    ///
154    /// For better performance, use [RotReceiver::receive_into] with an existing
155    /// [`Buf`].
156    fn receive(
157        &mut self,
158        choices: &[Choice],
159    ) -> impl Future<Output = Result<Vec<Block>, Self::Error>> + Send {
160        async {
161            let mut ots = Vec::zeroed(choices.len());
162            self.receive_into(&mut ots, choices).await?;
163            Ok(ots)
164        }
165    }
166
167    /// Store OTs in the provided [`Buf`]fer.
168    ///
169    /// Note that implementations might temporarily take ownership of the
170    /// [`Buf`] pointed to by `ots`. If the future returned by this method is
171    /// dropped befire completion, `ots` might point at an empty `Buf`.
172    ///
173    /// For large number of OTs, using
174    /// [`HugePageMemory`](`cryprot_core::alloc::HugePageMemory`) can
175    /// significantly improve performance on Linux systems.
176    fn receive_into(
177        &mut self,
178        ots: &mut impl Buf<Block>,
179        choices: &[Choice],
180    ) -> impl Future<Output = Result<(), Self::Error>> + Send;
181}
182
183impl<S: RotSender> RotSender for &mut S {
184    type Error = S::Error;
185
186    fn send_into(
187        &mut self,
188        ots: &mut impl Buf<[Block; 2]>,
189    ) -> impl Future<Output = Result<(), Self::Error>> + Send {
190        (*self).send_into(ots)
191    }
192}
193
194impl<R: RotReceiver> RotReceiver for &mut R {
195    type Error = R::Error;
196
197    fn receive_into(
198        &mut self,
199        ots: &mut impl Buf<Block>,
200        choices: &[Choice],
201    ) -> impl Future<Output = Result<(), Self::Error>> + Send {
202        (*self).receive_into(ots, choices)
203    }
204}
205
206/// Marker trait for R-OT Senders that are paired with a random choice receiver.
207pub trait RandChoiceRotSender {}
208
209/// Returns a random choice vector alongside OTs.
210pub trait RandChoiceRotReceiver: Connected + Send {
211    type Error;
212
213    /// Receive `count` many random OTs alongside their respective choices.
214    fn rand_choice_receive(
215        &mut self,
216        count: usize,
217    ) -> impl Future<Output = Result<(Vec<Block>, Vec<Choice>), Self::Error>> + Send {
218        async move {
219            let mut ots = Vec::zeroed(count);
220            let choices = self.rand_choice_receive_into(&mut ots).await?;
221            Ok((ots, choices))
222        }
223    }
224
225    /// Receive `ots.len()` many random OTs, stored into the buffer pointed to
226    /// by `ots` and returns the corresponding choices.
227    fn rand_choice_receive_into(
228        &mut self,
229        ots: &mut impl Buf<Block>,
230    ) -> impl Future<Output = Result<Vec<Choice>, Self::Error>> + Send;
231}
232
233/// Adapt any [`RotReceiver`] into a [`RandChoiceRotReceiver`] by securely
234/// sampling the random choices using [`random_choices`].
235impl<R: RotReceiver> RandChoiceRotReceiver for R {
236    type Error = R::Error;
237
238    async fn rand_choice_receive_into(
239        &mut self,
240        ots: &mut impl Buf<Block>,
241    ) -> Result<Vec<Choice>, Self::Error> {
242        let choices = random_choices(ots.len(), &mut rand::make_rng::<StdRng>());
243        self.receive_into(ots, &choices).await?;
244        Ok(choices)
245    }
246}
247
248/// Correlated OT sender (C-OT).
249pub trait CotSender: Connected + Send {
250    type Error;
251
252    /// Random OTs correlated by the `correlation`` function..
253    ///
254    /// The correlation function is passed the index of a C-OT and must output
255    /// the correlation for this C-OT.
256    fn correlated_send<F>(
257        &mut self,
258        count: usize,
259        correlation: F,
260    ) -> impl Future<Output = Result<Vec<Block>, Self::Error>> + Send
261    where
262        F: FnMut(usize) -> Block + Send,
263    {
264        async move {
265            let mut ots = Vec::zeroed(count);
266            self.correlated_send_into(&mut ots, correlation).await?;
267            Ok(ots)
268        }
269    }
270
271    fn correlated_send_into<B, F>(
272        &mut self,
273        ots: &mut B,
274        correlation: F,
275    ) -> impl Future<Output = Result<(), Self::Error>> + Send
276    where
277        B: Buf<Block>,
278        F: FnMut(usize) -> Block + Send;
279}
280
281pub trait CotReceiver: Connected + Send {
282    type Error;
283
284    fn correlated_receive(
285        &mut self,
286        choices: &[Choice],
287    ) -> impl Future<Output = Result<Vec<Block>, Self::Error>> + Send {
288        async {
289            let mut ots = Vec::zeroed(choices.len());
290            self.correlated_receive_into(&mut ots, choices).await?;
291            Ok(ots)
292        }
293    }
294
295    fn correlated_receive_into<B>(
296        &mut self,
297        ots: &mut B,
298        choices: &[Choice],
299    ) -> impl Future<Output = Result<(), Self::Error>> + Send
300    where
301        B: Buf<Block>;
302}
303
304/// Marker trait for OT implementations secure against semi-honest adversaries.
305pub trait SemiHonest {}
306
307/// Marker trait for OT implementations secure against malicious adversaries.
308pub trait Malicious: SemiHonest {}
309
310/// Used to abstract over [`SemiHonestMarker`] or [`MaliciousMarker`]
311pub trait Security: Send + Sync + Debug + Copy + Clone + private::Sealed {
312    const MALICIOUS_SECURITY: bool;
313}
314
315/// Used as a marker type for semi-honest security OT implementation.
316#[derive(Copy, Clone, Debug)]
317pub struct SemiHonestMarker;
318
319impl Security for SemiHonestMarker {
320    const MALICIOUS_SECURITY: bool = false;
321}
322
323/// Used as a marker type for malicious security OT implementation.
324#[derive(Copy, Clone, Debug)]
325pub struct MaliciousMarker;
326
327impl Security for MaliciousMarker {
328    const MALICIOUS_SECURITY: bool = true;
329}
330
331mod private {
332    pub trait Sealed {}
333
334    impl Sealed for super::SemiHonestMarker {}
335
336    impl Sealed for super::MaliciousMarker {}
337}
338
339/// Sample `count` many [`Choice`]es using the provided rng.
340pub fn random_choices<RNG: Rng + CryptoRng>(count: usize, rng: &mut RNG) -> Vec<Choice> {
341    let uniform = distr::Uniform::new(0, 2).expect("correct range");
342    uniform
343        .sample_iter(rng)
344        .take(count)
345        .map(Choice::from)
346        .collect()
347}