├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md └── src ├── __init__.py ├── bfv.py ├── bgv.py ├── codegen.py ├── config.py ├── interactive.py └── util.py /.gitignore: -------------------------------------------------------------------------------- 1 | a.out 2 | bench.cpp 3 | main.cpp 4 | Makefile 5 | __pycache__/ 6 | *.txt 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | ENV DEBIAN_FRONTEND=noninteractive 4 | 5 | RUN apt-get update && apt-get install -y \ 6 | build-essential \ 7 | wget \ 8 | m4 \ 9 | python3-dev \ 10 | sagemath \ 11 | && apt-get clean \ 12 | && rm -rf /var/lib/apt/lists/* 13 | 14 | RUN sage -python3 -m pip install prettytable scipy sphinx==5.3.0 furo pytest pytest-cov 15 | 16 | WORKDIR /home/fhegen/ 17 | 18 | COPY . . -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Cryptography Research Centre - Technology Innovation Institute 4 | Copyright (c) 2022 Chair for Security Engineering - Ruhr University Bochum 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FheGEN: Parameters Generation for FHE Schemes 2 | 3 | [![BGV Parameters](https://img.shields.io/badge/BGV%20Parameters-Download-green)](https://eprint.iacr.org/2022/706.pdf) 4 | [![BFV Parameters](https://img.shields.io/badge/BFV%20Parameters-Download-red)](https://eprint.iacr.org/2023/600.pdf) 5 | [![Slack](https://img.shields.io/badge/slack-@fhegen-yellow.svg?logo=slack)](https://join.slack.com/t/fhegen/shared_invite/zt-2rtezhwty-i9h4Vmcc~Oiw0bSwgHxTMw) 6 | [![Docker Guide](https://img.shields.io/badge/Docker-2496ED?style=flat&logo=docker&logoColor=orange)](Dockerfile) 7 | 8 | --- 9 | 10 | ## Introduction 🎉 11 | 12 | FheGEN is a tool for generating cryptographic parameters for leveled Fully Homomorphic Encryption (FHE) schemes, specifically BGV and BFV. Developed through a collaboration between the **Crypto Research Center at the Technology Innovation Institute** and the **Chair of Security Engineering at Ruhr University Bochum**, FheGEN helps researchers and practitioners efficiently determine secure and optimized FHE parameters. 13 | 14 | ## Getting Started 🚀 15 | 16 | FheGEN can be run interactively from the terminal to generate customized encryption parameters. 17 | 18 | ### Prerequisites ✔️ 19 | 20 | - **[SageMath](https://doc.sagemath.org/html/en/installation/index.html)** and **[Python 3](https://www.python.org/downloads/)** must be installed. 21 | 22 | ### Running FheGEN ✈️ 23 | 24 | 1. Clone the repository: 25 | 26 | ```md 27 | git clone https://github.com/Crypto-TII/fhegen.git 28 | cd fhegen 29 | ``` 30 | 2. Execute the interactive script: 31 | 32 | ```md 33 | sage -python3 src/interactive.py 34 | ``` 35 | 36 | ## Docker Support 🐳 37 | 38 | FheGEN includes a pre-configured **Dockerfile** for seamless deployment. Users can build and run the containerized environment as needed. 39 | > 💡 **Note:** You must have [Docker installed](https://docs.docker.com/get-docker/) on your system to use this feature. 40 | 41 | ### Build the Docker Image 42 | 43 | To create the Docker image, navigate to the project root directory and execute: 44 | 45 | ```md 46 | docker build -t fhegen . 47 | ``` 48 | 49 | ### Launch an Interactive Container 50 | 51 | 52 | To run the Docker container interactively, use the following command: 53 | 54 | ```md 55 | docker run -it --name fhegen-dev --entrypoint /bin/bash fhegen 56 | ``` 57 | 58 | Once inside the container, manually run: 59 | 60 | ```md 61 | sage -python3 src/interactive.py 62 | ``` 63 | 64 | **Note:** Feel free to customize the Docker setup to suit your workflow such as adding volumes (via docker-compose), changing entrypoints, or applying other preferences. 65 | 66 | ## Usage 67 | 68 | FheGEN will prompt you to configure key parameters, including `circuit model`, `multiplicative depth`, `key-switching method`, and `library selection`. 69 | 70 | ### Example Output 71 | 72 | Let `R = Z [x]/(x^n+1)`, where `n` is a power of `2`. 73 | We denote the plaintext modulus by `t` and the ciphertext modulus by `q`, which are typically chosen such that `t << q`. 74 | 75 | ```md 76 | Generated your BFV configuration! 77 | model: Base 78 | sec: 116 79 | n: 4096 80 | t: 65537 81 | logq: 84 82 | logP: 35 83 | ``` 84 | 85 | In the case of BGV, the ciphertext modulus `q` is a product of primes: 86 | `q =p_0 * ... * p_{L-1}`. The multiplicative depth `M` determines the number of moduli, with `L = M + 1`. 87 | 88 | ```md 89 | Generated your BGV configuration! 90 | model: Base # Chosen circuit model (e.g., Base) 91 | sec: 137 # Estimated security level 92 | n: 4096 # Polynomial degree 93 | t: 65537 # Plaintext modulus 94 | logq: 65 (31, 30, 4) # Ciphertext modulus size, split into bottom, middle, and top moduli 95 | logP: 36 # Key-switching modulus size 96 | ``` 97 | 98 | #### Moduli Breakdown for the BGV scheme 99 | 100 | A homomorphic encryption circuit uses `multiple levels` to control noise growth via `modulus switching`. The ciphertext modulus `q` is a product of primes `p_i` (with `i in [0, L-1]`) split into three types: top, middle, and bottom modulus as follows: 101 | 102 | | Modulus Type | Description | 103 | | ---------------- |------------------------------------------------------------------------------------| 104 | | `Top Modulus` | The top modulus is the first modulus in the prime chain. It is applied immediately after encryption to reduce the initial noise to a smaller value `B`. | 105 | | `Middle Moduli` | After the arithmetic operations defined by the circuit model (see below), we reduce the noise back to `B` using the modulus switching procedure.| 106 | | `Bottom Modulus` | The bottom modulus is the last modulus in the prime chain. No key switching or modulus switching is applied after the final multiplication; decryption is done directly using the extended secret key. This ensures correctness without further noise reduction. | 107 | 108 | #### Circuit Models 109 | 110 | FheGEN supports different circuit models (as in Fig.1 of [[1](https://eprint.iacr.org/2022/706.pdf)]). 111 | The selected circuits perform a sequence of operations on `η` ciphertexts `c_i` in parallel. The resulting ciphertext is then homomorphically multiplied with another one, computed in the same way. This process is repeated M times. 112 | 113 | | Model Type | Description | 114 | |-----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------| 115 | | `Base Model` | Performs a constant multiplication on each ciphertext, then sums the results before the homomorphic multiplication | 116 | | `Model 1` | Extends the Base Model performing `τ` rotations *after* the constant multiplications | 117 | | `Model 2` | Extends the Base Model performing `τ` rotations *before* the constant multiplications | 118 | | `OpenFHE Model` | Inspired by OpenFHE, each ciphertext `c_i` is the result of a homomorphic multiplication. These `η` ciphertexts are then summed and rotated `τ` times | 119 | 120 | ### Library Selection 121 | 122 | The generated code is a setup routine for the chosen library and provides references to further code examples within the respective library. 123 | 124 | > #### ⚠️ Limitations 125 | > Some parameter sets may not be supported by all libraries due to internal constraints. For example, OpenFHE only supports plaintext moduli up to 60 bits. 126 | Choosing a larger modulus will result in code that may not compile or execute correctly. 127 | 128 | 129 | ## Security 130 | 131 | To enable faster and more flexible parameter selection, we empirically fine-tune a formula that links the security level `λ` to the dimension `n`, given the ciphertext modulus size (`log q`) and the secret distribution. The formula is based on the best-known attacks on lattice problems and provides a quick security estimate for parameter generation. 132 | 133 | A detailed security analysis of the formula is provided in [[1](https://eprint.iacr.org/2022/706.pdf)]. 134 | 135 | ## Get Help 136 | 137 | For questions or bug reports, please open an [issue](https://github.com/Crypto-TII/fhegen/issues) or contact us at `chiara.marcolla (at) tii.ae` or `johannes.mono (at) rub.de`. 138 | 139 | ## Cite 📖 140 | 141 | ``` 142 | @misc{cryptoeprint:2022/706, 143 | author = {Johannes Mono and Chiara Marcolla and Georg Land and Tim Güneysu and Najwa Aaraj}, 144 | title = {Finding and Evaluating Parameters for {BGV}}, 145 | howpublished = {Cryptology ePrint Archive, Paper 2022/706}, 146 | year = {2022}, 147 | note = {\url{https://eprint.iacr.org/2022/706}}, 148 | url = {https://eprint.iacr.org/2022/706} 149 | } 150 | 151 | @misc{cryptoeprint:2023/600, 152 | author = {Beatrice Biasioli and Chiara Marcolla and Marco Calderini and Johannes Mono}, 153 | title = {Improving and Automating {BFV} Parameters Selection: An Average-Case Approach}, 154 | howpublished = {Cryptology {ePrint} Archive, Paper 2023/600}, 155 | year = {2023}, 156 | url = {https://eprint.iacr.org/2023/600} 157 | } 158 | ``` 159 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | sys.path.append(os.path.dirname(os.path.realpath(__file__))) 5 | -------------------------------------------------------------------------------- /src/bfv.py: -------------------------------------------------------------------------------- 1 | import config 2 | import math 3 | import util 4 | 5 | 6 | def _f(x, sdist): 7 | if sdist == 'Ternary': 8 | alpha = 0.22 9 | beta = 2.43 10 | gamma = 8.95 11 | else: 12 | # TODO: sdist == 'Error' 13 | raise NotImplementedError("sdist == 'Error'") 14 | 15 | return -(1 / math.exp(alpha * x - beta)) + gamma 16 | 17 | 18 | def _Vclean(m, t, D, Vs, Ve): 19 | n = util.phi(m) 20 | return t**2 * n * Ve * (Vs + Ve) 21 | 22 | 23 | def _Vconst(const, m, t, D, Vs, Ve): 24 | if not const: 25 | return 1 26 | 27 | n = util.phi(m) 28 | return t**2 * n / 12 29 | 30 | 31 | def _Vmul(V, sdist, m, t, D, Vs, Ve): 32 | n = util.phi(m) 33 | return t**2 * n**2 * Vs / 6 * V * _f(2, sdist) 34 | 35 | 36 | def _Vscale(m, t, D, Vs, Ve): 37 | n = util.phi(m) 38 | return t**2 / 12 * (1 + n * Vs) 39 | 40 | 41 | def _Vswitch(m, t, D, Vs, Ve, method, L, beta, omega): 42 | n = util.phi(m) 43 | V = t**2 * n * Ve / 12 44 | 45 | f0 = { 46 | 'BV': beta**2 * math.log(1 << (L * config.BITS), beta), 47 | 'BV-RNS': beta**2 * L * math.log(1 << config.BITS, beta), 48 | 'GHS': 1 / config.K**2, 49 | 'GHS-RNS': L / pow(config.K, L**2), 50 | 'Hybrid': math.log(1 << (L * config.BITS), beta) / config.K**2, 51 | 'Hybrid-RNS': omega * L / pow(config.K, L**2) 52 | }[method] 53 | f1 = { 54 | 'BV': 0, 55 | 'BV-RNS': 0, 56 | 'GHS': 1, 57 | 'GHS-RNS': L**2, 58 | 'Hybrid': 1, 59 | 'Hybrid-RNS': math.ceil(L / omega)**2 60 | }[method] 61 | 62 | return f0 * V + f1 * _Vscale(m, t, D, Vs, Ve) 63 | 64 | 65 | def _Vlevel(V, ops, Bargs, kswargs, sdist): 66 | model = ops['model'] 67 | sums = ops['sums'] 68 | rots = ops['rots'] 69 | const = ops['const'] 70 | 71 | Vconst = _Vconst(const, **Bargs) 72 | Vswitch = _Vswitch(**Bargs, **kswargs) 73 | 74 | return { 75 | 'Base': sums * Vconst * V, 76 | 'Model1': sums * (Vconst * V + rots * Vswitch), 77 | 'Model2': sums * Vconst * (V + rots * Vswitch), 78 | 'OpenFHE': sums * V + rots * Vswitch 79 | }[model] 80 | 81 | 82 | def _logq(ops, Bargs, kswargs, sdist): 83 | muls = ops['muls'] 84 | D = Bargs['D'] 85 | 86 | V = _Vclean(**Bargs) 87 | for _ in range(muls): 88 | V = _Vlevel(V, ops, Bargs, kswargs, sdist) 89 | V = _Vmul(V, sdist, **Bargs) + _Vswitch(**Bargs, **kswargs) 90 | 91 | return util.clog2(D * math.sqrt(8 * V)) 92 | 93 | 94 | def _logP(logq, kswargs, K=config.K): 95 | method = kswargs['method'] 96 | beta = kswargs['beta'] 97 | omega = kswargs['omega'] 98 | L = kswargs['L'] 99 | 100 | logK = int(math.log2(K)) 101 | logb = int(math.log2(beta)) 102 | 103 | if method.startswith('BV'): 104 | return None 105 | elif method.startswith('GHS'): 106 | return logK + logq 107 | elif method == 'Hybrid': 108 | return logK + logb + int(math.log2(math.sqrt(logq / logb))) 109 | else: # method == 'Hybrid-RNS' 110 | return logK + int(math.log2(math.sqrt(omega * L))) + int(math.ceil(logq / omega)) 111 | 112 | 113 | def logqP(ops, Bargs, kswargs, sdist): 114 | logq = _logq(ops, Bargs, kswargs, sdist) 115 | logP = _logP(logq, kswargs) 116 | 117 | return [logq], logP 118 | -------------------------------------------------------------------------------- /src/bgv.py: -------------------------------------------------------------------------------- 1 | from sage.all import var 2 | import config 3 | import math 4 | import util 5 | 6 | 7 | def _Bclean(m, t, D, Vs, Ve): 8 | n = util.phi(m) 9 | return D * t * math.sqrt(n * (1 / 12 + 2 * n * Vs * Ve + Ve)) 10 | 11 | def _Bconst(const, m, t, D, Vs, Ve): 12 | if not const: 13 | return 1 14 | 15 | n = util.phi(m) 16 | return D * t * math.sqrt(n / 12) 17 | 18 | 19 | def _Bscale(m, t, D, Vs, Ve): 20 | n = util.phi(m) 21 | return D * t * math.sqrt(n / 12 * (1 + n * Vs)) 22 | 23 | 24 | def _Bswitch(m, t, D, Vs, Ve, method, L, beta, omega): 25 | n = util.phi(m) 26 | B = D * t * n * math.sqrt(Ve / 12) 27 | 28 | f0 = { 29 | 'BV': beta * math.sqrt(math.log(1 << (L * config.BITS), beta)), 30 | 'BV-RNS': beta * math.sqrt(L * math.log(1 << config.BITS, beta)), 31 | 'GHS': 1 / config.K, 32 | 'GHS-RNS': math.sqrt(L) / pow(config.K, L), 33 | 'Hybrid': math.sqrt(math.log(1 << (L * config.BITS), beta)) / config.K, 34 | 'Hybrid-RNS': math.sqrt(omega * L) / pow(config.K, L) 35 | }[method] 36 | f1 = { 37 | 'BV': 0, 38 | 'BV-RNS': 0, 39 | 'GHS': 1, 40 | 'GHS-RNS': L, 41 | 'Hybrid': 1, 42 | 'Hybrid-RNS': math.ceil(L / omega) 43 | }[method] 44 | 45 | return f0 * B + f1 * _Bscale(m, t, D, Vs, Ve) 46 | 47 | 48 | def _fscale(method, L, beta, omega): 49 | return math.sqrt({ 50 | 'BV': 1, 51 | 'BV-RNS': 1, 52 | 'GHS': 1, 53 | 'GHS-RNS': L, 54 | 'Hybrid': 1, 55 | 'Hybrid-RNS': math.ceil(L / omega) 56 | }[method]) 57 | 58 | 59 | def _B(ops, Bargs, kswargs): 60 | model = ops['model'] 61 | sums = ops['sums'] 62 | rots = ops['rots'] 63 | const = ops['const'] 64 | 65 | Bconst = _Bconst(const, **Bargs) 66 | Bscale = _Bscale(**Bargs) 67 | Bswitch = _Bswitch(**Bargs, **kswargs) 68 | fscale = _fscale(**kswargs) 69 | 70 | B = var('B') 71 | f = { 72 | 'Base': ((sums * Bconst * B)**2 + Bswitch) / (B - fscale * Bscale), 73 | 'Model1': (sums**2 * (Bconst * B + rots * Bswitch)**2 + Bswitch) / (B - fscale * Bscale), 74 | 'Model2': (sums**2 * Bconst**2 * (B + rots * Bswitch)**2 + Bswitch) / (B - fscale * Bscale), 75 | 'OpenFHE': (sums * B**2 + (rots + 1) * Bswitch) / (B - fscale * Bscale) 76 | }[model] 77 | return f.find_local_minimum(fscale * Bscale, 1 << config.BITS)[1] 78 | 79 | 80 | def _B0(ops, B, Bargs, kswargs, c=1): 81 | model = ops['model'] 82 | sums = ops['sums'] 83 | rots = ops['rots'] 84 | const = ops['const'] 85 | 86 | Bconst = _Bconst(const, **Bargs) 87 | Bswitch = _Bswitch(**Bargs, **kswargs) 88 | 89 | return { 90 | 'Base': 2 * c * (sums * Bconst * B)**2, 91 | 'Model1': 2 * c * sums**2 * (Bconst * B + rots * Bswitch)**2, 92 | 'Model2': 2 * c * sums**2 * Bconst**2 * (B + rots * Bswitch)**2, 93 | 'OpenFHE': 2 * c * sums * B**2 + rots * Bswitch 94 | }[model] 95 | 96 | 97 | def _logp0(B0): 98 | return util.clog2(B0) 99 | 100 | 101 | def _logpi(B): 102 | return util.clog2(B) 103 | 104 | 105 | def _logpM(B, Bargs): 106 | Bclean = _Bclean(**Bargs) 107 | Bscale = _Bscale(**Bargs) 108 | 109 | return util.clog2(Bclean / (B - Bscale)) 110 | 111 | 112 | def _logP(logq, kswargs, K=config.K): 113 | method = kswargs['method'] 114 | beta = kswargs['beta'] 115 | omega = kswargs['omega'] 116 | 117 | L = len(logq) 118 | logp = logq[1] 119 | logK = int(math.log2(K)) 120 | logb = int(math.log2(beta)) 121 | 122 | if method.startswith('BV'): 123 | return None 124 | elif method.startswith('GHS'): 125 | return logK + L * logp 126 | elif method == 'Hybrid': 127 | return logK + logb + int(math.log2(math.sqrt(L * logp / logb))) 128 | else: # method == 'Hybrid-RNS' 129 | return logK + int(math.log2(math.sqrt(omega * L))) + int(math.ceil(L * logp / omega)) 130 | 131 | 132 | def logqP(ops, Bargs, kswargs, sdist): 133 | B = _B(ops, Bargs, kswargs) 134 | B0 = _B0(ops, B, Bargs, kswargs) 135 | 136 | logq = [_logp0(B0)] 137 | for _ in range(ops['muls'] - 1): 138 | logq.append(_logpi(B)) 139 | logq.append(_logpM(B, Bargs)) 140 | logP = _logP(logq, kswargs) 141 | 142 | return logq, logP 143 | -------------------------------------------------------------------------------- /src/codegen.py: -------------------------------------------------------------------------------- 1 | import config 2 | import util 3 | 4 | 5 | def openfhe(params): 6 | if params['scheme'] == 'BGV': 7 | ctx = (( 8 | f"\tCCParams params;\n" 9 | f"\tparams.SetMultiplicativeDepth({params['depth']});\n" 10 | f"\tparams.SetPlaintextModulus({params['t']});\n" 11 | f"\tparams.SetSecurityLevel(HEStd_NotSet);\n" 12 | f"\tparams.SetSecretKeyDist({params['sdist']});\n" 13 | f"\tparams.SetRingDim({params['n']});\n" 14 | f"\tparams.SetFirstModSize({params['q0bits']});\n" 15 | f"\tparams.SetScalingModSize({params['qlbits']});\n" 16 | f"\tparams.SetBatchSize({params['slots']});\n" 17 | f"\tparams.SetScalingTechnique(FLEXIBLEAUTOEXT);\n\n" 18 | f"\t// see {config.OPENFHE_LINK_BGV} for a full OpenFHE BGV example\n" 19 | f"\t// ...\n\n")) 20 | elif params['scheme'] == 'BFV': 21 | ctx = (( 22 | f"\tCCParams params;\n" 23 | f"\tparams.SetMultiplicativeDepth({params['depth']});\n" 24 | f"\tparams.SetPlaintextModulus({params['t']});\n" 25 | f"\tparams.SetSecurityLevel(HEStd_NotSet);\n" 26 | f"\tparams.SetSecretKeyDist({params['sdist']});\n" 27 | f"\tparams.SetRingDim({params['n']});\n" 28 | f"\tparams.SetFirstModSize({params['q0bits']});\n" 29 | f"\tparams.SetScalingModSize({params['qlbits']});\n" 30 | f"\tparams.SetBatchSize({params['slots']});\n\n" 31 | f"\t// see {config.OPENFHE_LINK_BFV} for a full OpenFHE BFV example\n" 32 | f"\t// ...\n\n")) 33 | 34 | with open("openfhe.cpp", "w+") as f: 35 | f.write("#include \"openfhe.h\"\nusing namespace lbcrypto;\n\nint main(void)\n{\n" + ctx + "\treturn 0;\n}\n") 36 | 37 | 38 | def palisade(params): 39 | if params['scheme'] == 'BGV': 40 | ctx = (( 41 | f"\tauto ctx = CryptoContextFactory::genCryptoContextBGVrns(\n" 42 | f"\t\t/* L - 1 */ {params['depth']},\n" 43 | f"\t\t/* plaintext modulus */ {params['t']},\n" 44 | f"\t\t/* security level */ HEStd_NotSet,\n" 45 | f"\t\t/* standard deviation */ {params['sigma']},\n" 46 | f"\t\t/* maximum depth */ 2,\n" 47 | f"\t\t/* key distribution */ {params['sdist']},\n" 48 | f"\t\t/* ring dimension */ {params['n']},\n" 49 | f"\t\t/* log2(p0) */ {params['q0bits']},\n" 50 | f"\t\t/* log2(pl) */ {params['qlbits']},\n" 51 | f"\t\t/* batch size */ {params['slots']},\n" 52 | f"\t\t/* modswitch method */ AUTO);\n\n" 53 | f"\t// see {config.PALISADE_LINK_BGV} for a full PALISADE BGV example\n" 54 | f"\t// ...\n\n")) 55 | elif params['scheme'] == 'BFV': 56 | ctx = (( 57 | f"\tauto ctx = CryptoContextFactory::genCryptoContextBFVrns(\n" 58 | f"\t\t/* plaintext modulus */ {params['t']},\n" 59 | f"\t\t/* security level */ HEStd_NotSet,\n" 60 | f"\t\t/* standard deviation */ {params['sigma']},\n" 61 | f"\t\t/* ciphertext sums */ {params['sums']},\n" 62 | f"\t\t/* L - 1 */ {params['depth']},\n" 63 | f"\t\t/* rotation count */ {params['rots']},\n" 64 | f"\t\t/* key distribution */ {params['sdist']},\n" 65 | f"\t\t/* maximum depth */ 2,\n" 66 | f"\t\t/* log2(pi) */ {params['qlbits']},\n" 67 | f"\t\t/* ring dimension */ {params['n']});\n\n" 68 | f"\t// see {config.PALISADE_LINK_BFV} for a full PALISADE BFV example\n" 69 | f"\t// ...\n\n")) 70 | 71 | with open("palisade.cpp", "w+") as f: 72 | f.write("#include \"palisade.h\"\nusing namespace lbcrypto;\n\nint main(void)\n{\n" + ctx + "\treturn 0;\n}\n") 73 | 74 | 75 | def seal(params): 76 | mods = [] 77 | for m in params['q']: 78 | mods.append(f"Modulus({m})") 79 | mvec = f"{{{', '.join(mods)}}}" 80 | 81 | if params['scheme'] == 'BGV': 82 | ctx = (( 83 | f"\tEncryptionParameters params(scheme_type::bgv);\n" 84 | f"\tparams.set_poly_modulus_degree({params['n']});\n" 85 | f"\tparams.set_coeff_modulus({mvec});\n" 86 | f"\tparams.set_plain_modulus({params['t']});\n\n" 87 | f"\t// see {config.SEAL_LINK_BGV} for a full SEAL BGV example\n" 88 | f"\t// ...\n\n")) 89 | elif params['scheme'] == 'BFV': 90 | ctx = (( 91 | f"\tEncryptionParameters params(scheme_type::bfv);\n" 92 | f"\tparams.set_poly_modulus_degree({params['n']});\n" 93 | f"\tparams.set_coeff_modulus({mvec});\n" 94 | f"\tparams.set_plain_modulus({params['t']});\n\n" 95 | f"\t// see {config.SEAL_LINK_BGV} for a full SEAL BFV example\n" 96 | f"\t// ...\n\n")) 97 | 98 | with open("seal.cpp", "w+") as f: 99 | f.write("#include \"seal/seal.h\"\nusing namespace seal;\n\nint main(void)\n{\n" + ctx + "\treturn 0;\n}\n") 100 | -------------------------------------------------------------------------------- /src/config.py: -------------------------------------------------------------------------------- 1 | BITS = 128 2 | K = 100 3 | 4 | OPENFHE_LINK_BFV = "src/pke/examples/simple-integers.cpp" 5 | OPENFHE_LINK_BGV = "src/pke/examples/simple-integers-bgvrns.cpp" 6 | PALISADE_LINK_BFV = "src/pke/examples/simple-integers.cpp" 7 | PALISADE_LINK_BGV = "src/pke/examples/simple-integers-bgvrns.cpp" 8 | SEAL_LINK_BFV = "native/examples/1_bfv_basics.cpp" 9 | SEAL_LINK_BGV = "native/examples/4_bgv_basics.cpp" 10 | -------------------------------------------------------------------------------- /src/interactive.py: -------------------------------------------------------------------------------- 1 | import bfv 2 | import bgv 3 | import codegen 4 | import math 5 | import util 6 | 7 | 8 | def pconfig(scheme, model, sec, m, t, logq, logP=None, lib=None): 9 | print(( 10 | f"\nGenerated your {scheme} configuration!\n" 11 | f"model: {model}\n" 12 | f"sec: {sec}\n" 13 | f"n: {util.phi(m)}\n" 14 | f"t: {t}")) 15 | 16 | if len(logq) > 1: 17 | print(f"logq: {sum(logq)} ({logq[0]}, {logq[1]}, {logq[-1]})") 18 | else: 19 | print(f"logq: {logq[0]}") 20 | 21 | if logP: 22 | print(f"logP: {logP}") 23 | if lib: 24 | print(f"slots: {util.slots(m, t)}") 25 | 26 | 27 | def doconst(): 28 | r = input("Do you want to perform a constant multiplication? [N/y]: ").lower() 29 | 30 | while True: 31 | if r in ['', 'n', 'no']: 32 | return False 33 | elif r in ['y', 'ye', 'yes']: 34 | return True 35 | 36 | print("Invalid choice. Possible options are No (default), Yes.") 37 | r = input("Your choice: ").lower() 38 | 39 | 40 | def fullbatch(lib, default=False): 41 | if default: 42 | return False 43 | 44 | r = input("Do you want to use full batching with your plaintext modulus? [N/y]: ").lower() 45 | 46 | while True: 47 | if r in ['', 'n', 'no']: 48 | return False 49 | elif r in ['y', 'ye', 'yes']: 50 | return True 51 | 52 | print("Invalid choice. Possible options are No (default), Yes.") 53 | r = input("Your choice: ").lower() 54 | 55 | 56 | def getbeta(lib, keyswitch, default=False): 57 | if default or lib: 58 | return 2**10 59 | 60 | r = input(f"Choose your beta for {keyswitch} key switching [2^10]: ") 61 | 62 | while True: 63 | if r == '': 64 | return 2**10 65 | elif r.isdigit() and int(r) > 1: 66 | return int(r) 67 | 68 | print("Invalid choice. Please enter an integer > 0 (default 2^10).") 69 | r = input("Your choice: ") 70 | 71 | 72 | def getkeyswitch(lib, default=False): 73 | if default or lib: 74 | return 'Hybrid-RNS' 75 | 76 | r = input("Which key switching method do you prefer? [Hybrid-RNS/?]: ").lower() 77 | 78 | while True: 79 | if r == '' or 'hybrid-rns'.startswith(r): 80 | return 'Hybrid-RNS' 81 | elif r == 'bv': 82 | return 'BV' 83 | elif r == 'bv-rns': 84 | return 'BV-RNS' 85 | elif r == 'ghs': 86 | return 'GHS' 87 | elif r == 'ghs-rns': 88 | return 'GHS-RNS' 89 | elif 'hybrid'.startswith(r): 90 | return 'Hybrid' 91 | elif r != '?': 92 | print("Invalid choice. ", end='') 93 | 94 | print("Possible options are: BV, BV-RNS, GHS, GHS-RNS, Hybrid, Hybrid-RNS (default).") 95 | r = input("Your choice: ").lower() 96 | 97 | 98 | def getlib(): 99 | r = input("Do you want to use a specific library? [N/?]: ").lower() 100 | 101 | while True: 102 | if r in ['', 'n', 'no']: 103 | return None 104 | elif r == 'openfhe': 105 | return 'OpenFHE' 106 | elif r == 'palisade': 107 | return 'PALISADE' 108 | elif r == 'seal': 109 | return 'SEAL' 110 | elif r != '?': 111 | print("Invalid choice. ", end='') 112 | 113 | print("Possible options are: No (default), OpenFHE, PALISADE, SEAL.") 114 | r = input("Your choice: ").lower() 115 | 116 | 117 | def getm(): 118 | r = input("Input your cyclotomic order [1024]: ") 119 | 120 | while True: 121 | if r == '': 122 | return 1024 123 | elif r.isdigit() and int(r) >= 4: 124 | return int(r) 125 | 126 | print("Invalid choice. Please enter an integer >= 4 (default 1024).") 127 | r = input("Your choice: ") 128 | 129 | 130 | def getmodel(): 131 | r = input("Which circuit model do you want to use? [Base]: ").lower() 132 | 133 | while True: 134 | if r == '' or 'base'.startswith(r): 135 | return 'Base' 136 | elif r == '1' or 'model1'.startswith(r): 137 | return 'Model1' 138 | elif r == '2' or 'model2'.startswith(r): 139 | return 'Model2' 140 | elif 'openfhe'.startswith(r): 141 | return 'OpenFHE' 142 | elif r != '?': 143 | print("Invalid choice. ", end='') 144 | 145 | print("Possible options are: Base (default), Model1, Model2, OpenFHE.") 146 | r = input("Your choice: ").lower() 147 | 148 | 149 | def getmuls(): 150 | r = input("How many multiplications do you want to perform? [2]: ") 151 | 152 | while True: 153 | if r == '': 154 | return 2 155 | elif r.isdigit() and int(r) > 0: 156 | return int(r) 157 | 158 | print("Invalid choice. Please enter an integer > 0 (default 2).") 159 | r = input("Your choice: ") 160 | 161 | 162 | def getomega(lib, keyswitch, default=False): 163 | if default or lib: 164 | return 3 165 | 166 | r = input(f"Choose your omega for {keyswitch} key switching [3]: ") 167 | 168 | while True: 169 | if r == '': 170 | return 3 171 | elif r.isdigit() and int(r) > 0: 172 | return int(r) 173 | 174 | print("Invalid choice. Please enter an integer > 0 (default 3).") 175 | r = input("Your choice: ") 176 | 177 | 178 | def getrots(): 179 | r = input("How many rotations do you want to perform? [0]: ") 180 | 181 | while True: 182 | if r == '': 183 | return 0 184 | elif r.isdigit() and int(r) >= 0: 185 | return int(r) 186 | 187 | print("Invalid choice. Please enter an integer >= 0 (default 0).") 188 | r = input("Your choice: ") 189 | 190 | 191 | def getscheme(): 192 | r = input("Which scheme do you want to generate paramters for? [BGV/?]: ").lower() 193 | 194 | while True: 195 | if r == '' or 'bgv'.startswith(r): 196 | return 'BGV' 197 | elif 'bfv'.startswith(r): 198 | return 'BFV' 199 | elif r != '?': 200 | print("Invalid choice. ", end='') 201 | 202 | print("Possible options are: BGV (default), BFV.") 203 | r = input("Your choice: ").lower() 204 | 205 | 206 | def getsdist(lib, default=False): 207 | if default: 208 | return 'Ternary' 209 | 210 | r = input("Choose your secret distribution [Ternary/?]: ").lower() 211 | 212 | while True: 213 | if r == '' or 'ternary'.startswith(r): 214 | return 'Ternary' 215 | elif 'error'.startswith(r): 216 | return 'Error' 217 | elif r != '?': 218 | print("Invalid choice. ", end='') 219 | 220 | print("Possible options are: Ternary (default), Error.") 221 | r = input("Your choice: ").lower() 222 | 223 | 224 | def getsecurity(): 225 | r = input("Choose your security level [128]: ") 226 | 227 | while True: 228 | if r == '': 229 | return 128 230 | elif r.isdigit() and int(r) >= 40: 231 | return int(r) 232 | 233 | print("Invalid choice. Please enter an integer > 40 (default 128).") 234 | r = input("Your choice: ") 235 | 236 | 237 | def getsums(): 238 | r = input("What is the maximum number of summands between each multiplication? [1]: ") 239 | 240 | while True: 241 | if r == '': 242 | return 1 243 | elif r.isdigit() and int(r) > 0: 244 | return int(r) 245 | 246 | print("Invalid choice. Please enter an integer > 0 (default 1).") 247 | r = input("Your choice: ") 248 | 249 | 250 | def gett(): 251 | t, logt = None, None 252 | r = input("Do you want a specific plaintext modulus? [N/?]: ").lower() 253 | 254 | while True: 255 | if r in ['', 'n', 'no']: 256 | break 257 | elif r.isdigit() and int(r) >= 2: 258 | t = int(r) 259 | break 260 | elif r != '?': 261 | print("Invalid choice. ", end='') 262 | 263 | print("Please enter No (default) or an integer >= 2.") 264 | r = input("Your choice: ").lower() 265 | 266 | if t is None: 267 | r = input("How many bits do you want at least for the plaintext modulus? [17/?]: ").lower() 268 | 269 | while t is None and logt is None: 270 | if r == '': 271 | logt = 17 272 | break 273 | elif r.isdigit() and int(r) >= 2: 274 | logt = int(r) 275 | break 276 | elif r != '?': 277 | print("Invalid choice. ", end='') 278 | 279 | print("Please enter an integer >= 2 (default 17).") 280 | r = input("Your choice: ").lower() 281 | else: 282 | logt = math.floor(math.log2(t)) 283 | 284 | return t, logt 285 | 286 | 287 | def setadvanced(): 288 | r = input("Do you want to continue to the advanced settings? [N/y]: ").lower() 289 | 290 | while True: 291 | if r in ['', 'n', 'no']: 292 | return False 293 | elif r in ['y', 'ye', 'yes']: 294 | return True 295 | 296 | print("Invalid choice. Possible options are No (default), Yes.") 297 | r = input("Your choice: ").lower() 298 | 299 | 300 | def setsecurity(secdef, t): 301 | if t == 2: 302 | return False 303 | 304 | r = input(f"Do you want to set the security level or choose a cyclotomic order? [{secdef}]: ").lower() 305 | 306 | while True: 307 | if r == '': 308 | return secdef.startswith('Security') 309 | elif 'security'.startswith(r): 310 | return True 311 | elif 'order'.startswith(r): 312 | return False 313 | 314 | print("Invalid choice. Possible options are Security (default) or order.") 315 | r = input("Your choice: ").lower() 316 | 317 | 318 | def usepow2(): 319 | return True 320 | 321 | 322 | def welcome(): 323 | print("Welcome to the interactive parameter generator! :)\n") 324 | 325 | 326 | def writelib(lib): 327 | print(f"Generating an example using your configuration for {lib}.") 328 | 329 | 330 | def main(): 331 | welcome() 332 | schemename = getscheme() 333 | if schemename == 'BGV': 334 | scheme = bgv 335 | elif schemename == 'BFV': 336 | scheme = bfv 337 | model = getmodel() 338 | 339 | t, logt = gett() 340 | secdef = 'Order/security' if t else 'Security/order' 341 | 342 | if setsecurity(secdef, t): 343 | m, sec = None, getsecurity() 344 | pow2 = usepow2() 345 | else: 346 | m, sec = getm(), None 347 | print() 348 | 349 | muls = getmuls() 350 | const = doconst() 351 | rots = getrots() 352 | sums = getsums() 353 | lib = getlib() 354 | 355 | batch = fullbatch(lib, default=True) 356 | sdist = getsdist(lib, default=True) 357 | keyswitch = getkeyswitch(lib, default=True) 358 | omega = getomega(lib, keyswitch, default=True) 359 | beta = getbeta(lib, keyswitch, default=True) 360 | 361 | if setadvanced(): 362 | print() 363 | batch = fullbatch(lib) 364 | sdist = getsdist(lib) 365 | keyswitch = getkeyswitch(lib) 366 | 367 | if keyswitch in ['BV', 'BV-RNS', 'Hybrid']: 368 | beta = getbeta(lib, keyswitch) 369 | if keyswitch in ['Hybrid-RNS']: 370 | omega = getomega(lib, keyswitch) 371 | 372 | sigma = 3.19 373 | Ve = sigma * sigma 374 | Vs = {'Ternary': 2 / 3, 'Error': Ve}[sdist] 375 | 376 | genm = False 377 | if not m: 378 | genm = True 379 | m = 4 380 | 381 | gent = False 382 | if not t: 383 | gent = True 384 | t = util.gent(m, gent, t, logt, batch) 385 | 386 | ops = {'model': model, 'muls': muls, 'const': const, 'rots': rots, 'sums': sums} 387 | targs = {'gen': gent, 't': t, 'logt': logt, 'batch': batch} 388 | Bargs = {'m': m, 't': t, 'D': 6, 'Vs': Vs, 'Ve': Ve} 389 | kswargs = {'method': keyswitch, 'L': muls + 1, 'beta': beta, 'omega': omega} 390 | 391 | if genm: 392 | if not pow2: 393 | raise NotImplementedError("pow2 == False") 394 | 395 | while True: 396 | logq, logP = scheme.logqP(ops, Bargs, kswargs, sdist) 397 | log = sum(logq) + logP if logP else sum(logq) 398 | if logP and util.estsecurity(m, log, sdist) > sec: 399 | break 400 | 401 | m <<= 1 402 | Bargs['m'] = m 403 | 404 | if gent: 405 | t = util.gent(m, **targs) 406 | targs['t'] = t 407 | Bargs['t'] = t 408 | else: 409 | logq, logP = scheme.logqP(ops, Bargs, kswargs, sdist) 410 | 411 | sec = util.estsecurity(m, sum(logq) + logP, sdist) 412 | pconfig(schemename, model, sec, m, t, logq, logP, lib) 413 | 414 | if lib == 'OpenFHE': 415 | if schemename == 'BGV': 416 | q0bits = logq[0] 417 | qlbits = logq[1] 418 | else: # schemename == 'BFV' 419 | q0bits = math.ceil(logq[0] / muls) 420 | qlbits = math.ceil(logq[0] / muls) 421 | 422 | writelib(lib) 423 | codegen.openfhe({ 424 | 'scheme': schemename, 425 | 'n': util.phi(m), 426 | 't': t, 427 | 'sdist': {'Ternary': 'UNIFORM_TERNARY', 'Error': 'GAUSSIAN'}[sdist], 428 | 'depth': muls + 1, 429 | 'q0bits': q0bits, 430 | 'qlbits': qlbits, 431 | 'slots': util.slots(m, t), 432 | }) 433 | if lib == 'PALISADE': 434 | if schemename == 'BGV': 435 | q0bits = logq[0] 436 | qlbits = logq[1] 437 | else: # schemename == 'BFV' 438 | q0bits = math.ceil(logq[0] / muls) 439 | qlbits = math.ceil(logq[0] / muls) 440 | 441 | writelib(lib) 442 | codegen.palisade({ 443 | 'scheme': schemename, 444 | 'sigma': sigma, 445 | 'n': util.phi(m), 446 | 't': t, 447 | 'sdist': {'Ternary': 'OPTIMIZED', 'Error': 'RLWE'}[sdist], 448 | 'depth': muls + 1, 449 | 'q0bits': q0bits, 450 | 'qlbits': qlbits, 451 | 'slots': util.slots(m, t), 452 | 'rots': rots, 453 | 'sums': sums, 454 | }) 455 | if lib == 'SEAL': 456 | if schemename == 'BGV': 457 | q0bits = logq[0] 458 | qlbits = logq[1] 459 | qMbits = logq[-1] 460 | else: # schemename == 'BFV' 461 | q0bits = math.ceil(logq[0] / muls) 462 | qlbits = math.ceil(logq[0] / muls) 463 | 464 | q = [] 465 | for i in range(muls): 466 | if i == 0: 467 | q.append(util.genprime(1 << q0bits, m, batch)) 468 | if i == 1: 469 | start = q[-1] if q0bits == qlbits else 1 << qlbits 470 | q.append(util.genprime(start, m, batch)) 471 | 472 | if schemename == 'BGV': 473 | if qMbits == qlbits: 474 | start = q[-1] 475 | elif qMbits == q0bits: 476 | start = q[0] 477 | else: 478 | start = 1 << qMbits 479 | q.append(util.genprime(start, m, batch)) 480 | 481 | writelib(lib) 482 | codegen.seal({ 483 | 'scheme': schemename, 484 | 'n': util.phi(m), 485 | 'q': q, 486 | 't': t, 487 | }) 488 | 489 | 490 | if __name__ == "__main__": 491 | main() 492 | -------------------------------------------------------------------------------- /src/util.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from sage.all import euler_phi 4 | from sage.all import Integer, Mod, Primes 5 | 6 | 7 | def clog2(x): 8 | return int(math.ceil(math.log2(x))) 9 | 10 | 11 | def phi(x): 12 | return euler_phi(x) 13 | 14 | 15 | def estsecurity(m, logq, sdist): 16 | n = phi(m) 17 | 18 | if sdist == 'Ternary': 19 | alpha = 0.05 20 | beta = 0.33 21 | gamma = 17.88 22 | delta = 0.65 23 | elif sdist == 'Error': 24 | alpha = 3.87 25 | beta = 0.74 26 | gamma = 12.72 27 | delta = 0.17 28 | 29 | est = -math.log2(alpha * logq / n) * beta * n / logq + gamma * pow(logq / n, delta) * math.log2(n / logq) 30 | return max(int(math.floor(est)), 0) 31 | 32 | 33 | def genprime(start, m=0, batch=False): 34 | P = Primes() 35 | 36 | p = P.next(Integer(start)) 37 | if batch: 38 | while p % m != 1: 39 | p = P.next(p) 40 | 41 | return p 42 | 43 | 44 | def gent(m, gen, t, logt, batch): 45 | if gen: 46 | return genprime(2**(logt - 1), m, batch) 47 | else: 48 | return t 49 | 50 | 51 | def slots(m, t): 52 | return euler_phi(m) / Mod(t, m).multiplicative_order() 53 | --------------------------------------------------------------------------------