1 // Copyright (c) Microsoft Corporation. All rights reserved.
2 // Licensed under the MIT license.
3 
4 #pragma once
5 
6 #include "seal/memorymanager.h"
7 #include "seal/modulus.h"
8 #include "seal/randomgen.h"
9 #include "seal/serialization.h"
10 #include "seal/version.h"
11 #include "seal/util/defines.h"
12 #include "seal/util/globals.h"
13 #include "seal/util/hash.h"
14 #include "seal/util/ztools.h"
15 #include <functional>
16 #include <iostream>
17 #include <memory>
18 #include <numeric>
19 
20 namespace seal
21 {
22     /**
23     Describes the type of encryption scheme to be used.
24     */
25     enum class scheme_type : std::uint8_t
26     {
27         // No scheme set; cannot be used for encryption
28         none = 0x0,
29 
30         // Brakerski/Fan-Vercauteren scheme
31         bfv = 0x1,
32 
33         // Cheon-Kim-Kim-Song scheme
34         ckks = 0x2
35     };
36 
37     /**
38     The data type to store unique identifiers of encryption parameters.
39     */
40     using parms_id_type = util::HashFunction::hash_block_type;
41 
42     /**
43     A parms_id_type value consisting of zeros.
44     */
45     extern const parms_id_type parms_id_zero;
46 
47     /**
48     Represents user-customizable encryption scheme settings. The parameters (most
49     importantly poly_modulus, coeff_modulus, plain_modulus) significantly affect
50     the performance, capabilities, and security of the encryption scheme. Once
51     an instance of EncryptionParameters is populated with appropriate parameters,
52     it can be used to create an instance of the SEALContext class, which verifies
53     the validity of the parameters, and performs necessary pre-computations.
54 
55     Picking appropriate encryption parameters is essential to enable a particular
56     application while balancing performance and security. Some encryption settings
57     will not allow some inputs (e.g. attempting to encrypt a polynomial with more
58     coefficients than poly_modulus or larger coefficients than plain_modulus) or,
59     support the desired computations (with noise growing too fast due to too large
60     plain_modulus and too small coeff_modulus).
61 
62     @par parms_id
63     The EncryptionParameters class maintains at all times a 256-bit hash of the
64     currently set encryption parameters called the parms_id. This hash acts as
65     a unique identifier of the encryption parameters and is used by all further
66     objects created for these encryption parameters. The parms_id is not intended
67     to be directly modified by the user but is used internally for pre-computation
68     data lookup and input validity checks. In modulus switching the user can use
69     the parms_id to keep track of the chain of encryption parameters. The parms_id
70     is not exposed in the public API of EncryptionParameters, but can be accessed
71     through the SEALContext::ContextData class once the SEALContext has been created.
72 
73     @par Thread Safety
74     In general, reading from EncryptionParameters is thread-safe, while mutating
75     is not.
76 
77     @warning Choosing inappropriate encryption parameters may lead to an encryption
78     scheme that is not secure, does not perform well, and/or does not support the
79     input and computation of the desired application. We highly recommend consulting
80     an expert in RLWE-based encryption when selecting parameters, as this is where
81     inexperienced users seem to most often make critical mistakes.
82     */
83     class EncryptionParameters
84     {
85         friend class SEALContext;
86 
87         friend struct std::hash<EncryptionParameters>;
88 
89     public:
90         /**
91         Creates an empty set of encryption parameters.
92 
93         @param[in] scheme The encryption scheme to be used
94         @see scheme_type for the supported schemes
95         */
96         EncryptionParameters(scheme_type scheme = scheme_type::none) : scheme_(scheme)
97         {
98             compute_parms_id();
99         }
100 
101         /**
102         Creates an empty set of encryption parameters.
103 
104         @param[in] scheme The encryption scheme to be used
105         @throws std::invalid_argument if scheme is not supported
106         */
107         EncryptionParameters(std::uint8_t scheme)
108         {
109             // Check that a valid scheme is given
110             if (!is_valid_scheme(scheme))
111             {
112                 throw std::invalid_argument("unsupported scheme");
113             }
114 
115             scheme_ = static_cast<scheme_type>(scheme);
116             compute_parms_id();
117         }
118 
119         /**
120         Creates a copy of a given instance of EncryptionParameters.
121 
122         @param[in] copy The EncryptionParameters to copy from
123         */
124         EncryptionParameters(const EncryptionParameters &copy) = default;
125 
126         /**
127         Overwrites the EncryptionParameters instance with a copy of a given instance.
128 
129         @param[in] assign The EncryptionParameters to copy from
130         */
131         EncryptionParameters &operator=(const EncryptionParameters &assign) = default;
132 
133         /**
134         Creates a new EncryptionParameters instance by moving a given instance.
135 
136         @param[in] source The EncryptionParameters to move from
137         */
138         EncryptionParameters(EncryptionParameters &&source) = default;
139 
140         /**
141         Overwrites the EncryptionParameters instance by moving a given instance.
142 
143         @param[in] assign The EncryptionParameters to move from
144         */
145         EncryptionParameters &operator=(EncryptionParameters &&assign) = default;
146 
147         /**
148         Sets the degree of the polynomial modulus parameter to the specified value.
149         The polynomial modulus directly affects the number of coefficients in
150         plaintext polynomials, the size of ciphertext elements, the computational
151         performance of the scheme (bigger is worse), and the security level (bigger
152         is better). In Microsoft SEAL the degree of the polynomial modulus must be
153         a power of 2 (e.g.  1024, 2048, 4096, 8192, 16384, or 32768).
154 
155         @param[in] poly_modulus_degree The new polynomial modulus degree
156         @throws std::logic_error if a valid scheme is not set and poly_modulus_degree
157         is non-zero
158         */
159         inline void set_poly_modulus_degree(std::size_t poly_modulus_degree)
160         {
161             if (scheme_ == scheme_type::none && poly_modulus_degree)
162             {
163                 throw std::logic_error("poly_modulus_degree is not supported for this scheme");
164             }
165 
166             // Set the degree
167             poly_modulus_degree_ = poly_modulus_degree;
168 
169             // Re-compute the parms_id
170             compute_parms_id();
171         }
172 
173         /**
174         Sets the coefficient modulus parameter. The coefficient modulus consists
175         of a list of distinct prime numbers, and is represented by a vector of
176         Modulus objects. The coefficient modulus directly affects the size
177         of ciphertext elements, the amount of computation that the scheme can
178         perform (bigger is better), and the security level (bigger is worse). In
179         Microsoft SEAL each of the prime numbers in the coefficient modulus must
180         be at most 60 bits, and must be congruent to 1 modulo 2*poly_modulus_degree.
181 
182         @param[in] coeff_modulus The new coefficient modulus
183         @throws std::logic_error if a valid scheme is not set and coeff_modulus is
184         is non-empty
185         @throws std::invalid_argument if size of coeff_modulus is invalid
186         */
187         inline void set_coeff_modulus(const std::vector<Modulus> &coeff_modulus)
188         {
189             // Check that a scheme is set
190             if (scheme_ == scheme_type::none)
191             {
192                 if (!coeff_modulus.empty())
193                 {
194                     throw std::logic_error("coeff_modulus is not supported for this scheme");
195                 }
196             }
197             else if (coeff_modulus.size() > SEAL_COEFF_MOD_COUNT_MAX || coeff_modulus.size() < SEAL_COEFF_MOD_COUNT_MIN)
198             {
199                 throw std::invalid_argument("coeff_modulus is invalid");
200             }
201 
202             coeff_modulus_ = coeff_modulus;
203 
204             // Re-compute the parms_id
205             compute_parms_id();
206         }
207 
208         /**
209         Sets the plaintext modulus parameter. The plaintext modulus is an integer
210         modulus represented by the Modulus class. The plaintext modulus
211         determines the largest coefficient that plaintext polynomials can represent.
212         It also affects the amount of computation that the scheme can perform
213         (bigger is worse). In Microsoft SEAL the plaintext modulus can be at most
214         60 bits long, but can otherwise be any integer. Note, however, that some
215         features (e.g. batching) require the plaintext modulus to be of a particular
216         form.
217 
218         @param[in] plain_modulus The new plaintext modulus
219         @throws std::logic_error if scheme is not scheme_type::BFV and plain_modulus
220         is non-zero
221         */
222         inline void set_plain_modulus(const Modulus &plain_modulus)
223         {
224             // Check that scheme is BFV
225             if (scheme_ != scheme_type::bfv && !plain_modulus.is_zero())
226             {
227                 throw std::logic_error("plain_modulus is not supported for this scheme");
228             }
229 
230             plain_modulus_ = plain_modulus;
231 
232             // Re-compute the parms_id
233             compute_parms_id();
234         }
235 
236         /**
237         Sets the plaintext modulus parameter. The plaintext modulus is an integer
238         modulus represented by the Modulus class. This constructor instead
239         takes a std::uint64_t and automatically creates the Modulus object.
240         The plaintext modulus determines the largest coefficient that plaintext
241         polynomials can represent. It also affects the amount of computation that
242         the scheme can perform (bigger is worse). In Microsoft SEAL the plaintext
243         modulus can be at most 60 bits long, but can otherwise be any integer. Note,
244         however, that some features (e.g. batching) require the plaintext modulus
245         to be of a particular form.
246 
247         @param[in] plain_modulus The new plaintext modulus
248         @throws std::invalid_argument if plain_modulus is invalid
249         */
250         inline void set_plain_modulus(std::uint64_t plain_modulus)
251         {
252             set_plain_modulus(Modulus(plain_modulus));
253         }
254 
255         /**
256         Sets the random number generator factory to use for encryption. By default,
257         the random generator is set to UniformRandomGeneratorFactory::default_factory().
258         Setting this value allows a user to specify a custom random number generator
259         source.
260 
261         @param[in] random_generator Pointer to the random generator factory
262         */
263         inline void set_random_generator(std::shared_ptr<UniformRandomGeneratorFactory> random_generator) noexcept
264         {
265             random_generator_ = std::move(random_generator);
266         }
267 
268         /**
269         Returns the encryption scheme type.
270         */
271         SEAL_NODISCARD inline scheme_type scheme() const noexcept
272         {
273             return scheme_;
274         }
275 
276         /**
277         Returns the degree of the polynomial modulus parameter.
278         */
279         SEAL_NODISCARD inline std::size_t poly_modulus_degree() const noexcept
280         {
281             return poly_modulus_degree_;
282         }
283 
284         /**
285         Returns a const reference to the currently set coefficient modulus parameter.
286         */
287         SEAL_NODISCARD inline auto coeff_modulus() const noexcept -> const std::vector<Modulus> &
288         {
289             return coeff_modulus_;
290         }
291 
292         /**
293         Returns a const reference to the currently set plaintext modulus parameter.
294         */
295         SEAL_NODISCARD inline const Modulus &plain_modulus() const noexcept
296         {
297             return plain_modulus_;
298         }
299 
300         /**
301         Returns a pointer to the random number generator factory to use for encryption.
302         */
303         SEAL_NODISCARD inline auto random_generator() const noexcept -> std::shared_ptr<UniformRandomGeneratorFactory>
304         {
305             return random_generator_;
306         }
307 
308         /**
309         Compares a given set of encryption parameters to the current set of
310         encryption parameters. The comparison is performed by comparing the
311         parms_ids of the parameter sets rather than comparing the parameters
312         individually.
313 
314         @parms[in] other The EncryptionParameters to compare against
315         */
316         SEAL_NODISCARD inline bool operator==(const EncryptionParameters &other) const noexcept
317         {
318             return (parms_id_ == other.parms_id_);
319         }
320 
321         /**
322         Compares a given set of encryption parameters to the current set of
323         encryption parameters. The comparison is performed by comparing
324         parms_ids of the parameter sets rather than comparing the parameters
325         individually.
326 
327         @parms[in] other The EncryptionParameters to compare against
328         */
329         SEAL_NODISCARD inline bool operator!=(const EncryptionParameters &other) const noexcept
330         {
331             return (parms_id_ != other.parms_id_);
332         }
333 
334         /**
335         Returns an upper bound on the size of the EncryptionParameters, as if it
336         was written to an output stream.
337 
338         @param[in] compr_mode The compression mode
339         @throws std::invalid_argument if the compression mode is not supported
340         @throws std::logic_error if the size does not fit in the return type
341         */
342         SEAL_NODISCARD inline std::streamoff save_size(
343             compr_mode_type compr_mode = Serialization::compr_mode_default) const
344         {
345             std::size_t coeff_modulus_total_size =
346                 coeff_modulus_.empty()
347                     ? std::size_t(0)
348                     : util::safe_cast<std::size_t>(coeff_modulus_[0].save_size(compr_mode_type::none));
349             coeff_modulus_total_size = util::mul_safe(coeff_modulus_total_size, coeff_modulus_.size());
350 
351             std::size_t members_size = Serialization::ComprSizeEstimate(
352                 util::add_safe(
353                     sizeof(scheme_),
354                     sizeof(std::uint64_t), // poly_modulus_degree_
355                     sizeof(std::uint64_t), // coeff_modulus_size
356                     coeff_modulus_total_size,
357                     util::safe_cast<std::size_t>(plain_modulus_.save_size(compr_mode_type::none))),
358                 compr_mode);
359 
360             return util::safe_cast<std::streamoff>(util::add_safe(sizeof(Serialization::SEALHeader), members_size));
361         }
362 
363         /**
364         Saves EncryptionParameters to an output stream. The output is in binary
365         format and is not human-readable. The output stream must have the "binary"
366         flag set.
367 
368         @param[out] stream The stream to save the EncryptionParameters to
369         @param[in] compr_mode The desired compression mode
370         @throws std::invalid_argument if the compression mode is not supported
371         @throws std::logic_error if the data to be saved is invalid, or if
372         compression failed
373         @throws std::runtime_error if I/O operations failed
374         */
375         inline std::streamoff save(
376             std::ostream &stream, compr_mode_type compr_mode = Serialization::compr_mode_default) const
377         {
378             using namespace std::placeholders;
379             return Serialization::Save(
380                 std::bind(&EncryptionParameters::save_members, this, _1), save_size(compr_mode_type::none), stream,
381                 compr_mode, false);
382         }
383 
384         /**
385         Loads EncryptionParameters from an input stream overwriting the current
386         EncryptionParameters.
387 
388         @param[in] stream The stream to load the EncryptionParameters from
389         @throws std::logic_error if the data cannot be loaded by this version of
390         Microsoft SEAL, if the loaded data is invalid or if decompression failed
391         @throws std::runtime_error if I/O operations failed
392         */
393         inline std::streamoff load(std::istream &stream)
394         {
395             using namespace std::placeholders;
396             EncryptionParameters new_parms(scheme_type::none);
397             auto in_size =
398                 Serialization::Load(std::bind(&EncryptionParameters::load_members, &new_parms, _1, _2), stream, false);
399             std::swap(*this, new_parms);
400             return in_size;
401         }
402 
403         /**
404         Saves EncryptionParameters to a given memory location. The output is in
405         binary format and is not human-readable.
406 
407         @param[out] out The memory location to write the EncryptionParameters to
408         @param[in] size The number of bytes available in the given memory location
409         @param[in] compr_mode The desired compression mode
410         @throws std::invalid_argument if out is null or if size is too small to
411         contain a SEALHeader, or if the compression mode is not supported
412         @throws std::logic_error if the data to be saved is invalid, or if
413         compression failed
414         @throws std::runtime_error if I/O operations failed
415         */
416         inline std::streamoff save(
417             seal_byte *out, std::size_t size, compr_mode_type compr_mode = Serialization::compr_mode_default) const
418         {
419             using namespace std::placeholders;
420             return Serialization::Save(
421                 std::bind(&EncryptionParameters::save_members, this, _1), save_size(compr_mode_type::none), out, size,
422                 compr_mode, false);
423         }
424 
425         /**
426         Loads EncryptionParameters from a given memory location overwriting the
427         current EncryptionParameters.
428 
429         @param[in] in The memory location to load the EncryptionParameters from
430         @param[in] size The number of bytes available in the given memory location
431         @throws std::invalid_argument if in is null or if size is too small to
432         contain a SEALHeader
433         @throws std::logic_error if the data cannot be loaded by this version of
434         Microsoft SEAL, if the loaded data is invalid, or if decompression failed
435         @throws std::runtime_error if I/O operations failed
436         */
437         inline std::streamoff load(const seal_byte *in, std::size_t size)
438         {
439             using namespace std::placeholders;
440             EncryptionParameters new_parms(scheme_type::none);
441             auto in_size = Serialization::Load(
442                 std::bind(&EncryptionParameters::load_members, &new_parms, _1, _2), in, size, false);
443             std::swap(*this, new_parms);
444             return in_size;
445         }
446 
447         /**
448         Enables access to private members of seal::EncryptionParameters for SEAL_C.
449         */
450         struct EncryptionParametersPrivateHelper;
451 
452     private:
453         /**
454         Helper function to determine whether given std::uint8_t represents a valid
455         value for scheme_type. The return value will be false is the scheme is set
456         to scheme_type::none.
457         */
458         SEAL_NODISCARD bool is_valid_scheme(std::uint8_t scheme) const noexcept
459         {
460             switch (scheme)
461             {
462             case static_cast<std::uint8_t>(scheme_type::none):
463                 /* fall through */
464 
465             case static_cast<std::uint8_t>(scheme_type::bfv):
466                 /* fall through */
467 
468             case static_cast<std::uint8_t>(scheme_type::ckks):
469                 return true;
470             }
471             return false;
472         }
473 
474         /**
475         Returns the parms_id of the current parameters. This function is intended
476         for internal use.
477         */
478         SEAL_NODISCARD inline auto &parms_id() const noexcept
479         {
480             return parms_id_;
481         }
482 
483         void compute_parms_id();
484 
485         void save_members(std::ostream &stream) const;
486 
487         void load_members(std::istream &stream, SEALVersion version);
488 
489         MemoryPoolHandle pool_ = MemoryManager::GetPool();
490 
491         scheme_type scheme_;
492 
493         std::size_t poly_modulus_degree_ = 0;
494 
495         std::vector<Modulus> coeff_modulus_{};
496 
497         std::shared_ptr<UniformRandomGeneratorFactory> random_generator_{ nullptr };
498 
499         Modulus plain_modulus_{};
500 
501         parms_id_type parms_id_ = parms_id_zero;
502     };
503 } // namespace seal
504 
505 /**
506 Specializes the std::hash template for parms_id_type.
507 */
508 namespace std
509 {
510     template <>
511     struct hash<seal::parms_id_type>
512     {
513         std::size_t operator()(const seal::parms_id_type &parms_id) const
514         {
515             std::uint64_t result = 17;
516             result = 31 * result + parms_id[0];
517             result = 31 * result + parms_id[1];
518             result = 31 * result + parms_id[2];
519             result = 31 * result + parms_id[3];
520             return static_cast<std::size_t>(result);
521         }
522     };
523 
524     template <>
525     struct hash<seal::EncryptionParameters>
526     {
527         std::size_t operator()(const seal::EncryptionParameters &parms) const
528         {
529             hash<seal::parms_id_type> parms_id_hash;
530             return parms_id_hash(parms.parms_id_);
531         }
532     };
533 } // namespace std
534