mpc_engine/primitives/
ot.rs

1//! This module implements "The Simplest Protocol for Oblivious Transfer" due to
2//! Orlandi and Chou.
3//! (cf. https://eprint.iacr.org/archive/2015/267/1527602042.pdf)
4//!
5//! The protocol works as follows in an elliptic curve group G with base point `B` and scalars `Scalars`
6//!
7//! ```text
8//! Sender(l, r)            Receiver(c)
9//! y <-$ Scalars
10//! S := yB
11//! T := yS    -- S -->     x <-$ Scalars
12//!                         R := cS + xB
13//!            <-- R --
14//! k_l                     k = H(S, R, xS)
15//!  = H(S, R, yR)       
16//! k_r
17//!  = H(S, R, yR - T)
18//!
19//! c_l = E(k_l, l)
20//! c_r = E(k_r, r)
21//!
22//!          -- c_l -->
23//!          -- c_r -->     output = D(k, c_l) if decryption successful
24//!                         otherwise output = D(k, c_r)
25//! ```
26//! We instantiate the primitives as follows:
27//!     - H: HKDF(SHA-256)
28//!     - group G: P256
29//!     - Encryption scheme: Chacha20Poly1305
30
31use hacspec_chacha20poly1305::{ChaChaPolyIV, ChaChaPolyKey};
32use hacspec_lib::Randomness;
33use p256::{p256_point_mul, p256_point_mul_base, P256Point};
34
35use crate::Error;
36
37/// The state of the sender
38pub struct OTSender {
39    y: p256::P256Scalar,
40    s: p256::P256Point,
41    t: p256::P256Point,
42    dst: Vec<u8>,
43}
44
45/// The state of the receiver
46pub struct OTReceiver {
47    x: p256::P256Scalar,
48    r: p256::P256Point,
49    s: P256Point,
50    dst: Vec<u8>,
51}
52
53/// The OT sender's first message.
54#[derive(Debug)]
55pub struct OTSenderInit(p256::P256Point);
56
57/// The OT receiver's first message.
58#[derive(Debug)]
59pub struct OTReceiverSelect(p256::P256Point);
60
61/// The encryption of an OT input.
62#[derive(Debug)]
63pub struct OTCiphertext {
64    iv: ChaChaPolyIV,
65    ciphertext: Vec<u8>,
66    tag: [u8; 16],
67}
68/// The OT sender's second message.
69#[derive(Debug)]
70pub struct OTSenderSend {
71    left: OTCiphertext,
72    right: OTCiphertext,
73}
74
75impl OTSender {
76    /// Generate the first sender message.
77    ///
78    /// Initiates an OT sender by picking a random P256 scalar `y` and deriving
79    /// `S = yB` and `T = yS`, where `B` is the P256 base point. These values
80    /// will later be used to derive encryption keys in the send stage of the
81    /// protocol. In addition, the domain separation tag `dst`, which will be
82    /// used in key generation is stored in the receiver and `S` is prepared for
83    /// sending to the receiver by wrapping it in an `OTSenderInit` message.
84    pub fn init(entropy: &mut Randomness, dst: &[u8]) -> Result<(Self, OTSenderInit), Error> {
85        let y = p256::random_scalar(entropy, dst)?;
86
87        let s = p256::p256_point_mul_base(y)?;
88        let t = p256_point_mul(y, s)?;
89
90        Ok((
91            OTSender {
92                y,
93                s,
94                t,
95                dst: dst.to_vec(),
96            },
97            OTSenderInit(s),
98        ))
99    }
100
101    /// Generate the second sender message based on the receiver's selection.
102    ///
103    /// Given the `OTReceiverSelect` message and the two sender inputs, the
104    /// sender can generate the transfer messages. It does so by deriving two
105    /// domain separated encryption keys, based on the values `S` and `T`
106    /// generated during initiation and on the masked choice bit sent by the
107    /// receiver. It then encrypts the left and right inputs under their
108    /// respective keys and prepares an `OTSenderSend` message with both
109    /// ciphertexts. This finishes the OT session for the sender. By the
110    /// security of the protocol, the receiver will only be able to generate one
111    /// of the decryption keys, namely that one corresponding to its choice bit.
112    pub fn send(
113        &self,
114        left_input: &[u8],
115        right_input: &[u8],
116        selection: &OTReceiverSelect,
117        entropy: &mut Randomness,
118    ) -> Result<OTSenderSend, Error> {
119        debug_assert_eq!(
120            left_input.len(),
121            right_input.len(),
122            "Left and right inputs to the OT must be of the same length."
123        );
124        let OTReceiverSelect(r) = selection;
125
126        let (left_key, right_key) = self.derive_keys(r)?;
127
128        let (left, right) = encrypt_inputs(entropy, left_key, left_input, right_key, right_input);
129
130        Ok(OTSenderSend { left, right })
131    }
132
133    /// Derive a pair of encryption keys for creating the `OTSenderSend`
134    /// message.
135    ///
136    /// Given the points `S`,`T` from the sender initialization, as well as the
137    /// masked receiver choice `R`, a pre-key is generated based on `S` and `R`.
138    /// Then the keys are generated by first extracting
139    /// `HKDF-SHA256-extract(pre-key||yR)` for the right input and
140    /// `HKDF-SHA256-extract(pre-key||yR - T)` for the left input where the
141    /// elliptic curve points are serialized and concatenated to the pre-key and
142    /// a fixed salt value is used to extract. From this, two 32-byte keys are
143    /// expanded for use in ChaCha20Poly1305.
144    fn derive_keys(
145        &self,
146        receiver_selection: &p256::P256Point,
147    ) -> Result<(ChaChaPolyKey, ChaChaPolyKey), Error> {
148        let (salt, ikm) = derive_prk(&self.s, receiver_selection);
149
150        let input_right = p256_point_mul(self.y, *receiver_selection)?;
151
152        let input_left = p256::point_add(input_right, -self.t)?;
153
154        let input_left_serialized = p256::serialize_point(&input_left);
155        let input_right_serialized = p256::serialize_point(&input_right);
156
157        let mut ikm_left = ikm.clone();
158        let mut ikm_right = ikm;
159
160        ikm_left.extend_from_slice(&input_left_serialized);
161        ikm_right.extend_from_slice(&input_right_serialized);
162
163        let prk_left = hmac::hkdf_extract(&salt, &ikm_left);
164        let prk_right = hmac::hkdf_extract(&salt, &ikm_right);
165        Ok((
166            hmac::hkdf_expand(&prk_left, &self.dst, 32)
167                .try_into()
168                .expect(
169                    "should have received the right number of bytes, because we requested them",
170                ),
171            hmac::hkdf_expand(&prk_right, &self.dst, 32)
172                .try_into()
173                .expect(
174                    "should have received the right number of bytes, because we requested them",
175                ),
176        ))
177    }
178}
179
180/// Create the pre-key for encrypting and decrypting `OTSenderSend` ciphertexts.
181///
182/// Based on points `S` and `R`, use `HKDF-SHA256-extract(S||R)` to generate a
183/// pre-key with a fixed salt value by serializing and concatenating the points.
184fn derive_prk(
185    sender_commitment: &p256::P256Point,
186    receiver_selection: &p256::P256Point,
187) -> (Vec<u8>, Vec<u8>) {
188    let serialized_s = p256::serialize_point(sender_commitment);
189    let serialized_r = p256::serialize_point(receiver_selection);
190    let salt = b"no-salt";
191    let mut ikm = Vec::from(serialized_s);
192    ikm.extend_from_slice(&serialized_r);
193    (salt.to_vec(), ikm)
194}
195
196/// Encrypt the OT sender inputs.
197///
198/// Using the keys generated by `derive_keys()` encrypt the sender's inputs with
199/// Chacha20Poly1305, without any additional authenticated data.
200fn encrypt_inputs(
201    entropy: &mut Randomness,
202    left_key: [u8; 32],
203    left_input: &[u8],
204    right_key: [u8; 32],
205    right_input: &[u8],
206) -> (OTCiphertext, OTCiphertext) {
207    let left_iv = entropy
208        .bytes(12)
209        .expect("sufficient randomness should have been provided externally")
210        .try_into()
211        .expect("should have received the right number of bytes, because we requested them");
212
213    let right_iv = entropy
214        .bytes(12)
215        .expect("sufficient randomness should have been provided externally")
216        .try_into()
217        .expect("should have received the right number of bytes, because we requested them");
218
219    let (left_enc, left_tag) =
220        hacspec_chacha20poly1305::chacha20_poly1305_encrypt(left_key, left_iv, &[], left_input);
221    let (right_enc, right_tag) =
222        hacspec_chacha20poly1305::chacha20_poly1305_encrypt(right_key, right_iv, &[], right_input);
223    (
224        OTCiphertext {
225            iv: left_iv,
226            ciphertext: left_enc,
227            tag: left_tag,
228        },
229        OTCiphertext {
230            iv: right_iv,
231            ciphertext: right_enc,
232            tag: right_tag,
233        },
234    )
235}
236
237impl OTReceiver {
238    /// Generate the first receiver message.
239    ///
240    /// Initiates the OT receiver by generating a random P256 Scalar `x` which
241    /// is used to mask the receivers choice bit `c` of output in the
242    /// computation of the masked receiver selection `R = cS + xB`, where `S` is
243    /// obtained from the initial sender message. The values required in the
244    /// decryption of the sender messages are stored in the `OTReceiver` struct
245    /// and the masked choice is wrapped in an `OTReceiverSelect` message for
246    /// sending to the sender.
247    pub fn select(
248        entropy: &mut Randomness,
249        dst: &[u8],
250        sender_message: OTSenderInit,
251        choose_left: bool,
252    ) -> Result<(Self, OTReceiverSelect), Error> {
253        let x = p256::random_scalar(entropy, dst)?;
254        let OTSenderInit(s) = sender_message;
255
256        let mut res = p256_point_mul_base(x)?;
257        let r = if choose_left {
258            res = p256::point_add(res, s)?;
259            res
260        } else {
261            res
262        };
263
264        Ok((
265            OTReceiver {
266                x,
267                r,
268                s,
269                dst: dst.to_vec(),
270            },
271            OTReceiverSelect(r),
272        ))
273    }
274
275    /// Receive the selected input from the sender.
276    ///
277    /// A decryption key is generated based on the initialization message
278    /// received from the sender and the masked choice generated during receiver
279    /// initialization. Then trial-decryption of the sender messages are
280    /// attempted. Correctness of the protocol guarantees that one decryption
281    /// will be successful (exactly one, by security of the protocol) and will
282    /// yield the receiver's chosen output ending the OT session for the
283    /// receiver.
284    pub fn receive(&self, sender_message: OTSenderSend) -> Result<Vec<u8>, Error> {
285        let key = self.derive_key()?;
286
287        let dec = hacspec_chacha20poly1305::chacha20_poly1305_decrypt(
288            key,
289            sender_message.left.iv,
290            &[],
291            &sender_message.left.ciphertext,
292            sender_message.left.tag,
293        )
294        .or_else(|_| {
295            hacspec_chacha20poly1305::chacha20_poly1305_decrypt(
296                key,
297                sender_message.right.iv,
298                &[],
299                &sender_message.right.ciphertext,
300                sender_message.right.tag,
301            )
302        });
303
304        dec.map_err(|e| e.into())
305    }
306
307    /// Derive a decryption key for trial decrypting the `OTSenderSend`
308    /// messages.
309    ///
310    /// Given the point `S` from the initial sender message, as well as the
311    /// scalar `x` generated during receiver initialization and the masked
312    /// receiver choice `R`, a pre-key is generated based on `S` and `R`. Then
313    /// the key is generated by first extracting
314    ///  `HKDF-SHA256-extract(pre-key||xS)` where the elliptic curve points are
315    /// serialized and concatenated to the pre-key and a fixed salt value is
316    /// used to extract. From this, a 32-byte key is expanded for use in
317    /// ChaCha20Poly1305.
318    fn derive_key(&self) -> Result<ChaChaPolyKey, Error> {
319        let (salt, mut ikm) = derive_prk(&self.s, &self.r);
320
321        let input = p256_point_mul(self.x, self.s)?;
322        let input_serialized = p256::serialize_point(&input);
323
324        ikm.extend_from_slice(&input_serialized);
325
326        let prk = hmac::hkdf_extract(&salt, &ikm);
327
328        Ok(hmac::hkdf_expand(&prk, &self.dst, 32)
329            .try_into()
330            .expect("should have received the required number of bytes because we requested them"))
331    }
332}
333
334#[test]
335fn simple() {
336    use rand::RngCore;
337    let mut rng = rand::thread_rng();
338    let mut entropy = [0u8; 88];
339    rng.fill_bytes(&mut entropy);
340    let mut entropy = Randomness::new(entropy.to_vec());
341
342    let dst = b"test-context";
343    let left_input = b"lefto";
344    let right_input = b"right";
345    let (sender, commitment) = OTSender::init(&mut entropy, dst).unwrap();
346    let (receiver, selection) = OTReceiver::select(&mut entropy, dst, commitment, false).unwrap();
347
348    let send_message = sender
349        .send(left_input, right_input, &selection, &mut entropy)
350        .unwrap();
351
352    let receiver_output = receiver.receive(send_message).unwrap();
353    debug_assert_eq!(right_input.to_vec(), receiver_output);
354}