oprf/coprf/
coprf_online.rs

1#![warn(missing_docs)]
2//! ## E.2. CoPRF Online Protocol
3//! ```text
4//!     Requester(bpk, input)                                        Evaluator(k)                                   Receiver(bpk, bsk)
5//!   --------------------------------------------------------------------------------------------------------------------------------
6//!   blindedElement = Blind(bpk, input)
7//!
8//!                                  blindedElement
9//!                                    ---------->
10//!
11//!                                               evaluatedElement = BlindEvaluate(k, blindedElement)
12//!
13//!                                                                                           evaluatedElement
14//!                                                                                             ---------->
15//!
16//!                                                                                                  output = Finalize(bsk, evaluatedElement)
17//! ```
18
19use elgamal::Ciphertext;
20use hacspec_lib::{hacspec_helper::NatMod, Randomness};
21
22use super::coprf_setup::{BlindingPublicKey, CoPRFKey};
23use crate::{coprf::coprf_setup::CoPRFReceiverContext, p256_sha256, Error};
24use p256::P256Point;
25
26/// CoPRF Inputs can be arbitrary byte strings.
27pub type Input<'a> = &'a [u8];
28/// The coPRF range is the range of the underlying PRF, in our case the
29/// group of points on P-256.
30pub type Output = P256Point;
31
32/// The blinded coPRF input corresponds to a ciphertext of the underlying
33/// rerandomizable encryption scheme, in our case an Elgamal ciphertext.
34pub type BlindInput = Ciphertext;
35/// Since blind evaluation is performed via the homomorphic properties and
36/// rerandomizability of the underlying encryption scheme, a coPRF output
37/// is also a ciphertext of the underlying encryption scheme.
38pub type BlindOutput = Ciphertext;
39
40/// The requester blinds a query for blind evaluation by Elgamal
41/// encryption with the blinding public key of the target receiver after
42/// applying the RO-mapping into the base group used by the encryption
43/// scheme to the input bytes.
44pub fn blind(
45    bpk: BlindingPublicKey,
46    input: Input,
47    context_string: Vec<u8>,
48    randomness: &mut Randomness,
49) -> Result<BlindInput, Error> {
50    let inputElement = p256_sha256::hash_to_group(input, &context_string)?;
51
52    if inputElement == p256_sha256::identity() {
53        return Err(Error::InvalidInputError);
54    }
55
56    let blindInput = elgamal::encrypt(bpk, inputElement, randomness)?;
57
58    Ok(blindInput)
59}
60
61/// Blind PRF Evaluation is performed using the homomorphic properties of
62/// Elgamal ciphertexts. Further, the converter rerandomizes every
63/// ciphertext that it receives in order to achieve resistance against
64/// collusion between requester and receiver.
65pub fn blind_evaluate(
66    key: CoPRFKey,
67    bpk: BlindingPublicKey,
68    blind_input: BlindInput,
69    randomness: &mut Randomness,
70) -> Result<BlindOutput, Error> {
71    let input_rerandomized = elgamal::rerandomize(bpk, blind_input, randomness)?;
72    elgamal::scalar_mul_ciphertext(key, input_rerandomized).map_err(|e| e.into())
73}
74
75/// To recover the PRF output, the receiver performs unblinding of the
76/// blind evaluation result by Elgamal decryption.
77pub fn finalize(
78    context: &CoPRFReceiverContext,
79    blind_output: BlindOutput,
80) -> Result<Output, Error> {
81    elgamal::decrypt(context.bsk, blind_output).map_err(|e| e.into())
82}
83
84/// A PRF output can be blinded for blind conversion by perfoming an
85/// Elgamal encryption of it under the target blinding public key.
86pub fn prepare_blind_convert(
87    bpk: BlindingPublicKey,
88    y: Output,
89    randomness: &mut Randomness,
90) -> Result<BlindInput, Error> {
91    elgamal::encrypt(bpk, y, randomness).map_err(|e| e.into())
92}
93
94/// Blind conversion is performed using the homomorphic properties of the
95/// Elgamal ciphertext.  Like all other ciphertexts received by the
96/// evaluator, the blinded output is rerandomized to provide
97/// collusion-resistance.
98pub fn blind_convert(
99    bpk: BlindingPublicKey,
100    key_from: CoPRFKey,
101    key_to: CoPRFKey,
102    blind_input: BlindInput,
103    randomness: &mut Randomness,
104) -> Result<BlindOutput, Error> {
105    let delta = key_to * key_from.inv();
106    let ctx_rerandomized = elgamal::rerandomize(bpk, blind_input, randomness)?;
107    elgamal::scalar_mul_ciphertext(delta, ctx_rerandomized).map_err(|e| e.into())
108}
109
110#[cfg(test)]
111mod tests {
112    use crate::coprf::coprf_setup::{derive_key, CoPRFEvaluatorContext};
113
114    use super::*;
115
116    // =========== Unblinded Operations ===========
117
118    /// The cleartext evaluation of the PRF based on the PRF by Naor, Pinkas, and Reingold:
119    ///
120    /// ```text
121    /// PRF: K x X -> G
122    ///
123    /// PRF(k, x) = k * H(x)
124    /// ```
125    ///
126    /// where
127    /// * `K` is a set of keys, in our case the scalars of P-256,
128    /// * `X` is the set of inputs, in our case arbitrary bitstrings,
129    /// * `G` is a group where DDH problem is assumed to be computationally hard, in our case P-256,
130    /// * `H` is a random oracle mapping bitstring to `G`, in our case as specified in [hash-to-curve].
131    pub fn evaluate(context_string: &[u8], key: CoPRFKey, input: Input) -> Result<Output, Error> {
132        let inputElement = p256_sha256::hash_to_group(input, context_string)?;
133
134        if inputElement == P256Point::AtInfinity {
135            return Err(Error::InvalidInputError);
136        }
137
138        let evaluatedElement = p256::p256_point_mul(key, inputElement)?;
139
140        Ok(evaluatedElement)
141    }
142
143    /// We require that a converted output is always the same as if the output
144    /// had been generated under the target key in the first place, i.e. for
145    /// all master secrets `msk`, all `k_i, k_j` output by `derive_key(msk,
146    /// i), derive_key(msk,j)` and all input `x` in `X` it should hold that
147    ///
148    /// ```text
149    /// evaluate(k_j, x) = convert(k_i, k_j, evaluate(k_i, x))
150    /// ```
151    ///
152    /// In our instantiation based on the Naor-Pinkas-Reingold PRF, conversion
153    /// is performed by first computing a `delta` scalar from both evaluation
154    /// keys, which when mulitplied with the output to convert will cancel out
155    /// the original evaluation key and multiply by the target evaluation key.
156    pub fn convert(
157        key_origin: CoPRFKey,
158        key_destination: CoPRFKey,
159        y: Output,
160    ) -> Result<Output, Error> {
161        let delta = key_destination * key_origin.inv();
162        let result = p256::p256_point_mul(delta, y)?;
163
164        Ok(result)
165    }
166
167    fn generate_randomness() -> Randomness {
168        use rand::prelude::*;
169
170        let mut rng = rand::thread_rng();
171        let mut randomness = [0u8; 1000000];
172        rng.fill_bytes(&mut randomness);
173        let randomness = Randomness::new(randomness.to_vec());
174
175        randomness
176    }
177
178    #[test]
179    fn self_test_eval_convert() {
180        let mut randomness = generate_randomness();
181
182        let test_context = b"Test";
183        let test_input = b"TestInput";
184        let evaluator_context = CoPRFEvaluatorContext::new(&mut randomness).unwrap();
185
186        let key_origin1 = derive_key(&evaluator_context, b"1").unwrap();
187        let key_origin2 = derive_key(&evaluator_context, b"2").unwrap();
188        let key_destination = derive_key(&evaluator_context, b"3").unwrap();
189
190        let y_under_origin1 = evaluate(test_context, key_origin1, test_input).unwrap();
191        let y_under_origin2 = evaluate(test_context, key_origin2, test_input).unwrap();
192
193        let y_under_destination = evaluate(test_context, key_destination, test_input).unwrap();
194        let converted_y_from_1 = convert(key_origin1, key_destination, y_under_origin1).unwrap();
195        let converted_y_from_2 = convert(key_origin2, key_destination, y_under_origin2).unwrap();
196
197        debug_assert_eq!(converted_y_from_1, converted_y_from_2);
198        debug_assert_eq!(converted_y_from_1, y_under_destination);
199    }
200
201    #[test]
202    fn test_blind_evaluate() {
203        let mut randomness = generate_randomness();
204
205        let test_context = b"Test";
206        let test_input = b"TestInput";
207        let evaluator_context = CoPRFEvaluatorContext::new(&mut randomness).unwrap();
208        let receiver_context = CoPRFReceiverContext::new(&mut randomness);
209
210        let blind_input = blind(
211            receiver_context.get_bpk(),
212            test_input,
213            test_context.to_vec(),
214            &mut randomness,
215        )
216        .unwrap();
217
218        let evaluation_key = derive_key(&evaluator_context, b"TestKey").unwrap();
219        let blind_result = blind_evaluate(
220            evaluation_key,
221            receiver_context.get_bpk(),
222            blind_input,
223            &mut randomness,
224        )
225        .unwrap();
226
227        let unblinded_result = finalize(&receiver_context, blind_result).unwrap();
228
229        let expected_result = evaluate(test_context, evaluation_key, test_input).unwrap();
230
231        debug_assert_eq!(unblinded_result, expected_result);
232    }
233
234    #[test]
235    fn blind_convergence() {
236        let mut randomness = generate_randomness();
237
238        let test_context = b"Test";
239        let test_input = b"TestInput";
240        let evaluator_context = CoPRFEvaluatorContext::new(&mut randomness).unwrap();
241        let receiver_context = CoPRFReceiverContext::new(&mut randomness);
242
243        let key_origin1 = derive_key(&evaluator_context, b"1").unwrap();
244        let key_origin2 = derive_key(&evaluator_context, b"2").unwrap();
245        let key_destination = derive_key(&evaluator_context, b"3").unwrap();
246
247        let y_under_destination = evaluate(test_context, key_destination, test_input).unwrap();
248        let y1 = evaluate(test_context, key_origin1, test_input).unwrap();
249        let y2 = evaluate(test_context, key_origin2, test_input).unwrap();
250
251        let blind1 =
252            prepare_blind_convert(receiver_context.get_bpk(), y1, &mut randomness).unwrap();
253        let blind2 =
254            prepare_blind_convert(receiver_context.get_bpk(), y2, &mut randomness).unwrap();
255
256        let blind_result_1 = blind_convert(
257            receiver_context.get_bpk(),
258            key_origin1,
259            key_destination,
260            blind1,
261            &mut randomness,
262        )
263        .unwrap();
264
265        let blind_result_2 = blind_convert(
266            receiver_context.get_bpk(),
267            key_origin2,
268            key_destination,
269            blind2,
270            &mut randomness,
271        )
272        .unwrap();
273
274        let res1 = finalize(&receiver_context, blind_result_1).unwrap();
275        let res2 = finalize(&receiver_context, blind_result_2).unwrap();
276
277        debug_assert_eq!(res1, res2);
278        debug_assert_eq!(res1, y_under_destination);
279    }
280    #[test]
281    fn test_blind_conversion() {
282        let mut randomness = generate_randomness();
283
284        let test_context = b"Test";
285        let test_input = b"TestInput";
286        let evaluator_context = CoPRFEvaluatorContext::new(&mut randomness).unwrap();
287        let receiver_context = CoPRFReceiverContext::new(&mut randomness);
288
289        let blind_input = blind(
290            receiver_context.get_bpk(),
291            test_input,
292            test_context.to_vec(),
293            &mut randomness,
294        )
295        .unwrap();
296
297        let key_eval = derive_key(&evaluator_context, b"TestKey").unwrap();
298        let key_destination = derive_key(&evaluator_context, b"DestinationKey").unwrap();
299
300        let blind_result = blind_evaluate(
301            key_eval,
302            receiver_context.get_bpk(),
303            blind_input,
304            &mut randomness,
305        )
306        .unwrap();
307
308        let expected_result = evaluate(test_context, key_destination, test_input).unwrap();
309
310        // converting the blinded result directly
311        let blind_converted_result = blind_convert(
312            receiver_context.get_bpk(),
313            key_eval,
314            key_destination,
315            blind_result,
316            &mut randomness,
317        )
318        .unwrap();
319
320        let unblinded_converted_result =
321            finalize(&receiver_context, blind_converted_result).unwrap();
322        debug_assert_eq!(expected_result, unblinded_converted_result);
323
324        // converting after unblinding and re-blinding
325        let unblinded_intermediate_result = finalize(&receiver_context, blind_result).unwrap();
326
327        let prepped_input = prepare_blind_convert(
328            receiver_context.get_bpk(),
329            unblinded_intermediate_result,
330            &mut randomness,
331        )
332        .unwrap();
333
334        let blind_converted_result = blind_convert(
335            receiver_context.get_bpk(),
336            key_eval,
337            key_destination,
338            prepped_input,
339            &mut randomness,
340        )
341        .unwrap();
342
343        let unblinded_converted_result =
344            finalize(&receiver_context, blind_converted_result).unwrap();
345        debug_assert_eq!(expected_result, unblinded_converted_result);
346    }
347}