tfhe/integer/server_key/
mod.rs

1//! Module with the definition of the ServerKey.
2//!
3//! This module implements the generation of the server public key, together with all the
4//! available homomorphic integer operations.
5pub mod comparator;
6pub(crate) mod crt;
7mod crt_parallel;
8pub(crate) mod radix;
9pub(crate) mod radix_parallel;
10
11use super::backward_compatibility::server_key::{CompressedServerKeyVersions, ServerKeyVersions};
12use crate::conformance::ParameterSetConformant;
13use crate::core_crypto::prelude::UnsignedInteger;
14use crate::integer::client_key::ClientKey;
15use crate::shortint::atomic_pattern::AtomicPatternParameters;
16use crate::shortint::ciphertext::{Degree, MaxDegree};
17/// Error returned when the carry buffer is full.
18pub use crate::shortint::CheckError;
19use crate::shortint::{CarryModulus, MessageModulus};
20pub use radix::scalar_mul::ScalarMultiplier;
21pub use radix::scalar_sub::TwosComplementNegation;
22pub use radix_parallel::{MatchValues, MiniUnsignedInteger, Reciprocable};
23use serde::{Deserialize, Serialize};
24use tfhe_versionable::Versionize;
25
26/// A structure containing the server public key.
27///
28/// The server key is generated by the client and is meant to be published: the client
29/// sends it to the server so it can compute homomorphic integer circuits.
30///
31/// Note: a [`ServerKey`] can be fairly large, if needed you can generate a [`CompressedServerKey`]
32/// instead to reduce storage and network bandwidth usage.
33#[derive(Serialize, Deserialize, Clone, Versionize)]
34#[versionize(ServerKeyVersions)]
35pub struct ServerKey {
36    pub(crate) key: crate::shortint::ServerKey,
37}
38
39impl From<ServerKey> for crate::shortint::ServerKey {
40    fn from(key: ServerKey) -> Self {
41        key.key
42    }
43}
44
45impl MaxDegree {
46    /// Compute the [`MaxDegree`] for an integer server key (compressed or uncompressed).
47    /// To allow carry propagation between shortint blocks in a
48    /// [`RadixCiphertext`](`crate::integer::RadixCiphertext`) (which includes adding the extracted
49    /// carry from one shortint block to the next block), this formula provisions space to add a
50    /// carry.
51    pub(crate) fn integer_radix_server_key(
52        message_modulus: MessageModulus,
53        carry_modulus: CarryModulus,
54    ) -> Self {
55        let full_max_degree = message_modulus.0 * carry_modulus.0 - 1;
56
57        let carry_max_degree = carry_modulus.0 - 1;
58
59        // We want to be have a margin to add a carry from another block
60        Self::new(full_max_degree - carry_max_degree)
61    }
62}
63
64impl MaxDegree {
65    /// Compute the [`MaxDegree`] for an integer server key (compressed or uncompressed).
66    /// This is tailored for [`CrtCiphertext`](`crate::integer::CrtCiphertext`) and not compatible
67    /// for use with [`RadixCiphertext`](`crate::integer::RadixCiphertext`).
68    fn integer_crt_server_key(
69        message_modulus: MessageModulus,
70        carry_modulus: CarryModulus,
71    ) -> Self {
72        let full_max_degree = message_modulus.0 * carry_modulus.0 - 1;
73
74        Self::new(full_max_degree)
75    }
76}
77
78impl ServerKey {
79    /// Generates a server key.
80    ///
81    /// # Example
82    ///
83    /// ```rust
84    /// use tfhe::integer::{ClientKey, ServerKey};
85    /// use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M128;
86    ///
87    /// // Generate the client key:
88    /// let cks = ClientKey::new(PARAM_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M128);
89    ///
90    /// // Generate the server key:
91    /// let sks = ServerKey::new_radix_server_key(cks);
92    /// ```
93    pub fn new_radix_server_key<C>(cks: C) -> Self
94    where
95        C: AsRef<ClientKey>,
96    {
97        // It should remain just enough space to add a carry
98        let client_key = cks.as_ref();
99        let max_degree = MaxDegree::integer_radix_server_key(
100            client_key.key.parameters.message_modulus(),
101            client_key.key.parameters.carry_modulus(),
102        );
103
104        let sks = crate::shortint::server_key::ServerKey::new_with_max_degree(
105            &client_key.key,
106            max_degree,
107        );
108
109        Self { key: sks }
110    }
111
112    pub fn new_crt_server_key<C>(cks: C) -> Self
113    where
114        C: AsRef<ClientKey>,
115    {
116        let client_key = cks.as_ref();
117        let max_degree = MaxDegree::integer_crt_server_key(
118            client_key.key.parameters.message_modulus(),
119            client_key.key.parameters.carry_modulus(),
120        );
121
122        let sks = crate::shortint::server_key::ServerKey::new_with_max_degree(
123            &client_key.key,
124            max_degree,
125        );
126
127        Self { key: sks }
128    }
129
130    /// Creates a ServerKey destined to be used with
131    /// [`RadixCiphertext`](`crate::integer::RadixCiphertext`) from an already generated
132    /// shortint::ServerKey.
133    ///
134    /// # Example
135    ///
136    /// ```rust
137    /// use tfhe::integer::{ClientKey, ServerKey};
138    /// use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M128;
139    /// use tfhe::shortint::ServerKey as ShortintServerKey;
140    ///
141    /// // Generate the client key:
142    /// let cks = ClientKey::new(PARAM_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M128);
143    ///
144    /// // Generate the shortint server key:
145    /// let shortint_sks = ShortintServerKey::new(cks.as_ref());
146    ///
147    /// // Generate the server key:
148    /// let sks = ServerKey::new_radix_server_key_from_shortint(shortint_sks);
149    /// ```
150    pub fn new_radix_server_key_from_shortint(
151        mut key: crate::shortint::server_key::ServerKey,
152    ) -> Self {
153        // It should remain just enough space add a carry
154        let max_degree =
155            MaxDegree::integer_radix_server_key(key.message_modulus, key.carry_modulus);
156
157        key.max_degree = max_degree;
158        Self { key }
159    }
160
161    /// Creates a ServerKey destined to be used with
162    /// [`CrtCiphertext`](`crate::integer::CrtCiphertext`) from an already generated
163    /// shortint::ServerKey.
164    ///
165    /// # Example
166    ///
167    /// ```rust
168    /// use tfhe::integer::{ClientKey, ServerKey};
169    /// use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M128;
170    /// use tfhe::shortint::ServerKey as ShortintServerKey;
171    ///
172    /// // Generate the client key:
173    /// let cks = ClientKey::new(PARAM_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M128);
174    ///
175    /// // Generate the shortint server key:
176    /// let shortint_sks = ShortintServerKey::new(cks.as_ref());
177    ///
178    /// // Generate the server key:
179    /// let sks = ServerKey::new_crt_server_key_from_shortint(shortint_sks);
180    /// ```
181    pub fn new_crt_server_key_from_shortint(
182        mut key: crate::shortint::server_key::ServerKey,
183    ) -> Self {
184        key.max_degree = MaxDegree::integer_crt_server_key(key.message_modulus, key.carry_modulus);
185        Self { key }
186    }
187
188    /// Deconstruct a [`ServerKey`] into its constituents.
189    pub fn into_raw_parts(self) -> crate::shortint::ServerKey {
190        self.key
191    }
192
193    /// Construct a [`ServerKey`] from its constituents.
194    pub fn from_raw_parts(key: crate::shortint::ServerKey) -> Self {
195        Self { key }
196    }
197
198    pub fn deterministic_pbs_execution(&self) -> bool {
199        self.key.deterministic_execution()
200    }
201
202    pub fn set_deterministic_pbs_execution(&mut self, new_deterministic_execution: bool) {
203        self.key
204            .set_deterministic_execution(new_deterministic_execution);
205    }
206
207    pub fn message_modulus(&self) -> MessageModulus {
208        self.key.message_modulus
209    }
210
211    pub fn carry_modulus(&self) -> CarryModulus {
212        self.key.carry_modulus
213    }
214
215    /// Returns how many blocks a radix ciphertext should have to
216    /// be able to represent the given unsigned integer
217    pub fn num_blocks_to_represent_unsigned_value<Clear>(&self, clear: Clear) -> usize
218    where
219        Clear: UnsignedInteger,
220    {
221        let num_bits_to_represent_output_value = num_bits_to_represent_unsigned_value(clear);
222        let num_bits_in_message = self.message_modulus().0.ilog2();
223        num_bits_to_represent_output_value.div_ceil(num_bits_in_message as usize)
224    }
225
226    /// Returns how many ciphertext can be summed at once
227    ///
228    /// The number of ciphertext that can be added together depends on the degree
229    /// (in order not to go beyond the carry space and keep results correct) but also
230    /// on the noise level (in order to have the correct error probability and so correctness and
231    /// security)
232    ///
233    /// - `degree` is expected degree of all elements to be summed
234    pub(crate) fn max_sum_size(&self, degree: Degree) -> usize {
235        let max_degree =
236            MaxDegree::from_msg_carry_modulus(self.message_modulus(), self.carry_modulus());
237        let max_sum_to_full_carry = max_degree.get() / degree.get();
238
239        max_sum_to_full_carry.min(self.key.max_noise_level.get()) as usize
240    }
241}
242
243impl AsRef<crate::shortint::ServerKey> for ServerKey {
244    fn as_ref(&self) -> &crate::shortint::ServerKey {
245        &self.key
246    }
247}
248
249#[derive(Clone, Serialize, Deserialize, Versionize)]
250#[versionize(CompressedServerKeyVersions)]
251pub struct CompressedServerKey {
252    pub(crate) key: crate::shortint::CompressedServerKey,
253}
254
255impl CompressedServerKey {
256    pub fn new_radix_compressed_server_key(client_key: &ClientKey) -> Self {
257        let max_degree = MaxDegree::integer_radix_server_key(
258            client_key.key.parameters.message_modulus(),
259            client_key.key.parameters.carry_modulus(),
260        );
261
262        let key =
263            crate::shortint::CompressedServerKey::new_with_max_degree(&client_key.key, max_degree);
264        Self { key }
265    }
266
267    pub fn new_crt_compressed_server_key(client_key: &ClientKey) -> Self {
268        let key = crate::shortint::CompressedServerKey::new(&client_key.key);
269        Self { key }
270    }
271
272    /// Decompress a [`CompressedServerKey`] into a [`ServerKey`].
273    pub fn decompress(&self) -> ServerKey {
274        ServerKey {
275            key: self.key.decompress(),
276        }
277    }
278
279    /// Deconstruct a [`CompressedServerKey`] into its constituents.
280    pub fn into_raw_parts(self) -> crate::shortint::CompressedServerKey {
281        self.key
282    }
283
284    /// Construct a [`CompressedServerKey`] from its constituents.
285    pub fn from_raw_parts(key: crate::shortint::CompressedServerKey) -> Self {
286        Self { key }
287    }
288}
289
290pub fn num_bits_to_represent_unsigned_value<Clear>(clear: Clear) -> usize
291where
292    Clear: UnsignedInteger,
293{
294    if clear == Clear::MAX {
295        Clear::BITS
296    } else {
297        let bits = (clear + Clear::ONE).ceil_ilog2() as usize;
298        if bits == 0 {
299            1
300        } else {
301            bits
302        }
303    }
304}
305
306impl ParameterSetConformant for ServerKey {
307    type ParameterSet = AtomicPatternParameters;
308
309    fn is_conformant(&self, parameter_set: &Self::ParameterSet) -> bool {
310        let Self { key } = self;
311
312        let expected_max_degree = MaxDegree::integer_radix_server_key(
313            parameter_set.message_modulus(),
314            parameter_set.carry_modulus(),
315        );
316
317        key.is_conformant(&(*parameter_set, expected_max_degree))
318    }
319}
320
321impl ParameterSetConformant for CompressedServerKey {
322    type ParameterSet = AtomicPatternParameters;
323
324    fn is_conformant(&self, parameter_set: &Self::ParameterSet) -> bool {
325        let Self { key } = self;
326
327        let expected_max_degree = MaxDegree::integer_radix_server_key(
328            parameter_set.message_modulus(),
329            parameter_set.carry_modulus(),
330        );
331
332        key.is_conformant(&(*parameter_set, expected_max_degree))
333    }
334}
335
336#[cfg(test)]
337mod test {
338    use super::*;
339    use crate::integer::RadixClientKey;
340    use crate::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
341
342    /// https://github.com/zama-ai/tfhe-rs/issues/460
343    /// Problem with CompressedServerKey degree being set to shortint MaxDegree not accounting for
344    /// the necessary carry bits for e.g. Radix carry propagation.
345    #[test]
346    fn test_compressed_server_key_max_degree() {
347        {
348            let cks = ClientKey::new(
349                crate::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M128,
350            );
351            // msg_mod = 4, carry_mod = 4, (msg_mod * carry_mod - 1) - (carry_mod - 1) = 12
352            let expected_radix_max_degree = MaxDegree::new(12);
353
354            let sks = ServerKey::new_radix_server_key(&cks);
355            assert_eq!(sks.key.max_degree, expected_radix_max_degree);
356
357            let csks = CompressedServerKey::new_radix_compressed_server_key(&cks);
358            assert_eq!(csks.key.max_degree, expected_radix_max_degree);
359
360            let decompressed_sks: ServerKey = csks.decompress();
361            assert_eq!(decompressed_sks.key.max_degree, expected_radix_max_degree);
362        }
363
364        {
365            let cks = ClientKey::new(
366                crate::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M128,
367            );
368            // msg_mod = 4, carry_mod = 4, msg_mod * carrymod - 1 = 15
369            let expected_crt_max_degree = MaxDegree::new(15);
370
371            let sks = ServerKey::new_crt_server_key(&cks);
372            assert_eq!(sks.key.max_degree, expected_crt_max_degree);
373
374            let csks = CompressedServerKey::new_crt_compressed_server_key(&cks);
375            assert_eq!(csks.key.max_degree, expected_crt_max_degree);
376
377            let decompressed_sks: ServerKey = csks.decompress();
378            assert_eq!(decompressed_sks.key.max_degree, expected_crt_max_degree);
379        }
380
381        // Repro case from the user
382        {
383            let client_key = RadixClientKey::new(PARAM_MESSAGE_2_CARRY_2, 14);
384            let compressed_eval_key =
385                CompressedServerKey::new_radix_compressed_server_key(client_key.as_ref());
386            let evaluation_key = compressed_eval_key.decompress();
387            let modulus = (client_key.parameters().message_modulus().0 as u128)
388                .pow(client_key.num_blocks() as u32);
389
390            let mut ct = client_key.encrypt(modulus - 1);
391            let mut res_ct = ct.clone();
392            for _ in 0..5 {
393                res_ct = evaluation_key.smart_add_parallelized(&mut res_ct, &mut ct);
394            }
395            let res: u128 = client_key.decrypt(&res_ct);
396            assert_eq!(modulus - 6, res);
397        }
398    }
399}