concrete_integer/server_key/radix/
bitwise_op.rs

1use crate::ciphertext::RadixCiphertext;
2use crate::ServerKey;
3use concrete_shortint::CheckError;
4use concrete_shortint::CheckError::CarryFull;
5
6impl ServerKey {
7    /// Computes homomorphically bitand between two ciphertexts encrypting integer values.
8    ///
9    /// This function computes the operation without checking if it exceeds the capacity of the
10    /// ciphertext.
11    ///
12    /// The result is returned as a new ciphertext.
13    ///
14    /// # Example
15    ///
16    /// ```rust
17    /// use concrete_integer::gen_keys_radix;
18    /// use concrete_shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
19    ///
20    /// // We have 4 * 2 = 8 bits of message
21    /// let size = 4;
22    /// let (cks, sks) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, size);
23    ///
24    /// let msg1 = 201;
25    /// let msg2 = 1;
26    ///
27    /// let ct1 = cks.encrypt(msg1);
28    /// let ct2 = cks.encrypt(msg2);
29    ///
30    /// // Compute homomorphically a bitwise and:
31    /// let ct_res = sks.unchecked_bitand(&ct1, &ct2);
32    ///
33    /// // Decrypt:
34    /// let dec = cks.decrypt(&ct_res);
35    /// assert_eq!(dec, msg1 & msg2);
36    /// ```
37    pub fn unchecked_bitand(
38        &self,
39        ct_left: &RadixCiphertext,
40        ct_right: &RadixCiphertext,
41    ) -> RadixCiphertext {
42        let mut result = ct_left.clone();
43        self.unchecked_bitand_assign(&mut result, ct_right);
44        result
45    }
46
47    pub fn unchecked_bitand_assign(
48        &self,
49        ct_left: &mut RadixCiphertext,
50        ct_right: &RadixCiphertext,
51    ) {
52        for (ct_left_i, ct_right_i) in ct_left.blocks.iter_mut().zip(ct_right.blocks.iter()) {
53            self.key.unchecked_bitand_assign(ct_left_i, ct_right_i);
54        }
55    }
56
57    /// Verifies if a bivariate functional pbs can be applied on ct_left and ct_right.
58    ///
59    /// # Example
60    ///
61    ///```rust
62    /// use concrete_integer::gen_keys_radix;
63    /// use concrete_shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
64    ///
65    /// let size = 4;
66    ///
67    /// // Generate the client key and the server key:
68    /// let (cks, sks) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, size);
69    ///
70    /// let msg1 = 46;
71    /// let msg2 = 87;
72    ///
73    /// let ct1 = cks.encrypt(msg1);
74    /// let ct2 = cks.encrypt(msg2);
75    ///
76    /// let res = sks.is_functional_bivariate_pbs_possible(&ct1, &ct2);
77    ///
78    /// assert_eq!(true, res);
79    /// ```
80    pub fn is_functional_bivariate_pbs_possible(
81        &self,
82        ct_left: &RadixCiphertext,
83        ct_right: &RadixCiphertext,
84    ) -> bool {
85        for (ct_left_i, ct_right_i) in ct_left.blocks.iter().zip(ct_right.blocks.iter()) {
86            if !self
87                .key
88                .is_functional_bivariate_pbs_possible(ct_left_i, ct_right_i)
89            {
90                return false;
91            }
92        }
93        true
94    }
95
96    /// Computes homomorphically a bitand between two ciphertexts encrypting integer values.
97    ///
98    /// If the operation can be performed, the result is returned in a new ciphertext.
99    /// Otherwise [CheckError::CarryFull] is returned.
100    ///
101    /// # Example
102    ///
103    /// ```rust
104    /// use concrete_integer::gen_keys_radix;
105    /// use concrete_shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
106    ///
107    /// let size = 4;
108    ///
109    /// // Generate the client key and the server key:
110    /// let (cks, sks) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, size);
111    ///
112    /// let msg1 = 41;
113    /// let msg2 = 101;
114    ///
115    /// let ct1 = cks.encrypt(msg1);
116    /// let ct2 = cks.encrypt(msg2);
117    ///
118    /// let ct_res = sks.checked_bitand(&ct1, &ct2);
119    ///
120    /// match ct_res {
121    ///     Err(x) => panic!("{:?}", x),
122    ///     Ok(y) => {
123    ///         let clear = cks.decrypt(&y);
124    ///         assert_eq!(msg1 & msg2, clear);
125    ///     }
126    /// }
127    /// ```
128    pub fn checked_bitand(
129        &self,
130        ct_left: &RadixCiphertext,
131        ct_right: &RadixCiphertext,
132    ) -> Result<RadixCiphertext, CheckError> {
133        if self.is_functional_bivariate_pbs_possible(ct_left, ct_right) {
134            Ok(self.unchecked_bitand(ct_left, ct_right))
135        } else {
136            Err(CarryFull)
137        }
138    }
139
140    /// Computes homomorphically a bitand between two ciphertexts encrypting integer values.
141    ///
142    /// If the operation can be performed, the result is stored in the `ct_left` ciphertext.
143    /// Otherwise [CheckError::CarryFull] is returned, and `ct_left` is not modified.
144    ///
145    /// # Example
146    ///
147    /// ```rust
148    /// use concrete_integer::gen_keys_radix;
149    /// use concrete_shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
150    ///
151    /// let size = 4;
152    ///
153    /// // Generate the client key and the server key:
154    /// let (cks, sks) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, size);
155    ///
156    /// let msg1 = 41;
157    /// let msg2 = 101;
158    ///
159    /// let mut ct1 = cks.encrypt(msg1);
160    /// let ct2 = cks.encrypt(msg2);
161    ///
162    /// let res = sks.checked_bitand_assign(&mut ct1, &ct2);
163    ///
164    /// assert!(res.is_ok());
165    ///
166    /// let clear = cks.decrypt(&ct1);
167    /// assert_eq!(msg1 & msg2, clear);
168    /// ```
169    pub fn checked_bitand_assign(
170        &self,
171        ct_left: &mut RadixCiphertext,
172        ct_right: &RadixCiphertext,
173    ) -> Result<(), CheckError> {
174        if self.is_functional_bivariate_pbs_possible(ct_left, ct_right) {
175            self.unchecked_bitand_assign(ct_left, ct_right);
176            Ok(())
177        } else {
178            Err(CarryFull)
179        }
180    }
181
182    /// Computes homomorphically a bitand between two ciphertexts encrypting integer values.
183    ///
184    /// # Example
185    ///
186    /// ```rust
187    /// use concrete_integer::gen_keys_radix;
188    /// use concrete_shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
189    ///
190    /// let size = 4;
191    ///
192    /// // Generate the client key and the server key:
193    /// let (cks, sks) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, size);
194    ///
195    /// let msg1 = 14;
196    /// let msg2 = 97;
197    ///
198    /// let mut ct1 = cks.encrypt(msg1);
199    /// let mut ct2 = cks.encrypt(msg2);
200    ///
201    /// let ct_res = sks.smart_bitand(&mut ct1, &mut ct2);
202    ///
203    /// // Decrypt:
204    /// let dec_result = cks.decrypt(&ct_res);
205    /// assert_eq!(dec_result, msg1 & msg2);
206    /// ```
207    pub fn smart_bitand(
208        &self,
209        ct_left: &mut RadixCiphertext,
210        ct_right: &mut RadixCiphertext,
211    ) -> RadixCiphertext {
212        if !self.is_functional_bivariate_pbs_possible(ct_left, ct_right) {
213            self.full_propagate(ct_left);
214            self.full_propagate(ct_right);
215        }
216        self.unchecked_bitand(ct_left, ct_right)
217    }
218
219    pub fn smart_bitand_assign(
220        &self,
221        ct_left: &mut RadixCiphertext,
222        ct_right: &mut RadixCiphertext,
223    ) {
224        if !self.is_functional_bivariate_pbs_possible(ct_left, ct_right) {
225            self.full_propagate(ct_left);
226            self.full_propagate(ct_right);
227        }
228        self.unchecked_bitand_assign(ct_left, ct_right);
229    }
230
231    /// Computes homomorphically bitor between two ciphertexts encrypting integer values.
232    ///
233    /// This function computes the operation without checking if it exceeds the capacity of the
234    /// ciphertext.
235    ///
236    /// The result is returned as a new ciphertext.
237    ///
238    /// # Example
239    ///
240    /// ```rust
241    /// use concrete_integer::gen_keys_radix;
242    /// use concrete_shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
243    ///
244    /// // We have 4 * 2 = 8 bits of message
245    /// let size = 4;
246    /// let (cks, sks) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, size);
247    ///
248    /// let msg1 = 200;
249    /// let msg2 = 1;
250    ///
251    /// let ct1 = cks.encrypt(msg1);
252    /// let ct2 = cks.encrypt(msg2);
253    ///
254    /// // Compute homomorphically a bitwise or:
255    /// let ct_res = sks.unchecked_bitor(&ct1, &ct2);
256    ///
257    /// // Decrypt:
258    /// let dec = cks.decrypt(&ct_res);
259    /// assert_eq!(dec, msg1 | msg2);
260    /// ```
261    pub fn unchecked_bitor(
262        &self,
263        ct_left: &RadixCiphertext,
264        ct_right: &RadixCiphertext,
265    ) -> RadixCiphertext {
266        let mut result = ct_left.clone();
267        self.unchecked_bitor_assign(&mut result, ct_right);
268        result
269    }
270
271    pub fn unchecked_bitor_assign(
272        &self,
273        ct_left: &mut RadixCiphertext,
274        ct_right: &RadixCiphertext,
275    ) {
276        for (ct_left_i, ct_right_i) in ct_left.blocks.iter_mut().zip(ct_right.blocks.iter()) {
277            self.key.unchecked_bitor_assign(ct_left_i, ct_right_i);
278        }
279    }
280
281    /// Computes homomorphically a bitor between two ciphertexts encrypting integer values.
282    ///
283    /// If the operation can be performed, the result is returned in a new ciphertext.
284    /// Otherwise [CheckError::CarryFull] is returned.
285    ///
286    /// # Example
287    ///
288    /// ```rust
289    /// use concrete_integer::gen_keys_radix;
290    /// use concrete_shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
291    ///
292    /// let size = 4;
293    ///
294    /// // Generate the client key and the server key:
295    /// let (cks, sks) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, size);
296    ///
297    /// let msg1 = 41;
298    /// let msg2 = 101;
299    ///
300    /// let ct1 = cks.encrypt(msg1);
301    /// let ct2 = cks.encrypt(msg2);
302    ///
303    /// // Compute homomorphically an addition:
304    /// let ct_res = sks.checked_bitor(&ct1, &ct2);
305    ///
306    /// match ct_res {
307    ///     Err(x) => panic!("{:?}", x),
308    ///     Ok(y) => {
309    ///         let clear = cks.decrypt(&y);
310    ///         assert_eq!(msg1 | msg2, clear);
311    ///     }
312    /// }
313    /// ```
314    pub fn checked_bitor(
315        &self,
316        ct_left: &RadixCiphertext,
317        ct_right: &RadixCiphertext,
318    ) -> Result<RadixCiphertext, CheckError> {
319        if self.is_functional_bivariate_pbs_possible(ct_left, ct_right) {
320            Ok(self.unchecked_bitor(ct_left, ct_right))
321        } else {
322            Err(CarryFull)
323        }
324    }
325
326    /// Computes homomorphically a bitand between two ciphertexts encrypting integer values.
327    ///
328    /// If the operation can be performed, the result is stored in the `ct_left` ciphertext.
329    /// Otherwise [CheckError::CarryFull] is returned, and `ct_left` is not modified.
330    ///
331    /// # Example
332    ///
333    /// ```rust
334    /// use concrete_integer::gen_keys_radix;
335    /// use concrete_shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
336    ///
337    /// let size = 4;
338    ///
339    /// // Generate the client key and the server key:
340    /// let (cks, sks) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, size);
341    ///
342    /// let msg1 = 41;
343    /// let msg2 = 101;
344    ///
345    /// let mut ct1 = cks.encrypt(msg1);
346    /// let ct2 = cks.encrypt(msg2);
347    ///
348    /// // Compute homomorphically an addition:
349    /// let res = sks.checked_bitor_assign(&mut ct1, &ct2);
350    ///
351    /// assert!(res.is_ok());
352    ///
353    /// let clear = cks.decrypt(&ct1);
354    /// assert_eq!(msg1 | msg2, clear);
355    /// ```
356    pub fn checked_bitor_assign(
357        &self,
358        ct_left: &mut RadixCiphertext,
359        ct_right: &RadixCiphertext,
360    ) -> Result<(), CheckError> {
361        if self.is_functional_bivariate_pbs_possible(ct_left, ct_right) {
362            self.unchecked_bitor_assign(ct_left, ct_right);
363            Ok(())
364        } else {
365            Err(CarryFull)
366        }
367    }
368
369    /// Computes homomorphically a bitor between two ciphertexts encrypting integer values.
370    ///
371    /// # Example
372    ///
373    /// ```rust
374    /// use concrete_integer::gen_keys_radix;
375    /// use concrete_shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
376    ///
377    /// let size = 4;
378    ///
379    /// // Generate the client key and the server key:
380    /// let (cks, sks) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, size);
381    ///
382    /// let msg1 = 14;
383    /// let msg2 = 97;
384    ///
385    /// let mut ct1 = cks.encrypt(msg1);
386    /// let mut ct2 = cks.encrypt(msg2);
387    ///
388    /// let ct_res = sks.smart_bitor(&mut ct1, &mut ct2);
389    ///
390    /// // Decrypt:
391    /// let dec_result = cks.decrypt(&ct_res);
392    /// assert_eq!(dec_result, msg1 | msg2);
393    /// ```
394    pub fn smart_bitor(
395        &self,
396        ct_left: &mut RadixCiphertext,
397        ct_right: &mut RadixCiphertext,
398    ) -> RadixCiphertext {
399        if !self.is_functional_bivariate_pbs_possible(ct_left, ct_right) {
400            self.full_propagate(ct_left);
401            self.full_propagate(ct_right);
402        }
403        self.unchecked_bitor(ct_left, ct_right)
404    }
405
406    pub fn smart_bitor_assign(
407        &self,
408        ct_left: &mut RadixCiphertext,
409        ct_right: &mut RadixCiphertext,
410    ) {
411        if !self.is_functional_bivariate_pbs_possible(ct_left, ct_right) {
412            self.full_propagate(ct_left);
413            self.full_propagate(ct_right);
414        }
415        self.unchecked_bitor_assign(ct_left, ct_right);
416    }
417
418    /// Computes homomorphically bitxor between two ciphertexts encrypting integer values.
419    ///
420    /// This function computes the operation without checking if it exceeds the capacity of the
421    /// ciphertext.
422    ///
423    /// The result is returned as a new ciphertext.
424    ///
425    /// # Example
426    ///
427    /// ```rust
428    /// use concrete_integer::gen_keys_radix;
429    /// use concrete_shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
430    ///
431    /// // We have 4 * 2 = 8 bits of message
432    /// let size = 4;
433    /// let (cks, sks) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, size);
434    ///
435    /// let msg1 = 49;
436    /// let msg2 = 64;
437    ///
438    /// let ct1 = cks.encrypt(msg1);
439    /// let ct2 = cks.encrypt(msg2);
440    ///
441    /// // Compute homomorphically a bitwise xor:
442    /// let ct_res = sks.unchecked_bitxor(&ct1, &ct2);
443    ///
444    /// // Decrypt:
445    /// let dec = cks.decrypt(&ct_res);
446    /// assert_eq!(msg1 ^ msg2, dec);
447    /// ```
448    pub fn unchecked_bitxor(
449        &self,
450        ct_left: &RadixCiphertext,
451        ct_right: &RadixCiphertext,
452    ) -> RadixCiphertext {
453        let mut result = ct_left.clone();
454        self.unchecked_bitxor_assign(&mut result, ct_right);
455        result
456    }
457
458    pub fn unchecked_bitxor_assign(
459        &self,
460        ct_left: &mut RadixCiphertext,
461        ct_right: &RadixCiphertext,
462    ) {
463        for (ct_left_i, ct_right_i) in ct_left.blocks.iter_mut().zip(ct_right.blocks.iter()) {
464            self.key.unchecked_bitxor_assign(ct_left_i, ct_right_i);
465        }
466    }
467
468    /// Computes homomorphically a bitxor between two ciphertexts encrypting integer values.
469    ///
470    /// If the operation can be performed, the result is returned in a new ciphertext.
471    /// Otherwise [CheckError::CarryFull] is returned.
472    ///
473    /// # Example
474    ///
475    /// ```rust
476    /// use concrete_integer::gen_keys_radix;
477    /// use concrete_shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
478    ///
479    /// let size = 4;
480    ///
481    /// // Generate the client key and the server key:
482    /// let (cks, sks) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, size);
483    ///
484    /// let msg1 = 41;
485    /// let msg2 = 101;
486    ///
487    /// let ct1 = cks.encrypt(msg1);
488    /// let ct2 = cks.encrypt(msg2);
489    ///
490    /// // Compute homomorphically an addition:
491    /// let ct_res = sks.checked_bitxor(&ct1, &ct2);
492    ///
493    /// match ct_res {
494    ///     Err(x) => panic!("{:?}", x),
495    ///     Ok(y) => {
496    ///         let clear = cks.decrypt(&y);
497    ///         assert_eq!(msg1 ^ msg2, clear);
498    ///     }
499    /// }
500    /// ```
501    pub fn checked_bitxor(
502        &self,
503        ct_left: &RadixCiphertext,
504        ct_right: &RadixCiphertext,
505    ) -> Result<RadixCiphertext, CheckError> {
506        if self.is_functional_bivariate_pbs_possible(ct_left, ct_right) {
507            Ok(self.unchecked_bitxor(ct_left, ct_right))
508        } else {
509            Err(CarryFull)
510        }
511    }
512
513    /// Computes homomorphically a bitxor between two ciphertexts encrypting integer values.
514    ///
515    /// If the operation can be performed, the result is stored in the `ct_left` ciphertext.
516    /// Otherwise [CheckError::CarryFull] is returned, and `ct_left` is not modified.
517    ///
518    /// # Example
519    ///
520    /// ```rust
521    /// use concrete_integer::gen_keys_radix;
522    /// use concrete_shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
523    ///
524    /// let size = 4;
525    ///
526    /// // Generate the client key and the server key:
527    /// let (cks, sks) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, size);
528    ///
529    /// let msg1 = 41;
530    /// let msg2 = 101;
531    ///
532    /// let mut ct1 = cks.encrypt(msg1);
533    /// let ct2 = cks.encrypt(msg2);
534    ///
535    /// // Compute homomorphically an addition:
536    /// let res = sks.checked_bitxor_assign(&mut ct1, &ct2);
537    ///
538    /// assert!(res.is_ok());
539    ///
540    /// let clear = cks.decrypt(&ct1);
541    /// assert_eq!(msg1 ^ msg2, clear);
542    /// ```
543    pub fn checked_bitxor_assign(
544        &self,
545        ct_left: &mut RadixCiphertext,
546        ct_right: &RadixCiphertext,
547    ) -> Result<(), CheckError> {
548        if self.is_functional_bivariate_pbs_possible(ct_left, ct_right) {
549            self.unchecked_bitxor_assign(ct_left, ct_right);
550            Ok(())
551        } else {
552            Err(CarryFull)
553        }
554    }
555
556    /// Computes homomorphically a bitxor between two ciphertexts encrypting integer values.
557    ///
558    /// # Example
559    ///
560    /// ```rust
561    /// use concrete_integer::gen_keys_radix;
562    /// use concrete_shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
563    ///
564    /// let size = 4;
565    ///
566    /// // Generate the client key and the server key:
567    /// let (cks, sks) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, size);
568    ///
569    /// let msg1 = 14;
570    /// let msg2 = 97;
571    ///
572    /// let mut ct1 = cks.encrypt(msg1);
573    /// let mut ct2 = cks.encrypt(msg2);
574    ///
575    /// let ct_res = sks.smart_bitxor(&mut ct1, &mut ct2);
576    ///
577    /// // Decrypt:
578    /// let dec_result = cks.decrypt(&ct_res);
579    /// assert_eq!(dec_result, msg1 ^ msg2);
580    /// ```
581    pub fn smart_bitxor(
582        &self,
583        ct_left: &mut RadixCiphertext,
584        ct_right: &mut RadixCiphertext,
585    ) -> RadixCiphertext {
586        if !self.is_functional_bivariate_pbs_possible(ct_left, ct_right) {
587            self.full_propagate(ct_left);
588            self.full_propagate(ct_right);
589        }
590        self.unchecked_bitxor(ct_left, ct_right)
591    }
592
593    pub fn smart_bitxor_assign(
594        &self,
595        ct_left: &mut RadixCiphertext,
596        ct_right: &mut RadixCiphertext,
597    ) {
598        if !self.is_functional_bivariate_pbs_possible(ct_left, ct_right) {
599            self.full_propagate(ct_left);
600            self.full_propagate(ct_right);
601        }
602        self.unchecked_bitxor_assign(ct_left, ct_right);
603    }
604}