1use tfhe_versionable::Versionize;
2
3use super::inner::RadixCiphertext;
4use crate::backward_compatibility::integers::FheUintVersions;
5use crate::conformance::ParameterSetConformant;
6use crate::core_crypto::prelude::{CastFrom, UnsignedInteger, UnsignedNumeric};
7#[cfg(feature = "gpu")]
8use crate::high_level_api::global_state::with_thread_local_cuda_streams;
9use crate::high_level_api::integers::signed::{FheInt, FheIntId};
10use crate::high_level_api::integers::IntegerId;
11use crate::high_level_api::keys::InternalServerKey;
12use crate::high_level_api::traits::{FheWait, Tagged};
13use crate::high_level_api::{global_state, Device};
14use crate::integer::block_decomposition::{DecomposableInto, RecomposableFrom};
15#[cfg(feature = "gpu")]
16use crate::integer::gpu::ciphertext::CudaIntegerRadixCiphertext;
17use crate::integer::parameters::RadixCiphertextConformanceParams;
18use crate::integer::server_key::MatchValues;
19use crate::named::Named;
20use crate::prelude::CastInto;
21use crate::shortint::ciphertext::NotTrivialCiphertextError;
22use crate::shortint::PBSParameters;
23#[cfg(feature = "gpu")]
24use crate::GpuIndex;
25use crate::{FheBool, ServerKey, Tag};
26use std::marker::PhantomData;
27
28#[cfg(feature = "hpu")]
29use crate::high_level_api::traits::{FheHpu, HpuHandle};
30#[cfg(feature = "hpu")]
31use tfhe_hpu_backend::prelude::*;
32
33#[derive(Debug)]
34pub enum GenericIntegerBlockError {
35 NumberOfBlocks(usize, usize),
36 CarryModulus(crate::shortint::CarryModulus, crate::shortint::CarryModulus),
37 MessageModulus(
38 crate::shortint::MessageModulus,
39 crate::shortint::MessageModulus,
40 ),
41}
42
43impl std::fmt::Display for GenericIntegerBlockError {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
45 match self {
46 Self::NumberOfBlocks(correct, incorrect) => write!(
47 f,
48 "Wrong number of blocks for creating
49 a GenericInteger: should have been {correct}, but
50 was {incorrect} instead"
51 ),
52 Self::CarryModulus(correct, incorrect) => write!(
53 f,
54 "Wrong carry modulus for creating
55 a GenericInteger: should have been {correct:?}, but
56 was {incorrect:?} instead"
57 ),
58 Self::MessageModulus(correct, incorrect) => write!(
59 f,
60 "Wrong message modulus for creating
61 a GenericInteger: should have been {correct:?}, but
62 was {incorrect:?} instead"
63 ),
64 }
65 }
66}
67
68pub trait FheUintId: IntegerId {}
69
70#[derive(Clone, serde::Deserialize, serde::Serialize, Versionize)]
86#[versionize(FheUintVersions)]
87pub struct FheUint<Id: FheUintId> {
88 pub(in crate::high_level_api) ciphertext: RadixCiphertext,
89 pub(in crate::high_level_api) id: Id,
90 pub(crate) tag: Tag,
91}
92
93#[derive(Copy, Clone)]
94pub struct FheUintConformanceParams<Id: FheUintId> {
95 pub(crate) params: RadixCiphertextConformanceParams,
96 pub(crate) id: PhantomData<Id>,
97}
98
99impl<Id: FheUintId, P: Into<PBSParameters>> From<P> for FheUintConformanceParams<Id> {
100 fn from(params: P) -> Self {
101 let params = params.into();
102 Self {
103 params: RadixCiphertextConformanceParams {
104 shortint_params: params.to_shortint_conformance_param(),
105 num_blocks_per_integer: Id::num_blocks(params.message_modulus()),
106 },
107 id: PhantomData,
108 }
109 }
110}
111
112impl<Id: FheUintId> From<&ServerKey> for FheUintConformanceParams<Id> {
113 fn from(sks: &ServerKey) -> Self {
114 Self {
115 params: RadixCiphertextConformanceParams {
116 shortint_params: sks.key.pbs_key().key.conformance_params(),
117 num_blocks_per_integer: Id::num_blocks(sks.key.pbs_key().message_modulus()),
118 },
119 id: PhantomData,
120 }
121 }
122}
123
124impl<Id: FheUintId> ParameterSetConformant for FheUint<Id> {
125 type ParameterSet = FheUintConformanceParams<Id>;
126
127 fn is_conformant(&self, params: &FheUintConformanceParams<Id>) -> bool {
128 let Self {
129 ciphertext,
130 id: _,
131 tag: _,
132 } = self;
133
134 ciphertext.on_cpu().is_conformant(¶ms.params)
135 }
136}
137
138impl<Id: FheUintId> Named for FheUint<Id> {
139 const NAME: &'static str = "high_level_api::FheUint";
140}
141
142impl<Id> Tagged for FheUint<Id>
143where
144 Id: FheUintId,
145{
146 fn tag(&self) -> &Tag {
147 &self.tag
148 }
149
150 fn tag_mut(&mut self) -> &mut Tag {
151 &mut self.tag
152 }
153}
154
155impl<Id> FheWait for FheUint<Id>
156where
157 Id: FheUintId,
158{
159 fn wait(&self) {
160 self.ciphertext.wait()
161 }
162}
163
164#[cfg(feature = "hpu")]
165impl<Id> FheHpu for FheUint<Id>
166where
167 Id: FheUintId,
168{
169 fn iop_exec(iop: &hpu_asm::AsmIOpcode, src: HpuHandle<&Self>) -> HpuHandle<Self> {
170 use crate::integer::hpu::ciphertext::HpuRadixCiphertext;
171 global_state::with_thread_local_hpu_device(|device| {
172 let mut srcs = Vec::new();
173 for n in src.native.iter() {
174 srcs.push(n.ciphertext.on_hpu(device).clone());
175 }
176 for b in src.boolean.iter() {
177 srcs.push(b.ciphertext.on_hpu(device).clone());
178 }
179
180 let (opcode, proto) = {
181 (
182 iop.opcode(),
183 &iop.format().expect("Unspecified IOP format").proto,
184 )
185 };
186 let hpu_res = HpuRadixCiphertext::exec(proto, opcode, &srcs, &src.imm);
188 HpuHandle {
189 native: hpu_res
190 .iter()
191 .filter(|x| !x.0.is_boolean())
192 .map(|x| Self::new(x.clone(), device.tag.clone()))
193 .collect::<Vec<_>>(),
194 boolean: hpu_res
195 .iter()
196 .filter(|x| x.0.is_boolean())
197 .map(|x| FheBool::new(x.clone(), device.tag.clone()))
198 .collect::<Vec<_>>(),
199 imm: Vec::new(),
200 }
201 })
202 }
203}
204
205impl<Id> FheUint<Id>
206where
207 Id: FheUintId,
208{
209 pub(in crate::high_level_api) fn new<T>(ciphertext: T, tag: Tag) -> Self
210 where
211 T: Into<RadixCiphertext>,
212 {
213 Self {
214 ciphertext: ciphertext.into(),
215 id: Id::default(),
216 tag,
217 }
218 }
219
220 pub fn into_raw_parts(self) -> (crate::integer::RadixCiphertext, Id, Tag) {
221 let Self {
222 ciphertext,
223 id,
224 tag,
225 } = self;
226
227 let ciphertext = ciphertext.into_cpu();
228
229 (ciphertext, id, tag)
230 }
231
232 pub fn from_raw_parts(ciphertext: crate::integer::RadixCiphertext, id: Id, tag: Tag) -> Self {
233 Self {
234 ciphertext: RadixCiphertext::Cpu(ciphertext),
235 id,
236 tag,
237 }
238 }
239
240 pub fn num_bits() -> usize {
241 Id::num_bits()
242 }
243
244 pub(in crate::high_level_api) fn move_to_device_of_server_key_if_set(&mut self) {
245 self.ciphertext.move_to_device_of_server_key_if_set();
246 }
247
248 pub fn current_device(&self) -> Device {
250 self.ciphertext.current_device()
251 }
252
253 pub fn move_to_device(&mut self, device: Device) {
257 self.ciphertext.move_to_device(device)
258 }
259
260 pub fn move_to_current_device(&mut self) {
266 self.ciphertext.move_to_device_of_server_key_if_set();
267 }
268
269 #[cfg(feature = "gpu")]
274 pub fn gpu_indexes(&self) -> &[GpuIndex] {
275 #[allow(clippy::match_wildcard_for_single_variants)]
276 match &self.ciphertext {
277 RadixCiphertext::Cuda(cuda_ct) => cuda_ct.gpu_indexes(),
278 _ => &[],
279 }
280 }
281 pub fn is_even(&self) -> FheBool {
299 global_state::with_internal_keys(|key| match key {
300 InternalServerKey::Cpu(cpu_key) => {
301 let result = cpu_key
302 .pbs_key()
303 .is_even_parallelized(&*self.ciphertext.on_cpu());
304 FheBool::new(result, cpu_key.tag.clone())
305 }
306 #[cfg(feature = "gpu")]
307 InternalServerKey::Cuda(cuda_key) => with_thread_local_cuda_streams(|streams| {
308 let result = cuda_key
309 .key
310 .key
311 .is_even(&*self.ciphertext.on_gpu(streams), streams);
312 FheBool::new(result, cuda_key.tag.clone())
313 }),
314 #[cfg(feature = "hpu")]
315 InternalServerKey::Hpu(_device) => {
316 panic!("Hpu does not support this operation yet.")
317 }
318 })
319 }
320
321 pub fn is_odd(&self) -> FheBool {
339 global_state::with_internal_keys(|key| match key {
340 InternalServerKey::Cpu(cpu_key) => {
341 let result = cpu_key
342 .pbs_key()
343 .is_odd_parallelized(&*self.ciphertext.on_cpu());
344 FheBool::new(result, cpu_key.tag.clone())
345 }
346 #[cfg(feature = "gpu")]
347 InternalServerKey::Cuda(cuda_key) => with_thread_local_cuda_streams(|streams| {
348 let result = cuda_key
349 .key
350 .key
351 .is_odd(&*self.ciphertext.on_gpu(streams), streams);
352 FheBool::new(result, cuda_key.tag.clone())
353 }),
354 #[cfg(feature = "hpu")]
355 InternalServerKey::Hpu(_device) => {
356 panic!("Hpu does not support this operation yet.")
357 }
358 })
359 }
360
361 pub fn try_decrypt_trivial<Clear>(&self) -> Result<Clear, NotTrivialCiphertextError>
393 where
394 Clear: UnsignedNumeric + RecomposableFrom<u64>,
395 {
396 self.ciphertext.on_cpu().decrypt_trivial()
397 }
398
399 pub fn is_trivial(&self) -> bool {
417 self.ciphertext.on_cpu().is_trivial()
418 }
419
420 pub fn sum<'a, C>(collection: C) -> Self
448 where
449 C: AsRef<[&'a Self]>,
450 {
451 collection.as_ref().iter().copied().sum()
452 }
453
454 pub fn leading_zeros(&self) -> super::FheUint32 {
472 global_state::with_internal_keys(|key| match key {
473 InternalServerKey::Cpu(cpu_key) => {
474 let result = cpu_key
475 .pbs_key()
476 .leading_zeros_parallelized(&*self.ciphertext.on_cpu());
477 let result = cpu_key.pbs_key().cast_to_unsigned(
478 result,
479 super::FheUint32Id::num_blocks(cpu_key.pbs_key().message_modulus()),
480 );
481 super::FheUint32::new(result, cpu_key.tag.clone())
482 }
483 #[cfg(feature = "gpu")]
484 InternalServerKey::Cuda(cuda_key) => with_thread_local_cuda_streams(|streams| {
485 let result = cuda_key
486 .key
487 .key
488 .leading_zeros(&*self.ciphertext.on_gpu(streams), streams);
489 let result = cuda_key.key.key.cast_to_unsigned(
490 result,
491 super::FheUint32Id::num_blocks(cuda_key.key.key.message_modulus),
492 streams,
493 );
494 super::FheUint32::new(result, cuda_key.tag.clone())
495 }),
496 #[cfg(feature = "hpu")]
497 InternalServerKey::Hpu(_device) => {
498 panic!("Hpu does not support this operation yet.")
499 }
500 })
501 }
502
503 pub fn leading_ones(&self) -> super::FheUint32 {
521 global_state::with_internal_keys(|key| match key {
522 InternalServerKey::Cpu(cpu_key) => {
523 let result = cpu_key
524 .pbs_key()
525 .leading_ones_parallelized(&*self.ciphertext.on_cpu());
526 let result = cpu_key.pbs_key().cast_to_unsigned(
527 result,
528 super::FheUint32Id::num_blocks(cpu_key.pbs_key().message_modulus()),
529 );
530 super::FheUint32::new(result, cpu_key.tag.clone())
531 }
532 #[cfg(feature = "gpu")]
533 InternalServerKey::Cuda(cuda_key) => with_thread_local_cuda_streams(|streams| {
534 let result = cuda_key
535 .key
536 .key
537 .leading_ones(&*self.ciphertext.on_gpu(streams), streams);
538 let result = cuda_key.key.key.cast_to_unsigned(
539 result,
540 super::FheUint32Id::num_blocks(cuda_key.key.key.message_modulus),
541 streams,
542 );
543 super::FheUint32::new(result, cuda_key.tag.clone())
544 }),
545 #[cfg(feature = "hpu")]
546 InternalServerKey::Hpu(_device) => {
547 panic!("Hpu does not support this operation yet.")
548 }
549 })
550 }
551
552 pub fn trailing_zeros(&self) -> super::FheUint32 {
570 global_state::with_internal_keys(|key| match key {
571 InternalServerKey::Cpu(cpu_key) => {
572 let result = cpu_key
573 .pbs_key()
574 .trailing_zeros_parallelized(&*self.ciphertext.on_cpu());
575 let result = cpu_key.pbs_key().cast_to_unsigned(
576 result,
577 super::FheUint32Id::num_blocks(cpu_key.pbs_key().message_modulus()),
578 );
579 super::FheUint32::new(result, cpu_key.tag.clone())
580 }
581 #[cfg(feature = "gpu")]
582 InternalServerKey::Cuda(cuda_key) => with_thread_local_cuda_streams(|streams| {
583 let result = cuda_key
584 .key
585 .key
586 .trailing_zeros(&*self.ciphertext.on_gpu(streams), streams);
587 let result = cuda_key.key.key.cast_to_unsigned(
588 result,
589 super::FheUint32Id::num_blocks(cuda_key.key.key.message_modulus),
590 streams,
591 );
592 super::FheUint32::new(result, cuda_key.tag.clone())
593 }),
594 #[cfg(feature = "hpu")]
595 InternalServerKey::Hpu(_device) => {
596 panic!("Hpu does not support this operation yet.")
597 }
598 })
599 }
600
601 pub fn trailing_ones(&self) -> super::FheUint32 {
619 global_state::with_internal_keys(|key| match key {
620 InternalServerKey::Cpu(cpu_key) => {
621 let result = cpu_key
622 .pbs_key()
623 .trailing_ones_parallelized(&*self.ciphertext.on_cpu());
624 let result = cpu_key.pbs_key().cast_to_unsigned(
625 result,
626 super::FheUint32Id::num_blocks(cpu_key.pbs_key().message_modulus()),
627 );
628 super::FheUint32::new(result, cpu_key.tag.clone())
629 }
630 #[cfg(feature = "gpu")]
631 InternalServerKey::Cuda(cuda_key) => with_thread_local_cuda_streams(|streams| {
632 let result = cuda_key
633 .key
634 .key
635 .trailing_ones(&*self.ciphertext.on_gpu(streams), streams);
636 let result = cuda_key.key.key.cast_to_unsigned(
637 result,
638 super::FheUint32Id::num_blocks(cuda_key.key.key.message_modulus),
639 streams,
640 );
641 super::FheUint32::new(result, cuda_key.tag.clone())
642 }),
643 #[cfg(feature = "hpu")]
644 InternalServerKey::Hpu(_device) => {
645 panic!("Hpu does not support this operation yet.")
646 }
647 })
648 }
649
650 pub fn count_ones(&self) -> super::FheUint32 {
669 global_state::with_internal_keys(|key| match key {
670 InternalServerKey::Cpu(cpu_key) => {
671 let result = cpu_key
672 .pbs_key()
673 .count_ones_parallelized(&*self.ciphertext.on_cpu());
674 let result = cpu_key.pbs_key().cast_to_unsigned(
675 result,
676 super::FheUint32Id::num_blocks(cpu_key.pbs_key().message_modulus()),
677 );
678 super::FheUint32::new(result, cpu_key.tag.clone())
679 }
680 #[cfg(feature = "gpu")]
681 InternalServerKey::Cuda(_) => {
682 panic!("Cuda devices do not support count_ones yet");
683 }
684 #[cfg(feature = "hpu")]
685 InternalServerKey::Hpu(_device) => {
686 panic!("Hpu does not support this operation yet.")
687 }
688 })
689 }
690
691 pub fn count_zeros(&self) -> super::FheUint32 {
710 global_state::with_internal_keys(|key| match key {
711 InternalServerKey::Cpu(cpu_key) => {
712 let result = cpu_key
713 .pbs_key()
714 .count_zeros_parallelized(&*self.ciphertext.on_cpu());
715 let result = cpu_key.pbs_key().cast_to_unsigned(
716 result,
717 super::FheUint32Id::num_blocks(cpu_key.pbs_key().message_modulus()),
718 );
719 super::FheUint32::new(result, cpu_key.tag.clone())
720 }
721 #[cfg(feature = "gpu")]
722 InternalServerKey::Cuda(_) => {
723 panic!("Cuda devices do not support count_zeros yet");
724 }
725 #[cfg(feature = "hpu")]
726 InternalServerKey::Hpu(_device) => {
727 panic!("Hpu does not support this operation yet.")
728 }
729 })
730 }
731
732 pub fn ilog2(&self) -> super::FheUint32 {
752 global_state::with_internal_keys(|key| match key {
753 InternalServerKey::Cpu(cpu_key) => {
754 let result = cpu_key
755 .pbs_key()
756 .ilog2_parallelized(&*self.ciphertext.on_cpu());
757 let result = cpu_key.pbs_key().cast_to_unsigned(
758 result,
759 super::FheUint32Id::num_blocks(cpu_key.pbs_key().message_modulus()),
760 );
761 super::FheUint32::new(result, cpu_key.tag.clone())
762 }
763 #[cfg(feature = "gpu")]
764 InternalServerKey::Cuda(cuda_key) => with_thread_local_cuda_streams(|streams| {
765 let result = cuda_key
766 .key
767 .key
768 .ilog2(&*self.ciphertext.on_gpu(streams), streams);
769 let result = cuda_key.key.key.cast_to_unsigned(
770 result,
771 super::FheUint32Id::num_blocks(cuda_key.key.key.message_modulus),
772 streams,
773 );
774 super::FheUint32::new(result, cuda_key.tag.clone())
775 }),
776 #[cfg(feature = "hpu")]
777 InternalServerKey::Hpu(_device) => {
778 panic!("Hpu does not support this operation yet.")
779 }
780 })
781 }
782
783 pub fn checked_ilog2(&self) -> (super::FheUint32, FheBool) {
807 global_state::with_internal_keys(|key| match key {
808 InternalServerKey::Cpu(cpu_key) => {
809 let (result, is_ok) = cpu_key
810 .pbs_key()
811 .checked_ilog2_parallelized(&*self.ciphertext.on_cpu());
812 let result = cpu_key.pbs_key().cast_to_unsigned(
813 result,
814 super::FheUint32Id::num_blocks(cpu_key.pbs_key().message_modulus()),
815 );
816 (
817 super::FheUint32::new(result, cpu_key.tag.clone()),
818 FheBool::new(is_ok, cpu_key.tag.clone()),
819 )
820 }
821 #[cfg(feature = "gpu")]
822 InternalServerKey::Cuda(cuda_key) => with_thread_local_cuda_streams(|streams| {
823 let (result, is_ok) = cuda_key
824 .key
825 .key
826 .checked_ilog2(&*self.ciphertext.on_gpu(streams), streams);
827 let result = cuda_key.key.key.cast_to_unsigned(
828 result,
829 super::FheUint32Id::num_blocks(cuda_key.key.key.message_modulus),
830 streams,
831 );
832 (
833 super::FheUint32::new(result, cuda_key.tag.clone()),
834 FheBool::new(is_ok, cuda_key.tag.clone()),
835 )
836 }),
837 #[cfg(feature = "hpu")]
838 InternalServerKey::Hpu(_device) => {
839 panic!("Hpu does not support this operation yet.")
840 }
841 })
842 }
843
844 pub fn match_value<Clear, OutId>(
882 &self,
883 matches: &MatchValues<Clear>,
884 ) -> crate::Result<(FheUint<OutId>, FheBool)>
885 where
886 Clear: UnsignedInteger + DecomposableInto<u64> + CastInto<usize>,
887 OutId: FheUintId,
888 {
889 global_state::with_internal_keys(|key| match key {
890 InternalServerKey::Cpu(cpu_key) => {
891 let (result, matched) = cpu_key
892 .pbs_key()
893 .match_value_parallelized(&self.ciphertext.on_cpu(), matches);
894 let target_num_blocks = OutId::num_blocks(cpu_key.message_modulus());
895 if target_num_blocks >= result.blocks.len() {
896 let result = cpu_key
897 .pbs_key()
898 .cast_to_unsigned(result, target_num_blocks);
899 Ok((
900 FheUint::new(result, cpu_key.tag.clone()),
901 FheBool::new(matched, cpu_key.tag.clone()),
902 ))
903 } else {
904 Err(crate::Error::new("Output type does not have enough bits to represent all possible output values".to_string()))
905 }
906 }
907 #[cfg(feature = "gpu")]
908 InternalServerKey::Cuda(cuda_key) => with_thread_local_cuda_streams(|streams| {
909 let (result, matched) = cuda_key.key.key.match_value(
910 &self.ciphertext.on_gpu(streams),
911 matches,
912 streams,
913 );
914 let target_num_blocks = OutId::num_blocks(cuda_key.key.key.message_modulus);
915 if target_num_blocks >= result.ciphertext.d_blocks.lwe_ciphertext_count().0 {
916 Ok((
917 FheUint::new(result, cuda_key.tag.clone()),
918 FheBool::new(matched, cuda_key.tag.clone()),
919 ))
920 } else {
921 Err(crate::Error::new("Output type does not have enough bits to represent all possible output values".to_string()))
922 }
923 }),
924 #[cfg(feature = "hpu")]
925 InternalServerKey::Hpu(_device) => {
926 panic!("Hpu does not support this operation yet.")
927 }
928 })
929 }
930
931 pub fn match_value_or<Clear, OutId>(
964 &self,
965 matches: &MatchValues<Clear>,
966 or_value: Clear,
967 ) -> crate::Result<FheUint<OutId>>
968 where
969 Clear: UnsignedInteger + DecomposableInto<u64> + CastInto<usize>,
970 OutId: FheUintId,
971 {
972 global_state::with_internal_keys(|key| match key {
973 InternalServerKey::Cpu(cpu_key) => {
974 let result = cpu_key.pbs_key().match_value_or_parallelized(
975 &self.ciphertext.on_cpu(),
976 matches,
977 or_value,
978 );
979 let target_num_blocks = OutId::num_blocks(cpu_key.message_modulus());
980 if target_num_blocks >= result.blocks.len() {
981 let result = cpu_key
982 .pbs_key()
983 .cast_to_unsigned(result, target_num_blocks);
984 Ok(FheUint::new(result, cpu_key.tag.clone()))
985 } else {
986 Err(crate::Error::new("Output type does not have enough bits to represent all possible output values".to_string()))
987 }
988 }
989 #[cfg(feature = "gpu")]
990 InternalServerKey::Cuda(cuda_key) => with_thread_local_cuda_streams(|streams| {
991 let result = cuda_key.key.key.match_value_or(
992 &self.ciphertext.on_gpu(streams),
993 matches,
994 or_value,
995 streams,
996 );
997 let target_num_blocks = OutId::num_blocks(cuda_key.key.key.message_modulus);
998 if target_num_blocks >= result.ciphertext.d_blocks.lwe_ciphertext_count().0 {
999 Ok(FheUint::new(result, cuda_key.tag.clone()))
1000 } else {
1001 Err(crate::Error::new("Output type does not have enough bits to represent all possible output values".to_string()))
1002 }
1003 }),
1004 #[cfg(feature = "hpu")]
1005 InternalServerKey::Hpu(_device) => {
1006 panic!("Hpu does not support this operation yet.")
1007 }
1008 })
1009 }
1010
1011 pub fn reverse_bits(&self) -> Self {
1032 global_state::with_internal_keys(|key| match key {
1033 InternalServerKey::Cpu(cpu_key) => {
1034 let sk = &cpu_key.pbs_key();
1035
1036 let ct = self.ciphertext.on_cpu();
1037
1038 Self::new(sk.reverse_bits_parallelized(&*ct), cpu_key.tag.clone())
1039 }
1040 #[cfg(feature = "gpu")]
1041 InternalServerKey::Cuda(_) => {
1042 panic!("Cuda devices do not support reverse yet");
1043 }
1044 #[cfg(feature = "hpu")]
1045 InternalServerKey::Hpu(_device) => {
1046 panic!("Hpu does not support this operation yet.")
1047 }
1048 })
1049 }
1050
1051 pub fn if_then_else<Clear>(condition: &FheBool, true_value: Clear, false_value: Clear) -> Self
1074 where
1075 Clear: UnsignedNumeric + DecomposableInto<u64>,
1076 {
1077 global_state::with_internal_keys(|key| match key {
1078 InternalServerKey::Cpu(cpu_key) => {
1079 let sk = cpu_key.pbs_key();
1080
1081 let result: crate::integer::RadixCiphertext = sk.scalar_if_then_else_parallelized(
1082 &condition.ciphertext.on_cpu(),
1083 true_value,
1084 false_value,
1085 Id::num_blocks(sk.message_modulus()),
1086 );
1087
1088 Self::new(result, cpu_key.tag.clone())
1089 }
1090 #[cfg(feature = "gpu")]
1091 InternalServerKey::Cuda(_) => {
1092 panic!("Cuda devices do not support if_then_else yet");
1093 }
1094 #[cfg(feature = "hpu")]
1095 InternalServerKey::Hpu(_) => {
1096 panic!("Hpu does not support this operation yet.");
1097 }
1098 })
1099 }
1100
1101 pub fn select<Clear>(condition: &FheBool, true_value: Clear, false_value: Clear) -> Self
1103 where
1104 Clear: UnsignedNumeric + DecomposableInto<u64>,
1105 {
1106 Self::if_then_else(condition, true_value, false_value)
1107 }
1108
1109 pub fn cmux<Clear>(condition: &FheBool, true_value: Clear, false_value: Clear) -> Self
1111 where
1112 Clear: UnsignedNumeric + DecomposableInto<u64>,
1113 {
1114 Self::if_then_else(condition, true_value, false_value)
1115 }
1116}
1117
1118impl<Id> TryFrom<crate::integer::RadixCiphertext> for FheUint<Id>
1119where
1120 Id: FheUintId,
1121{
1122 type Error = GenericIntegerBlockError;
1123
1124 fn try_from(other: crate::integer::RadixCiphertext) -> Result<Self, GenericIntegerBlockError> {
1125 let (correct_carry_mod, correct_message_mod) =
1127 global_state::with_internal_keys(|sks| match sks {
1128 InternalServerKey::Cpu(sks) => (
1129 sks.pbs_key().key.carry_modulus,
1130 sks.pbs_key().key.message_modulus,
1131 ),
1132 #[cfg(feature = "gpu")]
1133 InternalServerKey::Cuda(cuda_key) => (
1134 cuda_key.key.key.carry_modulus,
1135 cuda_key.key.key.message_modulus,
1136 ),
1137 #[cfg(feature = "hpu")]
1138 InternalServerKey::Hpu(_device) => {
1139 panic!("Hpu does not support this operation yet.")
1140 }
1141 });
1142
1143 let expected_num_blocks = Id::num_blocks(correct_message_mod);
1145 if other.blocks.len() != expected_num_blocks {
1146 return Err(GenericIntegerBlockError::NumberOfBlocks(
1147 expected_num_blocks,
1148 other.blocks.len(),
1149 ));
1150 }
1151
1152 for block in &other.blocks {
1154 let (input_carry_mod, input_message_mod) = (block.carry_modulus, block.message_modulus);
1155
1156 if input_carry_mod != correct_carry_mod {
1157 return Err(GenericIntegerBlockError::CarryModulus(
1158 correct_carry_mod,
1159 input_carry_mod,
1160 ));
1161 } else if input_message_mod != correct_message_mod {
1162 return Err(GenericIntegerBlockError::MessageModulus(
1163 correct_message_mod,
1164 input_message_mod,
1165 ));
1166 }
1167 }
1168
1169 let mut ciphertext = Self::new(other, Tag::default());
1170 ciphertext.move_to_device_of_server_key_if_set();
1171 Ok(ciphertext)
1172 }
1173}
1174
1175impl<Id, T> TryFrom<Vec<T>> for FheUint<Id>
1176where
1177 Id: FheUintId,
1178 crate::integer::RadixCiphertext: From<Vec<T>>,
1179{
1180 type Error = GenericIntegerBlockError;
1181 fn try_from(blocks: Vec<T>) -> Result<Self, GenericIntegerBlockError> {
1182 let ciphertext = crate::integer::RadixCiphertext::from(blocks);
1183 Self::try_from(ciphertext)
1184 }
1185}
1186
1187impl<FromId, IntoId> CastFrom<FheInt<FromId>> for FheUint<IntoId>
1188where
1189 FromId: FheIntId,
1190 IntoId: FheUintId,
1191{
1192 fn cast_from(input: FheInt<FromId>) -> Self {
1210 global_state::with_internal_keys(|keys| match keys {
1211 InternalServerKey::Cpu(cpu_key) => {
1212 let casted = cpu_key.pbs_key().cast_to_unsigned(
1213 input.ciphertext.into_cpu(),
1214 IntoId::num_blocks(cpu_key.message_modulus()),
1215 );
1216 Self::new(casted, cpu_key.tag.clone())
1217 }
1218 #[cfg(feature = "gpu")]
1219 InternalServerKey::Cuda(cuda_key) => with_thread_local_cuda_streams(|streams| {
1220 let casted = cuda_key.key.key.cast_to_unsigned(
1221 input.ciphertext.into_gpu(streams),
1222 IntoId::num_blocks(cuda_key.message_modulus()),
1223 streams,
1224 );
1225 Self::new(casted, cuda_key.tag.clone())
1226 }),
1227 #[cfg(feature = "hpu")]
1228 InternalServerKey::Hpu(_device) => {
1229 panic!("Hpu does not support this operation yet.")
1230 }
1231 })
1232 }
1233}
1234
1235impl<FromId, IntoId> CastFrom<FheUint<FromId>> for FheUint<IntoId>
1236where
1237 FromId: FheUintId,
1238 IntoId: FheUintId,
1239{
1240 fn cast_from(input: FheUint<FromId>) -> Self {
1258 global_state::with_internal_keys(|keys| match keys {
1259 InternalServerKey::Cpu(cpu_key) => {
1260 let casted = cpu_key.pbs_key().cast_to_unsigned(
1261 input.ciphertext.on_cpu().to_owned(),
1262 IntoId::num_blocks(cpu_key.message_modulus()),
1263 );
1264 Self::new(casted, cpu_key.tag.clone())
1265 }
1266 #[cfg(feature = "gpu")]
1267 InternalServerKey::Cuda(cuda_key) => with_thread_local_cuda_streams(|streams| {
1268 let casted = cuda_key.key.key.cast_to_unsigned(
1269 input.ciphertext.into_gpu(streams),
1270 IntoId::num_blocks(cuda_key.message_modulus()),
1271 streams,
1272 );
1273 Self::new(casted, cuda_key.tag.clone())
1274 }),
1275 #[cfg(feature = "hpu")]
1276 InternalServerKey::Hpu(_device) => {
1277 panic!("Hpu does not support this operation yet.")
1278 }
1279 })
1280 }
1281}
1282
1283impl<Id> CastFrom<FheBool> for FheUint<Id>
1284where
1285 Id: FheUintId,
1286{
1287 fn cast_from(input: FheBool) -> Self {
1305 global_state::with_internal_keys(|key| match key {
1306 InternalServerKey::Cpu(cpu_key) => {
1307 let ciphertext: crate::integer::RadixCiphertext = input
1308 .ciphertext
1309 .on_cpu()
1310 .into_owned()
1311 .into_radix(Id::num_blocks(cpu_key.message_modulus()), cpu_key.pbs_key());
1312 Self::new(ciphertext, cpu_key.tag.clone())
1313 }
1314 #[cfg(feature = "gpu")]
1315 InternalServerKey::Cuda(cuda_key) => with_thread_local_cuda_streams(|streams| {
1316 let inner = cuda_key.key.key.cast_to_unsigned(
1317 input.ciphertext.into_gpu(streams).0,
1318 Id::num_blocks(cuda_key.message_modulus()),
1319 streams,
1320 );
1321 Self::new(inner, cuda_key.tag.clone())
1322 }),
1323 #[cfg(feature = "hpu")]
1324 InternalServerKey::Hpu(_device) => {
1325 panic!("Hpu does not support this operation yet.")
1326 }
1327 })
1328 }
1329}
1330
1331#[cfg(test)]
1332mod test {
1333 use super::*;
1334 use crate::core_crypto::prelude::UnsignedInteger;
1335 use crate::prelude::*;
1336 use crate::shortint::parameters::{AtomicPatternKind, PARAM_MESSAGE_2_CARRY_2_KS_PBS};
1337 use crate::shortint::{CiphertextModulus, PBSOrder};
1338 use crate::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
1339 use rand::{thread_rng, Rng};
1340
1341 type IndexedParameterAccessor<Ct, T> = dyn Fn(usize, &mut Ct) -> &mut T;
1342
1343 type IndexedParameterModifier<'a, Ct> = dyn Fn(usize, &mut Ct) + 'a;
1344
1345 fn change_parameters<Ct, T: UnsignedInteger>(
1346 func: &IndexedParameterAccessor<Ct, T>,
1347 ) -> [Box<IndexedParameterModifier<'_, Ct>>; 3] {
1348 [
1349 Box::new(|i, ct| *func(i, ct) = T::ZERO),
1350 Box::new(|i, ct| *func(i, ct) = func(i, ct).wrapping_add(T::ONE)),
1351 Box::new(|i, ct| *func(i, ct) = func(i, ct).wrapping_sub(T::ONE)),
1352 ]
1353 }
1354
1355 #[test]
1356 fn test_invalid_generic_integer() {
1357 type Ct = FheUint8;
1358
1359 let config = ConfigBuilder::default().build();
1360
1361 let (client_key, _server_key) = generate_keys(config);
1362
1363 let ct = FheUint8::try_encrypt(0_u64, &client_key).unwrap();
1364
1365 assert!(ct.is_conformant(&FheUintConformanceParams::from(
1366 PARAM_MESSAGE_2_CARRY_2_KS_PBS,
1367 )));
1368
1369 let breaker_lists = [
1370 change_parameters(&|i, ct: &mut Ct| {
1371 &mut ct.ciphertext.as_cpu_mut().blocks[i].message_modulus.0
1372 }),
1373 change_parameters(&|i, ct: &mut Ct| {
1374 &mut ct.ciphertext.as_cpu_mut().blocks[i].carry_modulus.0
1375 }),
1376 change_parameters(&|i, ct: &mut Ct| {
1377 ct.ciphertext.as_cpu_mut().blocks[i].degree.as_mut()
1378 }),
1379 ];
1380
1381 for breaker_list in breaker_lists {
1382 for breaker in breaker_list {
1383 for i in 0..ct.ciphertext.on_cpu().blocks.len() {
1384 let mut ct_clone = ct.clone();
1385
1386 breaker(i, &mut ct_clone);
1387
1388 assert!(!ct_clone.is_conformant(&FheUintConformanceParams::from(
1389 PARAM_MESSAGE_2_CARRY_2_KS_PBS,
1390 )));
1391 }
1392 }
1393 }
1394 let breakers2: Vec<&IndexedParameterModifier<'_, Ct>> = vec![
1395 &|i, ct: &mut Ct| {
1396 *ct.ciphertext.as_cpu_mut().blocks[i]
1397 .ct
1398 .get_mut_ciphertext_modulus() =
1399 CiphertextModulus::try_new_power_of_2(1).unwrap();
1400 },
1401 &|i, ct: &mut Ct| {
1402 *ct.ciphertext.as_cpu_mut().blocks[i]
1403 .ct
1404 .get_mut_ciphertext_modulus() = CiphertextModulus::try_new(3).unwrap();
1405 },
1406 &|_i, ct: &mut Ct| {
1407 ct.ciphertext.as_cpu_mut().blocks.pop();
1408 },
1409 &|i, ct: &mut Ct| {
1410 let cloned_block = ct.ciphertext.on_cpu().blocks[i].clone();
1411 ct.ciphertext.as_cpu_mut().blocks.push(cloned_block);
1412 },
1413 &|i, ct: &mut Ct| {
1414 ct.ciphertext.as_cpu_mut().blocks[i].atomic_pattern =
1415 AtomicPatternKind::Standard(PBSOrder::BootstrapKeyswitch);
1416 },
1417 ];
1418
1419 for breaker in breakers2 {
1420 for i in 0..ct.ciphertext.on_cpu().blocks.len() {
1421 let mut ct_clone = ct.clone();
1422
1423 breaker(i, &mut ct_clone);
1424
1425 assert!(!ct_clone.is_conformant(&FheUintConformanceParams::from(
1426 PARAM_MESSAGE_2_CARRY_2_KS_PBS,
1427 )));
1428 }
1429 }
1430 }
1431
1432 #[test]
1433 fn test_valid_generic_integer() {
1434 let config = ConfigBuilder::default().build();
1435
1436 let (client_key, server_key) = generate_keys(config);
1437
1438 set_server_key(server_key);
1439
1440 let ct = FheUint8::try_encrypt(0_u64, &client_key).unwrap();
1441
1442 assert!(ct.is_conformant(&FheUintConformanceParams::from(
1443 PARAM_MESSAGE_2_CARRY_2_KS_PBS,
1444 )));
1445
1446 let mut rng = thread_rng();
1447
1448 let num_blocks = ct.ciphertext.on_cpu().blocks.len();
1449
1450 for _ in 0..10 {
1451 let mut ct_clone = ct.clone();
1452
1453 for i in 0..num_blocks {
1454 ct_clone.ciphertext.as_cpu_mut().blocks[i]
1455 .ct
1456 .as_mut()
1457 .fill_with(|| rng.gen::<u64>());
1458 }
1459
1460 assert!(ct.is_conformant(&FheUintConformanceParams::from(
1461 PARAM_MESSAGE_2_CARRY_2_KS_PBS,
1462 )));
1463
1464 ct_clone += &ct_clone.clone();
1465 }
1466 }
1467}