1 /* Copyright (C) 2020 HElib Project
2  * This program is Licensed under the Apache License, Version 2.0
3  * (the "License"); you may not use this file except in compliance
4  * with the License. You may obtain a copy of the License at
5  *   http://www.apache.org/licenses/LICENSE-2.0
6  * Unless required by applicable law or agreed to in writing, software
7  * distributed under the License is distributed on an "AS IS" BASIS,
8  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9  * See the License for the specific language governing permissions and
10  * limitations under the License. See accompanying LICENSE file.
11  */
12 
13 // This program suggests parameter-sets for CKKS, it takes as input the
14 // target bit-size of the modulus for fresh ciphertexts (which is loosely
15 // related to the bit-capacity of fresh ciphertexts), and potentially also
16 // target security level in the ranage 70-270 (default is 128) and target
17 // accuracy parameter in the range 8-32 (default is 12).
18 // It returns one or more suggestions for the parameters m, r, L, c to
19 // use to get this capacity and accuracy at the given security level.
20 // The parameters r, L will be roughly equal to the target accuracy and
21 // bitsize arguments in the input, but may not be exactly equal to them.
22 #include <cmath>
23 #include <iostream>
24 #include <helib/helib.h>
25 #include <helib/ArgMap.h>
26 
27 using namespace std;
28 
29 
30 struct CKKSparams {
31   int m, L, c;
32   int nCtxtPrimes;
33   double ctxtBits;
34 
35   // Look for the largest value of L (modulus bitlength) for the given
36   // m,c, and security, Returns true if snything is found, else false.
tryParametersCKKSparams37   bool tryParameters(int security, int theM, int c2try) {
38 
39     // Set the "accruacy parameter" r to 16, it should not have any effect
40     // The "plaintext space" parameter p is set to -1 for CKKS
41     helib::Context theContext(theM, /*p=*/-1, /*r=*/16);
42 
43     // Note: teContext has an empty modulus chain, we now try to build many
44     // chains with different values of L and check their security
45 
46     // Get largest modulus size (including special primes) for these value
47     // of m and security. The formula from Context.h is
48     //    security ~ 3.8*(phi(m)/log2(Q/sigma)) -20,
49     // we overshoot |Q| by ignoring sigma and the -20 and rounding up 3.8 to 4
50 
51     int phim = theContext.zMStar.getPhiM();
52     int totalBits = floor(double(4*phim)/security); // an over-estimate
53     while (helib::lweEstimateSecurity(phim, double(totalBits-3), /*hwt=*/0) < security)
54       totalBits--;
55 
56     if (totalBits < 50) // no point in doing this, can't suport even 50 bits
57       return false;
58 
59     // count down until you find something that actually works
60     this->L = 0;
61     this->ctxtBits = 0;
62     for (int L2try=totalBits; L2try>40; --L2try) {
63       // create a new context and build a chain for it
64       //helib::Context cntxt(theM, /*p=*/-1, /*r=*/16);
65       helib::Context& cntxt = theContext;
66       cntxt.clearModChain();
67       helib::buildModChain(cntxt, L2try, c2try);
68       if (cntxt.securityLevel() >= security) {
69         this->ctxtBits = cntxt.logOfProduct(cntxt.ctxtPrimes)/log(2.0);
70         this->L = L2try;
71         this->nCtxtPrimes = cntxt.ctxtPrimes.card();
72         break;
73       }
74     }
75     if (this->L==0) // nothing was found
76       return false;
77 
78     // Keep trying a few more values of L, since it is not clear that
79     // reducing L always reduces the actual number of bits
80     int betterL = 0;
81     for (int delta=1; delta<10; delta++) {
82       //helib::Context cntxt(theM, /*p=*/-1, /*r=*/16);
83       helib::Context& cntxt = theContext;
84       cntxt.clearModChain();
85       helib::buildModChain(cntxt, this->L -delta, c2try);
86       double realBits = cntxt.logOfProduct(cntxt.ctxtPrimes)/log(2.0);
87       if (realBits > this->ctxtBits) {
88         betterL = this->L -delta;
89         this->ctxtBits = realBits;
90         this->nCtxtPrimes = cntxt.ctxtPrimes.card();
91       }
92     }
93 
94     // Print a warning if a smaller L was found
95     if (betterL > 0) {
96       std::cerr << "** weird: L="<<betterL<<" gives more real bits than L="<<L
97         << " (m="<<theM<<", c="<<c2try <<')'<< std::endl;
98       L = betterL;
99     }
100 
101     // Record the m,c values that were used here
102     this->m = theM;
103     this->c = c2try;
104     return true;
105   }
106 };
107 
main(int argc,char * argv[])108 int main(int argc, char* argv[])
109 {
110   // get optional security level from command-line arguments
111   helib::ArgMap amap;
112   int sec = 128;
113   amap.arg("security", sec, "target security level in [70,270]");
114   amap.parse(argc, argv);
115 
116   // ensure that security level is in the range [70,270]
117   if (sec<70)       sec=70;
118   else if (sec>270) sec=270;
119 
120   // print resutls in CSV format
121   std::cout << "sec,m,c,L,bits" << std::endl;
122 
123   // Try power-of-two values of m from 2^11 upto 2^18
124   for (int m=2048; m<524288; m*=2) {
125     CKKSparams pprm; // pprm is "previous parameters"
126 
127     // Start with a ridicoulusly large value of c
128     if (!pprm.tryParameters(sec, m, /*c=*/100))
129       continue; // even this large c value does not work
130 
131     // get a more sensible upper-bound on c
132     int maxC = std::min(pprm.nCtxtPrimes, 100);
133     if (maxC == 1) // FIXME: this should not happen, but it does
134       continue;
135     if (maxC<100 && !pprm.tryParameters(sec, m, maxC))
136         continue; // even this large c value does not work
137 
138     // Try successvely smaller values of c, down to c=2. Print the
139     // previous params pprms every time the smaller c values results
140     // is less modulus bits
141     for (int c2try=maxC*0.86; c2try>1; c2try=0.86*c2try) {
142       CKKSparams prm;
143       if (prm.tryParameters(sec, m, c2try)) {
144         if (pprm.L > prm.L) // the smaller c values is worse than before
145           // print the previous (better) set of values
146           std::cout<<sec<<','<<pprm.m<<','<<pprm.c<<','<<pprm.L<<','
147             << round(pprm.ctxtBits)<<std::endl;
148         pprm = prm; // record the current values for next iteration
149       }
150       else { // c value too small, no solution
151         break;
152       }
153     }
154     // print the last value that was recorded
155     std::cout<<sec<<','<<pprm.m<<','<<pprm.c<<','<<pprm.L<<','
156       << round(pprm.ctxtBits)<<std::endl;
157   }
158   return 0;
159 }
160