tfhe/core_crypto/commons/
ciphertext_modulus.rs1use tfhe_versionable::Versionize;
4
5use crate::core_crypto::backward_compatibility::commons::ciphertext_modulus::SerializableCiphertextModulusVersions;
6use crate::core_crypto::commons::traits::UnsignedInteger;
7use crate::core_crypto::prelude::CastInto;
8use core::num::NonZeroU128;
9use std::cmp::Ordering;
10use std::fmt::Display;
11use std::marker::PhantomData;
12
13use super::parameters::CiphertextModulusLog;
14
15#[derive(Clone, Copy, PartialEq, Eq, Hash)]
16enum CiphertextModulusInner {
21 Native,
22 Custom(NonZeroU128),
23}
24
25#[derive(Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Versionize, Hash)]
26#[serde(
27 try_from = "SerializableCiphertextModulus",
28 into = "SerializableCiphertextModulus"
29)]
30#[versionize(
31 SerializableCiphertextModulusVersions,
32 try_from = "SerializableCiphertextModulus",
33 into = "SerializableCiphertextModulus"
34)]
35pub struct CiphertextModulus<Scalar: UnsignedInteger> {
37 inner: CiphertextModulusInner,
38 _scalar: PhantomData<Scalar>,
39}
40
41#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42pub enum CiphertextModulusKind {
43 Native,
44 NonNativePowerOfTwo,
45 Other,
46}
47
48#[derive(serde::Serialize, serde::Deserialize, Versionize)]
49#[versionize(SerializableCiphertextModulusVersions)]
50pub struct SerializableCiphertextModulus {
52 pub modulus: u128,
53 pub scalar_bits: usize,
54}
55
56#[derive(Clone, Copy, Debug)]
57pub enum CiphertextModulusDeserializationError {
58 InvalidBitWidth { expected: usize, found: usize },
59 ZeroCustomModulus,
60}
61
62impl Display for CiphertextModulusDeserializationError {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 match self {
65 Self::InvalidBitWidth { expected, found } => write!(
66 f,
67 "Expected an unsigned integer with {expected} bits, \
68 found {found} bits during deserialization of CiphertextModulus, \
69 have you mixed types during deserialization?",
70 ),
71 Self::ZeroCustomModulus => write!(
72 f,
73 "Got zero modulus for CiphertextModulusInner::Custom variant"
74 ),
75 }
76 }
77}
78
79impl std::error::Error for CiphertextModulusDeserializationError {}
80
81impl<Scalar: UnsignedInteger> From<CiphertextModulus<Scalar>> for SerializableCiphertextModulus {
82 fn from(value: CiphertextModulus<Scalar>) -> Self {
83 let modulus = match value.inner {
84 CiphertextModulusInner::Native => 0,
85 CiphertextModulusInner::Custom(modulus) => modulus.get(),
86 };
87
88 Self {
89 modulus,
90 scalar_bits: Scalar::BITS,
91 }
92 }
93}
94
95impl<Scalar: UnsignedInteger> TryFrom<SerializableCiphertextModulus> for CiphertextModulus<Scalar> {
96 type Error = CiphertextModulusDeserializationError;
97
98 fn try_from(value: SerializableCiphertextModulus) -> Result<Self, Self::Error> {
99 if value.scalar_bits != Scalar::BITS {
100 return Err(CiphertextModulusDeserializationError::InvalidBitWidth {
101 expected: Scalar::BITS,
102 found: value.scalar_bits,
103 });
104 }
105
106 let res = if value.modulus == 0 {
107 Self {
108 inner: CiphertextModulusInner::Native,
109 _scalar: PhantomData,
110 }
111 } else {
112 Self {
113 inner: CiphertextModulusInner::Custom(
114 NonZeroU128::new(value.modulus)
115 .ok_or(CiphertextModulusDeserializationError::ZeroCustomModulus)?,
116 ),
117 _scalar: PhantomData,
118 }
119 };
120 Ok(res.canonicalize())
121 }
122}
123
124#[derive(Clone, Copy, PartialEq, Eq)]
125pub enum CiphertextModulusCreationError {
126 ModulusTooBig,
127 CustomModuli64BitsOrLessOnly,
128}
129
130impl CiphertextModulusCreationError {
131 pub const fn const_err_msg(self) -> &'static str {
132 match self {
133 Self::ModulusTooBig => {
134 "Modulus is bigger than the maximum value of the associated Scalar type"
135 }
136 Self::CustomModuli64BitsOrLessOnly => {
137 "Non power of 2 moduli are not supported for types wider than u64"
138 }
139 }
140 }
141}
142
143impl From<CiphertextModulusCreationError> for &str {
144 fn from(value: CiphertextModulusCreationError) -> Self {
145 value.const_err_msg()
146 }
147}
148
149impl std::fmt::Debug for CiphertextModulusCreationError {
150 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151 let err_str: &str = (*self).into();
152 write!(f, "{err_str}")
153 }
154}
155
156impl<Scalar: UnsignedInteger> CiphertextModulus<Scalar> {
157 pub const fn new_native() -> Self {
158 Self {
159 inner: CiphertextModulusInner::Native,
160 _scalar: PhantomData,
161 }
162 }
163
164 #[track_caller]
165 pub const fn try_new_power_of_2(
166 exponent: usize,
167 ) -> Result<Self, CiphertextModulusCreationError> {
168 if exponent > Scalar::BITS {
169 Err(CiphertextModulusCreationError::ModulusTooBig)
170 } else {
171 let res = if let Some(modulus) = 1u128.checked_shl(exponent as u32) {
172 let Some(non_zero_modulus) = NonZeroU128::new(modulus) else {
173 panic!("Got zero modulus for CiphertextModulusInner::Custom variant")
174 };
175
176 Self {
177 inner: CiphertextModulusInner::Custom(non_zero_modulus),
178 _scalar: PhantomData,
179 }
180 } else {
181 assert!(exponent == 128);
182 assert!(Scalar::BITS == 128);
183 Self {
184 inner: CiphertextModulusInner::Native,
185 _scalar: PhantomData,
186 }
187 };
188 Ok(res.canonicalize())
189 }
190 }
191
192 #[track_caller]
193 pub const fn try_new(modulus: u128) -> Result<Self, CiphertextModulusCreationError> {
194 if Scalar::BITS < 128 && modulus > (1 << Scalar::BITS) {
195 Err(CiphertextModulusCreationError::ModulusTooBig)
196 } else {
197 let res = match modulus {
198 0 => Self::new_native(),
199 modulus => {
200 let Some(non_zero_modulus) = NonZeroU128::new(modulus) else {
201 panic!("Got zero modulus for CiphertextModulusInner::Custom variant")
202 };
203 Self {
204 inner: CiphertextModulusInner::Custom(non_zero_modulus),
205 _scalar: PhantomData,
206 }
207 }
208 };
209 let canonicalized_result = res.canonicalize();
210 if Scalar::BITS > 64 && !canonicalized_result.is_compatible_with_native_modulus() {
211 return Err(CiphertextModulusCreationError::CustomModuli64BitsOrLessOnly);
212 }
213 Ok(canonicalized_result)
214 }
215 }
216
217 pub const fn canonicalize(self) -> Self {
218 match self.inner {
219 CiphertextModulusInner::Native => self,
220 CiphertextModulusInner::Custom(modulus) => {
221 if Scalar::BITS < 128 && modulus.get() == (1 << Scalar::BITS) {
222 Self::new_native()
223 } else {
224 self
225 }
226 }
227 }
228 }
229
230 #[track_caller]
233 pub const fn new(modulus: u128) -> Self {
234 let res = match modulus {
235 0 => Self::new_native(),
236 _ => match Self::try_new(modulus) {
237 Ok(ciphertext_modulus) => ciphertext_modulus,
238 Err(err) => panic!("{}", err.const_err_msg()),
239 },
240 };
241 res.canonicalize()
242 }
243
244 #[track_caller]
247 pub fn get_power_of_two_scaling_to_native_torus(&self) -> Scalar {
248 match self.inner {
249 CiphertextModulusInner::Native => Scalar::ONE,
250 CiphertextModulusInner::Custom(modulus) => {
251 assert!(
252 modulus.is_power_of_two(),
253 "Cannot get scaling for non power of two modulus {modulus}"
254 );
255 Scalar::ONE.wrapping_shl(Scalar::BITS as u32 - modulus.ilog2())
256 }
257 }
258 }
259
260 pub const fn is_native_modulus(&self) -> bool {
265 matches!(self.inner, CiphertextModulusInner::Native)
266 }
267
268 #[track_caller]
270 pub const fn get_custom_modulus(&self) -> u128 {
271 match self.inner {
272 CiphertextModulusInner::Native => {
273 panic!("Tried getting custom modulus from native modulus")
274 }
275 CiphertextModulusInner::Custom(modulus) => modulus.get(),
276 }
277 }
278
279 pub fn into_modulus_log(self) -> CiphertextModulusLog {
280 match self.inner {
281 CiphertextModulusInner::Native => CiphertextModulusLog(Scalar::BITS),
282 CiphertextModulusInner::Custom(custom_mod) => {
283 CiphertextModulusLog(custom_mod.get().ceil_ilog2() as usize)
284 }
285 }
286 }
287
288 pub fn get_custom_modulus_as_optional_scalar(&self) -> Option<Scalar> {
289 match self.inner {
290 CiphertextModulusInner::Native => None,
291 CiphertextModulusInner::Custom(modulus) => Some(modulus.get().cast_into()),
292 }
293 }
294
295 pub const fn is_compatible_with_native_modulus(&self) -> bool {
296 self.is_native_modulus() || self.is_power_of_two()
297 }
298
299 pub const fn is_non_native_power_of_two(&self) -> bool {
300 match self.inner {
301 CiphertextModulusInner::Native => false,
302 CiphertextModulusInner::Custom(modulus) => modulus.is_power_of_two(),
303 }
304 }
305
306 pub const fn is_power_of_two(&self) -> bool {
307 match self.inner {
308 CiphertextModulusInner::Native => true,
309 CiphertextModulusInner::Custom(modulus) => modulus.is_power_of_two(),
310 }
311 }
312
313 pub fn try_to<ScalarTo: UnsignedInteger + CastInto<u128>>(
314 &self,
315 ) -> Result<CiphertextModulus<ScalarTo>, &'static str> {
316 let error_msg = "failed to convert ciphertext modulus";
317
318 let new_inner = match self.inner {
319 CiphertextModulusInner::Native => match ScalarTo::BITS.cmp(&Scalar::BITS) {
320 Ordering::Greater => {
321 CiphertextModulusInner::Custom(NonZeroU128::new(1u128 << Scalar::BITS).unwrap())
322 }
323 Ordering::Equal => CiphertextModulusInner::Native,
324 Ordering::Less => {
325 return Err(error_msg);
326 }
327 },
328 CiphertextModulusInner::Custom(v) => {
329 let max = NonZeroU128::new(ScalarTo::MAX.cast_into()).unwrap();
330 if v <= max {
331 CiphertextModulusInner::Custom(v)
332 } else if v.is_power_of_two() && v.ilog2() as usize == ScalarTo::BITS {
333 CiphertextModulusInner::Native
334 } else {
335 return Err(error_msg);
336 }
337 }
338 };
339
340 Ok(CiphertextModulus {
341 inner: new_inner,
342 _scalar: PhantomData,
343 }
344 .canonicalize())
345 }
346
347 pub const fn kind(&self) -> CiphertextModulusKind {
348 match self.inner {
349 CiphertextModulusInner::Native => CiphertextModulusKind::Native,
350 CiphertextModulusInner::Custom(modulus) => {
351 if modulus.is_power_of_two() {
352 CiphertextModulusKind::NonNativePowerOfTwo
353 } else {
354 CiphertextModulusKind::Other
355 }
356 }
357 }
358 }
359
360 pub fn raw_modulus_float(&self) -> f64 {
361 match self.inner {
362 CiphertextModulusInner::Native => 2_f64.powi(Scalar::BITS as i32),
363 CiphertextModulusInner::Custom(non_zero) => non_zero.get() as f64,
364 }
365 }
366
367 pub const fn associated_scalar_bits(&self) -> usize {
368 Scalar::BITS
369 }
370}
371
372impl<Scalar: UnsignedInteger> From<CiphertextModulus<Scalar>> for CiphertextModulusLog {
373 fn from(value: CiphertextModulus<Scalar>) -> Self {
374 value.into_modulus_log()
375 }
376}
377
378impl<Scalar: UnsignedInteger> std::fmt::Display for CiphertextModulus<Scalar> {
379 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
380 match self.inner {
381 CiphertextModulusInner::Native => write!(f, "CiphertextModulus(2^{})", Scalar::BITS),
382 CiphertextModulusInner::Custom(modulus) => {
383 write!(f, "CiphertextModulus({})", modulus.get())
384 }
385 }
386 }
387}
388
389impl<Scalar: UnsignedInteger> std::fmt::Debug for CiphertextModulus<Scalar> {
390 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
391 <Self as std::fmt::Display>::fmt(self, f)
392 }
393}
394
395#[cfg(test)]
396mod tests {
397 use super::CiphertextModulusCreationError;
398 use crate::core_crypto::prelude::CiphertextModulus;
399
400 #[test]
401 fn test_modulus_struct() {
402 assert!(std::mem::size_of::<CiphertextModulus<u32>>() == std::mem::size_of::<u128>());
403 assert!(std::mem::size_of::<CiphertextModulus<u64>>() == std::mem::size_of::<u128>());
404 assert!(std::mem::size_of::<CiphertextModulus<u128>>() == std::mem::size_of::<u128>());
405 assert!(std::mem::align_of::<CiphertextModulus<u32>>() == std::mem::align_of::<u128>());
406 assert!(std::mem::align_of::<CiphertextModulus<u64>>() == std::mem::align_of::<u128>());
407 assert!(std::mem::align_of::<CiphertextModulus<u128>>() == std::mem::align_of::<u128>());
408
409 {
410 let mod_32 = CiphertextModulus::<u32>::try_new_power_of_2(32).unwrap();
411
412 assert!(mod_32.is_native_modulus());
413
414 let std_fmt = format!("{mod_32}");
415 assert_eq!(&std_fmt, "CiphertextModulus(2^32)");
416
417 let dbg_fmt = format!("{mod_32:?}");
418 assert_eq!(&dbg_fmt, "CiphertextModulus(2^32)");
419 }
420
421 {
422 let bad_mod_32 = CiphertextModulus::<u32>::try_new_power_of_2(64);
423 assert!(bad_mod_32.is_err());
424 match bad_mod_32 {
425 Ok(_) => unreachable!(),
426 Err(e) => assert_eq!(e, CiphertextModulusCreationError::ModulusTooBig),
427 }
428 }
429
430 {
431 let native_mod_128 = CiphertextModulus::<u128>::new_native();
432 assert!(native_mod_128.is_native_modulus());
433
434 let ser = bincode::serialize(&native_mod_128).unwrap();
435 let deser: CiphertextModulus<u128> = bincode::deserialize(&ser).unwrap();
436
437 assert_eq!(native_mod_128, deser);
438
439 let deser_error: Result<CiphertextModulus<u32>, _> = bincode::deserialize(&ser);
440 assert!(deser_error.is_err());
441 match deser_error {
442 Ok(_) => unreachable!(),
443 Err(e) => match *e {
444 bincode::ErrorKind::Custom(err) => {
445 assert_eq!(
446 err.as_str(),
447 "Expected an unsigned integer with 32 bits, \
448 found 128 bits during deserialization of CiphertextModulus, \
449 have you mixed types during deserialization?",
450 );
451 }
452 _ => unreachable!(),
453 },
454 }
455 }
456
457 {
458 let mod_128 = CiphertextModulus::<u128>::try_new_power_of_2(64).unwrap();
459
460 assert_eq!(mod_128.get_custom_modulus(), 1 << 64);
461
462 let ser = bincode::serialize(&mod_128).unwrap();
463 let deser: CiphertextModulus<u128> = bincode::deserialize(&ser).unwrap();
464
465 assert_eq!(mod_128, deser);
466
467 let deser_error: Result<CiphertextModulus<u32>, _> = bincode::deserialize(&ser);
468 assert!(deser_error.is_err());
469 match deser_error {
470 Ok(_) => unreachable!(),
471 Err(e) => match *e {
472 bincode::ErrorKind::Custom(err) => {
473 assert_eq!(
474 err.as_str(),
475 "Expected an unsigned integer with 32 bits, \
476 found 128 bits during deserialization of CiphertextModulus, \
477 have you mixed types during deserialization?",
478 );
479 }
480 _ => unreachable!(),
481 },
482 }
483 }
484 }
485
486 #[test]
487 fn test_modulus_casting() {
488 let native_mod = CiphertextModulus::<u64>::try_new_power_of_2(64).unwrap();
490 assert!(native_mod.is_native_modulus());
491 let converted: CiphertextModulus<u64> = native_mod.try_to().unwrap();
492
493 assert!(converted.is_native_modulus());
494
495 let native_mod = CiphertextModulus::<u64>::try_new_power_of_2(64).unwrap();
497 let converted: CiphertextModulus<u128> = native_mod.try_to().unwrap();
498
499 assert!(!converted.is_native_modulus());
500 assert_eq!(converted.get_custom_modulus(), 1u128 << 64);
501
502 let native_mod = CiphertextModulus::<u64>::try_new_power_of_2(64).unwrap();
504 let converted: Result<CiphertextModulus<u32>, _> = native_mod.try_to();
505 assert!(converted.is_err());
506
507 let custom_mod = CiphertextModulus::<u64>::try_new(64).unwrap();
509 assert!(!custom_mod.is_native_modulus());
510 let converted: CiphertextModulus<u64> = custom_mod.try_to().unwrap();
511
512 assert!(!converted.is_native_modulus());
513 assert_eq!(converted.get_custom_modulus(), 64);
514
515 let custom_mod = CiphertextModulus::<u64>::try_new_power_of_2(32).unwrap();
517 assert!(!custom_mod.is_native_modulus());
518 let converted: CiphertextModulus<u32> = custom_mod.try_to().unwrap();
519 assert!(converted.is_native_modulus());
520
521 let custom_mod = CiphertextModulus::<u64>::try_new(1 << 48).unwrap();
523 assert!(!custom_mod.is_native_modulus());
524 let converted: Result<CiphertextModulus<u32>, _> = custom_mod.try_to();
525 assert!(converted.is_err());
526
527 let custom_mod = CiphertextModulus::<u64>::try_new(1 << 21).unwrap();
529 assert!(!custom_mod.is_native_modulus());
530 let converted: CiphertextModulus<u32> = custom_mod.try_to().unwrap();
531 assert!(!converted.is_native_modulus());
532 assert_eq!(converted.get_custom_modulus(), 1 << 21);
533 }
534}