concrete_shortint/server_key/
bitwise_op.rs

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