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}