Skip to content

Commit 955e04d

Browse files
authored
Merge pull request #408 from helibproject/master
Public Dev Sync-5 December 2020, CKKS mitigation
2 parents f8230cd + f1df95a commit 955e04d

File tree

23 files changed

+289
-270
lines changed

23 files changed

+289
-270
lines changed

CHANGES.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
HElib 1.3.0, December 2020
2+
=========================
3+
(tagged as v1.3.0)
4+
5+
November-December 2020
6+
---------------------
7+
* Mitigation for the CKKS vulnerability recently [observed](https://eprint.iacr.org/2020/1533.pdf) by Li and Micciancio
8+
* Context Builder unsing builder pattern
9+
* HElib now requires C++17
10+
111
HElib 1.2.0, November 2020
212
=========================
313
(tagged as v1.2.0)

CKKS-security.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Security of Approximate-Numbers Homomorphic Encryption
2+
3+
Security of standard (not homomorphic) encryption scheme is fairly well understood nowadays, and it usually comes in two flavors: An encryption scheme may only resists passive attackers (that can view encrypted data but not manipulate it), or it can also resist active attackers that can manipulate the encrypted data and then observe how it is decrypted.
4+
The crypto lingo for the weaker (passive) notion is [CPA-security](https://en.wikipedia.org/wiki/Chosen-plaintext_attack), while the stronger is called [CCA-security](https://en.wikipedia.org/wiki/Chosen-ciphertext_attack). (These acronyms stand for chosen-plaintext-attacks and chosen-ciphertext-attacks, respectively.)
5+
It is well understood that to protect data at rest or in transit, one must use CCA-secure encryption. CPA-security may suffice when the encryption is used as one component in a larger system, and protection against active attackers is provided by other components in that system.
6+
7+
For homomorphic encryption (HE) schemes, it is well known that they inherently cannot be CCA secure. One consequence of their non-CCA-security is that the system must ensure that decryption is never applied to invalid ciphertexts.<sup>[1](#validCtxt)</sup> Indeed, with contemporary HE schemes, allowing the attacker to submit invalid ciphertexts to be decrypted would typically result in exposure of the secret key. It is thus a commonly accepted practice for HE schemes to make do with CPA security, and rely on the system around the HE to provide the extra protection that may be needed.
8+
9+
Recently, Li and Micciancio [observed](https://eprint.iacr.org/2020/1533) that for *approximate-number* HE schemes, the common notion of CPA security may not be enough even against passive attackers. The crucial difference is that since the scheme itself adds some error, the attacker could learn something from the decryption result *even if it knows what the decrypted result was supposed to be*. Specifically, it learns the error, which may leak information about the secret key. Li and Micciancio described simple attacks on the CKKS approximate-numbers scheme that expose the secret key after seeing a small number of decryption results (sometimes as few as a single decryption).
10+
11+
## Mitigating the Li-Micciancio attacks
12+
13+
In response to this observation, HElib now includes countermeasures to mitigate this risk. In particular, the HElib decryption routine for CKKS was modified to add some key-independent noise, masking the key-dependent noise and making the Li-Micciancio attacks harder to mount. This extra noise, however, reduces the accuracy of the decrypted result. By default, the magnitude of this key-independent noise is chosen equal to the noise-bound that HElib keeps for the ciphertext to be decrypted, but that bound may be over-conservative and hence the added noise could sometimes cause a significant decrease in accuracy. HElib therefore provides the application the means to specify the required accuracy on decryption, and will then add the largest amount of noise subject to this accuracy constraint.
14+
15+
Applications that use CKKS need to walk a fine line, balancing security, accuracy, and performance. Below we describe a framework for developing CCKS-based applications while taking these aspect into account. Roughly, we recommend that the application try to get a tight estimate for the error, and then use that estimate instead of the noise bound that HElib computes.
16+
17+
1. Start by identifying the (class of) processes that the application needs to apply to the data;
18+
2. Identify the *required precision* from the processed results;
19+
3. Run these processes in HElib on test data to determine the parameters to be used, roughly as follows:
20+
1. Find some parameter setting where `context.securityLevel()` returns high enough security and the library does not report decryption errors;
21+
2. Experimentally find a fairly tight bound on the noise magnitude of the decrypted results, when called with the accuracy parameter from Step 2. This can be done by running HElib processing many times and comparing the decrypted results to what you get when applying the same processing to plaintext data;
22+
3. Compare the noise bounds that you get from Step 3.2 above with the estimated error that HElib computes just prior to decryption, which can be accessed via `ctxt.errorBound()`. If the experimental noise is significantly smaller than what `ctxt.errorBound()` returns then use `ctxt.bumpNoiseBound()` prior to decryption to force HElib to use the tighter error estimate.
23+
4. Repeat Steps 3.1-3.3, increasing the parameters until decryption no longer emits a warning about the added noise being too small.
24+
25+
We now elaborate more on each of these steps.
26+
27+
### Steps 1-2, Processes and Required Precision
28+
29+
As with any application of HE, the starting point is ensuring adequate functionality. The developers must therefore determine the operations to be computed and the required *output precision*. Some applications only need to compute a single function while others need to handle a wider class of functions, but at the very least the developers should determine the maximum depth of supported circuits and the maximum precision required.
30+
31+
In HElib, the application gets to specify the *input precision* for encrypting plaintext data, and each level in the circuit reduces the precision by one (or upto 1.5) bits, namely the error roughly doubles for each level. For example using the helper class `PtxtArray` one could call `ptxt.encrypt(ctxt, ptxtMagnitude, precision)`,<sup>[2](#optPrecision)</sup> which would result in encryption of the given input plaintext with error bounded by 2<sup>-precision</sup>.
32+
After evaluating a depth-three circuit (say), the error will grow to a little more than 2<sup>3-precision</sup>. An application requiring *p* bits of output precision after the evaluation of a depth-*d* circuit should then encrypt with input precision somewhere between *p+d* and *p+3d/2*.
33+
34+
### Step 3, Finding Matching Parameters
35+
36+
After determining the required depth *d* and output precision *p*, the application developers need to find some parameters that yield this output precision while ensuring security. This typically involves an iterative trial-and-error procedure, as follows:
37+
38+
1. Begin with some rough estimate of the parameters: The total bitlength of the modulus needed for a depth-*d* circuit is typically something like *22(d+1)*, and the ring dimension needed to get 128-bit security with this modulus is around *750(d+1)* (rounded up to a power of two, since the current CKKS support in HElib was only tested with power-of-two rings).
39+
40+
Denote by *m* the next power of two larger than *750(d+1)*, then these initial parameters can be set in HElib by using the `ContextBuilder` class, setting
41+
```
42+
Context context =
43+
ContextBuilder<CKKS>().m(m).bits(16*(d+1)).precision(p+d).c(3).build();
44+
```
45+
Note that the number of bits specified above is only *16(d+1)* rather than *22(d+1)*, and there is a mysterious additional parameter *c=3* satisfying *(c+1)/c ~ 22/16*. The quantity *bits=16(d+1)* is an estimate for what's needed for a depth-*d* circuit, and the total modulus bitsize in HElib is obtained roughly as *bits&middot;(c+1)/c*. (See discussion of *ciphertext vs. special primes* in the [HElib document](https://eprint.iacr.org/2020/1481), section 5.1.) One can get slightly smaller total bitsize for the same functionality by increasing the *c* parameter from its default value of *c=3*, but the keysize and running time grow almost linearly with *c*.
46+
47+
After setting these parameters, run HElib processing and check if the library complains about possible decryption errors, and use these experiments to increase or decrease the *bits* and *m* parameters. You should also use the interface `context.securityLevel()` to check that this combination of parameters *m,bits,precision* and *c* yields an acceptable level of security.
48+
49+
2. Check that these parameters indeed yield the required output precision. Namely run several experiments of HElib processing, decrypting the result with the optional precision argument (e.g., using the interface `ptxt.rawDecrypt(ctxt, secretKey)` of the `ptxtArray` class), and comparing the outcome with the result of plaintext processing. (At this point you should ignore any warning from HEib about the added noise being too small.) Revisit the parameters from Step 1 above until both security and output precision are acceptable.
50+
51+
3. Next, check the error bound that HElib reports via `ctxt.errorBound()` just prior to decryption, to the actual error that was observed in the previous step (which presumably is no more than *2<sup>-p</sup>*). If the HElib estimate is significantly larger than the actual noise, then use `ctxt.bumpNoiseBound()` prior to decryption to force HElib to use the "right estimate".
52+
53+
4. At this point, you need to switch to using `ptxt.decrypt(ctxt, secretKey, p)` and check that HElib no longer emits a warning message on decryption about the added noise being too small. If you still see that warning, then you need to revisit the parameter setting, increasing the *bits* parameter (and other parameters as needed to maintain security and precision), until this warning is no longer displayed.
54+
55+
As a final comment, we remark that this procedure is only adequate for cases where the threat model include the adversary only getting access to a handful of decryption results. If the total amount of information available to the adversary about decryption queries is much larger, then the application needs to increase the *bits* parameter from above. In particular, to withstand an attack that has access to the results from decryption of *D* ciphertexts, the *bits* parameter should be increased roughly by a factor of *<math><msqrt><mi>D</mi></msqrt></math>*, and the input-precision parameter should similarly be increased to *<math><msqrt><mi>D</mi></msqrt>(p+d)</math>*.
56+
57+
---------------------------------------
58+
<a name="validCtxt"><sup>1</sup></a>
59+
In HElib, a ciphertext is *valid* if it was produced using the library's encryption and evaluation routines and does not contain too much noise. In particular, decryption using should not emit a warning about "decrypting with too much noise".
60+
61+
<a name="optPrecision"><sup>2</sup></a>
62+
The `ptxtMagnitude`, and `precision` arguments are optional, with defaults that depend on the context and the actual plaintext to be encrypted. Importantly, the precision (if specified) indicates an absolute number, *not* a fraction of the plaintext magnitude. For example calling with `precision=3` yields an error of magnitude 1/8, whether it is called with `ptxtMagnitude=16`, and `ptxtMagnitude=1`. Finally note that the `precision` arguments can also be negative, indicating error magnitude larger than one.
63+

CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ if (NOT (CMAKE_SIZEOF_VOID_P EQUAL 8))
1919
message(FATAL_ERROR "HElib requires a 64-bit architecture.")
2020
endif ()
2121

22-
# Use -std=c++14 as default.
23-
set(CMAKE_CXX_STANDARD 14)
22+
# Use -std=c++17 as default.
23+
set(CMAKE_CXX_STANDARD 17)
2424
# Disable C++ extensions
2525
set(CMAKE_CXX_EXTENSIONS OFF)
2626
# Require full C++ standard

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ the [Smart-Vercauteren][2] ciphertext packing techniques and
1212
the [Gentry-Halevi-Smart][3] optimizations. See [this report][7] for a
1313
description of a few of the algorithms using in this library.
1414

15+
Please refer to [CKKS-security.md](CKKS-security.md) for the latest
16+
discussion on the security of the the CKKS scheme implementation in HElib.
17+
1518
Since mid-2018 HElib has been under extensive refactoring for *Reliability*,
1619
*Robustness & Serviceability*, *Performance*, and most importantly *Usability*
1720
for researchers and developers working on HE and its uses.

benchmarks/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212

1313
cmake_minimum_required(VERSION 3.10.2 FATAL_ERROR)
1414

15-
## Use -std=c++14 as default.
16-
set(CMAKE_CXX_STANDARD 14)
15+
## Use -std=c++17 as default.
16+
set(CMAKE_CXX_STANDARD 17)
1717
## Disable C++ extensions
1818
set(CMAKE_CXX_EXTENSIONS OFF)
1919
## Require full C++ standard

examples/BGV_binary_arithmetic/CMakeLists.txt

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,6 @@
99
# See the License for the specific language governing permissions and
1010
# limitations under the License. See accompanying LICENSE file.
1111

12-
cmake_minimum_required(VERSION 3.10.2 FATAL_ERROR)
13-
14-
# Use -std=c++14 as default.
15-
set(CMAKE_CXX_STANDARD 14)
16-
# Disable C++ extensions
17-
set(CMAKE_CXX_EXTENSIONS OFF)
18-
# Require full C++ standard
19-
set(CMAKE_CXX_STANDARD_REQUIRED ON)
20-
21-
project(BGV_binary_arithmetic LANGUAGES CXX)
22-
23-
find_package(helib 1.1.0 EXACT REQUIRED)
24-
2512
add_executable(BGV_binary_arithmetic BGV_binary_arithmetic.cpp)
2613

2714
target_link_libraries(BGV_binary_arithmetic helib)

examples/BGV_country_db_lookup/CMakeLists.txt

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,6 @@
99
# See the License for the specific language governing permissions and
1010
# limitations under the License. See accompanying LICENSE file.
1111

12-
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
13-
14-
## Use -std=c++14 as default.
15-
set(CMAKE_CXX_STANDARD 14)
16-
## Disable C++ extensions
17-
set(CMAKE_CXX_EXTENSIONS OFF)
18-
## Require full C++ standard
19-
set(CMAKE_CXX_STANDARD_REQUIRED ON)
20-
21-
project(BGV_country_db_lookup
22-
LANGUAGES CXX)
23-
24-
find_package(helib 1.1.0 EXACT REQUIRED)
25-
2612
add_executable(BGV_country_db_lookup BGV_country_db_lookup.cpp)
2713

2814
target_link_libraries(BGV_country_db_lookup helib)
29-

examples/BGV_packed_arithmetic/CMakeLists.txt

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,6 @@
99
# See the License for the specific language governing permissions and
1010
# limitations under the License. See accompanying LICENSE file.
1111

12-
cmake_minimum_required(VERSION 3.10.2 FATAL_ERROR)
13-
14-
# Use -std=c++14 as default.
15-
set(CMAKE_CXX_STANDARD 14)
16-
# Disable C++ extensions
17-
set(CMAKE_CXX_EXTENSIONS OFF)
18-
# Require full C++ standard
19-
set(CMAKE_CXX_STANDARD_REQUIRED ON)
20-
21-
project(BGV_packed_arithmetic LANGUAGES CXX)
22-
23-
find_package(helib 1.1.0 EXACT REQUIRED)
24-
2512
add_executable(BGV_packed_arithmetic BGV_packed_arithmetic.cpp)
2613

2714
target_link_libraries(BGV_packed_arithmetic helib)

examples/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111

1212
cmake_minimum_required(VERSION 3.10.2 FATAL_ERROR)
1313

14-
## Use -std=c++14 as default.
15-
set(CMAKE_CXX_STANDARD 14)
14+
## Use -std=c++17 as default.
15+
set(CMAKE_CXX_STANDARD 17)
1616
## Disable C++ extensions
1717
set(CMAKE_CXX_EXTENSIONS OFF)
1818
## Require full C++ standard

include/helib/Context.h

Lines changed: 51 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@
2727

2828
#include <NTL/Lazy.h>
2929

30-
#define FHE_DISABLE_CONTEXT_CONSTRUCTOR
31-
3230
namespace helib {
3331

3432
constexpr int MIN_SK_HWT = 120;
@@ -38,7 +36,7 @@ constexpr int BOOT_DFLT_SK_HWT = MIN_SK_HWT;
3836
* @brief An estimate for the security-level. This has a lower bound of 0.
3937
* @param n LWE dimension.
4038
* @param log2AlphaInv Variable containing the value of `log(1/alpha)` where
41-
* `alpha` is the noise.
39+
* `alpha` is the noise/modulus ratio.
4240
* @param hwt The Hamming weight.
4341
* @return The estimated security level.
4442
* @note This function uses experimental affine approximations to the
@@ -137,15 +135,28 @@ class ContextBuilder;
137135
**/
138136
class Context
139137
{
138+
private:
139+
template <typename SCHEME>
140+
friend class ContextBuilder;
141+
142+
// Forward declarations of useful param structs for Context and
143+
// ContextBuilder.
144+
struct ModChainParams;
145+
struct BootStrapParams;
146+
140147
std::vector<Cmodulus> moduli; // Cmodulus objects for the different primes
141148
// This is private since the implementation assumes that the list of
142149
// primes only grows and no prime is ever modified or removed.
143150

144-
public:
145-
// Here are some "getter" methods that give direct
146-
// access to important parameters. These are for convenience,
147-
// as well as allowing for future re-organization.
151+
Context(long m,
152+
long p,
153+
long r,
154+
const std::vector<long>& gens,
155+
const std::vector<long>& ords,
156+
const std::optional<ModChainParams>& mparams,
157+
const std::optional<BootStrapParams>& bparams);
148158

159+
public:
149160
// Parameters stored in zMStar.
150161
// These are invariant for any computations involving this Context
151162

@@ -512,49 +523,33 @@ class Context
512523
const std::vector<long>& gens = std::vector<long>(),
513524
const std::vector<long>& ords = std::vector<long>());
514525

515-
// FIXME: This is a temporary fix to allow proper copy of the context.
516-
// Without the fixes there would be discrepancies between context's zMStar and
517-
// alMod const reference one.
518-
// TODO: Add doxygen comments to the following methods.
519526
/**
520527
* @brief Default destructor.
521528
**/
522529
~Context() = default;
523530

524-
#ifdef FHE_DISABLE_CONTEXT_CONSTRUCTOR
525531
/**
526-
* @brief Default copy constructor.
532+
* @brief Deleted copy constructor.
527533
* @param other `Context` to copy.
528534
**/
529535
Context(const Context& other) = delete;
530536

531537
/**
532-
* @brief Default move constructor.
533-
* @param other `Context` to copy.
538+
* @brief Deleted move constructor.
539+
* @param other `Context` to move.
534540
**/
535541
Context(Context&& other) = delete;
536542

537-
template <typename SCHEME>
538-
explicit Context(const ContextBuilder<SCHEME>&);
539-
// Marked explicit to avoid dangerous implicit conversions.
540-
541-
#else
542-
543543
/**
544-
* @brief Default copy constructor.
544+
* @brief Deleted copy assignment.
545545
* @param other `Context` to copy.
546546
**/
547-
Context(const Context& other);
547+
Context& operator=(const Context& other) = delete;
548548

549549
/**
550-
* @brief Default move constructor.
551-
* @param other `Context` to copy.
550+
* @brief Deleted move assignment.
551+
* @param other `Context` to move.
552552
**/
553-
Context(Context&& other);
554-
#endif
555-
556-
// Deleted assignment operators.
557-
Context& operator=(const Context& other) = delete;
558553
Context& operator=(Context&& other) = delete;
559554

560555
/**
@@ -967,6 +962,11 @@ class ContextBuilder
967962
"scheme (CKKS or BGV)");
968963

969964
private:
965+
// Helper for building
966+
const std::pair<std::optional<Context::ModChainParams>,
967+
std::optional<Context::BootStrapParams>>
968+
makeParamsArgs() const;
969+
970970
// Default values by scheme.
971971
struct default_values;
972972

@@ -1157,6 +1157,20 @@ class ContextBuilder
11571157
return *this;
11581158
}
11591159

1160+
/**
1161+
* @brief Sets `mvec` the unique primes which are factors of `m`.
1162+
* @param mvec A `std::vector` of primes factors.
1163+
* @return Reference to the `ContextBuilder` object.
1164+
* @note Only exists when the `SCHEME` is `BGV`.
1165+
**/
1166+
template <typename S = SCHEME,
1167+
std::enable_if_t<std::is_same<S, BGV>::value>* = nullptr>
1168+
ContextBuilder& mvec(const std::vector<long>& mvec)
1169+
{
1170+
mvec_ = convert<NTL::Vec<long>>(mvec);
1171+
return *this;
1172+
}
1173+
11601174
/**
11611175
* @brief Sets boostrapping to be `thin`.
11621176
* @return Reference to the `ContextBuilder` object.
@@ -1219,15 +1233,14 @@ class ContextBuilder
12191233
* `ContextBuilder` object.
12201234
* @return A `Context` object.
12211235
**/
1222-
#ifdef FHE_DISABLE_CONTEXT_CONSTRUCTOR
1223-
1224-
// compatibility interface
1225-
ContextBuilder& build() { return *this; }
1226-
1227-
friend class Context;
1228-
#else
12291236
Context build() const;
1230-
#endif
1237+
1238+
/**
1239+
* @brief Builds a `Context` object from the arguments stored in the
1240+
* `ContextBuilder` object.
1241+
* @return A raw pointer to a `Context` object.
1242+
**/
1243+
Context* buildPtr() const;
12311244

12321245
friend std::ostream& operator<<<SCHEME>(std::ostream& os,
12331246
const ContextBuilder& cb);

0 commit comments

Comments
 (0)