├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── features.md └── workflows │ └── pytest-coverage.yml ├── .gitignore ├── .gitmodules ├── .readthedocs.yaml ├── CITATION.bib ├── Dockerfile ├── LICENSE.txt ├── MANIFEST.in ├── Pyfhel ├── Afhel │ ├── .gitignore │ ├── Afhel.h │ ├── Afhel.pxd │ ├── Afseal.cpp │ ├── Afseal.h │ ├── CMakeLists.txt │ └── Demos │ │ ├── Demo_Afhel.cpp │ │ ├── Demo_AfhelRestored.cpp │ │ ├── Demo_Afseal_IO.cpp │ │ ├── Demo_Afseal_batch.cpp │ │ └── Demo_Afseal_int.cpp ├── PyCtxt.pxd ├── PyCtxt.pyx ├── PyPoly.pxd ├── PyPoly.pyx ├── PyPtxt.pxd ├── PyPtxt.pyx ├── Pyfhel.pxd ├── Pyfhel.pyx ├── __init__.py ├── test │ ├── README.md │ ├── test_Demos.py │ ├── test_PyCtxt.py │ ├── test_PyPtxt.py │ ├── test_Pyfhel.py │ ├── test_ckks.py │ └── test_utils.py └── utils │ ├── Backend_t.py │ ├── Scheme_t.py │ ├── __init__.py │ ├── cy_type_converters.pxi │ ├── cy_utils.pxi │ ├── iostream.pxd │ └── utils.py ├── README.md ├── docs ├── .nojekyll ├── Makefile ├── README.md ├── conf.py ├── index.rst ├── make.bat ├── presentations │ └── Pyfhel_WAHC21_slides.pdf ├── requirements.txt ├── source │ ├── API_reference.rst │ ├── API_serialized.rst │ ├── getting_started.rst │ └── getting_started │ │ ├── 1_installation.rst │ │ └── 2_contributing.rst ├── sphinxext │ └── cython_highlighting.py ├── static │ ├── logo.eps │ ├── logo.png │ ├── logo_alpha.svg │ ├── logo_empty.eps │ ├── logo_empty.svg │ ├── logo_name.png │ ├── logo_old.pdf │ ├── logo_old.png │ ├── logo_title.png │ ├── style.css │ ├── thumbnails │ │ ├── clientServer.png │ │ ├── contextParameters.jpg │ │ ├── dotproduct.png │ │ ├── encoding.png │ │ ├── encrypting.jpg │ │ ├── float.png │ │ ├── hammingDist.png │ │ ├── helloworld.png │ │ ├── multDepth.jpg │ │ ├── numpy.png │ │ ├── saveRestore.png │ │ └── simd.png │ └── warning_icon.png └── templates │ └── autosummary │ ├── class.rst │ ├── function.rst │ └── module.rst ├── examples ├── Demo_1_HelloWorld.py ├── Demo_2_Integer_BFV.py ├── Demo_3_Float_CKKS.py ├── Demo_4_SaveNRestore.py ├── Demo_5_CS_Client.py ├── Demo_5bis_CS_Server.py ├── Demo_6_MultDepth.py ├── Demo_7_ScalarProd.py ├── Demo_8_HammingDist.py ├── Demo_9_Integer_BGV.py ├── Demo_WAHC21.py └── README.rst ├── pyproject.toml ├── setup.cfg └── setup.py /.gitattributes: -------------------------------------------------------------------------------- 1 | src/*/*.cpp linguist-vendored 2 | src/*/*.h linguist-vendored 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: I have a bug / error in the installation or execution of Pyfhel 4 | title: "[up to 10 words short bug description]: [one-line error message]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Description** 11 | A clear and concise description of what the bug is. If it is related to Installation, you can skip all the sections except the setup. 12 | 13 | **Code To Reproduce Error** 14 | [Well formatted python code (surrounded by triple \`\`\`). 15 | Include the full error code in text format (don't worry about the size of the issue, but don't use screenshots! This way we can search it and reference it in the future)] 16 | 17 | **Expected behavior** 18 | [A clear and concise description of what you expected to happen. Can be omitted if obvious] 19 | 20 | **Setup:** 21 | - OS: [e.g. Windows10, Ubuntu] 22 | - Python: [e.g. 3.8.6] 23 | - C compiler version: [e.g. MSC v.1916 64 bit (AMD64), can be easily found between brackets by running `python` in a terminal] 24 | - Pyfhel Version: [e.g. 2.2.5 --> If lower than latest github repo version, please try that one out first!] 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/features.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Features 3 | about: How can I do this or that with Pyfhel? DO NOT USE FOR ERRORS 4 | title: How to [feature]? 5 | labels: Functionality 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the feature** 11 | A short description of what you're trying to accomplish. Make sure there are no Demos covering this feature! 12 | 13 | **Is this feature related to a project/objective? Please describe briefly** 14 | Why do you want this feature? This will motivate us to solve it! Ex. I'm preparing my Master Thesis on this topic "..." [...] 15 | -------------------------------------------------------------------------------- /.github/workflows/pytest-coverage.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For MacOs builds, GNU GCC is installed via Homebrew, and the CC/CXX environment variables are set to use it. 3 | # The echo "CC=..." >> $GITHUB_ENV syntax is used to set environment variables for the next steps in the workflow. 4 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 5 | 6 | name: CI 7 | 8 | on: 9 | push: 10 | branches: [ "master", "dev" ] 11 | pull_request: 12 | branches: [ "master", "dev" ] 13 | workflow_dispatch: 14 | inputs: 15 | debug_enabled: 16 | type: boolean 17 | description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' 18 | required: false 19 | default: false 20 | 21 | jobs: 22 | build: 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | python-version: ["3.9"] 27 | os: [ubuntu-latest, windows-latest, macos-latest] 28 | runs-on: ${{ matrix.os }} 29 | steps: 30 | 31 | - name: Checkout repo 32 | uses: actions/checkout@v3 33 | with: 34 | submodules: recursive 35 | 36 | - name: (MacOs only) Install GNU GCC. 37 | if: matrix.os == 'macos-latest' 38 | run: | 39 | brew install gcc libomp 40 | 41 | - name: (MacOs only) Set CC/CXX environment variables to GNU GCC 42 | if: matrix.os == 'macos-latest' 43 | run: | 44 | 45 | # Check GCC installation path. Brew installs GCC in /opt/homebrew/bin on Apple Silicon and /usr/local/bin on Intel. 46 | if [[ $(uname -m) = "arm64" ]]; then BREW_GCC_PATH="/opt/homebrew/bin"; else BREW_GCC_PATH="/usr/local/bin"; fi 47 | echo "GCC installed at $BREW_GCC_PATH" 48 | 49 | # Set CC/CXX environment variables to GNU GCC 50 | echo "CC=$BREW_GCC_PATH/$(ls $BREW_GCC_PATH | grep ^gcc-[0-9] | sort -V -r | head -n 1)" >> $GITHUB_ENV 51 | echo "CXX=$BREW_GCC_PATH/$(ls $BREW_GCC_PATH | grep ^g++-[0-9] | sort -V -r | head -n 1)" >> $GITHUB_ENV 52 | echo "CC=${{ env.CC }}" 53 | echo "CXX=${{ env.CXX }}" 54 | 55 | # Set MACOSX_DEPLOYMENT_TARGET to avoid version mismatch warnings 56 | echo "MACOSX_DEPLOYMENT_TARGET=$(sw_vers -productVersion)" >> $GITHUB_ENV 57 | echo "MACOSX_DEPLOYMENT_TARGET=${{ env.MACOSX_DEPLOYMENT_TARGET }}" 58 | 59 | - name: Set up Python ${{ matrix.python-version }} 60 | uses: actions/setup-python@v4 61 | with: 62 | python-version: ${{ matrix.python-version }} 63 | cache: 'pip' # caching pip dependencies 64 | 65 | - name: (Manual) Setup tmate session for interactive debugging via SSH 66 | uses: mxschmitt/action-tmate@v3 67 | if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }} 68 | 69 | - name: Update pip 70 | run: python -m pip install --upgrade pip 71 | 72 | - name: Install package verbosely 73 | run: | 74 | 75 | touch .cov # Create a .cov file to trigger Cython compilation with coverage support 76 | ls 77 | python -m pip install -v -v . 78 | 79 | - name: Test with pytest 80 | run: | 81 | python -m pip install cython==3.0.2 pytest-cov 82 | pytest --cov . 83 | 84 | - name: Upload report to Codecov 85 | uses: codecov/codecov-action@v3.1.0 86 | with: 87 | token: ${{ secrets.CODECOV_TOKEN }} 88 | env_vars: OS,PYTHON 89 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | **.o 3 | **.lo 4 | 5 | # Compiled Dynamic libraries 6 | **.so* 7 | **.dylib* 8 | 9 | # Compiled Static libraries 10 | **.a 11 | **.la* 12 | 13 | # Executables 14 | **_x 15 | 16 | # Pyfhel building 17 | Pyfhel/*.pyc 18 | Pyfhel/*.pyd 19 | Pyfhel/*.lib 20 | Pyfhel/*.dll 21 | Pyfhel/*.exp 22 | Pyfhel/*.cpp 23 | examples/playground 24 | **/build 25 | **.eggs 26 | **.egg-info 27 | **/dist 28 | **/gmon.out 29 | **/venv 30 | 31 | # Coverage 32 | .cov 33 | .coverage 34 | .pytest_cache 35 | htmlcov 36 | coverage.xml 37 | 38 | # Makefiles 39 | **/Makefile 40 | 41 | # Pyfhel saved objects 42 | **.pycon 43 | **.pypk 44 | **.pysk 45 | **.pyrok 46 | **.pyrlk 47 | 48 | # Visual Studio Code 49 | **.vscode 50 | 51 | # ipython checkpoints 52 | **.ipynb 53 | **.ipynb_checkpoints 54 | **/__pycache__ 55 | 56 | # Documentation 57 | docs/_autosummary 58 | docs/_autoexamples 59 | **.doctree 60 | **.pickle 61 | docs/html 62 | docs/doctrees 63 | 64 | # macOS 65 | **/.DS_Store -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "SEAL"] 2 | path = Pyfhel/backend/SEAL 3 | url = https://github.com/microsoft/SEAL 4 | [submodule "palisade"] 5 | path = Pyfhel/backend/palisade 6 | url = https://gitlab.com/palisade/palisade-release 7 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml --> https://docs.readthedocs.io/en/stable/config-file/v2.html 2 | 3 | version: 2 4 | 5 | submodules: 6 | include: all 7 | recursive: true 8 | 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: "3.9" 13 | 14 | 15 | python: 16 | install: 17 | - requirements: docs/requirements.txt 18 | - method: pip 19 | path: . 20 | 21 | 22 | sphinx: 23 | builder: html 24 | configuration: docs/conf.py 25 | fail_on_warning: false 26 | -------------------------------------------------------------------------------- /CITATION.bib: -------------------------------------------------------------------------------- 1 | @inproceedings{ibarrondo2021pyfhel, 2 | title={Pyfhel: Python for homomorphic encryption libraries}, 3 | author={Ibarrondo, Alberto and Viand, Alexander}, 4 | booktitle={Proceedings of the 9th on Workshop on Encrypted Computing \& Applied Homomorphic Cryptography}, 5 | pages={11--16}, 6 | year={2021} 7 | } 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic 2 | RUN apt-get update && apt-get install lzip git make sudo -y 3 | RUN sudo apt-get install software-properties-common -y 4 | RUN sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test 5 | RUN sudo apt-get update 6 | RUN sudo apt-get install gcc-6 g++-6 python3.8 python3.8-dev python3.8-distutils build-essential libssl-dev libffi-dev curl -y 7 | # RUN sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-6 90 8 | # RUN sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-6 90 9 | RUN curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py && python3.8 get-pip.py 10 | RUN echo 'export PATH=$PATH:$HOME/.local/bin' >> ~/.bashrc 11 | RUN . ~/.bashrc 12 | RUN git clone --recursive https://github.com/ibarrond/Pyfhel 13 | RUN cd Pyfhel && pip3 install . -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # Include all cython source files 2 | global-include *.pyx *.pxd 3 | 4 | # Include python packages 5 | include Pyfhel/*.py 6 | include Pyfhel/utils/** 7 | 8 | # Exclude the cython generated cpp files, as they depend on the numpy version 9 | exclude Pyfhel/*.cpp 10 | 11 | # Include all Afhel source files and headers 12 | include Pyfhel/Afhel/*.cpp 13 | include Pyfhel/Afhel/*.h 14 | include Pyfhel/Afhel/*.pxd 15 | 16 | # Include backend sources 17 | global-include Pyfhel/backend/SEAL/** 18 | global-exclude Pyfhel/backend/palisade/** 19 | 20 | # Include examples 21 | include examples/*.py 22 | 23 | # Include standard project files 24 | include docs/static/* 25 | include README.md 26 | include LICENSE.txt 27 | include setup.py 28 | include pyproject.toml 29 | -------------------------------------------------------------------------------- /Pyfhel/Afhel/.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /cmake-* -------------------------------------------------------------------------------- /Pyfhel/Afhel/Afhel.h: -------------------------------------------------------------------------------- 1 | #ifndef AFHEL_H 2 | #define AFHEL_H 3 | 4 | 5 | #include /* Print in std::cout */ 6 | #include /* std::string class */ 7 | #include /* Vectorizing all operations */ 8 | #include /* memory pools, multithread*/ 9 | #include /* Smart Pointers*/ 10 | #include /* Complex Numbers */ 11 | #include /* map */ 12 | #include /* std::uint64_t */ 13 | 14 | // Forward Declarations 15 | class AfPoly; 16 | class AfCtxt; 17 | class AfPtxt; 18 | class Afhel; 19 | 20 | //----------------------------- FHE scheme type -------------------------------- 21 | enum class scheme_t : std::uint8_t{ 22 | // No scheme set; cannot be used for encryption 23 | none = 0x0, 24 | // Brakerski/Fan-Vercauteren scheme 25 | bfv = 0x1, 26 | // Cheon-Kim-Kim-Song scheme 27 | ckks = 0x2, 28 | // Brakerski-Gentry-Vaikunthanathan scheme 29 | bgv = 0x3 30 | }; 31 | 32 | static std::map scheme_t_str { 33 | {scheme_t::none, "none"}, 34 | {scheme_t::bfv, "bfv"}, 35 | {scheme_t::ckks, "ckks"}, 36 | {scheme_t::bgv, "bgv"}, 37 | }; 38 | static std::map scheme_t_map { 39 | {"none", scheme_t::none}, 40 | {"bfv", scheme_t::bfv}, 41 | {"ckks", scheme_t::ckks}, 42 | {"bgv", scheme_t::bgv}, 43 | }; 44 | 45 | //------------------------------- FHE backend ---------------------------------- 46 | enum class backend_t : std::uint8_t{ 47 | // No backend set; cannot be used 48 | none = 0xA, 49 | // SEAL - Microsoft Research (default) 50 | seal = 0xB, 51 | // PALISADE 52 | palisade = 0xC 53 | }; 54 | static std::map backend_t_str { 55 | {backend_t::none, "none"}, 56 | {backend_t::seal, "seal"}, 57 | {backend_t::palisade, "palisade"}, 58 | }; 59 | static std::map backend_t_map { 60 | {"none", backend_t::none}, 61 | {"seal", backend_t::seal}, 62 | {"palisade", backend_t::palisade}, 63 | }; 64 | 65 | 66 | // ============================================================================= 67 | // ================== ABSTRACTION FOR HOMOMORPHIC ENCR. LIBS =================== 68 | // ============================================================================= 69 | class Afhel { 70 | public: 71 | // FHE backend 72 | backend_t backend; 73 | 74 | // --------------------------- CLASS MANAGEMENT ------------------------------ 75 | virtual ~Afhel(){}; 76 | 77 | // ----------------------------- CRYPTOGRAPHY -------------------------------- 78 | // CONTEXT GENERATION 79 | virtual std::string ContextGen( 80 | scheme_t scheme, std::uint64_t poly_modulus_degree, 81 | std::uint64_t plain_modulus_bit_size, std::uint64_t plain_modulus, int sec, 82 | std::vector qi_sizes = {}, std::vector qi_values = {}) = 0; 83 | 84 | // KEY GENERATION 85 | virtual void KeyGen() = 0; 86 | virtual void relinKeyGen() = 0; 87 | virtual void rotateKeyGen(std::vector rot_steps) = 0; 88 | 89 | // ENCRYPTION 90 | virtual void encrypt(AfPtxt &plain1, AfCtxt &cipherOut) = 0; 91 | virtual void encrypt_v(std::vector> &plainV, std::vector> &cipherVOut) = 0; 92 | 93 | // DECRYPTION 94 | virtual void decrypt(AfCtxt &cipher1, AfPtxt &plainOut) = 0; 95 | virtual void decrypt_v(std::vector> &cipherV, std::vector> &plainVOut) = 0; 96 | 97 | // NOISE MEASUREMENT 98 | virtual int noise_level(AfCtxt &cipher1) = 0; 99 | 100 | // ------------------------------ CODEC ------------------------------- 101 | // ENCODE 102 | // bfv 103 | virtual void encode_i(std::vector &values, AfPtxt &plainOut) = 0; 104 | // ckks 105 | virtual void encode_f(std::vector &values, double scale, AfPtxt &plainVOut) = 0; 106 | virtual void encode_c(std::vector> &values, double scale, AfPtxt &plainVOut) = 0; 107 | // bgv 108 | virtual void encode_g(std::vector &values, AfPtxt &plainOut) = 0; 109 | 110 | // DECODE 111 | // bfv 112 | virtual void decode_i(AfPtxt &plain1, std::vector &valueVOut) = 0; 113 | // ckks 114 | virtual void decode_f(AfPtxt &plain1, std::vector &valueVOut) = 0; 115 | virtual void decode_c(AfPtxt &plain1, std::vector> &valueVOut) = 0; 116 | // bgv 117 | virtual void decode_g(AfPtxt &plain1, std::vector &valueVOut) = 0; 118 | 119 | // -------------------------- RELINEARIZATION ------------------------- 120 | virtual void relinearize(AfCtxt &cipher1) = 0; 121 | 122 | // ---------------------- HOMOMORPHIC OPERATIONS ---------------------- 123 | // NEGATE 124 | virtual void negate(AfCtxt &cipher1) = 0; 125 | virtual void negate_v(std::vector> &cipherV) = 0; 126 | 127 | // SQUARE 128 | virtual void square(AfCtxt &cipher1) = 0; 129 | virtual void square_v(std::vector> &cipherV) = 0; 130 | 131 | // ADDITION 132 | virtual void add(AfCtxt &cipherInOut, AfCtxt &cipher2) = 0; 133 | virtual void add_plain(AfCtxt &cipherInOut, AfPtxt &plain2) = 0; 134 | virtual void add_v(std::vector> &cipherVInOut, std::vector> &cipherV2) = 0; 135 | virtual void add_plain_v(std::vector> &cipherVInOut, std::vector> &plainV2) = 0; 136 | 137 | // SUBTRACTION 138 | virtual void sub(AfCtxt &cipherInOut, AfCtxt &cipher2) = 0; 139 | virtual void sub_plain(AfCtxt &cipherInOut, AfPtxt &plain2) = 0; 140 | virtual void sub_v(std::vector> &cipherVInOut, std::vector> &cipherV2) = 0; 141 | virtual void sub_plain_v(std::vector> &cipherVInOut, std::vector> &plainV2) = 0; 142 | 143 | 144 | // MULTIPLICATION 145 | virtual void multiply(AfCtxt &cipherVInOut, AfCtxt &cipher2) = 0; 146 | virtual void multiply_plain(AfCtxt &cipherVInOut, AfPtxt &plain1) = 0; 147 | virtual void multiply_v(std::vector> &cipherVInOut, std::vector> &cipherV2) = 0; 148 | virtual void multiply_plain_v(std::vector> &cipherVInOut, std::vector> &plainV2) = 0; 149 | 150 | // ROTATE 151 | virtual void rotate(AfCtxt &cipher1, int k) = 0; 152 | virtual void rotate_v(std::vector> &cipherV, int k) = 0; 153 | virtual void flip(AfCtxt &ctxt) = 0; 154 | virtual void flip_v(std::vector> &ctxtV) = 0; 155 | 156 | // POWER 157 | virtual void exponentiate(AfCtxt &cipher1, std::uint64_t &expon) = 0; 158 | virtual void exponentiate_v(std::vector> &cipherV, std::uint64_t &expon) = 0; 159 | 160 | // CKKS -> Rescaling and mod switching 161 | virtual void rescale_to_next(AfCtxt &cipher1) = 0; 162 | virtual void mod_switch_to_next(AfCtxt &cipher1) = 0; 163 | virtual void mod_switch_to_next_plain(AfPtxt &ptxt) = 0; 164 | 165 | 166 | // -------------------------------- I/O ------------------------------- 167 | // SAVE/LOAD CONTEXT 168 | virtual size_t save_context(std::ostream &out_stream, std::string &compr_mode) = 0; 169 | virtual size_t load_context(std::istream &in_stream, int sec) = 0; 170 | 171 | // SAVE/LOAD PUBLICKEY 172 | virtual size_t save_public_key(std::ostream &out_stream, std::string &compr_mode) = 0; 173 | virtual size_t load_public_key(std::istream &in_stream) = 0; 174 | 175 | // SAVE/LOAD SECRETKEY 176 | virtual size_t save_secret_key(std::ostream &out_stream, std::string &compr_mode) = 0; 177 | virtual size_t load_secret_key(std::istream &in_stream) = 0; 178 | 179 | // SAVE/LOAD RELINKEY 180 | virtual size_t save_relin_keys(std::ostream &out_stream, std::string &compr_mode) = 0; 181 | virtual size_t load_relin_keys(std::istream &in_stream) = 0; 182 | 183 | // SAVE/LOAD ROTKEYS 184 | virtual size_t save_rotate_keys(std::ostream &out_stream, std::string &compr_mode) = 0; 185 | virtual size_t load_rotate_keys(std::istream &in_stream) = 0; 186 | 187 | // SAVE/LOAD PLAINTEXT --> Could be achieved outside of Afhel 188 | virtual size_t save_plaintext(std::ostream &out_stream, std::string &compr_mode, AfPtxt &plain) = 0; 189 | virtual size_t load_plaintext(std::istream &in_stream, AfPtxt &plain) = 0; 190 | 191 | // SAVE/LOAD CIPHERTEXT --> Could be achieved outside of Afhel 192 | virtual size_t save_ciphertext(std::ostream &out_stream, std::string &compr_mode, AfCtxt &ciphert) = 0; 193 | virtual size_t load_ciphertext(std::istream &in_stream, AfCtxt &plain) = 0; 194 | 195 | // ----------------------------- AUXILIARY ---------------------------- 196 | // GETTERS 197 | virtual std::vector get_qi() = 0; 198 | virtual std::uint64_t get_plain_modulus() = 0; 199 | virtual size_t get_poly_modulus_degree() = 0; 200 | virtual scheme_t get_scheme() = 0; 201 | 202 | 203 | // SIZES 204 | virtual size_t sizeof_context(std::string &compr_mode) = 0; 205 | virtual size_t sizeof_public_key(std::string &compr_mode) = 0; 206 | virtual size_t sizeof_secret_key(std::string &compr_mode) = 0; 207 | virtual size_t sizeof_relin_keys(std::string &compr_mode) = 0; 208 | virtual size_t sizeof_rotate_keys(std::string &compr_mode) = 0; 209 | virtual size_t sizeof_plaintext(std::string &compr_mode, AfPtxt &pt) = 0; 210 | virtual size_t sizeof_ciphertext(std::string &compr_mode, AfCtxt &ct) = 0; 211 | 212 | // ------------------------------- AFPOLY ----------------------------- 213 | friend class AfPoly; 214 | 215 | // POLY OPS 216 | virtual void add_inplace(AfPoly &p1, AfPoly &p2) = 0; 217 | virtual void subtract_inplace(AfPoly &p1, AfPoly &p2) = 0; 218 | virtual void multiply_inplace(AfPoly &p1, AfPoly &p2) = 0; 219 | virtual void invert_inplace(AfPoly &p) = 0; 220 | 221 | // I/O 222 | virtual void poly_to_ciphertext(AfPoly &p, AfCtxt &ctxt, size_t i) = 0; 223 | virtual void poly_to_plaintext(AfPoly &p, AfPtxt &ptxt) = 0; 224 | 225 | // Coefficient Access 226 | virtual std::complex get_coeff(AfPoly& poly, size_t i) = 0; 227 | virtual void set_coeff(AfPoly& poly, std::complex &val, size_t i) = 0; 228 | virtual std::vector> to_coeff_list(AfPoly& poly) = 0; 229 | }; 230 | 231 | 232 | 233 | // ============================================================================= 234 | // ======================= ABSTRACTION FOR PLAINTEXTS ========================== 235 | // ============================================================================= 236 | class AfPtxt{ 237 | public: 238 | virtual ~AfPtxt(){}; 239 | }; 240 | 241 | 242 | // ============================================================================= 243 | // ====================== ABSTRACTION FOR CIPHERTEXTS ========================== 244 | // ============================================================================= 245 | class AfCtxt{ 246 | public: 247 | virtual ~AfCtxt(){}; 248 | }; 249 | 250 | 251 | // ============================================================================= 252 | // ====================== ABSTRACTION FOR POLYNOMIALS ========================== 253 | // ============================================================================= 254 | // Wrapper for underlying polynomials that make up plaintexts, ciphertexts, etc. 255 | class AfPoly { 256 | public: 257 | virtual ~AfPoly(){}; 258 | 259 | // ----------- COEFF ACCESSORS ------------ 260 | virtual std::vector> to_coeff_list(Afhel &afhel) = 0; 261 | virtual std::complex get_coeff(Afhel &afhel, size_t i) = 0; 262 | virtual void set_coeff(Afhel &afhel, std::complex &val, size_t i) = 0; 263 | 264 | // -------------- OPERATIONS -------------- 265 | //inplace ops -> result in first operand 266 | virtual void add_inplace(const AfPoly &other) = 0; 267 | virtual void subtract_inplace(const AfPoly &other) = 0; 268 | virtual void multiply_inplace(const AfPoly &other) = 0; 269 | virtual bool invert_inplace() = 0; 270 | }; 271 | 272 | 273 | // Include all backends 274 | #include "Afseal.h" 275 | 276 | #endif /*AFHEL_H*/ 277 | -------------------------------------------------------------------------------- /Pyfhel/Afhel/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This can be used to build locally Afhel and test demos. 2 | cmake_minimum_required (VERSION 3.9...3.17) 3 | 4 | project(Afhel) 5 | 6 | find_package(SEAL 3.7 REQUIRED) 7 | 8 | add_library(afseal Afseal.cpp) 9 | target_link_libraries(afseal SEAL::seal) 10 | 11 | add_executable(demo_afseal_batch Demos/Demo_Afseal_batch.cpp) 12 | target_link_libraries(demo_afseal_batch afseal) 13 | -------------------------------------------------------------------------------- /Pyfhel/Afhel/Demos/Demo_Afhel.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #define VECTOR_SIZE 5 13 | 14 | int main(int argc, char **argv) 15 | { 16 | string fileName = "DemoAfhelEnv"; 17 | Afhel he; 18 | he.flagPrint = true; // Enable print for all functions 19 | // Values for the modulus p (size of p): 20 | // - 2 (Binary) 21 | // - 257 (Byte) 22 | // - 65537 (Word) 23 | // - 4294967311 (Long) 24 | long p = 2; 25 | long r = 32; 26 | long d = 1; 27 | long c = 2; 28 | long sec = 128; 29 | long w = 64; 30 | long L = 40; 31 | 32 | he.keyGen(p, r, c, d, sec, w, L); 33 | vector v1; 34 | vector v2; 35 | for(int i=0; i vRes = he.decrypt(k1); 47 | 48 | 49 | // Multiplication 50 | k1 = he.encrypt(v1); 51 | k2 = he.encrypt(v2); 52 | he.mult(k1, k2); 53 | vector vRes2 = he.decrypt(k1); 54 | 55 | // Scalar product 56 | k1 = he.encrypt(v1); 57 | k2 = he.encrypt(v2); 58 | he.scalarProd(k1, k2); 59 | vector vRes3 = he.decrypt(k1); 60 | 61 | // Square 62 | k1 = he.encrypt(v1); 63 | he.square(k1); 64 | vector vRes4 = he.decrypt(k1); 65 | 66 | // Store & retrieve environment 67 | he.saveEnv(fileName); 68 | std::cout << "Saved env with values: m=" << he.getM() << 69 | ", p=" << he.getP() << ", r=" << he.getR() << endl; 70 | std::cout << "END OF DEMO" << endl; 71 | }; 72 | 73 | -------------------------------------------------------------------------------- /Pyfhel/Afhel/Demos/Demo_AfhelRestored.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "Afhel/Afhel.h" 6 | 7 | #include 8 | #include 9 | 10 | int main(int argc, char **argv) 11 | { 12 | string fileName = "DemoAfhelEnv"; 13 | Afhel he; 14 | he.flagPrint = true; // Enable print for all functions 15 | // Values for the modulus p (size of p): 16 | // - 2 (Binary) 17 | // - 257 (Byte) 18 | // - 65537 (Word) 19 | // - 4294967311 (Long) 20 | /* 21 | long p = 2; 22 | long r = 1; 23 | long d = 1; 24 | long c = 2; 25 | long sec = 80; 26 | long w = 64; 27 | long L = 10; 28 | */ 29 | // Store & retrieve environment 30 | he.restoreEnv(fileName); 31 | 32 | vector v1; 33 | vector v2; 34 | for(int i=0; i vRes = he.decrypt(k1); 42 | 43 | 44 | // Multiplication 45 | k1 = he.encrypt(v1); 46 | k2 = he.encrypt(v2); 47 | he.mult(k1, k2); 48 | vector vRes2 = he.decrypt(k1); 49 | 50 | // Scalar product 51 | k1 = he.encrypt(v1); 52 | k2 = he.encrypt(v2); 53 | he.scalarProd(k1, k2); 54 | vector vRes3 = he.decrypt(k1); 55 | 56 | // Square 57 | k1 = he.encrypt(v1); 58 | he.square(k1); 59 | vector vRes4 = he.decrypt(k1); 60 | 61 | std::cout << "END OF DEMO" << endl; 62 | }; 63 | 64 | -------------------------------------------------------------------------------- /Pyfhel/Afhel/Demos/Demo_Afseal_IO.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include // Measure time 4 | 5 | #include 6 | #include 7 | 8 | class timing_map { 9 | public: 10 | std::map timings; 11 | }; 12 | 13 | class timer { 14 | public: 15 | timer(timing_map& newtmap, std::string newname) 16 | : tmap(newtmap), 17 | name(newname), 18 | start(std::clock()) {} 19 | 20 | ~timer() { 21 | tmap.timings[name] = static_cast(std::clock() - start) 22 | / static_cast(CLOCKS_PER_SEC); 23 | } 24 | 25 | timing_map& tmap; 26 | std::string name; 27 | std::clock_t start; 28 | }; 29 | 30 | timing_map ctx; 31 | 32 | 33 | int main(int argc, char **argv) 34 | { 35 | Afseal he; 36 | // Values for the modulus p (size of p): 37 | // - 2 (Binary) 38 | // - 257 (Byte) 39 | // - 65537 (Word) 40 | // - 4294967311 (Long) 41 | long p =1964769281; 42 | long m = 8192; 43 | long base = 3; 44 | long sec = 192; 45 | bool flagBatching=false; 46 | 47 | he.ContextGen(p, m, flagBatching, base, sec); 48 | std::cout << " Afseal - Contextcreated" << endl; 49 | he.KeyGen(); 50 | std::cout << " Afseal - Keys Generated" << endl; 51 | 52 | int64_t v1=3, v2=-2, vRes; 53 | 54 | Plaintext p1, p2; 55 | p1 = he.encode(v1); 56 | p2 = he.encode(v2); 57 | 58 | Ciphertext c1, c2; 59 | c1 = he.encrypt(p1); 60 | c2 = he.encrypt(p2); 61 | 62 | std::cout << " Afseal - Encoding and encryption OK" << endl; 63 | 64 | he.saveContext("obj_context.pycon"); 65 | std::cout << " Afseal - Context saved" << endl; 66 | he.savepublicKey("obj_pubkey.pypk"); 67 | std::cout << " Afseal - Public Key Saved" << endl; 68 | he.savesecretKey("obj_seckey.pysk"); 69 | std::cout << " Afseal - SecretKey Saved" << endl; 70 | 71 | Afseal he2; 72 | bool done = he2.restoreContext("obj_context.pycon"); 73 | std::cout << " Afseal - Context restored " << done << endl; 74 | he2.restorepublicKey("obj_pubkey.pypk"); 75 | std::cout << " Afseal - Public Key restored " << endl; 76 | he2.restoresecretKey("obj_seckey.pysk"); 77 | std::cout << " Afseal - SecretKey restored " << endl; 78 | 79 | Plaintext p3, p4; 80 | p3 = he2.encode(v1); 81 | p4 = he2.encode(v2); 82 | 83 | Ciphertext c3, c4; 84 | c3 = he2.encrypt(p3); 85 | c4 = he2.encrypt(p4); 86 | std::cout << " Afseal - Encoding and encryption OK" << endl; 87 | 88 | }; 89 | 90 | -------------------------------------------------------------------------------- /Pyfhel/Afhel/Demos/Demo_Afseal_batch.cpp: -------------------------------------------------------------------------------- 1 | #include "../Afseal.h" 2 | 3 | #include // Measure time 4 | 5 | #include 6 | #include 7 | #include 8 | #define VECTOR_SIZE 1000 9 | 10 | 11 | #if (defined(SEAL_USE_ZSTD)) 12 | bool use_zstd = true; 13 | #else 14 | bool use_zstd = false; 15 | #endif 16 | 17 | class timing_map { 18 | public: 19 | std::map timings; 20 | }; 21 | 22 | class timer { 23 | public: 24 | timer(timing_map& newtmap, std::string newname) 25 | : tmap(newtmap), 26 | name(newname), 27 | start(std::clock()) {} 28 | 29 | ~timer() { 30 | tmap.timings[name] = static_cast(std::clock() - start) 31 | / static_cast(CLOCKS_PER_SEC); 32 | } 33 | 34 | timing_map& tmap; 35 | std::string name; 36 | std::clock_t start; 37 | }; 38 | 39 | timing_map ctx; 40 | 41 | 42 | int main(int argc, char **argv) 43 | { 44 | Afhel* he; 45 | 46 | he = new Afseal(); 47 | // Values for the modulus p (size of p): 48 | // - 2 (Binary) 49 | // - 257 (Byte) 50 | // - 65537 (Word) 51 | // - 4294967311 (Long) 52 | uint64_t p =65537; 53 | size_t n = 4096; 54 | int sec = 128; 55 | 56 | std::cout << " Afseal - Creating Context" << endl; 57 | {timer t(ctx, "contextgen");he->ContextGen(scheme_t::bfv, n, 20, -1, sec);} 58 | std::cout << " Afseal - Context CREATED" << endl; 59 | 60 | //TODO: print parameters 61 | 62 | std::cout << " Afseal - Generating Keys" << endl; 63 | {timer t(ctx, "keygen"); he->KeyGen();} 64 | {timer t(ctx, "rotkeygen"); he->rotateKeyGen();} 65 | std::cout << " Afseal - Keys Generated" << endl; 66 | 67 | vector v1; 68 | vector v2; 69 | vector vRes; 70 | 71 | for(int64_t i=0; i<1000; i++){ 72 | if(iencode_i(v1, *p1); 95 | he->encode_i(v2, *p2); 96 | 97 | // Encryption 98 | {timer t(ctx, "encr11");he->encrypt(*p1, *c1);} 99 | {timer t(ctx, "encr12");he->encrypt(*p2, *c2);} 100 | 101 | 102 | // Sum 103 | std::cout << " Afseal - SUM" << endl; 104 | {timer t(ctx, "add"); he->add(*c1, *c2);} 105 | {timer t(ctx, "decr1"); he->decrypt(*c1, *pres);} 106 | he->decode_i(*pres, vRes); 107 | for(int64_t i=0; i<20; i++){ 108 | std::cout << vRes[i] << ' '; 109 | } std::cout << endl; 110 | 111 | // Multiplication 112 | std::cout << " Afseal - MULT" << endl; 113 | {timer t(ctx, "encr21");he->encrypt(*p1, *c1);} 114 | {timer t(ctx, "encr22");he->encrypt(*p1, *c2);} 115 | {timer t(ctx, "mult");he->multiply(*c1, *c2);} 116 | {timer t(ctx, "decr2");he->decrypt(*c1, *pres);} 117 | he->decode_i(*pres, vRes); 118 | for(int64_t i=0; i<20; i++){ 119 | std::cout << vRes[i] << ' '; 120 | } std::cout << endl; 121 | 122 | 123 | // Subtraction 124 | std::cout << " Afseal - SUB" << endl; 125 | {timer t(ctx, "encr31");he->encrypt(*p1, *c1);} 126 | {timer t(ctx, "encr32");he->encrypt(*p1, *c2);} 127 | {timer t(ctx, "sub");he->sub(*c1, *c2);} 128 | {timer t(ctx, "decr3");he->decrypt(*c1, *pres);} 129 | he->decode_i(*pres, vRes); 130 | for(int64_t i=0; i<20; i++){ 131 | std::cout << vRes[i] << ' '; 132 | } std::cout << endl; 133 | 134 | // Square 135 | std::cout << " Afseal - SQUARE" << endl; 136 | {timer t(ctx, "encr41"); he->encrypt(*p1, *c1);} 137 | {timer t(ctx, "square"); he->square(*c1);} 138 | {timer t(ctx, "decr4");he->decrypt(*c1, *pres);} 139 | he->decode_i(*pres, vRes); 140 | for(int64_t i=0; i<20; i++){ 141 | std::cout << vRes[i] << ' '; 142 | } std::cout << endl; 143 | 144 | // Rotation 145 | std::cout << " Afseal - ROTATE" << endl; 146 | int rot_pos = 3; 147 | {timer t(ctx, "encr51"); he->encrypt(*p1, *c1);} 148 | {timer t(ctx, "rotate"); he->rotate(*c1, rot_pos);} 149 | {timer t(ctx, "decr4");he->decrypt(*c1, *pres);} 150 | he->decode_i(*pres, vRes); 151 | for(int64_t i=0; i<20; i++){ 152 | std::cout << vRes[i] << ' '; 153 | } std::cout << endl; 154 | 155 | // Relinearlization 156 | he->relinKeyGen(); 157 | he->relinearize(*c1); 158 | 159 | // Relinearlization 160 | std::cout << " Afseal - SAVE OBJS" << endl; 161 | string compr_mode = "zstd"; 162 | ofstream f("obj_context.pycon", ios::binary); 163 | size_t saved1 =he->save_context(f, compr_mode); 164 | f.close(); 165 | ofstream f2("obj_pubkey.pypk", ios::binary); 166 | size_t saved2 =he->save_public_key(f2, compr_mode); 167 | f2.close(); 168 | ofstream f3("obj_seckey.pysk", ios::binary); 169 | size_t saved3 = he->save_secret_key(f3, compr_mode); 170 | f3.close(); 171 | ofstream fc("obj_ctxt.cx", ios::binary); 172 | size_t saved4 = he->save_ciphertext(fc, compr_mode, *c1); 173 | f3.close(); 174 | 175 | std::cout << " - Saved " << saved1 << " " << saved2 << " " << saved3 << " " << saved4 <load_ciphertext(fc2, *c1); 192 | f3.close(); 193 | std::cout << " " << loaded7 <(he)), *(static_cast(c1))); 203 | std::cout << " - created from ctxt" << endl; 204 | AfsealPoly* poly2 = new AfsealPoly(*poly1); 205 | 206 | 207 | // Timings and results 208 | auto te = (ctx.timings["encr11"] + ctx.timings["encr12"] + ctx.timings["encr21"] + ctx.timings["encr22"] + ctx.timings["encr31"] + ctx.timings["encr32"] + ctx.timings["encr41"])/7.0; 209 | auto td = (ctx.timings["decr1"] + ctx.timings["decr2"] + ctx.timings["decr3"] + ctx.timings["decr4"])/4.0; 210 | auto tadd = ctx.timings["add"]; 211 | auto tmult = ctx.timings["mult"]; 212 | auto tsub = ctx.timings["sub"]; 213 | auto tsquare= ctx.timings["square"]; 214 | auto trot= ctx.timings["rotate"]; 215 | 216 | std::cout << endl << endl << "RESULTS:" << endl; 217 | std::cout << " nSlots = " << dynamic_cast(he)->get_nSlots() << endl; 218 | std::cout << " Times: " << endl; 219 | std::cout << " - Encryption: " << te << endl; 220 | std::cout << " - Decryption: " << td << endl; 221 | std::cout << " - Add: " << tadd << endl; 222 | std::cout << " - Mult: " << tmult << endl; 223 | std::cout << " - Sub: " << tsub << endl; 224 | std::cout << " - Square: " << tsquare << endl; 225 | std::cout << " - Rotate: " << trot << endl; 226 | 227 | }; 228 | 229 | -------------------------------------------------------------------------------- /Pyfhel/Afhel/Demos/Demo_Afseal_int.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include // Measure time 4 | 5 | #include 6 | #include 7 | 8 | class timing_map { 9 | public: 10 | std::map timings; 11 | }; 12 | 13 | class timer { 14 | public: 15 | timer(timing_map& newtmap, std::string newname) 16 | : tmap(newtmap), 17 | name(newname), 18 | start(std::clock()) {} 19 | 20 | ~timer() { 21 | tmap.timings[name] = static_cast(std::clock() - start) 22 | / static_cast(CLOCKS_PER_SEC); 23 | } 24 | 25 | timing_map& tmap; 26 | std::string name; 27 | std::clock_t start; 28 | }; 29 | 30 | timing_map ctx; 31 | 32 | 33 | int main(int argc, char **argv) 34 | { 35 | Afseal he; 36 | // Values for the modulus p (size of p): 37 | // - 2 (Binary) 38 | // - 257 (Byte) 39 | // - 65537 (Word) 40 | // - 4294967311 (Long) 41 | long p =1964769281; 42 | long m = 8192; 43 | long base = 3; 44 | long sec = 192; 45 | bool flagBatching=false; 46 | 47 | std::cout << " Afseal - Creating Context" << endl; 48 | {timer t(ctx, "contextgen");he.ContextGen(p, m, flagBatching, base, sec);} 49 | std::cout << " Afseal - Context CREATED" << endl; 50 | 51 | //TODO: print parameters 52 | 53 | std::cout << " Afseal - Generating Keys" << endl; 54 | {timer t(ctx, "keygen"); he.KeyGen();} 55 | std::cout << " Afseal - Keys Generated" << endl; 56 | 57 | int64_t v1; 58 | int64_t v2; 59 | int64_t vRes; 60 | Ciphertext k1, k2; 61 | 62 | v1=3; 63 | v2=-2; 64 | vRes = 0; 65 | std::cout << v1 << endl; 66 | std::cout << v2 << endl; 67 | 68 | Plaintext p1, p2; 69 | p1 = he.encode(v1); 70 | p2 = he.encode(v2); 71 | Plaintext p3 = p1 ; 72 | 73 | // Encryption 74 | {timer t(ctx, "encr11");k1 = he.encrypt(v1);} 75 | {timer t(ctx, "encr12");k2 = he.encrypt(v2);} 76 | 77 | 78 | // Sum 79 | std::cout << " Afseal - SUM" << endl; 80 | {timer t(ctx, "add"); he.add(k1, k2);} 81 | {timer t(ctx, "decr1"); he.decrypt(k1, vRes);} 82 | std::cout << vRes << endl; 83 | 84 | // Multiplication 85 | std::cout << " Afseal - MULT" << endl; 86 | {timer t(ctx, "encr21");k1 = he.encrypt(v1);} 87 | {timer t(ctx, "encr22");k2 = he.encrypt(v2);} 88 | {timer t(ctx, "mult");he.multiply(k1, k2);} 89 | {timer t(ctx, "decr2"); he.decrypt(k1, vRes);} 90 | std::cout << vRes << endl; 91 | 92 | // Substraction 93 | std::cout << " Afseal - SUB" << endl; 94 | {timer t(ctx, "encr31");k1 = he.encrypt(v1);} 95 | {timer t(ctx, "encr32");k2 = he.encrypt(v2);} 96 | {timer t(ctx, "sub");he.sub(k1, k2);} 97 | {timer t(ctx, "decr3");he.decrypt(k1, vRes);} 98 | std::cout << vRes << endl; 99 | 100 | // Square 101 | std::cout << " Afseal - SQUARE" << endl; 102 | {timer t(ctx, "encr41"); k1 = he.encrypt(v1);} 103 | {timer t(ctx, "square"); he.square(k1);} 104 | {timer t(ctx, "decr4");he.decrypt(k1, vRes);} 105 | std::cout << vRes << endl; 106 | 107 | // Timings and results 108 | auto te = (ctx.timings["encr11"] + ctx.timings["encr12"] + ctx.timings["encr21"] + ctx.timings["encr22"] + ctx.timings["encr31"] + ctx.timings["encr32"] + ctx.timings["encr41"])/7.0; 109 | auto td = (ctx.timings["decr1"] + ctx.timings["decr2"] + ctx.timings["decr3"] + ctx.timings["decr4"])/4.0; 110 | auto tadd = ctx.timings["add"]; 111 | auto tmult = ctx.timings["mult"]; 112 | auto tsub = ctx.timings["sub"]; 113 | auto tsquare= ctx.timings["square"]; 114 | 115 | std::cout << endl << endl << "RESULTS:" << endl; 116 | std::cout << " Times: " << endl; 117 | std::cout << " - Encryption: " << te << endl; 118 | std::cout << " - Decryption: " << td << endl; 119 | std::cout << " - Add: " << tadd << endl; 120 | std::cout << " - Mult: " << tmult << endl; 121 | std::cout << " - Sub: " << tsub << endl; 122 | std::cout << " - Square: " << tsquare << endl; 123 | 124 | }; 125 | 126 | -------------------------------------------------------------------------------- /Pyfhel/PyCtxt.pxd: -------------------------------------------------------------------------------- 1 | # distutils: language = c++ 2 | #cython: language_level=3, boundscheck=False 3 | 4 | # -------------------------------- CIMPORTS ------------------------------------ 5 | # Import from Cython libs required C/C++ types for the Afhel API 6 | from libcpp.string cimport string 7 | from libcpp cimport bool 8 | from libcpp.cast cimport dynamic_cast 9 | from libcpp.memory cimport shared_ptr, make_shared, dynamic_pointer_cast as dyn_cast 10 | 11 | # Used for all kinds of operations. Includes utility functions 12 | from Pyfhel.Pyfhel cimport * 13 | 14 | # Import our own wrapper for iostream classes, used for I/O ops 15 | from Pyfhel.utils.iostream cimport ifstream, ofstream, ostringstream, stringstream, binary 16 | 17 | # Import Abstract Ciphertext class 18 | from Pyfhel.Afhel.Afhel cimport * 19 | 20 | # ---------------------------- CYTHON DECLARATION ------------------------------ 21 | cdef class PyCtxt: 22 | cdef shared_ptr[AfCtxt] _ptr_ctxt 23 | cdef Pyfhel _pyfhel 24 | cdef scheme_t _scheme 25 | cdef backend_t _backend 26 | cdef int _mod_level 27 | cpdef PyCtxt copy(self) 28 | cpdef int size(self) 29 | cpdef void set_scale(self, double scale) 30 | cpdef void round_scale(self) 31 | cpdef size_t save(self, str fileName, str compr_mode=*) 32 | cpdef size_t load(self, str fileName, object scheme=*) 33 | cpdef bytes to_bytes(self, str compr_mode=*) 34 | cpdef void from_bytes(self, bytes content, object scheme=*) 35 | cpdef size_t sizeof_ciphertext(self, str compr_mode=*) 36 | 37 | # ---------------------------- VECTOR/ARRAY CLASS ------------------------------ 38 | cdef extern from "" namespace "std" nogil: 39 | vector[void*] move(vector[void*]) 40 | -------------------------------------------------------------------------------- /Pyfhel/PyPoly.pxd: -------------------------------------------------------------------------------- 1 | # distutils: language = c++ 2 | #cython: language_level=3, boundscheck=False 3 | 4 | # -------------------------------- CIMPORTS ------------------------------------ 5 | # Import from Cython libs required C/C++ types for the Afhel API 6 | from libcpp.string cimport string 7 | from libcpp.complex cimport complex as c_complex 8 | from libcpp cimport bool 9 | 10 | # Used for all kinds of operations 11 | from Pyfhel.Pyfhel cimport * 12 | 13 | # Import our own wrapper for iostream classes, used for I/O ops 14 | from Pyfhel.utils.iostream cimport ifstream, ofstream, ostringstream, stringstream, binary 15 | 16 | # Import AfsealPoly class 17 | from Pyfhel.Afhel.Afhel cimport AfPoly, AfsealPoly, scheme_t, backend_t, cy_complex 18 | 19 | # ------------------------------- DECLARATION --------------------------------- 20 | 21 | cdef class PyPoly: 22 | cdef AfsealPoly* _afpoly 23 | cdef Pyfhel _pyfhel 24 | cdef scheme_t _scheme 25 | cdef backend_t _backend 26 | cpdef vector[cy_complex] to_coeff_list(self) 27 | cpdef cy_complex get_coeff(self, size_t i) 28 | cpdef void set_coeff(self, cy_complex&val, size_t i) 29 | cpdef void check_afpoly(self) 30 | cpdef void from_coeff_list(self, vector[cy_complex] coeff_list, PyCtxt ref) 31 | 32 | # Serialize 33 | cpdef void save(self, str fileName) 34 | cpdef void load(self, str fileName, encoding) 35 | cpdef bytes to_bytes(self) 36 | cpdef void from_bytes(self, bytes content, encoding) 37 | -------------------------------------------------------------------------------- /Pyfhel/PyPtxt.pxd: -------------------------------------------------------------------------------- 1 | # distutils: language = c++ 2 | #cython: language_level=3, boundscheck=False 3 | 4 | # -------------------------------- CIMPORTS ------------------------------------ 5 | # Import from Cython libs required C/C++ types for the Afhel API 6 | from libcpp.string cimport string 7 | from libcpp cimport bool 8 | 9 | # Used for all kinds of operations 10 | from Pyfhel.Pyfhel cimport * 11 | 12 | # Import our own wrapper for iostream classes, used for I/O ops 13 | from Pyfhel.utils.iostream cimport ifstream, ofstream, ostringstream, stringstream, binary 14 | 15 | # Import Plaintext class, original from SEAL 16 | from Pyfhel.Afhel.Afhel cimport AfPtxt, AfsealPtxt, scheme_t, backend_t 17 | 18 | # ------------------------------- DECLARATION --------------------------------- 19 | 20 | cdef class PyPtxt: 21 | cdef AfPtxt* _ptr_ptxt 22 | cdef Pyfhel _pyfhel 23 | cdef scheme_t _scheme 24 | cdef backend_t _backend 25 | cdef int _mod_level 26 | cpdef bool is_zero(self) 27 | cpdef bool is_ntt_form(self) 28 | cpdef string to_poly_string(self) 29 | cpdef void save(self, str fileName, str compr_mode=*) 30 | cpdef void load(self, str fileName, object scheme=*) 31 | cpdef bytes to_bytes(self, str compr_mode=*) 32 | cpdef void from_bytes(self, bytes content, object scheme=*) 33 | cpdef void set_scale (self, double new_scale) -------------------------------------------------------------------------------- /Pyfhel/Pyfhel.pxd: -------------------------------------------------------------------------------- 1 | # distutils: language = c++ 2 | #cython: language_level=3, boundscheck=False 3 | 4 | # -------------------------------- CIMPORTS ----------------------------------- 5 | # import both numpy and the Cython declarations for numpy 6 | cimport numpy as np 7 | 8 | # Import from Cython libs required C/C++ types for the Afhel API 9 | from libcpp.vector cimport vector 10 | from libcpp.string cimport string 11 | from libcpp.cast cimport reinterpret_cast 12 | from libcpp.memory cimport shared_ptr, make_shared, dynamic_pointer_cast as dyn_cast 13 | from libcpp cimport bool 14 | 15 | ctypedef long long int64_t 16 | ctypedef unsigned long long uint64_t 17 | 18 | # Import our own wrapper for iostream classes, used for I/O ops 19 | from Pyfhel.utils.iostream cimport istream, ostream, ifstream, ofstream, ostringstream, stringstream, binary 20 | 21 | from Pyfhel.Afhel.Afhel cimport * 22 | 23 | # Import the Cython Plaintext, Ciphertext and Poly classes 24 | from Pyfhel.PyPtxt cimport PyPtxt 25 | from Pyfhel.PyCtxt cimport PyCtxt 26 | from Pyfhel.PyPoly cimport PyPoly 27 | 28 | # ---------------------------- CYTHON DECLARATION ------------------------------ 29 | cdef class Pyfhel: 30 | cdef Afhel* afseal # The C++ methods are accessed via a pointer 31 | cdef int _sec 32 | cdef vector[int] _qi_sizes 33 | cdef double _scale 34 | # =========================== CRYPTOGRAPHY ================================= 35 | # CONTEXT & KEY GENERATION 36 | cpdef string contextGen(self, 37 | str scheme, int n, int t_bits=*, int64_t t=*, 38 | int sec=*, double scale=*, int scale_bits=*, 39 | vector[int] qi_sizes =*, vector[uint64_t] qi =*) 40 | cpdef void keyGen(self) 41 | cpdef void relinKeyGen(self) 42 | cpdef void rotateKeyGen(self, vector[int] rot_steps =*) 43 | 44 | # ENCRYPTION 45 | cpdef PyCtxt encryptInt(self, int64_t[:] arr, PyCtxt ctxt=*) 46 | cpdef PyCtxt encryptFrac(self, double[:] arr, PyCtxt ctxt=*, 47 | double scale=*, int scale_bits=*) 48 | cpdef PyCtxt encryptComplex(self, complex[:] arr, PyCtxt ctxt=*, 49 | double scale=*, int scale_bits=*) 50 | cpdef PyCtxt encryptPtxt(self, PyPtxt ptxt, PyCtxt ctxt=*) 51 | cpdef PyCtxt encryptBGV(self, int64_t[:] arr, PyCtxt ctxt=*) 52 | # vectorized 53 | cpdef np.ndarray[object, ndim=1] encryptAInt(self, int64_t[:,::1] arr) 54 | cpdef np.ndarray[object, ndim=1] encryptAFrac(self, double[:,::1] arr, 55 | double scale=*, int scale_bits=*) 56 | cpdef np.ndarray[object, ndim=1] encryptAComplex(self, complex[:,::1] arr, 57 | double scale=*, int scale_bits=*) 58 | cpdef np.ndarray[object, ndim=1] encryptAPtxt(self, PyPtxt[:] ptxt) 59 | cpdef np.ndarray[object, ndim=1] encryptABGV(self, int64_t[:,::1] arr) 60 | 61 | # DECRYPTION 62 | cpdef np.ndarray[int64_t, ndim=1] decryptInt(self, PyCtxt ctxt) 63 | cpdef np.ndarray[double, ndim=1] decryptFrac(self, PyCtxt ctxt) 64 | cpdef np.ndarray[complex, ndim=1] decryptComplex(self, PyCtxt ctxt) 65 | cpdef PyPtxt decryptPtxt(self, PyCtxt ctxt, PyPtxt ptxt=*) 66 | cpdef np.ndarray[int64_t, ndim=1] decryptBGV(self, PyCtxt ctxt) 67 | # vectorized 68 | cpdef np.ndarray[int64_t, ndim=2] decryptAInt(self, PyCtxt ctxt) 69 | cpdef np.ndarray[double, ndim=2] decryptAFrac(self, PyCtxt ctxt) 70 | cpdef np.ndarray[double, ndim=2] decryptAComplex(self, PyCtxt ctxt) 71 | cpdef np.ndarray[object, ndim=1] decryptAPtxt(self, PyCtxt ctxt) 72 | cpdef np.ndarray[int64_t, ndim=2] decryptABGV(self, PyCtxt ctxt) 73 | 74 | # NOISE LEVEL 75 | cpdef int noise_level(self, PyCtxt ctxt) 76 | 77 | # ============================= ENCODING =================================== 78 | # ENCODE 79 | cpdef PyPtxt encodeInt(self, int64_t[::1] arr, PyPtxt ptxt=*) 80 | cpdef PyPtxt encodeFrac(self, double[::1] arr, PyPtxt ptxt=*, 81 | double scale=*, int scale_bits=*, ) 82 | cpdef PyPtxt encodeComplex(self, complex[::1] arr, PyPtxt ptxt=*, 83 | double scale=*, int scale_bits=*) 84 | cpdef PyPtxt encodeBGV(self, int64_t[::1] arr, PyPtxt ptxt=*) 85 | # vectorized 86 | cpdef np.ndarray[object, ndim=1] encodeAInt(self, int64_t[:,::1] arr) 87 | cpdef np.ndarray[object, ndim=1] encodeAFrac(self, double[:,::1] arr, 88 | double scale=*, int scale_bits=*) 89 | cpdef np.ndarray[object, ndim=1] encodeAComplex(self, complex[:,::1] arr, 90 | double scale=*, int scale_bits=*) 91 | cpdef np.ndarray[object, ndim=1] encodeABGV(self, int64_t[:,::1] arr) 92 | 93 | # DECODE 94 | cpdef np.ndarray[int64_t, ndim=1] decodeInt(self, PyPtxt ptxt) 95 | cpdef np.ndarray[double, ndim=1] decodeFrac(self, PyPtxt ptxt) 96 | cpdef np.ndarray[complex, ndim=1] decodeComplex(self, PyPtxt ptxt) 97 | cpdef np.ndarray[int64_t, ndim=1] decodeBGV(self, PyPtxt ptxt) 98 | # vectorized 99 | cpdef np.ndarray[int64_t, ndim=2] decodeAInt(self, PyPtxt[:] ptxt) 100 | cpdef np.ndarray[double, ndim=2] decodeAFrac(self, PyPtxt[:] ptxt) 101 | cpdef np.ndarray[complex, ndim=2] decodeAComplex(self, PyPtxt[:] ptxt) 102 | cpdef np.ndarray[int64_t, ndim=2] decodeABGV(self, PyPtxt[:] ptxt) 103 | 104 | # RELINEARIZE 105 | cpdef void relinearize(self, PyCtxt ctxt) 106 | 107 | # ============================ OPERATIONS ================================== 108 | cpdef PyCtxt negate(self, PyCtxt ctxt, bool in_new_ctxt=*) 109 | cpdef PyCtxt square(self, PyCtxt ctxt, bool in_new_ctxt=*) 110 | cpdef PyCtxt add(self, PyCtxt ctxt, PyCtxt ctxt_other, bool in_new_ctxt=*) 111 | cpdef PyCtxt add_plain(self, PyCtxt ctxt, PyPtxt ptxt, bool in_new_ctxt=*) 112 | cpdef PyCtxt cumul_add(self, PyCtxt ctxt, bool in_new_ctxt=*, size_t n_elements=*) 113 | cpdef PyCtxt sub(self, PyCtxt ctxt, PyCtxt ctxt_other, bool in_new_ctxt=*) 114 | cpdef PyCtxt sub_plain(self, PyCtxt ctxt, PyPtxt ptxt, bool in_new_ctxt=*) 115 | cpdef PyCtxt multiply(self, PyCtxt ctxt, PyCtxt ctxt_other, bool in_new_ctxt=*) 116 | cpdef PyCtxt multiply_plain(self, PyCtxt ctxt, PyPtxt ptxt, bool in_new_ctxt=*) 117 | cpdef PyCtxt scalar_prod(self, PyCtxt ctxt, PyCtxt ctxt_other, 118 | bool in_new_ctxt=*, bool with_relin=*, bool with_mod_switch=*, size_t n_elements=*) 119 | cpdef PyCtxt scalar_prod_plain(self, PyCtxt ctxt, PyPtxt ptxt_other, 120 | bool in_new_ctxt=*, bool with_relin=*, bool with_mod_switch=*, size_t n_elements=*) 121 | cpdef PyCtxt rotate(self, PyCtxt ctxt, int k, bool in_new_ctxt=*) 122 | cpdef PyCtxt flip(self, PyCtxt ctxt, bool in_new_ctxt=*) 123 | cpdef PyCtxt power(self, PyCtxt ctxt, uint64_t expon, bool in_new_ctxt=*) 124 | # ckks 125 | cpdef void rescale_to_next(self, PyCtxt ctxt) 126 | cpdef PyCtxt mod_switch_to_next_ctxt(self, PyCtxt ctxt, bool in_new_ctxt=*) 127 | cpdef PyPtxt mod_switch_to_next_ptxt(self, PyPtxt ptxt, bool in_new_ptxt=*) 128 | # ================================ I/O ===================================== 129 | # FILES 130 | cpdef size_t save_context(self, fileName, str compr_mode=*) 131 | cpdef size_t load_context(self, fileName) 132 | 133 | cpdef size_t save_public_key(self, fileName, str compr_mode=*) 134 | cpdef size_t load_public_key(self, fileName) 135 | 136 | cpdef size_t save_secret_key(self, fileName, str compr_mode=*) 137 | cpdef size_t load_secret_key(self, fileName) 138 | 139 | cpdef size_t save_relin_key(self, fileName, str compr_mode=*) 140 | cpdef size_t load_relin_key(self, fileName) 141 | 142 | cpdef size_t save_rotate_key(self, fileName, str compr_mode=*) 143 | cpdef size_t load_rotate_key(self, fileName) 144 | 145 | # BYTES 146 | cpdef bytes to_bytes_context(self, str compr_mode=*) 147 | cpdef size_t from_bytes_context(self, bytes content) 148 | 149 | cpdef bytes to_bytes_public_key(self, str compr_mode=*) 150 | cpdef size_t from_bytes_public_key(self, bytes content) 151 | 152 | cpdef bytes to_bytes_secret_key(self, str compr_mode=*) 153 | cpdef size_t from_bytes_secret_key(self, bytes content) 154 | 155 | cpdef bytes to_bytes_relin_key(self, str compr_mode=*) 156 | cpdef size_t from_bytes_relin_key(self, bytes content) 157 | 158 | cpdef bytes to_bytes_rotate_key(self, str compr_mode=*) 159 | cpdef size_t from_bytes_rotate_key(self, bytes content) 160 | 161 | # SIZES 162 | cpdef size_t sizeof_context(self, str compr_mode=*) 163 | cpdef size_t sizeof_public_key(self, str compr_mode=*) 164 | cpdef size_t sizeof_secret_key(self, str compr_mode=*) 165 | cpdef size_t sizeof_relin_key(self, str compr_mode=*) 166 | cpdef size_t sizeof_rotate_key(self, str compr_mode=*) 167 | 168 | 169 | # ============================== AUXILIARY ================================= 170 | cpdef long maxBitCount(self, long poly_modulus_degree, int sec_level) 171 | 172 | # GETTERS 173 | cpdef bool batchEnabled(self) 174 | cpdef size_t get_nSlots(self) 175 | cpdef vector[uint64_t] get_qi(self) 176 | cpdef uint64_t get_plain_modulus(self) 177 | cpdef size_t get_poly_modulus_degree(self) 178 | cpdef scheme_t get_scheme(self) 179 | 180 | cpdef bool is_secret_key_empty(self) 181 | cpdef bool is_public_key_empty(self) 182 | cpdef bool is_rotate_key_empty(self) 183 | cpdef bool is_relin_key_empty(self) 184 | cpdef bool is_context_empty(self) 185 | 186 | # ============================ POLYNOMIAL ================================= 187 | cpdef PyPoly empty_poly(self, PyCtxt ref) 188 | cpdef PyPoly poly_from_ciphertext(self, PyCtxt ctxt, size_t i) 189 | cpdef PyPoly poly_from_plaintext(self, PyCtxt ref, PyPtxt ptxt) 190 | cpdef PyPoly poly_from_coeff_vector(self, vector[cy_complex] coeff_vector, PyCtxt ref) 191 | cpdef list polys_from_ciphertext(self, PyCtxt ctxt) 192 | 193 | cpdef PyPoly poly_add(self, PyPoly p, PyPoly p_other, bool in_new_poly=*) 194 | cpdef PyPoly poly_subtract(self, PyPoly p, PyPoly p_other, bool in_new_poly=*) 195 | cpdef PyPoly poly_multiply(self, PyPoly p, PyPoly p_other, bool in_new_poly=*) 196 | cpdef PyPoly poly_invert(self, PyPoly p, bool in_new_poly=*) 197 | 198 | cpdef void poly_to_ciphertext(self, PyPoly p, PyCtxt ctxt, size_t i) 199 | cpdef void poly_to_plaintext(self, PyPoly p, PyPtxt ptxt) 200 | 201 | # --------------------------------- UTILS -------------------------------------- 202 | cpdef to_Scheme_t(object scheme) 203 | cpdef to_Backend_t(object backend) 204 | cdef double _get_valid_scale(int& scale_bits, double& scale, double& pyfhel_scale) 205 | cdef void _write_cy_attributes(Pyfhel he, ostream& ostr) 206 | cdef void _read_cy_attributes(Pyfhel he, istream& istr) 207 | 208 | cpdef np.ndarray[dtype=np.int64_t, ndim=1] vec_to_array_i(vector[int64_t] vec) 209 | cpdef np.ndarray[dtype=np.uint64_t, ndim=1] vec_to_array_u(vector[uint64_t] vec) 210 | cpdef np.ndarray[dtype=double, ndim=1] vec_to_array_f(vector[double] vec) 211 | cdef shared_ptr[AfsealCtxt] _dyn_c(shared_ptr[AfCtxt] c) -------------------------------------------------------------------------------- /Pyfhel/__init__.py: -------------------------------------------------------------------------------- 1 | from .Pyfhel import Pyfhel 2 | from .PyCtxt import PyCtxt 3 | from .PyPtxt import PyPtxt 4 | from .PyPoly import PyPoly 5 | 6 | __all__ = ["Pyfhel", "PyCtxt", "PyPtxt", "PyPoly"] 7 | __name__ = "Pyfhel" 8 | __author__ = "Alberto Ibarrondo" 9 | 10 | __version__ = "3.4.2" 11 | -------------------------------------------------------------------------------- /Pyfhel/test/README.md: -------------------------------------------------------------------------------- 1 | # Pyfhel Tests 2 | These tests require `pytest` to run. Once installed (e.g. `pip install pytest`), run the tests by executing `pytest`: 3 | ``` 4 | python -m pytest . 5 | ``` 6 | 7 | To run tests with code coverage, create an empty `.cov` file in the top level directory, then build and install the package, then run the tests with `` 8 | ``` 9 | python -m pytest --cov . 10 | ``` 11 | To obtain a report, just run `coverage html` and open the file `htmlcov/index.html` in your browser. 12 | 13 | -------------------------------------------------------------------------------- /Pyfhel/test/test_Demos.py: -------------------------------------------------------------------------------- 1 | 2 | import sys, contextlib 3 | from io import StringIO 4 | from pathlib import Path 5 | 6 | # Redirect stdout of Demos to a string, recover if error. 7 | # https://stackoverflow.com/a/41614610/9670056 8 | @contextlib.contextmanager 9 | def stdoutIO(stdout=None): 10 | old = sys.stdout 11 | if stdout is None: 12 | stdout = StringIO() 13 | sys.stdout = stdout 14 | yield stdout 15 | sys.stdout = old 16 | 17 | # Demo execution from https://stackoverflow.com/a/41658338/9670056 18 | def execfile(filepath, globals=None, locals=None): 19 | filepath = str(filepath) 20 | if globals is None: 21 | globals = {} 22 | globals.update({ 23 | "__file__": filepath, 24 | "__name__": "__main__", 25 | }) 26 | with open(filepath, 'rb') as file: 27 | with stdoutIO() as s: 28 | exec(compile(file.read(), filepath, 'exec'), globals, locals) 29 | # Examples folder 30 | EXAMPLES_FOLDER = Path(__file__).parents[2].absolute() / 'examples' 31 | 32 | 33 | 34 | ################################################################################ 35 | # COVERAGE TESTS # 36 | ################################################################################ 37 | def test_Demo_1_Helloworld(): 38 | execfile(EXAMPLES_FOLDER / 'Demo_1_HelloWorld.py') 39 | 40 | def test_Demo_2_Integer_BFV(): 41 | execfile(EXAMPLES_FOLDER / 'Demo_2_Integer_BFV.py') 42 | 43 | def test_Demo_3_Float_CKKS(): 44 | execfile(EXAMPLES_FOLDER / 'Demo_3_Float_CKKS.py') 45 | 46 | def test_Demo_4_SaveNRestore(): 47 | execfile(EXAMPLES_FOLDER / 'Demo_4_SaveNRestore.py') 48 | 49 | def test_Demo_5_CS_Client(): 50 | execfile(EXAMPLES_FOLDER / 'Demo_5_CS_Client.py') 51 | 52 | def test_Demo_6_MultDepth(): 53 | execfile(EXAMPLES_FOLDER / 'Demo_6_MultDepth.py') 54 | 55 | def test_Demo_7_ScalarProd(): 56 | execfile(EXAMPLES_FOLDER / 'Demo_7_ScalarProd.py') 57 | 58 | def test_Demo_8_HammingDist(): 59 | execfile(EXAMPLES_FOLDER / 'Demo_8_HammingDist.py') 60 | 61 | def test_Demo_9_Integer_BGV(): 62 | execfile(EXAMPLES_FOLDER / 'Demo_9_Integer_BGV.py') 63 | 64 | def test_Demo_WAHC21(): 65 | execfile(EXAMPLES_FOLDER / 'Demo_WAHC21.py') -------------------------------------------------------------------------------- /Pyfhel/test/test_PyCtxt.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from sys import getsizeof 3 | import numpy as np 4 | from Pyfhel import Pyfhel, PyCtxt 5 | from Pyfhel.utils import Scheme_t 6 | 7 | ################################################################################ 8 | # SETUP FIXTURES # 9 | ################################################################################ 10 | # The list of context parameters to be tested for each test 11 | context_params_list = [ 12 | {"scheme": "bfv", "n": 16384, "t_bits": 30, "sec":128,}, 13 | {"scheme": "ckks", "n": 16384, "scale": 2**40, "qi_sizes": [60]+[40]*7+[60],}, 14 | ] 15 | 16 | # Pyfhel object setup 17 | @pytest.fixture(scope="class", params=context_params_list) 18 | def HE(request): 19 | HE = Pyfhel() 20 | HE.contextGen(**request.param) 21 | HE.keyGen() 22 | HE.relinKeyGen() 23 | HE.rotateKeyGen() 24 | return HE 25 | 26 | 27 | ################################################################################ 28 | # COVERAGE TESTS # 29 | ################################################################################ 30 | 31 | class TestPyCtxt: 32 | def test_PyCtxt_creation(self, HE): 33 | # CONSTRUCTORS 34 | # Copy constructor 35 | c1 = HE.encrypt(1) 36 | c2 = PyCtxt(copy_ctxt=c1) 37 | c3 = c1.copy() 38 | del c1 39 | assert np.round(HE.decrypt(c2)[0])==1 40 | # Empty constructor 41 | c3 = PyCtxt(scheme=HE.scheme.name) 42 | assert c3.scheme == HE.scheme, "Wrong initialized scheme" 43 | # File-reading constructor without scheme --> exception 44 | with pytest.raises(TypeError, match=".*.*") as e_info: 45 | c4 = PyCtxt(fileName="fakectxt.pyctxt") 46 | # Byte-reading constructor without scheme --> exception 47 | with pytest.raises(TypeError, match=".*.*") as e_info: 48 | c4 = PyCtxt(bytestring=b"fakectxtcontent") 49 | 50 | def test_PyCtxt_properties(self, HE): 51 | c = HE.encrypt(1) 52 | # PROPERTIES 53 | # Scheme 54 | with pytest.raises(TypeError, match=".*.*") as e_info: 55 | c.scheme = {"a wrong type of object": "woops!"} 56 | del c.scheme 57 | assert c.scheme == Scheme_t.none 58 | assert "?" in c.__repr__() # --> When scheme is not known 59 | c.scheme = HE.scheme 60 | assert c.scheme == HE.scheme 61 | # Mod Level 62 | c.mod_level = 1 63 | del c.mod_level 64 | assert c.mod_level == 0 65 | # Pyfhel 66 | with pytest.raises(TypeError, match=".*.*") as e_info: 67 | c._pyfhel = "a wrong type of object" 68 | c._pyfhel = Pyfhel() 69 | c._pyfhel = HE 70 | assert id(c._pyfhel)==id(HE) 71 | # Size 72 | assert c.size() == 2 73 | # memory size 74 | with pytest.raises(ValueError, match=".*ciphertext sizing requires a Pyfhel instance.*"): 75 | PyCtxt().sizeof_ciphertext() 76 | assert getsizeof(c) >= 2000000 # 2MB each 77 | # Scale 78 | c.scale = 1 79 | assert c.scale == 1 80 | 81 | @pytest.mark.filterwarnings('ignore::pytest.PytestUnraisableExceptionWarning') 82 | def test_PyCtxt_save_load(self, HE, tmp_path): 83 | # Saving with empty pyfhel should raise an error --> Pytest cannot capture it, raises a warning instead 84 | c = PyCtxt() 85 | with pytest.raises(ValueError, match=".*.*") as e_info: 86 | bts = c.to_bytes() 87 | with pytest.raises(ValueError, match=".*ciphertext saving requires a Pyfhel instance.*"): 88 | c.save("dummy.file") 89 | # File loading with custom scheme should override HE's scheme 90 | c = HE.encrypt(1) 91 | c.save(str(tmp_path / "c1")) 92 | c.load(str(tmp_path / "c1"), scheme=Scheme_t.none) 93 | assert c.scheme == Scheme_t.none 94 | # Byte deserializing with custom scheme should override HE's scheme 95 | c = HE.encrypt(1) 96 | bts = c.to_bytes() 97 | c.from_bytes(bts, scheme=Scheme_t.none) 98 | assert c.scheme == Scheme_t.none 99 | # Loading without pyfhel should raise an error 100 | c = PyCtxt() 101 | with pytest.raises(ValueError, match=".*ciphertext loading requires a Pyfhel instance.*"): 102 | c.from_bytes(b"dummy") 103 | with pytest.raises(ValueError, match=".*ciphertext loading requires a Pyfhel instance.*"): 104 | c.load("dummy.file") 105 | 106 | def test_PyCtxt_add(self, HE): 107 | c = HE.encrypt(1) 108 | # Wrong summand type 109 | with pytest.raises(TypeError, match=".*.*") as e_info: 110 | no_numeric_sumand = c + "1" 111 | with pytest.raises(TypeError, match=".*.*") as e_info: 112 | c += "1" 113 | # Plaintext iadd 114 | c += 1 115 | assert np.round(HE.decrypt(c)[0])==2 116 | # Cumul add 117 | c_cumul = +c 118 | assert np.round(HE.decrypt(c_cumul)[0])==2*HE.get_nSlots() 119 | 120 | def test_PyCtxt_sub(self, HE): 121 | c1 = HE.encrypt(1) 122 | c2 = HE.encrypt(2) 123 | # Wrong input type 124 | with pytest.raises(TypeError, match=".*.*") as e_info: 125 | c1 -= {"1"} 126 | with pytest.raises(TypeError, match=".*.*") as e_info: 127 | c1 = c1 - "1" 128 | # Plaintext & ciphertext isub 129 | c1 -= 2 130 | c1 -= c2 # c1[0]=0 131 | assert np.round(HE.decrypt(c1)[0])==-3 132 | 133 | def test_PyCtxt_mul(self, HE): 134 | c = HE.encrypt(1) 135 | c2 = HE.encrypt(2) 136 | # Wrong factor type 137 | with pytest.raises(TypeError, match=".*.*") as e_info: 138 | c *= {"1"} 139 | with pytest.raises(TypeError, match=".*.*") as e_info: 140 | c = c * "1" 141 | # Plaintext imul 142 | c *= c 143 | c *= 1 144 | assert np.round(HE.decrypt(c)[0])==1 145 | 146 | def test_PyCtxt_scalar_prod(self, HE): 147 | c1 = HE.encrypt(1) 148 | c2 = HE.encrypt(2) 149 | assert np.round(HE.decrypt(c1 @ 2)[0])==2*HE.get_nSlots() 150 | c1 @= c1 151 | assert np.round(HE.decrypt(c1)[0])==HE.get_nSlots() 152 | c1 @= np.array([2]) 153 | assert np.round(HE.decrypt(c1)[0])==2*HE.get_nSlots() 154 | c2 @= c2 155 | assert np.round(HE.decrypt(c2)[0])==4*HE.get_nSlots() 156 | 157 | def test_PyCtxt_truediv(self, HE): 158 | c1 = HE.encrypt(1) 159 | c2 = HE.encrypt(2) 160 | p = HE.encode(1) 161 | # Wrong summand type 162 | with pytest.raises(TypeError, match=".*.*") as e_info: 163 | c1 /= {"2"} 164 | with pytest.raises(TypeError, match=".*.*") as e_info: 165 | c1 = c1 / c2 # Divison requires float/int/plaintext to get the inverse 166 | # truediv 167 | c1 = c1 / 1 168 | c1 = c1 / p 169 | c1 /= 1 170 | c1 /= p 171 | assert np.round(HE.decrypt(c1)[0])==1 172 | # Wrong scheme 173 | with pytest.raises(TypeError, match=".*.*") as e_info: 174 | del c1.scheme # = Scheme_t.none 175 | c1 /= 1 176 | with pytest.raises(TypeError, match=".*.*") as e_info: 177 | del c1.scheme 178 | c1 = c1 / 1 179 | 180 | def test_PyCtxt_pow(self, HE): 181 | c = HE.encrypt(2) 182 | c **=2 183 | assert np.round(HE.decrypt(c)[0])==4 184 | # Exponentiate oly available in bfv 185 | if c._pyfhel.scheme == Scheme_t.bfv: 186 | with pytest.raises(ValueError, match=".*not enough relinearization keys.*") as e_info: 187 | c **= 3 188 | # TODO: make enough relin keys to make this work 189 | # assert np.round(HE.decrypt(c)[0])==64 190 | else: # ckks scheme raises an error 191 | with pytest.raises(RuntimeError, match=".*unsupported scheme.*") as e_info: 192 | c **= 3 193 | 194 | def test_PyCtxt_rotate(self, HE): 195 | c1 = HE.encrypt(1) 196 | c1 >>= 1 197 | c1 <<= 1 198 | assert np.round(HE.decrypt(c1)[0])==1 199 | # flip 200 | if c1._pyfhel.scheme == Scheme_t.bfv: 201 | c1 ^= 1 202 | c1 ^= 1 203 | assert np.round(HE.decrypt(c1)[0])==1 204 | 205 | def test_PyCtxt_io(self, HE): 206 | c = HE.encrypt(1) 207 | assert bytes(c) == c.to_bytes() 208 | # Cannot serialize without a pyfhel object 209 | with pytest.raises(ValueError, match=".*.*") as e_info: 210 | del c._pyfhel 211 | c.to_bytes() 212 | # c._pyfhel = HE 213 | # with pytest.raises(ValueError, match=".*.*") as e_info: 214 | # del c._pyfhel 215 | # c.save("dummy.file") 216 | # Cannot deserialize without pyfhel object 217 | 218 | def test_PyCtxt_encrypt(self, HE): 219 | c = HE.encrypt(1) 220 | c.encrypt(2) 221 | assert np.round(HE.decrypt(c)[0])==2 222 | 223 | def test_PyCtxt_encode_operand(self, HE): 224 | c = HE.encrypt(1) 225 | if c.scheme == Scheme_t.bfv: 226 | pass # Complex values not supported in bfv 227 | else: 228 | p = c.encode_operand([1+1j, 1j]) # Encode complex number 229 | c += p 230 | res = HE.decryptComplex(c) 231 | assert np.round(np.real(res[0]))==2 232 | assert np.round(np.imag(res[1]))==1 -------------------------------------------------------------------------------- /Pyfhel/test/test_PyPtxt.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | from Pyfhel import Pyfhel, PyPtxt 4 | from Pyfhel.utils import Scheme_t 5 | 6 | ################################################################################ 7 | # SETUP FIXTURES # 8 | ################################################################################ 9 | # The list of context parameters to be tested for each test 10 | context_params_list = [ 11 | {"scheme": "bfv", "n": 16384, "t_bits": 30, "sec":128,}, 12 | {"scheme": "ckks", "n": 16384, "scale": 2**30, "qi_sizes": [60]+[30]*7+[60],}, 13 | ] 14 | 15 | # Pyfhel object setup 16 | @pytest.fixture(scope="class", params=context_params_list) 17 | def HE(request): 18 | HE = Pyfhel() 19 | HE.contextGen(**request.param) 20 | HE.keyGen() 21 | HE.relinKeyGen() 22 | HE.rotateKeyGen() 23 | return HE 24 | 25 | @pytest.fixture(scope="function") 26 | def input_one(HE): 27 | return 1 if HE.scheme==Scheme_t.bfv else 1. 28 | 29 | @pytest.fixture(scope="function") 30 | def input_zero(HE): 31 | return 0 if HE.scheme==Scheme_t.bfv else 0. 32 | 33 | ################################################################################ 34 | # COVERAGE TESTS # 35 | ################################################################################ 36 | 37 | class TestPyPtxt: 38 | def test_PyPtxt_creation(self, HE, input_one): 39 | # CONSTRUCTORS 40 | # Copy constructor 41 | p1 = HE.encode(input_one) 42 | p2 = PyPtxt(copy_ptxt=p1) 43 | del p1 44 | assert np.round(HE.decode(p2)[0])==1 45 | # Empty constructor 46 | p3 = PyPtxt(scheme=HE.scheme.name) 47 | assert p3.scheme == HE.scheme, "Wrong initialized scheme" 48 | # File-reading constructor without scheme --> exception 49 | with pytest.raises(TypeError, match=".*.*") as e_info: 50 | p4 = PyPtxt(fileName="fakeptxt.PyPtxt") 51 | # Byte-reading constructor without scheme --> exception 52 | with pytest.raises(TypeError, match=".*.*") as e_info: 53 | p4 = PyPtxt(bytestring=b"fakectxtcontent") 54 | 55 | def test_PyPtxt_properties(self, HE, input_one, input_zero): 56 | p = HE.encode(input_one) 57 | # PROPERTIES 58 | # Scheme 59 | with pytest.raises(TypeError, match=".*.*") as e_info: 60 | p.scheme = {"a wrong type of object": "woops!"} 61 | del p.scheme 62 | assert p.scheme == Scheme_t.none 63 | p.scheme = HE.scheme 64 | assert p.scheme == HE.scheme 65 | # Mod Level 66 | p.mod_level = 1 67 | del p.mod_level 68 | assert p.mod_level == 0 69 | # Pyfhel 70 | with pytest.raises(TypeError, match=".*.*") as e_info: 71 | p._pyfhel = "a wrong type of object" 72 | p._pyfhel = Pyfhel() 73 | p._pyfhel = HE 74 | assert id(p._pyfhel)==id(HE) 75 | # Scale 76 | p.scale = 1 77 | assert p.scale == 1 78 | # is_zero 79 | assert p.is_zero()==False 80 | p2 = HE.encode(input_zero) 81 | assert p2.is_zero()==True 82 | 83 | @pytest.mark.filterwarnings('ignore::pytest.PytestUnraisableExceptionWarning') 84 | def test_PyPtxt_save_load(self, HE, input_one, tmp_path): 85 | # Saving with empty pyfhel should raise an error --> Pytest cannot capture it, raises a warning instead 86 | p = PyPtxt() 87 | with pytest.raises(ValueError, match=".*.*") as e_info: 88 | bts = p.to_bytes() 89 | with pytest.raises(ValueError, match=".*plaintext saving requires a Pyfhel instance.*"): 90 | p.save("dummy.file") 91 | # File loading with custom scheme should override HE's scheme 92 | p = HE.encode(input_one) 93 | p.save(str(tmp_path / "p1")) 94 | p.load(str(tmp_path / "p1"), scheme=Scheme_t.none) 95 | assert p.scheme == Scheme_t.none 96 | # Byte deserializing with custom scheme should override HE's scheme 97 | p = HE.encode(input_one) 98 | bts = p.to_bytes() 99 | p.from_bytes(bts, scheme=Scheme_t.none) 100 | assert p.scheme == Scheme_t.none 101 | # Loading without pyfhel should raise an error 102 | p = PyPtxt() 103 | with pytest.raises(ValueError, match=".*plaintext loading requires a Pyfhel instance.*"): 104 | p.from_bytes(b"dummy") 105 | with pytest.raises(ValueError, match=".*plaintext loading requires a Pyfhel instance.*"): 106 | p.load("dummy.file") 107 | 108 | def test_PyPtxt_encode(self, HE, input_one, input_zero): 109 | p = HE.encode(input_one) 110 | p.encode(input_zero) 111 | if HE.scheme==Scheme_t.ckks: 112 | assert np.round(HE.decode(p)[0])==0 113 | 114 | def test_PyPtxt_cast_int_float(self, HE, input_one): 115 | p = HE.encode(input_one) 116 | if p.scheme == Scheme_t.bfv: 117 | assert int(p)==1 118 | with pytest.raises(RuntimeError, match=".*.*"): 119 | float(p) 120 | else: 121 | assert np.round(float(p))==1 122 | with pytest.raises(RuntimeError, match=".*.*"): 123 | int(p) -------------------------------------------------------------------------------- /Pyfhel/test/test_ckks.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from Pyfhel import Pyfhel 3 | 4 | ################################################################################ 5 | # SETUP FIXTURES # 6 | ################################################################################ 7 | # The list of context parameters to be tested for each test 8 | context_params_list = [ 9 | {"scheme": "ckks", "n": 16384, "scale": 2**53, "qi_sizes": [60]+[53]*6+[60]} 10 | ] 11 | 12 | # Pyfhel object setup 13 | @pytest.fixture 14 | def HE(request): 15 | HE = Pyfhel() 16 | HE.contextGen(**request.param) 17 | HE.keyGen() 18 | HE.relinKeyGen() 19 | return HE 20 | 21 | ################################################################################ 22 | # COVERAGE TESTS # 23 | ################################################################################ 24 | 25 | 26 | 27 | ################################################################################ 28 | # REGRESSION TESTS # 29 | ################################################################################ 30 | @pytest.mark.parametrize("HE", context_params_list, indirect=True) 31 | def test_issue128_ptxt_ctxt__mul__(HE): 32 | ctxt1 = HE.encrypt(42.0) * HE.encrypt(42.0) 33 | ptxt1 = HE.encode(42.0) 34 | 35 | fixed_ctxt1, fixed_ptxt1 = HE.align_mod_n_scale(ctxt1, ptxt1, only_mod=True) 36 | 37 | print(HE.decrypt(ctxt1)) 38 | print(HE.decrypt(fixed_ctxt1)) 39 | print(HE.decode(ptxt1)) 40 | print(HE.decode(fixed_ptxt1)) 41 | print(HE.decrypt(ctxt1 * ptxt1)) 42 | print(HE.decrypt(fixed_ctxt1 * fixed_ptxt1)) 43 | 44 | assert round(HE.decrypt(fixed_ctxt1 * fixed_ptxt1)[0]) == \ 45 | round(HE.decrypt(ctxt1 * ptxt1)[0]) == \ 46 | (42 * 42) * 42 -------------------------------------------------------------------------------- /Pyfhel/test/test_utils.py: -------------------------------------------------------------------------------- 1 | import time 2 | import pytest 3 | import numpy as np 4 | from Pyfhel import Pyfhel, PyPtxt, PyCtxt 5 | from Pyfhel.utils import Scheme_t, Backend_t, _to_valid_file_str, modular_pow 6 | 7 | ################################################################################ 8 | # COVERAGE TESTS # 9 | ################################################################################ 10 | 11 | def test_utils_Scheme_t(): 12 | none_scheme = Scheme_t.none 13 | assert none_scheme.value == 0x0 14 | assert none_scheme.name == "none" 15 | bfv_scheme = Scheme_t.bfv 16 | assert bfv_scheme.value == 0x1 17 | assert bfv_scheme.name == "bfv" 18 | ckks_scheme = Scheme_t.ckks 19 | assert ckks_scheme.value == 0x2 20 | assert ckks_scheme.name == "ckks" 21 | 22 | def test_utils_Backend_t(): 23 | none_backend = Backend_t.none 24 | assert none_backend.value == 0x0 25 | assert none_backend.name == "none" 26 | seal_backend = Backend_t.seal 27 | assert seal_backend.value == 0x1 28 | assert seal_backend.name == "seal" 29 | palisade_backend = Backend_t.palisade 30 | assert palisade_backend.value == 0x2 31 | assert palisade_backend.name == "palisade" 32 | 33 | def test_utils_to_valid_file_str(): 34 | with pytest.raises(FileNotFoundError, match=".*not found.*") as e_info: 35 | _to_valid_file_str("fake_seck_file.zip", True) 36 | with pytest.raises(TypeError, match=".*type str or Path.*") as e_info: 37 | _to_valid_file_str(1, True) 38 | assert _to_valid_file_str("pyproject.toml", True) == "pyproject.toml" 39 | 40 | def test_utils_modular_pow(): 41 | assert np.allclose(modular_pow([2,3], 3, 11), np.array([2,3])**3 % 11) 42 | -------------------------------------------------------------------------------- /Pyfhel/utils/Backend_t.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | class Backend_t(Enum): 3 | """An Enum to define the backend""" 4 | 5 | none=0x0 6 | """Default value of 0 for non defined backend.""" 7 | 8 | seal=0x1 9 | """Microsof's SEAL library. https://github.com/microsoft/SEAL/""" 10 | 11 | palisade=0x2 12 | """PALISADE library. https://gitlab.com/palisade/""" -------------------------------------------------------------------------------- /Pyfhel/utils/Scheme_t.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | class Scheme_t(Enum): 3 | """An Enum to define the scheme type""" 4 | 5 | none=0x0 6 | """Default value of 0 for non defined scheme.""" 7 | 8 | bfv=0x1 9 | """Integer encoding, used with encryptInt/decryptInt and encodeInt/decodeInt.""" 10 | 11 | ckks=0x2 12 | """Fractional encoding, used with encryptFrac/decryptFrac and encodeFrac/decodeFrac.""" 13 | 14 | bgv=0x3 15 | """Integer encoding, used with encryptBGV/decryptBGV and encodeBGV/decodeBGV.""" -------------------------------------------------------------------------------- /Pyfhel/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from Pyfhel.utils.Scheme_t import Scheme_t 2 | from Pyfhel.utils.Backend_t import Backend_t 3 | from Pyfhel.utils.utils import _to_valid_file_str, modular_pow 4 | __all__ = ["Backend_t", "Scheme_t", "_to_valid_file_str", "modular_pow"] 5 | -------------------------------------------------------------------------------- /Pyfhel/utils/cy_type_converters.pxi: -------------------------------------------------------------------------------- 1 | from Pyfhel.utils.Scheme_t import Scheme_t 2 | from Pyfhel.utils.Backend_t import Backend_t 3 | 4 | cpdef to_Scheme_t(object scheme): 5 | """Turns `scheme` into an scheme_t.{bfv, bgv, ckks} enum. 6 | 7 | Arguments: 8 | scheme (str, type, int, scheme_t): One of the following: 9 | 10 | * (str): ('int', 'integer', 'bfv') for bfv scheme, 11 | ('float', 'double', 'ckks') for ckks scheme. 12 | ('bgv') for bgv scheme. 13 | 14 | * Python class: (int) for bfv scheme, (float) for ckks scheme. 15 | 16 | * (int): (1) for bfv, (2) for ckks, (3) for bgv schemes. 17 | 18 | * (scheme_t) Enum (does nothing) 19 | 20 | Returns: 21 | scheme_t: bfv, bgv or ckks. 22 | """ 23 | if type(scheme) is type: scheme = scheme.__class__.__name__ 24 | if type(scheme) is str: 25 | if scheme.lower() in ('int', 'integer', 'bfv'): scheme = "bfv" 26 | elif scheme.lower() in ('float', 'double', 'ckks'): scheme = "ckks" 27 | elif scheme.lower() == 'bgv': scheme = "bgv" 28 | return Scheme_t[scheme] 29 | elif isinstance(scheme, (int, float)): 30 | return Scheme_t(int(scheme)) 31 | elif isinstance(scheme, Scheme_t): 32 | return scheme 33 | raise TypeError(": scheme unknown. Could not convert to Scheme_t.") 34 | 35 | 36 | cpdef to_Backend_t(object backend): 37 | """Turns `backend` into a backend_t.{seal, palisade} enum. 38 | 39 | Arguments: 40 | backend (str, backend_t): One of the following: 41 | 42 | * (str): ('seal' | 'palisade') with uppercase variants. 43 | * (backend_t) Enum (does nothing) 44 | 45 | Returns: 46 | backend_t: seal or palisade. 47 | """ 48 | if type(backend) is type: backend = backend.__class__.__name__ 49 | if type(backend) is str: 50 | if backend.lower() in ('seal'): backend = "seal" 51 | elif backend.lower() in ('palisade'): backend = "palisade" 52 | return Backend_t[backend] 53 | elif isinstance(backend, (int, float)): 54 | return Backend_t(int(backend)) 55 | elif isinstance(backend, Backend_t): 56 | return backend 57 | raise TypeError(": backend unknown. Could not convert to Backend_t.") 58 | -------------------------------------------------------------------------------- /Pyfhel/utils/cy_utils.pxi: -------------------------------------------------------------------------------- 1 | @cython.wraparound(False) 2 | @cython.boundscheck(False) 3 | cpdef np.ndarray[dtype=np.int64_t, ndim=1] vec_to_array_i(vector[int64_t] vec): 4 | cdef np.ndarray[dtype=np.int64_t, ndim=1] arr = np.empty(vec.size(), dtype=np.int64) 5 | cdef int64_t i 6 | for i in range(vec.size()): 7 | arr[i]=vec[i] 8 | return arr 9 | 10 | @cython.wraparound(False) 11 | @cython.boundscheck(False) 12 | cpdef np.ndarray[dtype=np.uint64_t, ndim=1] vec_to_array_u(vector[uint64_t] vec): 13 | cdef np.ndarray[dtype=np.uint64_t, ndim=1] arr = np.empty(vec.size(), dtype=np.uint64) 14 | cdef int64_t i 15 | for i in range(vec.size()): 16 | arr[i]=vec[i] 17 | return arr 18 | 19 | @cython.wraparound(False) 20 | @cython.boundscheck(False) 21 | cpdef np.ndarray[dtype=double, ndim=1] vec_to_array_f(vector[double] vec): 22 | cdef int64_t l = (vec.size()) 23 | cdef np.ndarray[dtype=double, ndim=1] arr = np.empty(l, dtype=np.float64) 24 | cdef int64_t i 25 | for i in range(l): 26 | arr[i]=vec[i] 27 | return arr 28 | 29 | cdef inline double _get_valid_scale(int& scale_bits, double& scale, double& pyfhel_scale): 30 | """Choose a non-zero scale""" 31 | if scale_bits > 0: 32 | return 2**scale_bits 33 | elif scale > 0: 34 | return scale 35 | elif pyfhel_scale > 0: 36 | return pyfhel_scale 37 | else: 38 | raise ValueError(" ckks scale must be non-zero.") 39 | 40 | cdef inline void _write_cy_attributes(Pyfhel he, ostream& ostr): 41 | """Serializes the security level, the moduli qi and the scale of `he`""" 42 | ostr.write(&he._sec, sizeof(int)) 43 | cdef size_t qi_len = he._qi_sizes.size() 44 | ostr.write(&qi_len, sizeof(size_t)) 45 | ostr.write(he._qi_sizes.data(), qi_len*sizeof(int)) 46 | ostr.write(&he._scale, sizeof(double)) 47 | 48 | cdef inline void _read_cy_attributes(Pyfhel he, istream& istr): 49 | """Deserializes the security level, the moduli qi and the scale back to `he`""" 50 | istr.read(&he._sec, sizeof(int)) 51 | cdef size_t qi_len 52 | istr.read(&qi_len, sizeof(size_t)) 53 | he._qi_sizes = vector[int](qi_len) 54 | istr.read(he._qi_sizes.data(), qi_len*sizeof(int)) 55 | istr.read(&he._scale, sizeof(double)) 56 | 57 | cdef inline shared_ptr[AfsealCtxt] _dyn_c(shared_ptr[AfCtxt] c): 58 | """Converts a shared_ptr[AfCtxt] to a shared_ptr[AfsealCtxt]""" 59 | return dynamic_pointer_cast[AfsealCtxt, AfCtxt](c) 60 | 61 | -------------------------------------------------------------------------------- /Pyfhel/utils/iostream.pxd: -------------------------------------------------------------------------------- 1 | # distutils: language = c++ 2 | #cython: language_level=3, boundscheck=False 3 | # -------------------------------- CIMPORTS ------------------------------------ 4 | from libcpp.string cimport string 5 | 6 | # ---------------------------- CYTHON DECLARATION ------------------------------ 7 | 8 | cdef extern from "" namespace "std": 9 | cdef cppclass streamoff: 10 | pass 11 | cdef extern from "" namespace "std": 12 | cdef cppclass ostream: 13 | ostream& write(const char*, int) except + 14 | cdef cppclass istream: 15 | istream& read(char*, int) except + 16 | 17 | cdef extern from "" namespace "std::ios_base": 18 | cdef cppclass open_mode: 19 | pass 20 | cdef open_mode binary 21 | 22 | cdef extern from "" namespace "std": 23 | cdef cppclass ofstream(ostream): 24 | # constructors 25 | ofstream()except + 26 | ofstream(const char*) except + 27 | ofstream(const char*, open_mode) except+ 28 | ofstream(const string&) except + 29 | ofstream(const string&, open_mode) except+ 30 | void open(const char*) except + 31 | void open(const string&) except + 32 | void close() except + 33 | cdef cppclass ifstream(istream): 34 | # constructors 35 | ifstream()except + 36 | ifstream(const char*) except + 37 | ifstream(const char*, open_mode) except+ 38 | ifstream(const string&) except + 39 | ifstream(const string&, open_mode) except+ 40 | void open(const char&) except + 41 | void open(const string&) except + 42 | void close() except + 43 | 44 | cdef extern from "" namespace "std": 45 | cdef cppclass stringstream(istream): 46 | istringstream() except + 47 | istringstream(const char*) except + 48 | istringstream(const string&) except + 49 | stringstream& write(const char*, int) except + 50 | cdef cppclass ostringstream(ostream): 51 | ostringstream() except + 52 | string str() except + 53 | -------------------------------------------------------------------------------- /Pyfhel/utils/utils.py: -------------------------------------------------------------------------------- 1 | 2 | from pathlib import Path 3 | import numpy as np 4 | 5 | def _to_valid_file_str(fileName, check=False): 6 | """Checks that the fileName is valid, and returns a str with the valid fileName. 7 | """ 8 | if not isinstance(fileName, (str, Path)): 9 | raise TypeError(" fileName must be of type str or Path.") 10 | if check: 11 | if not Path(fileName).is_file(): 12 | raise FileNotFoundError(f" File {str(fileName)} not found.") 13 | return str(fileName) 14 | 15 | def modular_pow(base, exponent, modulus): 16 | """Modular exponentiation, from https://github.com/numpy/numpy/issues/8804""" 17 | return np.array([pow(int(b), exponent, modulus) for b in base]) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![iCodecov](https://codecov.io/gh/ibarrond/Pyfhel/branch/master/graph/badge.svg?token=S8J8Jlp1Fc)](https://codecov.io/gh/ibarrond/Pyfhel) 4 | [![Documentation Status](https://readthedocs.org/projects/pyfhel/badge/?version=latest)](https://pyfhel.readthedocs.io/en/latest/?badge=latest) 5 | [![PyPI version](https://badge.fury.io/py/Pyfhel.svg)](https://badge.fury.io/py/Pyfhel) 6 | [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-brightgreen.svg)](https://GitHub.com/ibarrond/Pyfhel/graphs/commit-activity) 7 | [![GitHub issues](https://img.shields.io/github/issues/ibarrond/Pyfhel.svg)](https://github.com/ibarrond/Pyfhel/issues) 8 | [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) 9 | 10 | 11 | Python library for Addition, Subtraction, Multiplication and Scalar Product over *encrypted* integers (BFV/BGV schemes) and approximated floating point values (CKKS scheme). This library acts as an optimized Python API for C++ Homomorphic Encryption libraries. 12 | 13 | | | | 14 | |--------------------------------------------|--------------------------------------------------------------------------------------------| 15 | | :flags: **Language** | Python (3.7+), with Cython and C++ (:warning: _requires a [C++17 compiler][3]_ :warning:.) | 16 | | :computer: **OS** | Linux, Windows & MacOS. | 17 | | :1234: **Version** | 3.4.2 (stable) | 18 | | :books: **Docs** | In [readthedocs][1]! | 19 | | :pencil2: **Demos/Examples** | [In the docs][4] with the outputs, sources in the [`examples`][2] folder. | 20 | | :electric_plug: **Backends** | [SEAL][5], [OpenFHE (WIP)][6]. Shipped alongside Pyfhel. | 21 | | :construction_worker: **Authors** | [Alberto Ibarrondo][7] (IDEMIA & EURECOM) and [Alexander Viand][8] (ETH Zurich). | 22 | | :mortar_board: **Original Collaborators** | [Melek Onen][9] (EURECOM), [Laurent Gomez][10] (SAP Labs). | 23 | | | | 24 | 25 | 26 | If you wish to cite Pyfhel in your derived work, please use the following BibTeX entry: 27 | ```bibtex 28 | @inproceedings{ibarrondo2021pyfhel, 29 | title={Pyfhel: Python for homomorphic encryption libraries}, 30 | author={Ibarrondo, Alberto and Viand, Alexander}, 31 | booktitle={Proceedings of the 9th on Workshop on Encrypted Computing \& Applied Homomorphic Cryptography}, 32 | pages={11--16}, 33 | year={2021} 34 | } 35 | ``` 36 | 37 | [1]:https://pyfhel.readthedocs.io/en/latest/ 38 | [2]:https://github.com/ibarrond/Pyfhel/tree/master/examples 39 | [3]:https://en.cppreference.com/w/cpp/compiler_support 40 | [4]:https://pyfhel.readthedocs.io/en/latest/_autoexamples/index.html 41 | [5]:https://github.com/microsoft/SEAL/ 42 | [6]:https://github.com/openfheorg/openfhe-development 43 | [7]:https://scholar.google.com/citations?hl=en&user=hl-5WRQAAAAJ 44 | [8]:https://pps-lab.com/people/alexanderviand/ 45 | [9]:http://www.eurecom.fr/~onen/ 46 | [10]:https://scholar.google.com/citations?user=QJv4B9EAAAAJ 47 | 48 |
49 | 50 | ------------- 51 | [`Install & Uninstall`](#install--uninstall)  [`Summary`](#summary)  [`Contributing`](#contributing)  [`Bugs & Feature Requests`](#bugs--feature-requests)  [`Legal Disclaimer`](#legal-disclaimer) 52 | 53 | ------------- 54 |
55 | 56 | ## Install & Uninstall 57 | To install `Pyfhel` from [PyPI](https://pypi.org/project/Pyfhel/), run (*WARNING! it takes several minutes to compile and install, be patient!*): 58 | ```bash 59 | pip install Pyfhel 60 | ``` 61 | 62 | To install the latest version, you can clone this repository with [all the submodules](https://stackoverflow.com/questions/3796927/how-to-git-clone-including-submodules) and install it by running: 63 | ```bash 64 | git clone --recursive https://github.com/ibarrond/Pyfhel.git 65 | pip install . 66 | ``` 67 | 68 | To uninstall, just run: 69 | ```bash 70 | pip uninstall Pyfhel 71 | ``` 72 | 73 | ### Installing a C/C++ Compiler 74 | `Pyfhel` requires a C/C++ compiler with C++17 support. We have tested: 75 | - *gcc6* to *gcc12* in Linux/MacOS/Windows WSL. To install: 76 | - Ubuntu: `sudo apt install gcc g++` 77 | - MacOS: `brew install gcc`. MacOS users must also set several environment variables by running: 78 | ```bash 79 | # Brew installs GCC in /opt/homebrew/bin on Apple Silicon and /usr/local/bin on Intel. 80 | if [[ $(uname -m) = "arm64" ]]; then BREW_GCC_PATH="/opt/homebrew/bin"; else BREW_GCC_PATH="/usr/local/bin"; fi 81 | 82 | # Set CC/CXX environment variables to the most recent GNU GCC 83 | export CC="$BREW_GCC_PATH/$(ls $BREW_GCC_PATH | grep ^gcc-[0-9] | sort -V -r | head -n 1)" 84 | export CXX="$BREW_GCC_PATH/$(ls $BREW_GCC_PATH | grep ^g++-[0-9] | sort -V -r | head -n 1)" 85 | 86 | # Set MACOSX_DEPLOYMENT_TARGET to avoid version mismatch warnings 87 | echo "MACOSX_DEPLOYMENT_TARGET=$(sw_vers -productVersion)" >> $GITHUB_ENV 88 | echo "MACOSX_DEPLOYMENT_TARGET=${{ env.MACOSX_DEPLOYMENT_TARGET }}" 89 | ``` 90 | - *MSVC2017* and *MSVC2019* in Windows. To install: 91 | - Install Visual C++ Build tools (Download [here](https://learn.microsoft.com/en-US/cpp/windows/latest-supported-vc-redist?view=msvc-170), guide in [here](https://stackoverflow.com/questions/40504552)) 92 | 93 | ## Summary 94 | **PY**thon **F**or **H**omomorphic **E**ncryption **L**ibraries, **Pyfhel** implements functionalities of multiple Homomorphic Encryption libraries such as addition, multiplication, exponentiation or scalar product in Python. **Pyfhel** uses a syntax similar to normal arithmetics (+,-,\*). This library is useful both for simple Homomorphic Encryption Demos as well as for complex problems such as Machine Learning algorithms. 95 | 96 | **Pyfhel** is built on top of **Afhel**, an **A**bstraction **H**omomorphic **E**ncryption **L**ibraries in C++. **Afhel** serves as common API for all backends. Additionally, this project contains a large series of Demos & Tests for **Pyfhel**. 97 | 98 | This repository contains: 99 | - `docs/` Documentation, generated automatically using sphinx and pushed to [readthedocs](https://pyfhel.readthedocs.io). 100 | - `examples/` Demos and small programs to showcase multiple functionalities. 101 | - `Pyfhel/` contains the source code for Pyfhel and Afhel. 102 | - `Pyfhel/backend`, underlying C++ libraries SEAL & PALISADE. 103 | 104 | 105 | ## Contributing 106 | This is the standard process to develop/contribute: 107 | 1. _Code a new feature/fix a bug_. Using [Cython](https://cython.readthedocs.io/en/latest/) for the `.pyx` and `.pxd` extensions, C++ for `Afhel` or Python for examples/tests/other. 108 | 109 | 2. _Build/Install Pyfhel locally_. Use `pip install -v -v .` for a verbose installation. 110 | 111 | 3. _Test changes (requires installing `pytest`)_. Run the tests locally by executing `pytest .` in the root directory, and make sure all tests pass. 112 | 113 | - _Code coverage (requires installing `pytest-cov`)_. Add an empty `.cov` file in the root directory, and build/install the project locally (`pip install .`). To run coverage tests, execute `pytest --cov .` in the root directory, and then `coverage html` to obtain a report. 114 | 115 | You're ready to go! Just create a pull request to the original repo. 116 | 117 | ## Bugs & Feature Requests 118 | Please fill the [**Bug Report**](https://github.com/ibarrond/Pyfhel/issues/new/choose) template to provide all the essential info to reproduce your issue and solve the problem. 119 | 120 | If you wish to have new functionality added to Pyfhel, you are more than welcome to request it via the [**Feature**](https://github.com/ibarrond/Pyfhel/issues/new/choose) template. 121 | 122 | ## Legal disclaimer 123 | This project is Open Source under the Apache V2 License (LICENSE file). Hence, Pyfhel can be used, modified, and copied freely provided that developers: 124 | 125 | 1. Acknowledge and mention the original authors of Pyfhel in any derived development, that is, `Alberto Ibarrondo (IDEMIA & EURECOM) and Alexander Viand (ETH Zurich)` (maybe even cite the paper!). 126 | 127 | 2. Maintain the same License, and provide a statement of changes. 128 | 129 | We encourage **any software using Pyfhel to be Open Source**, for the benefit of everyone using it. 130 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibarrond/Pyfhel/e8788d1abd55877e8dcfaf759966531a9b34b6ad/docs/.nojekyll -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = . 8 | PAPER = a4 9 | BUILDDIR = . 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Pyfhel Documentation 2 | 3 | This folder contains the up-to-date documentation of Pyfhel. 4 | 5 | The documentation is built with `sphinx` and `sphinx-gallery` (you can install them using `pip install -r docs/requirements.txt` for Python3). To generate it, just run `make html` (Linux) or `.\make.bat html` (Windows). The resulting **`html\index.html`** file serves as homepage for the entire static documentation. 6 | 7 | To contribute to the documentation, your best shot is to create a new `Demo_*.py` file showcasing your particular usecase, and add extensive comments following the same comment format from the other demos (just use one of them as a template). `sphinx` will execute it and add all your comments to the existing documentation. 8 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Configuration file for the Sphinx documentation builder. 5 | # 6 | # This file does only contain a selection of the most common options. For a 7 | # full list see the documentation: 8 | # http://www.sphinx-doc.org/en/master/config 9 | 10 | # -- Path setup -------------------------------------------------------------- 11 | import sys, os, os.path, re 12 | import datetime 13 | 14 | YEAR = datetime.date.today().strftime('%Y') 15 | # If extensions (or modules to document with autodoc) are in another directory, 16 | # add these directories to sys.path here. If the directory is relative to the 17 | # documentation root, use os.path.abspath to make it absolute, like shown here. 18 | sys.path.append(os.path.abspath('sphinxext')) 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = 'Pyfhel' 23 | author = 'Alberto Ibarrondo' 24 | copyright = '%s, %s' % (YEAR, author) 25 | 26 | import Pyfhel 27 | 28 | # The short X.Y version 29 | version = re.sub(r'(.*)(\.[0-9a-z]+)','\\1', Pyfhel.__version__) 30 | # The full version, including alpha/beta/rc tags 31 | release = Pyfhel.__version__ 32 | 33 | 34 | # -- General configuration --------------------------------------------------- 35 | 36 | # If your documentation needs a minimal Sphinx version, state it here. 37 | # 38 | needs_sphinx = '3.2' 39 | highlight_language = 'cython' 40 | 41 | # Add any Sphinx extension module names here, as strings. They can be 42 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 43 | # ones. 44 | extensions = [ 45 | 'cython_highlighting', 46 | 'sphinxcontrib.fulltoc', 47 | 'sphinx.ext.autodoc', 48 | 'sphinx.ext.autosummary', 49 | 'sphinx.ext.napoleon', 50 | 'sphinx.ext.autosectionlabel', 51 | 'sphinx.ext.doctest', 52 | 'sphinx.ext.intersphinx', 53 | 'sphinx.ext.todo', 54 | 'sphinx.ext.coverage', 55 | 'sphinx.ext.mathjax', 56 | 'sphinx.ext.viewcode', 57 | 'sphinx.ext.githubpages', 58 | 'sphinx_gallery.gen_gallery', 59 | ] 60 | 61 | # Add any paths that contain templates here, relative to this directory. 62 | templates_path = ['templates'] 63 | 64 | # The suffix(es) of source filenames. 65 | # You can specify multiple suffix as a list of string: 66 | # 67 | # source_suffix = ['.rst', '.md'] 68 | source_suffix = '.rst' 69 | 70 | # The master toctree document. 71 | master_doc = 'index' 72 | 73 | # The language for content autogenerated by Sphinx. Refer to documentation 74 | # for a list of supported languages. 75 | # 76 | # This is also used if you do content translation via gettext catalogs. 77 | # Usually you set "language" from the command line for these cases. 78 | language = None 79 | 80 | # List of patterns, relative to source directory, that match files and 81 | # directories to ignore when looking for source files. 82 | # This pattern also affects html_static_path and html_extra_path. 83 | exclude_patterns = [] 84 | 85 | # The name of the Pygments (syntax highlighting) style to use. 86 | pygments_style = 'sphinx' 87 | 88 | # -- Options for HTML output ------------------------------------------------- 89 | 90 | # The theme to use for HTML and HTML Help pages. See the documentation for 91 | # a list of builtin themes. 92 | # 93 | 94 | html_theme = 'default' 95 | try: 96 | import sphinx 97 | if os.path.isdir(os.path.join(os.path.dirname(sphinx.__file__), 'themes', 'nature')): 98 | html_theme = 'nature' 99 | except (ImportError, AttributeError): 100 | pass# use default theme 101 | 102 | # Theme options are theme-specific and customize the look and feel of a theme 103 | # further. For a list of options available for each theme, see the 104 | # documentation. 105 | # 106 | # html_theme_options = {} 107 | 108 | # Add any paths that contain custom static files (such as style sheets) here, 109 | # relative to this directory. They are copied after the builtin static files, 110 | # so a file named "default.css" will overwrite the builtin "default.css". 111 | html_static_path = ['static'] 112 | html_css_files = ['style.css'] 113 | html_domain_indices = True 114 | 115 | 116 | # Custom sidebar templates, must be a dictionary that maps document names 117 | # to template names. 118 | # 119 | # The default sidebars (for documents that don't match any pattern) are 120 | # defined by theme itself. Builtin themes are using these templates by 121 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 122 | # 'searchbox.html']``. 123 | # 124 | # html_sidebars = {} 125 | 126 | # The name of an image file (relative to this directory) to place at the top 127 | # of the sidebar. 128 | html_favicon = 'static/logo_empty.svg' 129 | html_logo = "static/logo_empty.svg" 130 | 131 | # -- Options for HTMLHelp output --------------------------------------------- 132 | 133 | # Output file base name for HTML help builder. 134 | htmlhelp_basename = 'Pyfheldoc' 135 | 136 | 137 | # -- Options for LaTeX output ------------------------------------------------ 138 | 139 | latex_elements = { 140 | # The paper size ('letterpaper' or 'a4paper'). 141 | # 142 | # 'papersize': 'letterpaper', 143 | 144 | # The font size ('10pt', '11pt' or '12pt'). 145 | # 146 | # 'pointsize': '10pt', 147 | 148 | # Additional stuff for the LaTeX preamble. 149 | # 150 | # 'preamble': '', 151 | 152 | # Latex figure (float) alignment 153 | # 154 | # 'figure_align': 'htbp', 155 | } 156 | 157 | # Grouping the document tree into LaTeX files. List of tuples 158 | # (source start file, target name, title, 159 | # author, documentclass [howto, manual, or own class]). 160 | latex_documents = [ 161 | (master_doc, 'Pyfhel.tex', 'Pyfhel Documentation', 162 | 'Alberto Ibarrondo', 'manual'), 163 | ] 164 | 165 | 166 | # -- Options for manual page output ------------------------------------------ 167 | 168 | # One entry per manual page. List of tuples 169 | # (source start file, name, description, authors, manual section). 170 | man_pages = [ 171 | (master_doc, 'pyfhel', 'Pyfhel Documentation', 172 | [author], 1) 173 | ] 174 | 175 | 176 | # -- Options for Texinfo output ---------------------------------------------- 177 | 178 | # Grouping the document tree into Texinfo files. List of tuples 179 | # (source start file, target name, title, author, 180 | # dir menu entry, description, category) 181 | texinfo_documents = [ 182 | (master_doc, 'Pyfhel', 'Pyfhel Documentation', 183 | author, 'Pyfhel', 'One line description of project.', 184 | 'Miscellaneous'), 185 | ] 186 | 187 | 188 | # -- Options for Epub output ------------------------------------------------- 189 | 190 | # Bibliographic Dublin Core info. 191 | epub_title = project 192 | 193 | # The unique identifier of the text. This can be a ISBN number 194 | # or the project homepage. 195 | # 196 | # epub_identifier = '' 197 | 198 | # A unique identification for the text. 199 | # 200 | # epub_uid = '' 201 | 202 | # A list of files that should not be packed into the epub file. 203 | epub_exclude_files = ['search.html'] 204 | 205 | 206 | # -- Extension configuration ------------------------------------------------- 207 | autodoc_member_order = 'groupwise' 208 | autosummary_generate = True 209 | 210 | # -- Options for sphinx_gallery extension ------------------------------------ 211 | import platform 212 | sphinx_gallery_conf = { 213 | 'examples_dirs': '../examples', # path to your example scripts 214 | 'gallery_dirs': '_autoexamples', # path to where to save gallery generated output 215 | 'filename_pattern': r'Demo_*', 216 | 'ignore_pattern': r'__init__\.py|.*bis.*.py', 217 | 'min_reported_time': 0, 218 | 'remove_config_comments': True, 219 | 'expected_failing_examples': [], 220 | 'show_memory': platform.system() != 'Windows', # RAM consumption if not in Windows 221 | ## Links to examples disabled 222 | # # directory where function/class granular galleries are stored 223 | # 'backreferences_dir' : '_backreferences', 224 | # # Modules for which function/class level galleries are created. In 225 | # # this case sphinx_gallery and numpy in a tuple of strings. 226 | # 'doc_module' : ('Pyfhel'), 227 | } 228 | 229 | # -- Options for intersphinx extension --------------------------------------- 230 | 231 | # intersphinx for standard :keyword:s (def, for, etc.) 232 | intersphinx_mapping = { 233 | 'python': ('https://docs.python.org/{.major}'.format(sys.version_info), None), 234 | 'numpy': ('https://numpy.org/doc/stable/', None), 235 | 'np': ('https://numpy.org/doc/stable/', None), 236 | 'pandas': ('https://pandas.pydata.org/pandas-docs/stable/', None), 237 | 'Pyfhel': ('https://pyfhel.readthedocs.io/en/latest/', None), 238 | } 239 | 240 | 241 | # -- Options for todo extension ---------------------------------------------- 242 | 243 | # If true, `todo` and `todoList` produce output, else they produce nothing. 244 | todo_include_todos = True 245 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Pyfhel documentation master file, created by 2 | sphinx-quickstart on Mon Aug 27 21:01:48 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | .. role:: bolditalic 7 | :class: bolditalic 8 | 9 | .. |warning| image:: static/warning_icon.png 10 | :align: middle 11 | :width: 20 12 | 13 | .. image:: static/logo_title.png 14 | :target: https://github.com/ibarrond/Pyfhel 15 | 16 | .. image:: https://travis-ci.org/ibarrond/Pyfhel.svg?branch=master 17 | :target: https://travis-ci.org/ibarrond/Pyfhel 18 | .. image:: https://badge.fury.io/py/Pyfhel.svg 19 | :target: https://badge.fury.io/py/Pyfhel 20 | .. image:: https://img.shields.io/badge/Maintained%3F-yes-brightgreen.svg 21 | :target: https://GitHub.com/ibarrond/Pyfhel/graphs/commit-activity 22 | .. image:: https://img.shields.io/github/issues/ibarrond/Pyfhel.svg 23 | :target: https://github.com/ibarrond/Pyfhel/issues 24 | .. image:: https://img.shields.io/pypi/pyversions/Pyfhel.svg 25 | :target: https://pypi.org/project/Pyfhel 26 | 27 | **PY**\ thon **F**\ or **H**\ omomorphic **E**\ ncryption **L**\ ibraries, **Pyfhel** implements functionalities of multiple Homomorphic Encryption libraries such as addition, multiplication, exponentiation or scalar product in Python. **Pyfhel** uses a syntax similar to normal arithmetics (+,-,\*). This library is useful both for simple Homomorphic Encryption Demos as well as for complex problems such as Machine Learning algorithms. 28 | 29 | **Pyfhel** is built on top of **Afhel**, an **A**\ bstraction **F**\ or **H**\ momorphic **E**\ ncryption **L**\ ibraries in C++. **Afhel** serves as common API for all backends. 30 | 31 | * :bolditalic:`Version`: |release|. 32 | * :bolditalic:`Status`: STABLE. 33 | * :bolditalic:`Language`: Python (3.7+) & Cython on top of C++17. 34 | * :bolditalic:`OS`: Windows (tested with ``MSVC2017``, ``MSVC2019``), Linux / WSL (tested on ``gcc6`` upwards) and MacOS (``gcc6`` upwards, NO CLANG). 35 | 36 | .. warning:: |warning| *REQUIRED: Python must have been compiled with C++17:* ``g++>=6`` *|* ``MSVC 2017+`` |warning| 37 | 38 | * :bolditalic:`Dependencies`: There are two possible backends (both shipped alongside Pyfhel), HE libraries in C++: 39 | 40 | 1. `SEAL `_ (no external dependencies, default). 41 | 2. `PALISADE `_ (no external dependencies) **WIP** 42 | 43 | .. note:: The current version supports `SEAL `_ only 44 | 45 | Index 46 | ==================== 47 | 48 | .. toctree:: 49 | :glob: 50 | :maxdepth: 2 51 | 52 | source/getting_started 53 | _autoexamples/index 54 | source/API_reference 55 | source/API_serialized 56 | 57 | Glossary and tables 58 | ==================== 59 | 60 | * :ref:`genindex` 61 | * :ref:`search` 62 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=. 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/presentations/Pyfhel_WAHC21_slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibarrond/Pyfhel/e8788d1abd55877e8dcfaf759966531a9b34b6ad/docs/presentations/Pyfhel_WAHC21_slides.pdf -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx>=3.2 2 | sphinx-gallery>=0.8 3 | memory-profiler>=0.55 4 | sphinxcontrib-fulltoc>=1.2.0 5 | matplotlib==3.6.3 -------------------------------------------------------------------------------- /docs/source/API_reference.rst: -------------------------------------------------------------------------------- 1 | .. _sphx_glr_api_reference: 2 | 3 | API reference 4 | ================== 5 | 6 | .. currentmodule:: Pyfhel 7 | 8 | .. rubric:: Main Classes 9 | 10 | .. autosummary:: 11 | :toctree: ../_autosummary 12 | :recursive: 13 | 14 | Pyfhel 15 | PyCtxt 16 | PyPtxt 17 | PyPoly 18 | 19 | .. rubric:: Utils 20 | 21 | .. autosummary:: 22 | :recursive: 23 | :toctree: ../_autosummary 24 | 25 | utils.Scheme_t 26 | utils.Backend_t -------------------------------------------------------------------------------- /docs/source/API_serialized.rst: -------------------------------------------------------------------------------- 1 | API serialized 2 | =============== 3 | 4 | .. automodule:: Pyfhel 5 | :noindex: 6 | :members: 7 | :undoc-members: 8 | :private-members: 9 | :special-members: 10 | :show-inheritance: -------------------------------------------------------------------------------- /docs/source/getting_started.rst: -------------------------------------------------------------------------------- 1 | Getting started 2 | ================== 3 | 4 | Your first steps with this library: 5 | 6 | .. toctree:: 7 | :glob: 8 | :maxdepth: 2 9 | 10 | getting_started/* -------------------------------------------------------------------------------- /docs/source/getting_started/1_installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ================== 3 | 4 | This project has been uploaded to `PyPI `_. In order to install it from source (*WARNING! it takes several minutes to compile, be patient!*), run: 5 | 6 | .. code-block:: bash 7 | 8 | pip install Pyfhel 9 | 10 | Locally, you can clone this repository (use `--recursive `_ to download all submodules) and install it by running: 11 | 12 | .. code-block:: bash 13 | 14 | git clone --recursive https://github.com/ibarrond/Pyfhel.git 15 | pip install . 16 | 17 | To uninstall, just run: 18 | 19 | .. code-block:: bash 20 | 21 | pip uninstall Pyfhel 22 | 23 | 24 | **Installing a C/C++ Compiler** 25 | 26 | `Pyfhel` requires a C/C++ compiler with C++17 support. We have tested: 27 | - *gcc6* to *gcc12* in Linux/MacOS/Windows WSL. To install: 28 | - Ubuntu: `sudo apt install gcc g++` 29 | - MacOS: `brew install gcc`. MacOS users must also set several environment variables by running: 30 | ```bash 31 | # Brew installs GCC in /opt/homebrew/bin on Apple Silicon and /usr/local/bin on Intel. 32 | if [[ $(uname -m) = "arm64" ]]; then BREW_GCC_PATH="/opt/homebrew/bin"; else BREW_GCC_PATH="/usr/local/bin"; fi 33 | 34 | # Set CC/CXX environment variables to the most recent GNU GCC 35 | export CC="$BREW_GCC_PATH/$(ls $BREW_GCC_PATH | grep ^gcc-[0-9] | sort -V -r | head -n 1)" 36 | export CXX="$BREW_GCC_PATH/$(ls $BREW_GCC_PATH | grep ^g++-[0-9] | sort -V -r | head -n 1)" 37 | 38 | # Set MACOSX_DEPLOYMENT_TARGET to avoid version mismatch warnings 39 | echo "MACOSX_DEPLOYMENT_TARGET=$(sw_vers -productVersion)" >> $GITHUB_ENV 40 | echo "MACOSX_DEPLOYMENT_TARGET=${{ env.MACOSX_DEPLOYMENT_TARGET }}" 41 | ``` 42 | - *MSVC2017* and *MSVC2019* in Windows. To install: 43 | - Install Visual C++ Build tools (Download `here `, guide in `here `) -------------------------------------------------------------------------------- /docs/source/getting_started/2_contributing.rst: -------------------------------------------------------------------------------- 1 | Contributing 2 | ================== 3 | 4 | This is the process to develop/contribute to Pyfhel: 5 | 6 | This is the standard process to develop/contribute: 7 | 1. *Code a new feature/fix a bug*. Using [Cython](https://cython.readthedocs.io/en/latest/) for the ``.pyx`` and ``.pxd`` extensions, C++ for ``Afhel`` or Python for examples/tests/other. 8 | 9 | 2. *Build/Install Pyfhel locally*. Use ``pip install -v -v .`` for a verbose installation. 10 | 11 | 3. *Test changes (requires installing `pytest`)*. Run the tests locally by executing ``pytest .`` in the root directory, and make sure all tests pass. 12 | 13 | - *Code coverage (requires installing ``pytest-cov``)*. Add an empty ``.cov`` file in the root directory, and build/install the project locally (``pip install .``). To run coverage tests, execute ``pytest --cov .`` in the root directory, and then ``coverage html`` to obtain a report. 14 | 15 | You're ready to go! Just create a pull request to the original repo. 16 | -------------------------------------------------------------------------------- /docs/sphinxext/cython_highlighting.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from pygments.lexer import Lexer, RegexLexer, ExtendedRegexLexer, \ 4 | LexerContext, include, combined, do_insertions, bygroups, using 5 | from pygments.token import Error, Text, \ 6 | Comment, Operator, Keyword, Name, String, Number, Generic, Punctuation 7 | from pygments.util import get_bool_opt, get_list_opt, shebang_matches 8 | from pygments import unistring as uni 9 | 10 | from sphinx import highlighting 11 | 12 | 13 | line_re = re.compile('.*?\n') 14 | 15 | class CythonLexer(RegexLexer): 16 | """ 17 | For `Cython `_ source code. 18 | """ 19 | 20 | name = 'Cython' 21 | aliases = ['cython', 'pyx'] 22 | filenames = ['*.pyx', '*.pxd', '*.pxi'] 23 | mimetypes = ['text/x-cython', 'application/x-cython'] 24 | 25 | tokens = { 26 | 'root': [ 27 | (r'\n', Text), 28 | (r'^(\s*)("""(?:.|\n)*?""")', bygroups(Text, String.Doc)), 29 | (r"^(\s*)('''(?:.|\n)*?''')", bygroups(Text, String.Doc)), 30 | (r'[^\S\n]+', Text), 31 | (r'#.*$', Comment), 32 | (r'[]{}:(),;[]', Punctuation), 33 | (r'\\\n', Text), 34 | (r'\\', Text), 35 | (r'(in|is|and|or|not)\b', Operator.Word), 36 | (r'(<)([a-zA-Z0-9.?]+)(>)', 37 | bygroups(Punctuation, Keyword.Type, Punctuation)), 38 | (r'!=|==|<<|>>|[-~+/*%=<>&^|.?]', Operator), 39 | (r'(from)(\d+)(<=)(\s+)(<)(\d+)(:)', 40 | bygroups(Keyword, Number.Integer, Operator, Name, Operator, 41 | Name, Punctuation)), 42 | include('keywords'), 43 | (r'(def|property)(\s+)', bygroups(Keyword, Text), 'funcname'), 44 | (r'(cp?def)(\s+)', bygroups(Keyword, Text), 'cdef'), 45 | (r'(class|struct)(\s+)', bygroups(Keyword, Text), 'classname'), 46 | (r'(from)(\s+)', bygroups(Keyword, Text), 'fromimport'), 47 | (r'(c?import)(\s+)', bygroups(Keyword, Text), 'import'), 48 | include('builtins'), 49 | include('backtick'), 50 | ('(?:[rR]|[uU][rR]|[rR][uU])"""', String, 'tdqs'), 51 | ("(?:[rR]|[uU][rR]|[rR][uU])'''", String, 'tsqs'), 52 | ('(?:[rR]|[uU][rR]|[rR][uU])"', String, 'dqs'), 53 | ("(?:[rR]|[uU][rR]|[rR][uU])'", String, 'sqs'), 54 | ('[uU]?"""', String, combined('stringescape', 'tdqs')), 55 | ("[uU]?'''", String, combined('stringescape', 'tsqs')), 56 | ('[uU]?"', String, combined('stringescape', 'dqs')), 57 | ("[uU]?'", String, combined('stringescape', 'sqs')), 58 | include('name'), 59 | include('numbers'), 60 | ], 61 | 'keywords': [ 62 | (r'(assert|break|by|continue|ctypedef|del|elif|else|except\??|exec|' 63 | r'finally|for|gil|global|if|include|lambda|nogil|pass|print|raise|' 64 | r'return|try|while|yield|as|with)\b', Keyword), 65 | (r'(DEF|IF|ELIF|ELSE)\b', Comment.Preproc), 66 | ], 67 | 'builtins': [ 68 | (r'(? li:hover{ 25 | list-style: none; 26 | font-size: 18px; 27 | padding: 10px 30px; 28 | border-width: 2px 2px 2px 2px; 29 | border-style: solid; 30 | background-image: linear-gradient(300deg, #fff 40%, #f1eaff); 31 | border-bottom-left-radius: 35px; 32 | border-top-left-radius: 10px; 33 | border-color: #fff; 34 | font-variant: small-caps; 35 | transition:.3s; 36 | } 37 | 38 | #index li:hover{ 39 | background-image: linear-gradient(300deg, #fff 40%, #ececec); 40 | border-bottom-left-radius: 35px; 41 | border-top-left-radius: 10px; 42 | text-decoration: none; 43 | font-size: 20px; 44 | 45 | } 46 | 47 | #index li li:hover{ 48 | background-image: linear-gradient(300deg, #fff 40%, #d5d5d5); 49 | 50 | } 51 | 52 | #index li li { 53 | background-image: linear-gradient(300deg,#fff 40%, #f2ecff); 54 | border-style: None; 55 | font-variant: initial; 56 | font-size: 16px; 57 | transition:.3s; 58 | } 59 | 60 | #index li a { 61 | color: black; 62 | text-decoration: none; 63 | transition:.3s; 64 | } 65 | 66 | 67 | table.docutils{ 68 | margin: 0px; 69 | width: 100%; 70 | } -------------------------------------------------------------------------------- /docs/static/thumbnails/clientServer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibarrond/Pyfhel/e8788d1abd55877e8dcfaf759966531a9b34b6ad/docs/static/thumbnails/clientServer.png -------------------------------------------------------------------------------- /docs/static/thumbnails/contextParameters.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibarrond/Pyfhel/e8788d1abd55877e8dcfaf759966531a9b34b6ad/docs/static/thumbnails/contextParameters.jpg -------------------------------------------------------------------------------- /docs/static/thumbnails/dotproduct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibarrond/Pyfhel/e8788d1abd55877e8dcfaf759966531a9b34b6ad/docs/static/thumbnails/dotproduct.png -------------------------------------------------------------------------------- /docs/static/thumbnails/encoding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibarrond/Pyfhel/e8788d1abd55877e8dcfaf759966531a9b34b6ad/docs/static/thumbnails/encoding.png -------------------------------------------------------------------------------- /docs/static/thumbnails/encrypting.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibarrond/Pyfhel/e8788d1abd55877e8dcfaf759966531a9b34b6ad/docs/static/thumbnails/encrypting.jpg -------------------------------------------------------------------------------- /docs/static/thumbnails/float.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibarrond/Pyfhel/e8788d1abd55877e8dcfaf759966531a9b34b6ad/docs/static/thumbnails/float.png -------------------------------------------------------------------------------- /docs/static/thumbnails/hammingDist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibarrond/Pyfhel/e8788d1abd55877e8dcfaf759966531a9b34b6ad/docs/static/thumbnails/hammingDist.png -------------------------------------------------------------------------------- /docs/static/thumbnails/helloworld.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibarrond/Pyfhel/e8788d1abd55877e8dcfaf759966531a9b34b6ad/docs/static/thumbnails/helloworld.png -------------------------------------------------------------------------------- /docs/static/thumbnails/multDepth.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibarrond/Pyfhel/e8788d1abd55877e8dcfaf759966531a9b34b6ad/docs/static/thumbnails/multDepth.jpg -------------------------------------------------------------------------------- /docs/static/thumbnails/numpy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibarrond/Pyfhel/e8788d1abd55877e8dcfaf759966531a9b34b6ad/docs/static/thumbnails/numpy.png -------------------------------------------------------------------------------- /docs/static/thumbnails/saveRestore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibarrond/Pyfhel/e8788d1abd55877e8dcfaf759966531a9b34b6ad/docs/static/thumbnails/saveRestore.png -------------------------------------------------------------------------------- /docs/static/thumbnails/simd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibarrond/Pyfhel/e8788d1abd55877e8dcfaf759966531a9b34b6ad/docs/static/thumbnails/simd.png -------------------------------------------------------------------------------- /docs/static/warning_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibarrond/Pyfhel/e8788d1abd55877e8dcfaf759966531a9b34b6ad/docs/static/warning_icon.png -------------------------------------------------------------------------------- /docs/templates/autosummary/class.rst: -------------------------------------------------------------------------------- 1 | {{ objname | escape | underline}} 2 | 3 | .. currentmodule:: {{ module }} 4 | 5 | .. autoclass:: {{ objname }} 6 | :members: <-- add at least this line 7 | :special-members: __add__, __sub__, __neg__, __mul__, __pow__, __rshift__, __invert__, __repr__, __bytes__ 8 | :private-members: 9 | :show-inheritance: <-- plus I want to show inheritance... 10 | :inherited-members: <-- ...and inherited members too 11 | {% block methods %} 12 | .. automethod:: __init__ 13 | 14 | {% if methods %} 15 | .. rubric:: {{ _('Methods') }} 16 | 17 | .. autosummary:: 18 | {% for specialitem in ('__add__','__sub__', '__neg__', '__mul__', '__pow__', '__rshift__', '__invert__', '__repr__', '__bytes__') %} 19 | {% if specialitem in members %} 20 | ~{{ name }}.{{ specialitem }} 21 | {% endif %} 22 | {%- endfor %} 23 | {% for item in methods %} 24 | ~{{ name }}.{{ item }} 25 | {%- endfor %} 26 | {% endif %} 27 | {% endblock %} 28 | 29 | {% block attributes %} 30 | {% if attributes %} 31 | .. rubric:: {{ _('Attributes') }} 32 | 33 | .. autosummary:: 34 | {% for item in attributes %} 35 | ~{{ name }}.{{ item }} 36 | {%- endfor %} 37 | {% endif %} 38 | {% endblock %} 39 | .. rubric:: {{ _('API description') }} -------------------------------------------------------------------------------- /docs/templates/autosummary/function.rst: -------------------------------------------------------------------------------- 1 | {{ objname | escape | underline}} 2 | 3 | 4 | .. currentmodule:: {{ module }} 5 | 6 | .. auto{{ objtype }}:: {{ objname }} -------------------------------------------------------------------------------- /docs/templates/autosummary/module.rst: -------------------------------------------------------------------------------- 1 | {{ fullname | escape | underline}} 2 | 3 | .. automodule:: {{ fullname }} 4 | 5 | {% block attributes %} 6 | {% if attributes %} 7 | .. rubric:: Module Attributes 8 | 9 | .. autosummary:: 10 | :toctree: <-- add this line 11 | {% for item in attributes %} 12 | {{ item }} 13 | {%- endfor %} 14 | {% endif %} 15 | {% endblock %} 16 | 17 | {% block functions %} 18 | {% if functions %} 19 | .. rubric:: {{ _('Functions') }} 20 | 21 | .. autosummary:: 22 | :toctree: <-- add this line 23 | {% for item in functions %} 24 | {{ item }} 25 | {%- endfor %} 26 | {% endif %} 27 | {% endblock %} 28 | 29 | {% block classes %} 30 | {% if classes %} 31 | .. rubric:: {{ _('Classes') }} 32 | 33 | .. autosummary:: 34 | :toctree: <-- add this line 35 | :template: custom-class-template.rst <-- add this line 36 | {% for item in classes %} 37 | {{ item }} 38 | {%- endfor %} 39 | {% endif %} 40 | {% endblock %} 41 | 42 | {% block exceptions %} 43 | {% if exceptions %} 44 | .. rubric:: {{ _('Exceptions') }} 45 | 46 | .. autosummary:: 47 | :toctree: <-- add this line 48 | {% for item in exceptions %} 49 | {{ item }} 50 | {%- endfor %} 51 | {% endif %} 52 | {% endblock %} 53 | 54 | {% block modules %} 55 | {% if modules %} 56 | .. rubric:: Modules 57 | 58 | .. autosummary:: 59 | :toctree: 60 | :template: custom-module-template.rst <-- add this line 61 | :recursive: 62 | {% for item in modules %} 63 | {{ item }} 64 | {%- endfor %} 65 | {% endif %} 66 | {% endblock %} -------------------------------------------------------------------------------- /examples/Demo_1_HelloWorld.py: -------------------------------------------------------------------------------- 1 | """ 2 | HelloWorld 3 | =========================== 4 | 5 | This basic example shows the simplest use of the library by 6 | encrypting two integers, operating with them (+,-,*) and decrypting 7 | the results. 8 | """ 9 | # %% 10 | # 1. Imports 11 | # --------------------------- 12 | # We start by importing the library with the three main classes: 13 | # 14 | # * Pyfhel class contains most of the functions. 15 | # * PyPtxt is the plaintext class 16 | # * PyCtxt is the ciphertext class 17 | import numpy as np 18 | from Pyfhel import Pyfhel 19 | print("1. Import Pyfhel class, and numpy for the inputs to encrypt.") 20 | # %% 21 | # 2. Context and key setup 22 | # --------------------------- 23 | # We will start the Helloworld by generating a context and a public/secret key pair. 24 | # This is all managed by a Pyfhel instance under the hood. 25 | HE = Pyfhel() # Creating empty Pyfhel object 26 | HE.contextGen(scheme='bfv', n=2**14, t_bits=20) # Generate context for 'bfv'/'ckks' scheme 27 | # The n defines the number of plaintext slots. 28 | # There are many configurable parameters on this step 29 | # More info in Demo_2, Demo_3, and Pyfhel.contextGen() 30 | HE.keyGen() # Key Generation: generates a pair of public/secret keys 31 | # %% 32 | # The best way to obtain information from a created Pyfhel object is to print it: 33 | print("2. Context and key setup") 34 | print(HE) 35 | 36 | # %% 37 | # 3. Integer Encryption 38 | # --------------------------- 39 | # we will define two integers and encrypt them using `encryptInt`: 40 | integer1 = np.array([127], dtype=np.int64) 41 | integer2 = np.array([-2], dtype=np.int64) 42 | ctxt1 = HE.encryptInt(integer1) # Encryption makes use of the public key 43 | ctxt2 = HE.encryptInt(integer2) # For integers, encryptInt function is used. 44 | print("3. Integer Encryption, ") 45 | print(" int ",integer1,'-> ctxt1 ', type(ctxt1)) 46 | print(" int ",integer2,'-> ctxt2 ', type(ctxt2)) 47 | # %% 48 | # # The best way to obtain information from a ciphertext is to print it: 49 | print(ctxt1) 50 | print(ctxt2) 51 | 52 | # %% 53 | # 4. Operating with encrypted integers 54 | # -------------------------------------- 55 | # Relying on the context defined before, we will now operate 56 | # (addition, substaction, multiplication) the two ciphertexts: 57 | ctxtSum = ctxt1 + ctxt2 # `ctxt1 += ctxt2` for inplace operation 58 | ctxtSub = ctxt1 - ctxt2 # `ctxt1 -= ctxt2` for inplace operation 59 | ctxtMul = ctxt1 * ctxt2 # `ctxt1 *= ctxt2` for inplace operation 60 | print("4. Operating with encrypted integers") 61 | print(f"Sum: {ctxtSum}") 62 | print(f"Sub: {ctxtSub}") 63 | print(f"Mult:{ctxtMul}") 64 | 65 | # %% 66 | # 5. Decrypting integers 67 | # --------------------------- 68 | # Once we're finished with the encrypted operations, we can use 69 | # the Pyfhel instance to decrypt the results using `decryptInt`: 70 | resSum = HE.decryptInt(ctxtSum) # Decryption must use the corresponding function 71 | # decryptInt. 72 | resSub = HE.decryptInt(ctxtSub) 73 | resMul = HE.decryptInt(ctxtMul) 74 | print("#. Decrypting result:") 75 | print(" addition: decrypt(ctxt1 + ctxt2) = ", resSum) 76 | print(" substraction: decrypt(ctxt1 - ctxt2) = ", resSub) 77 | print(" multiplication: decrypt(ctxt1 + ctxt2) = ", resMul) 78 | 79 | 80 | 81 | # sphinx_gallery_thumbnail_path = 'static/thumbnails/helloworld.png' 82 | -------------------------------------------------------------------------------- /examples/Demo_2_Integer_BFV.py: -------------------------------------------------------------------------------- 1 | """ 2 | Integer FHE with BFV scheme 3 | ======================================== 4 | 5 | Integer FHE Demo for Pyfhel, covering all the posibilities offered by Pyfhel 6 | regarding the BFV scheme (see https://eprint.iacr.org/2012/144.pdf). 7 | """ 8 | 9 | import numpy as np 10 | from Pyfhel import Pyfhel 11 | 12 | # %% 13 | # 1. BFV context and key setup 14 | # ------------------------------------------------------------------------------ 15 | # We take a look at the different parameters that can be set for the BFV scheme. 16 | # Ideally, one should use as little `n` and `t` as possible while keeping the 17 | # correctness in the operations. 18 | # The noise budget in a freshly encrypted ciphertext is 19 | # 20 | # ~ log2(coeff_modulus/plain_modulus) (bits) 21 | # 22 | # By far the most demanding operation is the homomorphic (ciphertext-ciphertext) 23 | # multiplication, consuming a noise budget of around: 24 | # 25 | # log2(plain_modulus) + (other terms). 26 | 27 | HE = Pyfhel() # Creating empty Pyfhel object 28 | bfv_params = { 29 | 'scheme': 'BFV', # can also be 'bfv' 30 | 'n': 2**13, # Polynomial modulus degree, the num. of slots per plaintext, 31 | # of elements to be encoded in a single ciphertext in a 32 | # 2 by n/2 rectangular matrix (mind this shape for rotations!) 33 | # Typ. 2^D for D in [10, 16] 34 | 't': 65537, # Plaintext modulus. Encrypted operations happen modulo t 35 | # Must be prime such that t-1 be divisible by 2^N. 36 | 't_bits': 20, # Number of bits in t. Used to generate a suitable value 37 | # for t. Overrides t if specified. 38 | 'sec': 128, # Security parameter. The equivalent length of AES key in bits. 39 | # Sets the ciphertext modulus q, can be one of {128, 192, 256} 40 | # More means more security but also slower computation. 41 | } 42 | HE.contextGen(**bfv_params) # Generate context for bfv scheme 43 | HE.keyGen() # Key Generation: generates a pair of public/secret keys 44 | HE.rotateKeyGen() # Rotate key generation --> Allows rotation/shifting 45 | HE.relinKeyGen() # Relinearization key generation 46 | 47 | print("\n1. Pyfhel FHE context generation") 48 | print(f"\t{HE}") 49 | 50 | # %% 51 | # 2. Integer Array Encoding & Encryption 52 | # ------------------------------------------------------------------------------ 53 | # we will define two 1D integer arrays, encode and encrypt them: 54 | # arr1 = [0, 1, ... n-1] (length n) 55 | # arr2 = [-t//2, -1, 1] (length 3) --> Encoding fills the rest of the array with zeros 56 | 57 | arr1 = np.arange(bfv_params['n'], dtype=np.int64) # Max possible value is t/2-1. Always use type int64! 58 | arr2 = np.array([-bfv_params['t']//2, -1, 1], dtype=np.int64) # Min possible value is -t/2. 59 | 60 | ptxt1 = HE.encodeInt(arr1) # Creates a PyPtxt plaintext with the encoded arr1 61 | ptxt2 = HE.encodeInt(arr2) # plaintexts created from arrays shorter than 'n' are filled with zeros. 62 | 63 | ctxt1 = HE.encryptPtxt(ptxt1) # Encrypts the plaintext ptxt1 and returns a PyCtxt 64 | ctxt2 = HE.encryptPtxt(ptxt2) # Alternatively you can use HE.encryptInt(arr2) 65 | 66 | # Otherwise, a single call to `HE.encrypt` would detect the data type, 67 | # encode it and encrypt it 68 | #> ctxt1 = HE.encrypt(arr1) 69 | 70 | print("\n2. Integer Encoding & Encryption, ") 71 | print("->\tarr1 ", arr1,'\n\t==> ptxt1 ', ptxt1,'\n\t==> ctxt1 ', ctxt1) 72 | print("->\tarr2 ", arr2,'\n\t==> ptxt2 ', ptxt2,'\n\t==> ctxt2 ', ctxt2) 73 | 74 | # %% 75 | # 3. Securely operating on encrypted ingeger arrays 76 | # ------------------------------------------------------------------------------ 77 | # We try all the operations supported by Pyfhel. 78 | # Note that, to operate, the ciphertexts/plaintexts must be built with the same 79 | # context. Internal checks prevent ops between ciphertexts of different contexts. 80 | 81 | # Ciphertext-ciphertext ops: 82 | ccSum = ctxt1 + ctxt2 # Calls HE.add(ctxt1, ctxt2, in_new_ctxt=True) 83 | # `ctxt1 += ctxt2` for inplace operation 84 | ccSub = ctxt1 - ctxt2 # Calls HE.sub(ctxt1, ctxt2, in_new_ctxt=True) 85 | # `ctxt1 -= ctxt2` for inplace operation 86 | ccMul = ctxt1 * ctxt2 # Calls HE.multiply(ctxt1, ctxt2, in_new_ctxt=True) 87 | # `ctxt1 *= ctxt2` for inplace operation 88 | cSq = ctxt1**2 # Calls HE.square(ctxt1, in_new_ctxt=True) 89 | # `ctxt1 **= 2` for inplace operation 90 | cNeg = -ctxt1 # Calls HE.negate(ctxt1, in_new_ctxt=True) 91 | # 92 | cPow = ctxt1**3 # Calls HE.power(ctxt1, 3, in_new_ctxt=True) 93 | # `ctxt1 **= 3` for inplace operation 94 | cRotR = ctxt1 >> 2 # Calls HE.rotate(ctxt1, k=2, in_new_ctxt=True) 95 | # `ctxt1 >>= 2` for inplace operation 96 | # WARNING! the encoded data is placed in a n//2 by 2 97 | # matrix. Hence, these rotations apply independently 98 | # to each of the rows! 99 | cRotL = ctxt1 << 2 # Calls HE.rotate(ctxt1, k=-2, in_new_ctxt=True) 100 | # `ctxt1 <<= 2` for inplace operation 101 | cFlip = ctxt1 ^ 1 # Calls HE.flip(ctxt1, k=1, in_new_ctxt=True) 102 | # `ctxt1 |= 1` for inplace operation 103 | cCuAdd = (+ctxt1) # Calls HE.cumul_add(ctxt1, in_new_ctxt=True) 104 | # There is no equivalent for in-place operator, use 105 | # the above call with `in_new_ctxt=False` if required. 106 | 107 | # Ciphetext-plaintext ops 108 | cpSum = ctxt1 + ptxt2 # Calls HE.add_plain(ctxt1, ptxt2, in_new_ctxt=True) 109 | # `ctxt1 += ctxt2` for inplace operation 110 | cpSub = ctxt1 - ptxt2 # Calls HE.sub_plain(ctxt1, ptxt2, in_new_ctxt=True) 111 | # `ctxt1 -= ctxt2` for inplace operation 112 | cpMul = ctxt1 * ptxt2 # Calls HE.multiply_plain(ctxt1, ptxt2, in_new_ctxt=True) 113 | # `ctxt1 *= ctxt2` for inplace operation 114 | 115 | 116 | print("3. Secure operations") 117 | print(" Ciphertext-ciphertext: ") 118 | print("->\tctxt1 + ctxt2 = ccSum: ", ccSum) 119 | print("->\tctxt1 - ctxt2 = ccSub: ", ccSub) 120 | print("->\tctxt1 * ctxt2 = ccMul: ", ccMul) 121 | print(" Single ciphertext: ") 122 | print("->\tctxt1**2 = cSq : ", cSq ) 123 | print("->\t- ctxt1 = cNeg : ", cNeg ) 124 | print("->\tctxt1**3 = cPow : ", cPow ) 125 | print("->\tctxt1 >> 2 = cRotR: ", cRotR) 126 | print("->\tctxt1 << 2 = cRotL: ", cRotL) 127 | print("->\tctxt1 | 1 = cFlip: ", cFlip) 128 | print("->\t(+ctxt1) = cCuAdd: ", cCuAdd) 129 | print(" Ciphertext-plaintext: ") 130 | print("->\tctxt1 + ptxt2 = cpSum: ", cpSum) 131 | print("->\tctxt1 - ptxt2 = cpSub: ", cpSub) 132 | print("->\tctxt1 * ptxt2 = cpMul: ", cpMul) 133 | 134 | 135 | # %% 136 | # 4. BFV Relinearization: What, why, when 137 | # ------------------------------------------------------------------------------ 138 | # Ciphertext-ciphertext multiplications increase the size of the polynoms 139 | # representing the resulting ciphertext. To prevent this growth, the 140 | # relinearization technique is used (typically right after each c-c mult) to 141 | # reduce the size of a ciphertext back to the minimal size (two polynoms c0 & c1). 142 | # For this, a special type of public key called Relinearization Key is used. 143 | # 144 | # In Pyfhel, you can either generate a relin key with HE.RelinKeyGen() or skip it 145 | # and call HE.relinearize() directly, in which case a warning is issued. 146 | # 147 | # Note that HE.power performs relinearization after every multiplication. 148 | 149 | print("\n4. Relinearization-> Right after each multiplication.") 150 | print(f"ccMul before relinearization (size {ccMul.size()}): {ccMul}") 151 | ~ccMul # Equivalent to HE.relinearize(ccMul). Relin always happens in-place. 152 | print(f"ccMul after relinearization (size {ccMul.size()}): {ccMul}") 153 | print(f"cPow after 2 mult&relin rounds: (size {cPow.size()}): {cPow}") 154 | 155 | # %% 156 | # 5. Decrypt & Decode results 157 | # ------------------------------------------------------------------------------ 158 | # Time to decrypt results! We use HE.decryptInt for this. 159 | # HE.decrypt() could also be used, in which case the decryption type would be 160 | # inferred from the ciphertext metadata. 161 | r1 = HE.decryptInt(ctxt1) 162 | r2 = HE.decryptInt(ctxt2) 163 | rccSum = HE.decryptInt(ccSum) 164 | rccSub = HE.decryptInt(ccSub) 165 | rccMul = HE.decryptInt(ccMul) 166 | rcSq = HE.decryptInt(cSq ) 167 | rcNeg = HE.decryptInt(cNeg ) 168 | rcPow = HE.decryptInt(cPow ) 169 | rcRotR = HE.decryptInt(cRotR) 170 | rcRotL = HE.decryptInt(cRotL) 171 | rcFlip = HE.decryptInt(cFlip) 172 | rcCuAdd = HE.decryptInt(cCuAdd) 173 | rcpSum = HE.decryptInt(cpSum) 174 | rcpSub = HE.decryptInt(cpSub) 175 | rcpMul = HE.decryptInt(cpMul) 176 | 177 | print("5. Decrypting results") 178 | print(" Original ciphertexts: ") 179 | print(" ->\tctxt1 --(decr)--> ", r1) 180 | print(" ->\tctxt2 --(decr)--> ", r2) 181 | print(" Ciphertext-ciphertext Ops: ") 182 | print(" ->\tctxt1 + ctxt2 = ccSum --(decr)--> ", rccSum) 183 | print(" ->\tctxt1 - ctxt2 = ccSub --(decr)--> ", rccSub) 184 | print(" ->\tctxt1 * ctxt2 = ccMul --(decr)--> ", rccMul) 185 | print(" Single ciphertext: ") 186 | print(" ->\tctxt1**2 = cSq --(decr)--> ", rcSq ) 187 | print(" ->\t- ctxt1 = cNeg --(decr)--> ", rcNeg ) 188 | print(" ->\tctxt1**3 = cPow --(decr)--> ", rcPow ) 189 | print(" ->\tctxt1 >> 2 = cRotR --(decr)--> ", rcRotR) 190 | print(" ->\tctxt1 << 2 = cRotL --(decr)--> ", rcRotL) 191 | print(" ->\tctxt1 | 1 = cFlip --(decr)--> ", rcFlip) 192 | print(" ->\t(+tctxt1) = cCuAdd -(decr)--> ", rcCuAdd) 193 | print(" Ciphertext-plaintext ops: ") 194 | print(" ->\tctxt1 + ptxt2 = cpSum --(decr)--> ", rcpSum) 195 | print(" ->\tctxt1 - ptxt2 = cpSub --(decr)--> ", rcpSub) 196 | print(" ->\tctxt1 * ptxt2 = cpMul --(decr)--> ", rcpMul) 197 | 198 | 199 | # sphinx_gallery_thumbnail_path = 'static/thumbnails/encoding.png' 200 | # %% 201 | -------------------------------------------------------------------------------- /examples/Demo_3_Float_CKKS.py: -------------------------------------------------------------------------------- 1 | """ 2 | Fixed-point FHE with CKKS scheme 3 | ======================================== 4 | 5 | Fixed-point Demo for Pyfhel, operating with fixed point encoded values following 6 | the CKKS scheme (https://eprint.iacr.org/2016/421.pdf) 7 | """ 8 | 9 | # %% 10 | # 1. CKKS context and key setup 11 | # ------------------------------- 12 | # We take a look at the different parameters that can be set for the CKKS scheme. 13 | # A generally good strategy to choose `qi_sizes` (bitsize for each prime moduli) 14 | # for the CKKS scheme is as follows: 15 | # 16 | # 1. Choose a 60-bit prime as the first prime in coeff_modulus. This will 17 | # give the highest precision when decrypting; 18 | # 2. Choose another 60-bit prime as the last element of coeff_modulus, as 19 | # this will be used as the special prime and should be as large as the 20 | # largest of the other primes; 21 | # 3. Choose the intermediate primes to be close to each other. 22 | # 23 | import numpy as np 24 | from Pyfhel import Pyfhel 25 | 26 | HE = Pyfhel() # Creating empty Pyfhel object 27 | ckks_params = { 28 | 'scheme': 'CKKS', # can also be 'ckks' 29 | 'n': 2**14, # Polynomial modulus degree. For CKKS, n/2 values can be 30 | # encoded in a single ciphertext. 31 | # Typ. 2^D for D in [10, 15] 32 | 'scale': 2**30, # All the encodings will use it for float->fixed point 33 | # conversion: x_fix = round(x_float * scale) 34 | # You can use this as default scale or use a different 35 | # scale on each operation (set in HE.encryptFrac) 36 | 'qi_sizes': [60, 30, 30, 30, 60] # Number of bits of each prime in the chain. 37 | # Intermediate values should be close to log2(scale) 38 | # for each operation, to have small rounding errors. 39 | } 40 | HE.contextGen(**ckks_params) # Generate context for ckks scheme 41 | HE.keyGen() # Key Generation: generates a pair of public/secret keys 42 | HE.rotateKeyGen() 43 | 44 | # %% 45 | # 2. Float Array Encoding & Encryption 46 | # ---------------------------------------- 47 | # we will define two floating-point arrays, encode and encrypt them: 48 | # arr_x = [0.1, 0.2, -0.3] (length 3) 49 | # arr_y = [-1.5, 2.3, 4.7] (length 3) 50 | 51 | arr_x = np.array([0.1, 0.2, -0.3], dtype=np.float64) # Always use type float64! 52 | arr_y = np.array([-1.5, 2.3, 4.7], dtype=np.float64) 53 | 54 | ptxt_x = HE.encodeFrac(arr_x) # Creates a PyPtxt plaintext with the encoded arr_x 55 | ptxt_y = HE.encodeFrac(arr_y) # plaintexts created from arrays shorter than 'n' are filled with zeros. 56 | 57 | ctxt_x = HE.encryptPtxt(ptxt_x) # Encrypts the plaintext ptxt_x and returns a PyCtxt 58 | ctxt_y = HE.encryptPtxt(ptxt_y) # Alternatively you can use HE.encryptFrac(arr_y) 59 | 60 | # Otherwise, a single call to `HE.encrypt` would detect the data type, 61 | # encode it and encrypt it 62 | #> ctxt_x = HE.encrypt(arr_x) 63 | 64 | print("\n2. Fixed-point Encoding & Encryption, ") 65 | print("->\tarr_x ", arr_x,'\n\t==> ptxt_x ', ptxt_x,'\n\t==> ctxt_x ', ctxt_x) 66 | print("->\tarr_y ", arr_y,'\n\t==> ptxt_y ', ptxt_y,'\n\t==> ctxt_y ', ctxt_y) 67 | 68 | # %% 69 | # 3. Securely operating on encrypted fixed-point arrays 70 | # ------------------------------------------------------- 71 | # We try all the operations supported by Pyfhel. 72 | # Note that, to operate, the ciphertexts/plaintexts must be built with the same 73 | # context. Internal checks prevent ops between ciphertexts of different contexts. 74 | 75 | # Ciphertext-ciphertext ops: 76 | ccSum = ctxt_x + ctxt_y # Calls HE.add(ctxt_x, ctxt_y, in_new_ctxt=True) 77 | # `ctxt_x += ctxt_y` for inplace operation 78 | ccSub = ctxt_x - ctxt_y # Calls HE.sub(ctxt_x, ctxt_y, in_new_ctxt=True) 79 | # `ctxt_x -= ctxt_y` for inplace operation 80 | ccMul = ctxt_x * ctxt_y # Calls HE.multiply(ctxt_x, ctxt_y, in_new_ctxt=True) 81 | # `ctxt_x *= ctxt_y` for inplace operation 82 | cSq = ctxt_x**2 # Calls HE.square(ctxt_x, in_new_ctxt=True) 83 | # `ctxt_x **= 2` for inplace operation 84 | cNeg = -ctxt_x # Calls HE.negate(ctxt_x, in_new_ctxt=True) 85 | # 86 | # cPow = ctxt_x**3 # pow Not supported in CKKS 87 | cRotR = ctxt_x >> 2 # Calls HE.rotate(ctxt_x, k=2, in_new_ctxt=True) 88 | # `ctxt_x >>= 2` for inplace operation 89 | cRotL = ctxt_x << 2 # Calls HE.rotate(ctxt_x, k=-2, in_new_ctxt=True) 90 | # `ctxt_x <<= 2` for inplace operation 91 | 92 | # Ciphetext-plaintext ops 93 | cpSum = ctxt_x + ptxt_y # Calls HE.add_plain(ctxt_x, ptxt_y, in_new_ctxt=True) 94 | # `ctxt_x += ctxt_y` for inplace operation 95 | cpSub = ctxt_x - ptxt_y # Calls HE.sub_plain(ctxt_x, ptxt_y, in_new_ctxt=True) 96 | # `ctxt_x -= ctxt_y` for inplace operation 97 | cpMul = ctxt_x * ptxt_y # Calls HE.multiply_plain(ctxt_x, ptxt_y, in_new_ctxt=True) 98 | # `ctxt_x *= ctxt_y` for inplace operation 99 | 100 | 101 | print("3. Secure operations") 102 | print(" Ciphertext-ciphertext: ") 103 | print("->\tctxt_x + ctxt_y = ccSum: ", ccSum) 104 | print("->\tctxt_x - ctxt_y = ccSub: ", ccSub) 105 | print("->\tctxt_x * ctxt_y = ccMul: ", ccMul) 106 | print(" Single ciphertext: ") 107 | print("->\tctxt_x**2 = cSq : ", cSq ) 108 | print("->\t- ctxt_x = cNeg : ", cNeg ) 109 | print("->\tctxt_x >> 4 = cRotR: ", cRotR) 110 | print("->\tctxt_x << 4 = cRotL: ", cRotL) 111 | print(" Ciphertext-plaintext: ") 112 | print("->\tctxt_x + ptxt_y = cpSum: ", cpSum) 113 | print("->\tctxt_x - ptxt_y = cpSub: ", cpSub) 114 | print("->\tctxt_x * ptxt_y = cpMul: ", cpMul) 115 | 116 | 117 | # %% 118 | # 4. CKKS relinearization: What, why, when 119 | # ----------------------------------------- 120 | # Ciphertext-ciphertext multiplications increase the size of the polynoms 121 | # representing the resulting ciphertext. To prevent this growth, the 122 | # relinearization technique is used (typically right after each c-c mult) to 123 | # reduce the size of a ciphertext back to the minimal size (two polynoms c0 & c1). 124 | # For this, a special type of public key called Relinearization Key is used. 125 | # 126 | # In Pyfhel, you can either generate a relin key with HE.RelinKeyGen() or skip it 127 | # and call HE.relinearize() directly, in which case a warning is issued. 128 | 129 | print("\n4. Relinearization-> Right after each multiplication.") 130 | print(f"ccMul before relinearization (size {ccMul.size()}): {ccMul}") 131 | HE.relinKeyGen() 132 | ~ccMul # Equivalent to HE.relinearize(ccMul). Relin always happens in-place. 133 | print(f"ccMul after relinearization (size {ccMul.size()}): {ccMul}") 134 | 135 | # %% 136 | # 5. Rescaling & Mod Switching 137 | # ------------------------------- 138 | # More complex operations with CKKS require keeping track of the CKKS scale. 139 | # Operating with two CKKS ciphertexts (or a ciphertext and a plaintext) requires 140 | # them to have the same scale and the same modulus level. 141 | # 142 | # -> Scale: Multiplications yield a new scale, which is the product of the scales 143 | # of the two operands. To scale down a ciphertext, the HE.rescale_to_next(ctxt) 144 | # function is used, which switches the modulus to the next one in the qi chain 145 | # and divides the ciphertext by the previous modulus.# Since this is the only 146 | # scale-down operation, it is advised to use `scale_bits` with the same size as 147 | # the intermediate moduli sizes in HE.qi_sizes. 148 | # -> Mod Switching: Switches to the next modulus in the qi chain, but without 149 | # rescaling. This is achieved by the HE.mod_switch_to_next(ctxt) function. 150 | # 151 | # To ease the life of the user, Pyfhel provides `HE.align_mod_n_scale(this, other)`, 152 | # which automatically does the rescaling and mod switching. All the 2-input 153 | # overloaded operators (+, -, \*, /) of PyCtxt automatically call this function. 154 | # The respective HE.add, HE.sub, HE.multiply don't. 155 | # 156 | # NOTE: For more information, check the SEAL example #4_ccks_basics.cpp 157 | # 158 | # In this example we will compute the mean squared error, treating the average of 159 | # the two ciphertexts as the true distribution. We check the scale and mod-level 160 | # step by step: 161 | 162 | # 1. Mean 163 | c_mean = (ctxt_x + ctxt_y) / 2 164 | # 2. MSE 165 | c_mse_1 = ~((ctxt_x - c_mean)**2) 166 | c_mse_2 = (~(ctxt_y - c_mean)**2) 167 | c_mse = (c_mse_1 + c_mse_2)/ 3 168 | # 3. Cumulative sum 169 | c_mse += (c_mse << 1) 170 | c_mse += (c_mse << 2) # element 0 contains the result 171 | print("\n5. Rescaling & Mod Switching.") 172 | print("->\tMean: ", c_mean) 173 | print("->\tMSE_1: ", c_mse_1) 174 | print("->\tMSE_2: ", c_mse_2) 175 | print("->\tMSE: ", c_mse) 176 | 177 | 178 | 179 | # %% 180 | # 6. Decrypt & Decode results 181 | # ------------------------------ 182 | # Time to decrypt results! We use HE.decryptFrac for this. 183 | # HE.decrypt() could also be used, in which case the decryption type would be 184 | # inferred from the ciphertext metadata. 185 | r_x = HE.decryptFrac(ctxt_x) 186 | r_y = HE.decryptFrac(ctxt_y) 187 | rccSum = HE.decryptFrac(ccSum) 188 | rccSub = HE.decryptFrac(ccSub) 189 | rccMul = HE.decryptFrac(ccMul) 190 | rcSq = HE.decryptFrac(cSq ) 191 | rcNeg = HE.decryptFrac(cNeg ) 192 | rcRotR = HE.decryptFrac(cRotR) 193 | rcRotL = HE.decryptFrac(cRotL) 194 | rcpSum = HE.decryptFrac(cpSum) 195 | rcpSub = HE.decryptFrac(cpSub) 196 | rcpMul = HE.decryptFrac(cpMul) 197 | rmean = HE.decryptFrac(c_mean) 198 | rmse = HE.decryptFrac(c_mse) 199 | 200 | # Note: results are approximate! if you increase the decimals, you will notice 201 | # the errors 202 | _r = lambda x: np.round(x, decimals=3) 203 | print("6. Decrypting results") 204 | print(" Original ciphertexts: ") 205 | print(" ->\tctxt_x --(decr)--> ", _r(r_x)) 206 | print(" ->\tctxt_y --(decr)--> ", _r(r_y)) 207 | print(" Ciphertext-ciphertext Ops: ") 208 | print(" ->\tctxt_x + ctxt_y = ccSum --(decr)--> ", _r(rccSum)) 209 | print(" ->\tctxt_x - ctxt_y = ccSub --(decr)--> ", _r(rccSub)) 210 | print(" ->\tctxt_x * ctxt_y = ccMul --(decr)--> ", _r(rccMul)) 211 | print(" Single ciphertext: ") 212 | print(" ->\tctxt_x**2 = cSq --(decr)--> ", _r(rcSq )) 213 | print(" ->\t- ctxt_x = cNeg --(decr)--> ", _r(rcNeg )) 214 | print(" ->\tctxt_x >> 4 = cRotR --(decr)--> ", _r(rcRotR)) 215 | print(" ->\tctxt_x << 4 = cRotL --(decr)--> ", _r(rcRotL)) 216 | print(" Ciphertext-plaintext ops: ") 217 | print(" ->\tctxt_x + ptxt_y = cpSum --(decr)--> ", _r(rcpSum)) 218 | print(" ->\tctxt_x - ptxt_y = cpSub --(decr)--> ", _r(rcpSub)) 219 | print(" ->\tctxt_x * ptxt_y = cpMul --(decr)--> ", _r(rcpMul)) 220 | print(" Mean Squared error: ") 221 | print(" ->\tmean(ctxt_x, ctxt_y) = c_mean --(decr)--> ", _r(rmean)) 222 | print(" ->\tmse(ctxt_x, ctxt_y) = c_mse --(decr)--> ", _r(rmse)) 223 | # sphinx_gallery_thumbnail_path = 'static/thumbnails/float.png' 224 | # %% 225 | -------------------------------------------------------------------------------- /examples/Demo_4_SaveNRestore.py: -------------------------------------------------------------------------------- 1 | """ 2 | Saving and Restoring 3 | ============================== 4 | 5 | This demo creates and saves every kind of object managed by Pyfhel. 6 | Subsequently, these objects are loaded and verified. 7 | 8 | There are three methods: files, byte-strings or pickling. 9 | """ 10 | 11 | import os 12 | import numpy as np 13 | from Pyfhel import Pyfhel, PyPtxt, PyCtxt 14 | 15 | # %% 16 | # 1. Create one of each serializable object in Pyfhel 17 | # ------------------------------------------------------ 18 | # First we create a Pyfhel object with all types of keys 19 | HE = Pyfhel(context_params={'scheme':'bfv', 'n':2**14, 't_bits':32}) 20 | HE.keyGen() 21 | HE.rotateKeyGen() 22 | HE.relinKeyGen() 23 | # Then we encrypt some data 24 | c = HE.encrypt(np.array([42])) 25 | p = HE.encode(np.array([-1])) 26 | 27 | print("1. Creating serializable objects") 28 | print(f" Pyfhel object HE: {HE}") 29 | print(f" PyCtxt c=HE.encrypt([42]): {c}") 30 | print(f" PyPtxt p=HE.encode([-1]): {p}") 31 | 32 | 33 | # %% 34 | # 2. Checking size of objects before serializing 35 | # ------------------------------------------------------ 36 | con_size, con_size_zstd = HE.sizeof_context(), HE.sizeof_context(compr_mode="zstd") 37 | pk_size, pk_size_zstd = HE.sizeof_public_key(), HE.sizeof_public_key(compr_mode="zstd") 38 | sk_size, sk_size_zstd = HE.sizeof_secret_key(), HE.sizeof_secret_key(compr_mode="zstd") 39 | rotk_size,rotk_size_zstd = HE.sizeof_rotate_key(), HE.sizeof_rotate_key(compr_mode="zstd") 40 | rlk_size, rlk_size_zstd = HE.sizeof_relin_key(), HE.sizeof_relin_key(compr_mode="zstd") 41 | c_size, c_size_zstd = c.sizeof_ciphertext(), c.sizeof_ciphertext(compr_mode="zstd") 42 | # alternatively, for ciphertext sizes you can use sys.getsizeof(c) 43 | 44 | print("2. Checking size of serializable objects (with and without compression)") 45 | print(f" - context: [ \"zstd\" --> {con_size_zstd} | No compression --> {con_size}]") 46 | print(f" - public_key: [ \"zstd\" --> {pk_size_zstd} | No compression --> {pk_size}]") 47 | print(f" - secret_key: [ \"zstd\" --> {sk_size_zstd} | No compression --> {sk_size}]") 48 | print(f" - relin_key: [ \"zstd\" --> {rotk_size_zstd} | No compression --> {rotk_size}]") 49 | print(f" - rotate_key: [ \"zstd\" --> {rlk_size_zstd} | No compression --> {rlk_size}]") 50 | print(f" - c: [ \"zstd\" --> {c_size_zstd} | No compression --> {c_size}]") 51 | 52 | # %% 53 | # 3. Save & restore everything into/from files 54 | # ------------------------------------------------ 55 | # We will be using a temporary directory for this 56 | import tempfile 57 | tmp_dir = tempfile.TemporaryDirectory() 58 | tmp_dir_name = tmp_dir.name 59 | 60 | # Now we save all objects into files 61 | HE.save_context(tmp_dir_name + "/context") 62 | HE.save_public_key(tmp_dir_name + "/pub.key") 63 | HE.save_secret_key(tmp_dir_name + "/sec.key") 64 | HE.save_relin_key(tmp_dir_name + "/relin.key") 65 | HE.save_rotate_key(tmp_dir_name + "/rotate.key") 66 | c.save(tmp_dir_name + "/c.ctxt") 67 | p.save(tmp_dir_name + "/p.ptxt") 68 | 69 | print("2a. Saving everything into files. Let's check the temporary dir:") 70 | print("\n\t".join(os.listdir(tmp_dir_name))) 71 | 72 | # Now we restore everything and quickly check if it works. 73 | # Note! make sure to set the `pyfhel` parameter in PyCtxt/PyPtxt creation! 74 | HE_f = Pyfhel() # Empty creation 75 | HE_f.load_context(tmp_dir_name + "/context") 76 | HE_f.load_public_key(tmp_dir_name + "/pub.key") 77 | HE_f.load_secret_key(tmp_dir_name + "/sec.key") 78 | HE_f.load_relin_key(tmp_dir_name + "/relin.key") 79 | HE_f.load_rotate_key(tmp_dir_name + "/rotate.key") 80 | c_f = PyCtxt(pyfhel=HE_f, fileName=tmp_dir_name + "/c.ctxt") 81 | p_f = PyPtxt(pyfhel=HE_f, fileName=tmp_dir_name + "/p.ptxt", scheme='bfv') 82 | 83 | print("2b. Loading everything from files into a new environment.") 84 | # Some checks 85 | assert HE_f.decryptInt(HE_f.encrypt(np.array([42])))[0]==42, "Incorrect encryption" 86 | assert HE_f.decryptInt(c_f)[0]==42, "Incorrect decryption/ciphertext" 87 | assert HE_f.decodeInt(p_f)[0]==-1, "Incorrect decoding" 88 | assert HE_f.decryptInt(c_f >> 1)[1]==42, "Incorrect Rotation" 89 | c_relin = c_f**2 90 | ~c_relin 91 | assert c_relin.size()==2, "Incorrect relinearization" 92 | print(" All checks passed! Loaded from files correctly") 93 | # Cleaning up temporary directory 94 | tmp_dir.cleanup() 95 | 96 | 97 | # %% 98 | # 4. Save everything into byte-strings 99 | # ---------------------------------------- 100 | # Now we save all objects into bytestrings 101 | # This is useful to send objects over the network as payload (See Client/Server demo) 102 | s_context = HE.to_bytes_context() 103 | s_public_key= HE.to_bytes_public_key() 104 | s_secret_key= HE.to_bytes_secret_key() 105 | s_relin_key = HE.to_bytes_relin_key() 106 | s_rotate_key= HE.to_bytes_rotate_key() 107 | s_c = c.to_bytes() 108 | s_p = p.to_bytes() 109 | 110 | print("4a. Save all objects into byte-strings") 111 | print(f" - s_context: {s_context[:10]}...") 112 | print(f" - s_public_key: {s_public_key[:10]}...") 113 | print(f" - s_secret_key: {s_secret_key[:10]}...") 114 | print(f" - s_relin_key: {s_relin_key[:10]}...") 115 | print(f" - s_rotate_key: {s_rotate_key[:10]}...") 116 | print(f" - s_c: {s_c[:10]}...") 117 | print(f" - s_p: {s_p[:10]}...") 118 | 119 | 120 | # Now we load everything and quickly check if it works. 121 | HE_b = Pyfhel() # Empty creation 122 | HE_b.from_bytes_context(s_context) 123 | HE_b.from_bytes_public_key(s_public_key) 124 | HE_b.from_bytes_secret_key(s_secret_key) 125 | HE_b.from_bytes_relin_key(s_relin_key) 126 | HE_b.from_bytes_rotate_key(s_rotate_key) 127 | c_b = PyCtxt(pyfhel=HE_b, bytestring=s_c) 128 | p_b = PyPtxt(pyfhel=HE_b, bytestring=s_p) 129 | 130 | 131 | print("4b. Loading everything from bytestrings.") 132 | # Some checks 133 | assert HE_b.decryptInt(HE_b.encryptInt(np.array([42], dtype=np.int64)))[0]==42, "Incorrect encryption" 134 | assert HE_b.decryptInt(c_b)[0]==42, "Incorrect decryption/ciphertext" 135 | assert HE_b.decodeInt(p_b)[0]==-1, "Incorrect decoding" 136 | assert HE_b.decryptInt(c_b >> 1)[1]==42, "Incorrect Rotation" 137 | c_relin = c_b**2 138 | ~c_relin 139 | assert c_relin.size()==2, "Incorrect relinearization" 140 | print(" All checks passed! Loaded from bytestrings correctly") 141 | 142 | 143 | # %% 144 | # 5. Pickling Python objects 145 | # ------------------------------- 146 | # A last alternative is to pickle the python objects and then unpickle them. 147 | # > WARNING! when pickling a Pyfhel object only the context is picked inside < 148 | # (To avoid accidental sharing of the secret key) 149 | # 150 | # To share keys, use one of the two methods above (files, bytestrings) 151 | 152 | import pickle 153 | pkls_pyfhel = pickle.dumps(HE) # pickle.dump(HE, file) to dump in a file 154 | pkls_ctxt = pickle.dumps(c) 155 | pkls_ptxt = pickle.dumps(p) 156 | 157 | print("5a. Pickling Pyfhel, PyCtxt & PyPtxt objects.") 158 | print(f" - pkls_pyfhel: {pkls_pyfhel[:10]}...") 159 | print(f" - pkls_ctxt: {pkls_ctxt[:10]}...") 160 | print(f" - pkls_ptxt: {pkls_ptxt[:10]}...") 161 | 162 | # To load the objects, just call `pickle.loads` 163 | HE_pkl = pickle.loads(pkls_pyfhel) # pickle.load(file) to load from file 164 | c_pkl = pickle.loads(pkls_ctxt) 165 | p_pkl = pickle.loads(pkls_ptxt) 166 | print("5b. Loaded pickled objects") 167 | print(f" - HE_pkl: {HE_pkl}") 168 | print(f" - c_pkl: {c_pkl}") 169 | print(f" - p_pkl: {p_pkl}") 170 | 171 | # sphinx_gallery_thumbnail_path = 'static/thumbnails/saveRestore.png' -------------------------------------------------------------------------------- /examples/Demo_5_CS_Client.py: -------------------------------------------------------------------------------- 1 | """ 2 | Client/Server demo with Pyfhel 3 | ======================================== 4 | 5 | This demo shows an example of Client-Server interaction, where the client sends 6 | an encrypted vector and the server answers with the weighted average based on his 7 | weights. 8 | 9 | To run with real Client/Server separation (using `flask` and Demo_5bis_CS_Server.py), 10 | change the flag below to True. 11 | """ 12 | 13 | USE_REAL_SERVER: bool = False 14 | 15 | # %% 16 | # 1. Setup Client 17 | # -------------------------- 18 | import numpy as np 19 | from Pyfhel import Pyfhel, PyCtxt 20 | if USE_REAL_SERVER: 21 | try: 22 | import requests 23 | except ImportError: 24 | print("This demo requires the `requests` python module (install with pip). Exiting.") 25 | exit(0) 26 | 27 | # Generate Pyfhel session 28 | print(f"[Client] Initializing Pyfhel session and data...") 29 | HE_client = Pyfhel(context_params={'scheme':'ckks', 'n':2**13, 'scale':2**30, 'qi_sizes':[30]*5}) 30 | HE_client.keyGen() # Generates both a public and a private key 31 | HE_client.relinKeyGen() 32 | HE_client.rotateKeyGen() 33 | 34 | # Generate and encrypt data 35 | x = np.array([1.5, 2, 3.3, 4]) 36 | cx = HE_client.encrypt(x) 37 | 38 | # Serializing data and public context information 39 | s_context = HE_client.to_bytes_context() 40 | s_public_key = HE_client.to_bytes_public_key() 41 | s_relin_key = HE_client.to_bytes_relin_key() 42 | s_rotate_key = HE_client.to_bytes_rotate_key() 43 | s_cx = cx.to_bytes() 44 | 45 | print(f"[Client] sending HE_client={HE_client} and cx={cx}") 46 | 47 | 48 | # %% 49 | # 2. Setup Server 50 | # ----------------------- 51 | 52 | print(f"[Client] launching server (could be launched separately)...") 53 | if(USE_REAL_SERVER): 54 | import subprocess, os 55 | from pathlib import Path 56 | dir = Path(os.path.realpath("__file__")).parent 57 | process = subprocess.Popen( 58 | ["python", str(dir / "Demo_5bis_CS_Server.py")], 59 | stderr=subprocess.STDOUT, 60 | ) 61 | import time 62 | time.sleep(6) # Wait for server initialization 63 | else: 64 | print(f"[Server] mock started!...") 65 | print("[Client] server initialized...") 66 | 67 | # %% 68 | # 3. Launch a request to the server 69 | # ---------------------------------------- 70 | # We map the bytes into strings based on https://stackoverflow.com/a/27527728 71 | if(USE_REAL_SERVER): 72 | r = requests.post('http://127.0.0.1:5000/fhe_mse', 73 | json={ 74 | 'context': s_context.decode('cp437'), 75 | 'pk': s_public_key.decode('cp437'), 76 | 'rlk':s_relin_key.decode('cp437'), 77 | 'rtk':s_rotate_key.decode('cp437'), 78 | 'cx': s_cx.decode('cp437'), 79 | }) 80 | c_res = PyCtxt(pyfhel=HE_client, bytestring=r.text.encode('cp437')) 81 | else: # Mocking server code (from Demo_5bis_CS_Server.py) 82 | # Read all bytestrings 83 | HE_server = Pyfhel() 84 | HE_server.from_bytes_context(s_context) 85 | HE_server.from_bytes_public_key(s_public_key) 86 | HE_server.from_bytes_relin_key(s_relin_key) 87 | HE_server.from_bytes_rotate_key(s_rotate_key) 88 | cx = PyCtxt(pyfhel=HE_server, bytestring=s_cx) 89 | print(f"[Server] received HE_server={HE_server} and cx={cx}") 90 | 91 | # Encode weights in plaintext 92 | w = np.array([0.5, -1.5, 4, 5]) 93 | ptxt_w = HE_server.encode(w) 94 | 95 | # Compute weighted average 96 | c_mean = (cx * ptxt_w) 97 | c_mean /= 4 # 4 98 | c_mean += (c_mean >> 1) # cumulative sum 99 | c_mean += (c_mean >> 2) # element [3] contains the result 100 | print(f"[Server] Average computed! Responding: c_mean={c_mean}") 101 | 102 | c_res = c_mean.copy() # Copying with a single command 103 | 104 | # %% 105 | # 4. Process Response 106 | # -------------------------- 107 | # Decrypting result 108 | res = HE_client.decryptFrac(c_res) 109 | 110 | # Checking result 111 | w = np.array([0.5, -1.5, 4, 5]) # in the server 112 | expected = np.mean(x*w) 113 | print(f"[Client] Response received! Result is {np.round(res[3], 4)}, should be {expected}") 114 | 115 | 116 | # %% 5. Stop server 117 | if USE_REAL_SERVER: 118 | process.kill() 119 | 120 | # sphinx_gallery_thumbnail_path = 'static/thumbnails/encrypting.jpg' -------------------------------------------------------------------------------- /examples/Demo_5bis_CS_Server.py: -------------------------------------------------------------------------------- 1 | """ 2 | Client/Server demo with Pyfhel 3 | ======================================== 4 | 5 | Context Parameters shows how several parameters affect performance. 6 | """ 7 | USE_REAL_SERVER: bool = False 8 | 9 | if not USE_REAL_SERVER: 10 | exit() # skip demo if not running on real server mode 11 | 12 | import numpy as np 13 | from Pyfhel import Pyfhel, PyCtxt 14 | from base64 import decodebytes 15 | try: 16 | from flask import Flask, request 17 | except ImportError: 18 | print("This demo requires the `flask` python module (install with pip). Exiting.") 19 | exit(0) 20 | 21 | # Server weights -> encode in plaintext 22 | w = np.array([0.5, -1.5, 4, 5]) 23 | 24 | # Quick setup of the server using flask 25 | app = Flask(__name__) 26 | 27 | @app.route('/fhe_mse', methods=['POST']) 28 | def post(): 29 | print("Received Request!") 30 | 31 | # Read all bytestrings 32 | HE_server = Pyfhel() 33 | HE_server.from_bytes_context(request.json.get('context').encode('cp437')) 34 | HE_server.from_bytes_public_key(request.json.get('pk').encode('cp437')) 35 | HE_server.from_bytes_relin_key(request.json.get('rlk').encode('cp437')) 36 | HE_server.from_bytes_rotate_key(request.json.get('rtk').encode('cp437')) 37 | cx = PyCtxt(pyfhel=HE_server, bytestring=request.json.get('cx').encode('cp437')) 38 | print(f"[Server] received HE_server={HE_server} and cx={cx}") 39 | 40 | # Encode weights in plaintext 41 | ptxt_w = HE_server.encode(w) 42 | 43 | # Compute weighted average 44 | c_mean = (cx * ptxt_w) 45 | c_mean /= 4 # 4 46 | c_mean += (c_mean >> 1) # cumulative sum 47 | c_mean += (c_mean >> 2) # element [3] contains the result 48 | print(f"[Server] Average computed! Responding: c_mean={c_mean}") 49 | 50 | # Serialize encrypted result and answer it back 51 | return c_mean.to_bytes().decode('cp437') 52 | 53 | app.run(host='0.0.0.0', port=5000) # Run, accessible via http://localhost:5000/ 54 | 55 | 56 | # sphinx_gallery_thumbnail_path = 'static/thumbnails/clientServer.png' -------------------------------------------------------------------------------- /examples/Demo_6_MultDepth.py: -------------------------------------------------------------------------------- 1 | """ 2 | MultDepth and Relineraization 3 | ============================== 4 | 5 | This demo focuses on managing ciphertext size and noise budget. 6 | 7 | Each ciphertext can be seen as (oversimplifying a bit) a polynomial with noise. 8 | When you perform encrypted multiplication, the size of the output polynomial grows 9 | based on the size of the tho muliplicands, which increases its memory footprint 10 | and slows down operations. In order to keep ciphertext sizes low (min. 2), it 11 | is recommended to relinearize after each multiplication, effectively shrinking 12 | the ciphertext back to minimum size. 13 | 14 | Besides this, the noise inside the ciphertext grows greatly when multiplying. 15 | Each ciphertext is given a noise budget when first encrypted, which decreases 16 | irreversibly for each multiplication. If the noise budget reaches zero, the 17 | resultin ciphertext cannot be decrypted correctly anymore. 18 | """ 19 | # %% 20 | # ---------------------------------------------------------------------------- # 21 | # A) BFV - MULTIPLICATIVE DEPTH WITH INTEGERS 22 | # ---------------------------------------------------------------------------- # 23 | # A1. Context and key setup 24 | # --------------------------- 25 | # Feel free to change these parameters! 26 | import numpy as np 27 | from Pyfhel import Pyfhel 28 | HE = Pyfhel(key_gen=True, context_params= { 29 | 'scheme': 'BFV', 30 | 'n': 2**13, # num. of slots per plaintext. Higher n yields higher noise 31 | # budget, but operations take longer. 32 | 't': 65537, # Plaintext modulus (and bits). Higher t yields higher noise 33 | 't_bits': 20, # budget, but operations take longer. 34 | 'sec': 128, # Security parameter. 35 | }) 36 | HE.relinKeyGen() 37 | 38 | print("\nA1. BFV context generation") 39 | print(f"\t{HE}") 40 | 41 | # %% 42 | # 2. BFV Array Encoding & Encryption 43 | # ----------------------------------------- 44 | # We will define two 1D integer arrays, encode and encrypt them: 45 | # arr1 = [0, 1, ... n-1] 46 | # arr2 = [-t//2, -1, 1, 0, 0..., 0] 47 | 48 | arr1 = np.arange(HE.n, dtype=np.int64) 49 | arr2 = np.array([1, -1, 1], dtype=np.int64) 50 | 51 | ctxt1 = HE.encryptInt(arr1) 52 | ctxt2 = HE.encryptInt(arr2) 53 | 54 | print("\nA2. Integer Encryption") 55 | print("->\tarr1 ", arr1,'\n\t==> ctxt1 ', ctxt1) 56 | print("->\tarr2 ", arr2,'\n\t==> ctxt2 ', ctxt2) 57 | 58 | 59 | # %% 60 | # 3. BFV: Securely multiplying as much as we can 61 | # ------------------------------------------------- 62 | # You can measure the noise level using HE.noise_level(ctxt). 63 | # IMPORTANT! To get the noise Level you need the private key! otherwise the only 64 | # way to know if above the noise is to decrypt the ciphertext and check the result. 65 | 66 | print("A3. Securely multiplying as much as we can") 67 | step = 0 68 | lvl = HE.noise_level(ctxt1) 69 | while lvl > 0: 70 | print(f"\tStep {step}: noise_lvl {lvl}, res {HE.decryptInt(ctxt1)[:4]}") 71 | step += 1 72 | ctxt1 *= ctxt2 # Multiply in-place 73 | ctxt1 = ~(ctxt1) # Always relinearize after each multiplication! 74 | lvl = HE.noise_level(ctxt1) 75 | print(f"\tFinal Step {step}: noise_lvl {lvl}, res {HE.decryptInt(ctxt1)[:4]}") 76 | print("---------------------------------------") 77 | 78 | 79 | # %% 80 | # ---------------------------------------------------------------------------- # 81 | # B) CKKS - MULTIPLICATIVE DEPTH WITH FIXED-POINTS 82 | # ---------------------------------------------------------------------------- # 83 | # In this case we don't have a noise_level operation to check the current noise, 84 | # since the noise is considered as part of the encoding and adds up to a loss in 85 | # precision of the encoded values. 86 | # However, we can precisely control the maximum # of multiplications by setting 87 | # qi_sizes (or even manually selecting primes qi). 88 | # 89 | # B1. Context and key setup 90 | # --------------------------- 91 | import numpy as np 92 | from Pyfhel import Pyfhel 93 | 94 | # Feel free to change this number! 95 | n_mults = 8 96 | 97 | HE = Pyfhel(key_gen=True, context_params={ 98 | 'scheme': 'CKKS', 99 | 'n': 2**14, # For CKKS, n/2 values can be encoded in a single ciphertext. 100 | 'scale': 2**30, # Each multiplication grows the final scale 101 | 'qi_sizes': [60]+ [30]*n_mults +[60] # Number of bits of each prime in the chain. 102 | # Intermediate prime sizes should be close to log2(scale). 103 | # One per multiplication! More/higher qi_sizes means bigger 104 | # ciphertexts and slower ops. 105 | }) 106 | HE.relinKeyGen() 107 | print("\nB1. CKKS context generation") 108 | print(f"\t{HE}") 109 | 110 | # %% 111 | # B2. CKKS Array Encoding & Encryption 112 | # ---------------------------------------- 113 | arr_x = np.array([1.1, 2.2, -3.3], dtype=np.float64) 114 | arr_y = np.array([1, -1, 1], dtype=np.float64) 115 | 116 | ctxt_x = HE.encryptFrac(arr_x) 117 | ctxt_y = HE.encryptFrac(arr_y) 118 | 119 | print("\nB2. Fixed-point Encoding & Encryption, ") 120 | print("->\tarr_x ", arr_x,'\n\t==> ctxt_x ', ctxt_x) 121 | print("->\tarr_y ", arr_y,'\n\t==> ctxt_y ', ctxt_y) 122 | 123 | # %% 124 | # B3. Multiply n_mult times! 125 | # ----------------------------- 126 | # Besides rescaling, we also need to perform rescaling & mod switching. Luckily 127 | # Pyfhel does it for us by calling HE.align_mod_n_scale() before each operation. 128 | 129 | _r = lambda x: np.round(x, decimals=6)[:4] 130 | print(f"B3. Securely multiplying {n_mults} times!") 131 | for step in range(1,n_mults+1): 132 | ctxt_x *= ctxt_y # Multiply in-place --> implicit align_mod_n_scale() 133 | ctxt_x = ~(ctxt_x) # Always relinearize after each multiplication! 134 | print(f"\tStep {step}: res {_r(HE.decryptFrac(ctxt_x))}") 135 | try: 136 | ctxt_x *= ctxt_y 137 | except ValueError as e: 138 | assert str(e)=='scale out of bounds' 139 | print(f"If we multiply further we get: {str(e)}") 140 | print("---------------------------------------") 141 | 142 | 143 | 144 | # sphinx_gallery_thumbnail_path = 'static/thumbnails/multDepth.jpg' -------------------------------------------------------------------------------- /examples/Demo_8_HammingDist.py: -------------------------------------------------------------------------------- 1 | """ 2 | Hamming Distance. 3 | ============================== 4 | 5 | This demo shows how to compute the Hamming Distance between two vectors {0,1}*. 6 | 7 | We will translate the hamming distance into a scalar product by translating the 8 | XOR op. to the arithmetic domain: 9 | 10 | xor(x,y) = (x-y)^2 = x + y - 2*(x*y) 11 | HD(x,y) = sum (x[i] xor y[i]) = sum(x[i]) + sum(y[i]) - 2*sum(x[i]*y[i]) 12 | 13 | To do so, we will encode both the vectors and their elementwise squared 14 | cumul_add in independent vectors, compute the scalar product and add the 15 | remaining terms. 16 | 17 | As in the scalar product, we encode each vector into one (if l <= n_slots) or 18 | several ciphertexts (l>n), then compute the SIMD multiplication and cumulative 19 | sum (via rotations and sums), to finally add the extra terms and decrypt. 20 | 21 | We perform this operation in BFV to get the exact result. CKKS could also be 22 | used, but the result would be an approximation. 23 | """ 24 | # %% 25 | # ---------------------------------------------------------------------------- # 26 | # HAMMING DISTANCE WITH BFV 27 | # ---------------------------------------------------------------------------- # 28 | 29 | # %% 30 | # 1. Vector Generation 31 | # --------------------------- 32 | # We will define a pair of 1D integer vectors of size l in {0,1}*. 33 | # For the purpose of this demo, we will focus on the case #1 of ScalarProd: 34 | # #1: small l (l <= n) and high v_max. --> encoding with trailing zeros 35 | # 36 | # Users can modify l. 37 | 38 | import warnings 39 | import numpy as np 40 | rng = np.random.default_rng(42) 41 | 42 | l = 256 43 | 44 | # Generate two random vectors with l elements of values <= |v_max| 45 | v1 = rng.integers(0, 2, size=l) 46 | v2 = rng.integers(0, 2, size=l) 47 | hdRes = np.sum(v1^v2) 48 | print("\nA1. Vector generation") 49 | print(f"\tv1 = {str(v1)[:40]+'...'}") 50 | print(f"\tv2 = {str(v2)[:40]+'...'}") 51 | print(f"\tvRes = {hdRes}") 52 | 53 | 54 | # %% 55 | # 2. Hamming Distance setup 56 | # --------------------------- 57 | # Parameter selection goes according to vector length and max element size. 58 | from Pyfhel import Pyfhel 59 | 60 | # utilities to generate context parameters 61 | bitsize = lambda x: np.ceil(np.log2(x)) 62 | get_closest_power_of_two = lambda x: int(2**(bitsize(x))) 63 | 64 | def get_BFV_context_hammingDist( 65 | l: int, sec: int=128, 66 | use_n_min: bool=True 67 | ) ->Pyfhel: 68 | """ 69 | Returns the best context parameters to compute hamming dist. in BFV scheme. 70 | 71 | The important context parameters to set are: 72 | - n: the polynomial modulus degree (= n_slots) 73 | - t: the plaintext modulus (plaintext element size) 74 | 75 | *Optimal n*: Chosen among {2**12, 2**13, 2**14, 2**15}. The min value is: 76 | - n = 2**12 if sec in {128, 192} 77 | - n = 2**13 if sec = 256 78 | The bigger n, the more secure the scheme, but the slower the 79 | computations. (it might be worth using n t_bits = 2 * bitsize(l) 89 | ^ ^ 90 | square | cumul. sum 91 | There is, however, a minimal size on t_bits for a given `n`: 92 | t_bits_min = 14 if n <= 2**11 93 | t_bits_min = 16 if n <= 2**12 94 | t_bits_min = 17 otherwise 95 | 96 | Arguments: 97 | l: vector length 98 | 99 | Returns: 100 | Pyfhel: context to perform homomorphic encryption 101 | """ 102 | #> OPTIMAL t --> minimum for chosen `n`, or big enough to hold v1@v2 103 | t_bits_min = 17 104 | t_bits = max(t_bits_min, 2*bitsize(l)) 105 | if t_bits > 60: 106 | raise ValueError(f"t_bits = {t_bits} > 60.") 107 | 108 | #> OPTIMAL n 109 | n_min = 2**12 if sec in [128, 192] else 2**13 110 | if use_n_min: n = n_min # use n_min regardless of l 111 | elif 2*l < n_min: n = n_min # Smallest 112 | elif 2*l > 2**15: n = 2**15 # Largest 113 | else: n = get_closest_power_of_two(2*l) 114 | 115 | context_params = { 116 | 'scheme': 'BFV', 117 | 'n': n, # Poly modulus degree. BFV ptxt is a n//2 by 2 matrix. 118 | 't_bits': t_bits,# Plaintext modulus. 119 | 'sec': sec, # Security level. 120 | } 121 | 122 | # Increasing `n` to get enough noise budget for the c1*c2 multiplication. 123 | # Since the noise budget in a multiplication consumes t_bits (+...) noise 124 | # budget and we start with `total_coeff_modulus_bit_count-t_bits` budget, # we check if this is high enough to decrypt correctly. 125 | HE = Pyfhel() 126 | total_coeff_modulus_bit_count = 0 127 | while total_coeff_modulus_bit_count - 2*t_bits <= 0: 128 | context_status = HE.contextGen(**context_params) 129 | total_coeff_modulus_bit_count = HE.total_coeff_modulus_bit_count 130 | # if (context_status != "success"): 131 | # warnings.warn(f" n={context_params['n']} Doesn't produce valid parameters. Trying 2*n") 132 | context_params['n'] *= 2 133 | if context_params['n'] > 2**15: 134 | raise ValueError(f"n = {context_params['n']} > 2**15. Parameters are not valid.") 135 | return HE 136 | 137 | # Setup parameters for HE context 138 | HE = get_BFV_context_hammingDist(l, sec=128, use_n_min=True) 139 | HE.keyGen() 140 | HE.relinKeyGen() 141 | HE.rotateKeyGen() 142 | 143 | print("\n2. BFV context generation") 144 | print(f"\t{HE}") 145 | 146 | 147 | # %% 148 | # 3. BFV Encryption 149 | # ----------------------------------------- 150 | 151 | # Encrypt each vector into one (if l <= n_slots) or several ciphertexts (l>n) 152 | c1 = [HE.encrypt(v1[j:j+HE.get_nSlots()]) for j in range(0,l,HE.get_nSlots())] 153 | c2 = [HE.encrypt(v2[j:j+HE.get_nSlots()]) for j in range(0,l,HE.get_nSlots())] 154 | 155 | sumx = np.expand_dims(np.sum(v1),0) 156 | sumy = np.expand_dims(np.sum(v2),0) 157 | c1_sumx = HE.encrypt(sumx) 158 | c2_sumy = HE.encrypt(sumy) 159 | 160 | print("\n3. Boolean Encryption as integers") 161 | print("->\tv1= ", str(v1)[:40],'...]\n\t--> c1= ', c1) 162 | print("->\tv2= ", str(v2)[:40],'...]\n\t--> c2= ', c2) 163 | print("->\tsumx= ", str(sumx),'\n\t--> c1_sumx= ', c1_sumx) 164 | print("->\tsumy= ", str(sumy),'\n\t--> c2_sumy= ', c2_sumy) 165 | 166 | # %% 167 | # 4. BFV Hamming Distance Evaluation 168 | # ----------------------------------------- 169 | # Evaluate the scalar product, then add the independent terms 170 | c_sp = HE.cumul_add(sum([~(c1[i]*c2[i]) for i in range(len(c1))])) 171 | 172 | # Hamming Distance 173 | c_hd = c1_sumx + c2_sumy - (c_sp*2) 174 | 175 | # Lets decrypt and check the result! 176 | res = HE.decrypt(c_hd)[0] 177 | 178 | print("\n4. Hamming Distance Evaluation") 179 | print("->\tHamming(c1, c2)= ", c_hd) 180 | print("->\tHE.decrypt(c1@c2)= ", res) 181 | print("->\tExpected: v1@v2= ", hdRes) 182 | assert res == hdRes, "Incorrect result!" 183 | print("---------------------------------------") 184 | 185 | 186 | 187 | # sphinx_gallery_thumbnail_path = 'static/thumbnails/hammingDist.png' 188 | # %% 189 | -------------------------------------------------------------------------------- /examples/Demo_9_Integer_BGV.py: -------------------------------------------------------------------------------- 1 | """ 2 | Integer FHE with BGV scheme 3 | ======================================== 4 | //TODO 5 | """ 6 | 7 | # %% 8 | # 1. Imports 9 | # --------------------------- 10 | # We start by importing the library 11 | 12 | import numpy as np 13 | from Pyfhel import Pyfhel 14 | 15 | print("\n1. Pyfhel Import") 16 | 17 | 18 | # %% 19 | # 2. BGV context and key setup 20 | # ------------------------------------------------------------------------------ 21 | # We take a look at the different parameters that can be set for the BGV scheme. 22 | HE = Pyfhel() # Creating empty Pyfhel object 23 | 24 | # HE.contextGen(scheme='bgv', n=2**14, t_bits=20) # Generate context for 'bfv'/'bgv'/'ckks' scheme 25 | 26 | bgv_params = { 27 | 'scheme': 'BGV', # can also be 'bgv' 28 | 'n': 2**13, # Polynomial modulus degree, the num. of slots per plaintext, 29 | # of elements to be encoded in a single ciphertext in a 30 | # 2 by n/2 rectangular matrix (mind this shape for rotations!) 31 | # Typ. 2^D for D in [10, 16] 32 | 't': 65537, # Plaintext modulus. Encrypted operations happen modulo t 33 | # Must be prime such that t-1 be divisible by 2^N. 34 | 't_bits': 20, # Number of bits in t. Used to generate a suitable value 35 | # for t. Overrides t if specified. 36 | 'sec': 128, # Security parameter. The equivalent length of AES key in bits. 37 | # Sets the ciphertext modulus q, can be one of {128, 192, 256} 38 | # More means more security but also slower computation. 39 | } 40 | HE.contextGen(**bgv_params) # Generate context for bgv scheme 41 | HE.keyGen() # Key Generation: generates a pair of public/secret keys 42 | HE.rotateKeyGen() # Rotate key generation --> Allows rotation/shifting 43 | HE.relinKeyGen() # Relinearization key generation 44 | 45 | print("\n2. Pyfhel FHE context generation") 46 | print(f"\t{HE}") 47 | 48 | 49 | # %% 50 | # 3. BGV Integer Encryption 51 | # --------------------------- 52 | # we will define two integers and encrypt them using `encryptBGV`: 53 | integer1 = np.array([127], dtype=np.int64) 54 | integer2 = np.array([-2], dtype=np.int64) 55 | ctxt1 = HE.encryptBGV(integer1) # Encryption makes use of the public key 56 | ctxt2 = HE.encryptBGV(integer2) # For BGV, encryptBGV function is used. 57 | print("\n3. BGV Encryption, ") 58 | print(" int ",integer1,'-> ctxt1 ', type(ctxt1)) 59 | print(" int ",integer2,'-> ctxt2 ', type(ctxt2)) 60 | # %% 61 | # # The best way to obtain information from a ciphertext is to print it: 62 | print(ctxt1) 63 | print(ctxt2) 64 | 65 | # %% 66 | # 4. Operating with encrypted integers in BGV 67 | # --------------------------------------------- 68 | # Relying on the context defined before, we will now operate 69 | # (addition, substaction, multiplication) the two ciphertexts: 70 | ctxtSum = ctxt1 + ctxt2 # `ctxt1 += ctxt2` for inplace operation 71 | ctxtSub = ctxt1 - ctxt2 # `ctxt1 -= ctxt2` for inplace operation 72 | ctxtMul = ctxt1 * ctxt2 # `ctxt1 *= ctxt2` for inplace operation 73 | print("\n4. Operating with encrypted integers") 74 | print(f"Sum: {ctxtSum}") 75 | print(f"Sub: {ctxtSub}") 76 | print(f"Mult:{ctxtMul}") 77 | 78 | # %% 79 | # 5. Decrypting BGV integers 80 | # ------------------------------ 81 | # Once we're finished with the encrypted operations, we can use 82 | # the Pyfhel instance to decrypt the results using `decryptBGV`: 83 | resSum = HE.decryptBGV(ctxtSum) # Decryption must use the corresponding function 84 | # decryptBGV. 85 | resSub = HE.decrypt(ctxtSub) # `decrypt` function detects the scheme and 86 | # calls the corresponding decryption function. 87 | resMul = HE.decryptBGV(ctxtMul) 88 | print("\n5. Decrypting result:") 89 | print(" addition: decrypt(ctxt1 + ctxt2) = ", resSum) 90 | print(" substraction: decrypt(ctxt1 - ctxt2) = ", resSub) 91 | print(" multiplication: decrypt(ctxt1 + ctxt2) = ", resMul) 92 | 93 | 94 | # %% 95 | # 6. Integer Array Encoding & Encryption 96 | # ------------------------------------------------------------------------------ 97 | # we will define two 1D integer arrays, encode and encrypt them: 98 | # arr1 = [0, 1, ... n-1] (length n) 99 | # arr2 = [-t//2, -1, 1] (length 3) --> Encoding fills the rest of the array with zeros 100 | 101 | arr1 = np.arange(bgv_params['n'], dtype=np.int64) # Max possible value is t/2-1. Always use type int64! 102 | arr2 = np.array([-bgv_params['t']//2, -1, 1], dtype=np.int64) # Min possible value is -t/2. 103 | 104 | ptxt1 = HE.encodeBGV(arr1) # Creates a PyPtxt plaintext with the encoded arr1 105 | # plaintexts created from arrays shorter than 'n' are filled with zeros. 106 | ptxt2 = HE.encode(arr2) # `encode` function detects the scheme and calls the 107 | # corresponding encoding function. 108 | 109 | assert np.allclose(HE.decodeBGV(ptxt1), arr1) # Decoding the encoded array should return the original array 110 | assert np.allclose(HE.decode(ptxt2)[:3], arr2) # `decode` function detects the scheme 111 | 112 | ctxt1 = HE.encryptPtxt(ptxt1) # Encrypts the plaintext ptxt1 and returns a PyCtxt 113 | ctxt2 = HE.encryptPtxt(ptxt2) # Alternatively you can use HE.encryptInt(arr2) 114 | 115 | # Otherwise, a single call to `HE.encrypt` would detect the data type, 116 | # encode it and encrypt it 117 | #> ctxt1 = HE.encrypt(arr1) 118 | 119 | print("\n6. Integer Array Encoding & Encryption, ") 120 | print("->\tarr1 ", arr1,'\n\t==> ptxt1 ', ptxt1,'\n\t==> ctxt1 ', ctxt1) 121 | print("->\tarr2 ", arr2,'\n\t==> ptxt2 ', ptxt2,'\n\t==> ctxt2 ', ctxt2) 122 | 123 | # %% 124 | # 7. Securely operating on encrypted ingeger arrays 125 | # ------------------------------------------------------------------------------ 126 | # We try all the operations supported by Pyfhel. 127 | # Note that, to operate, the ciphertexts/plaintexts must be built with the same 128 | # context. Internal checks prevent ops between ciphertexts of different contexts. 129 | 130 | # Ciphertext-ciphertext ops: 131 | ccSum = ctxt1 + ctxt2 # Calls HE.add(ctxt1, ctxt2, in_new_ctxt=True) 132 | # `ctxt1 += ctxt2` for inplace operation 133 | ccSub = ctxt1 - ctxt2 # Calls HE.sub(ctxt1, ctxt2, in_new_ctxt=True) 134 | # `ctxt1 -= ctxt2` for inplace operation 135 | ccMul = ctxt1 * ctxt2 # Calls HE.multiply(ctxt1, ctxt2, in_new_ctxt=True) 136 | # `ctxt1 *= ctxt2` for inplace operation 137 | cSq = ctxt1**2 # Calls HE.square(ctxt1, in_new_ctxt=True) 138 | # `ctxt1 **= 2` for inplace operation 139 | cNeg = -ctxt1 # Calls HE.negate(ctxt1, in_new_ctxt=True) 140 | # 141 | cPow = ctxt1**3 # Calls HE.power(ctxt1, 3, in_new_ctxt=True) 142 | # `ctxt1 **= 3` for inplace operation 143 | cRotR = ctxt1 >> 2 # Calls HE.rotate(ctxt1, k=2, in_new_ctxt=True) 144 | # `ctxt1 >>= 2` for inplace operation 145 | # WARNING! the encoded data is placed in a n//2 by 2 146 | # matrix. Hence, these rotations apply independently 147 | # to each of the rows! 148 | cRotL = ctxt1 << 2 # Calls HE.rotate(ctxt1, k=-2, in_new_ctxt=True) 149 | # `ctxt1 <<= 2` for inplace operation 150 | 151 | # Ciphetext-plaintext ops 152 | cpSum = ctxt1 + ptxt2 # Calls HE.add_plain(ctxt1, ptxt2, in_new_ctxt=True) 153 | # `ctxt1 += ctxt2` for inplace operation 154 | cpSub = ctxt1 - ptxt2 # Calls HE.sub_plain(ctxt1, ptxt2, in_new_ctxt=True) 155 | # `ctxt1 -= ctxt2` for inplace operation 156 | cpMul = ctxt1 * ptxt2 # Calls HE.multiply_plain(ctxt1, ptxt2, in_new_ctxt=True) 157 | # `ctxt1 *= ctxt2` for inplace operation 158 | 159 | 160 | print("\n7. Secure operations") 161 | print(" Ciphertext-ciphertext: ") 162 | print("->\tctxt1 + ctxt2 = ccSum: ", ccSum) 163 | print("->\tctxt1 - ctxt2 = ccSub: ", ccSub) 164 | print("->\tctxt1 * ctxt2 = ccMul: ", ccMul) 165 | print(" Single ciphertext: ") 166 | print("->\tctxt1**2 = cSq : ", cSq ) 167 | print("->\t- ctxt1 = cNeg : ", cNeg ) 168 | print("->\tctxt1**3 = cPow : ", cPow ) 169 | print("->\tctxt1 >> 2 = cRotR: ", cRotR) 170 | print("->\tctxt1 << 2 = cRotL: ", cRotL) 171 | print(" Ciphertext-plaintext: ") 172 | print("->\tctxt1 + ptxt2 = cpSum: ", cpSum) 173 | print("->\tctxt1 - ptxt2 = cpSub: ", cpSub) 174 | print("->\tctxt1 * ptxt2 = cpMul: ", cpMul) 175 | 176 | 177 | # %% 178 | # 8. BGV Relinearization: What, why, when 179 | # ------------------------------------------------------------------------------ 180 | # Ciphertext-ciphertext multiplications increase the size of the polynoms 181 | # representing the resulting ciphertext. To prevent this growth, the 182 | # relinearization technique is used (typically right after each c-c mult) to 183 | # reduce the size of a ciphertext back to the minimal size (two polynoms c0 & c1). 184 | # For this, a special type of public key called Relinearization Key is used. 185 | # 186 | # In Pyfhel, you can either generate a relin key with HE.RelinKeyGen() or skip it 187 | # and call HE.relinearize() directly, in which case a warning is issued. 188 | # 189 | # Note that HE.power performs relinearization after every multiplication. 190 | 191 | print("\n8. Relinearization-> Right after each multiplication.") 192 | print(f"ccMul before relinearization (size {ccMul.size()}): {ccMul}") 193 | ~ccMul # Equivalent to HE.relinearize(ccMul). Relin always happens in-place. 194 | print(f"ccMul after relinearization (size {ccMul.size()}): {ccMul}") 195 | print(f"cPow after 2 mult&relin rounds: (size {cPow.size()}): {cPow}") 196 | 197 | # %% 198 | # 9. Decrypt & Decode results 199 | # ------------------------------------------------------------------------------ 200 | # Time to decrypt results! We use HE.decryptBGV for this. 201 | # HE.decrypt() could also be used, in which case the decryption type would be 202 | # inferred from the ciphertext metadata. 203 | r1 = HE.decryptBGV(ctxt1) 204 | r2 = HE.decryptBGV(ctxt2) 205 | rccSum = HE.decryptBGV(ccSum) 206 | rccSub = HE.decryptBGV(ccSub) 207 | rccMul = HE.decryptBGV(ccMul) 208 | rcSq = HE.decryptBGV(cSq ) 209 | rcNeg = HE.decryptBGV(cNeg ) 210 | rcPow = HE.decryptBGV(cPow ) 211 | rcRotR = HE.decryptBGV(cRotR) 212 | rcRotL = HE.decryptBGV(cRotL) 213 | rcpSum = HE.decryptBGV(cpSum) 214 | rcpSub = HE.decryptBGV(cpSub) 215 | rcpMul = HE.decryptBGV(cpMul) 216 | 217 | print("\n9. Decrypting results") 218 | print(" Original ciphertexts: ") 219 | print(" ->\tctxt1 --(decr)--> ", r1) 220 | print(" ->\tctxt2 --(decr)--> ", r2) 221 | print(" Ciphertext-ciphertext Ops: ") 222 | print(" ->\tctxt1 + ctxt2 = ccSum --(decr)--> ", rccSum) 223 | print(" ->\tctxt1 - ctxt2 = ccSub --(decr)--> ", rccSub) 224 | print(" ->\tctxt1 * ctxt2 = ccMul --(decr)--> ", rccMul) 225 | print(" Single ciphertext: ") 226 | print(" ->\tctxt1**2 = cSq --(decr)--> ", rcSq ) 227 | print(" ->\t- ctxt1 = cNeg --(decr)--> ", rcNeg ) 228 | print(" ->\tctxt1**3 = cPow --(decr)--> ", rcPow ) 229 | print(" ->\tctxt1 >> 2 = cRotR --(decr)--> ", rcRotR) 230 | print(" ->\tctxt1 << 2 = cRotL --(decr)--> ", rcRotL) 231 | print(" Ciphertext-plaintext ops: ") 232 | print(" ->\tctxt1 + ptxt2 = cpSum --(decr)--> ", rcpSum) 233 | print(" ->\tctxt1 - ptxt2 = cpSub --(decr)--> ", rcpSub) 234 | print(" ->\tctxt1 * ptxt2 = cpMul --(decr)--> ", rcpMul) 235 | 236 | 237 | # sphinx_gallery_thumbnail_path = 'static/thumbnails/encoding.png' 238 | # %% -------------------------------------------------------------------------------- /examples/Demo_WAHC21.py: -------------------------------------------------------------------------------- 1 | """ 2 | WAHC21 3 | =========================== 4 | 5 | This demo includes all the code snippets from the WAHC21 Pyfhel paper (corresponding sections in parenthesis). 6 | """ 7 | # %% -------------------------------------------------- 8 | # 1. Setup and Parameters (Sec. 4.1) 9 | # ----------------------------------------------------- 10 | # We start by importing the library with the three main classes: 11 | # * Pyfhel class contains most of the functions. 12 | # * PyPtxt is the plaintext class 13 | # * PyCtxt is the ciphertext class 14 | # 15 | # Then we generate a context a context and a public/secret key pair. 16 | # -> This is all managed by a Pyfhel instance under the hood. 17 | from Pyfhel import Pyfhel, PyPtxt, PyCtxt 18 | 19 | HE = Pyfhel() # Creating empty Pyfhel object 20 | HE.contextGen(scheme='BFV', n=4096, t_bits=20, sec=128) # Generating context. The p defines the plaintext modulo. 21 | # There are many configurable parameters on this step 22 | # More info in Demo_ContextParameters, and 23 | # in Pyfhel.contextGen() 24 | HE.keyGen() # Key Generation: generates a pair of public/secret keys 25 | print("-----------------------------------------------------") 26 | print("1. Setup: ", HE) 27 | 28 | 29 | # %% -------------------------------------------------- 30 | # 2. Encryption & Decryption (Sec. 4.2) 31 | # ----------------------------------------------------- 32 | # We show how to encrypt and decrypt a plaintex, including encoding and decoding. 33 | integer = 45 34 | int_ptxt = HE.encode(integer) # PyPtxt [45, 45,...] 35 | int_ctxt = HE.encrypt(int_ptxt) # PyCtxt 36 | 37 | import numpy as np 38 | np_array = np.array([6, 5, 4, 3, 2, 1],dtype=np.int64) 39 | array_ptxt = HE.encode(np_array) # Accepts list too 40 | array_ctxt = HE.encrypt(array_ptxt) # PyCtxt 41 | 42 | # Decrypt and Decode 43 | ptxt_dec = HE.decrypt(int_ctxt, decode=False) # PyPtxt 44 | integer_dec = HE.decode(ptxt_dec) # integer 45 | print("-----------------------------------------------------") 46 | print("2. Encryption & Decryption: ") 47 | print("Encrypting integer: ", integer, " -> ", int_ptxt , " -> ", int_ctxt) 48 | print("Decrypting integer: ", int_ctxt, " -> ", ptxt_dec, " -> ", integer_dec[:3],"...") 49 | 50 | 51 | 52 | 53 | # %% -------------------------------------------------- 54 | # 3. Homomorphic Operations (Sec. 4.3) 55 | # ----------------------------------------------------- 56 | # we will define two integers, encrypt them and operate: 57 | ptxt_a = HE.encode(-12) 58 | ptxt_b = HE.encode(34) 59 | ctxt_a = HE.encrypt(56) 60 | ctxt_b = HE.encrypt(-78) 61 | 62 | # ctxt-ctxt operations 63 | ctxt_s = ctxt_a + ctxt_b # or ctxt_a += ctxt_b (in place) 64 | ctxt_m = ctxt_a * ctxt_b 65 | 66 | # ctxt-ptxt operations 67 | ctxt_s_p = ptxt_a + ctxt_b # or 12 + ctxt_b 68 | ctxt_m_p = ctxt_a * ptxt_b # or ctxt_a * 34 69 | 70 | # maintenance operations 71 | HE.relinKeyGen() # bfv only 72 | HE.relinearize(ctxt_s) # requires relinKey 73 | HE.rotateKeyGen() 74 | # HE.rescale_to_next(ctxt_r) # ckks only 75 | 76 | # rotations (length n) 77 | ctxt_c = HE.encrypt([1,2,3,4]) 78 | ctxt_rotated = ctxt_c << 1 # [2,3,4,0,...,0,1 79 | 80 | 81 | # %% -------------------------------------------------- 82 | # 4. IO & Serialization (Sec. 4.4) 83 | # ----------------------------------------------------- 84 | import os # to cleanup files 85 | 86 | ##### CLIENT 87 | HE = Pyfhel() 88 | HE.contextGen(scheme='BFV', n=4096, t_bits=0, t=65537, sec=128) 89 | HE.keyGen() # Generates public and private key 90 | # Save context and public key only 91 | HE.save_public_key("mypk.pk") 92 | HE.save_context("mycontext.con") 93 | # Encrypt and save inputs 94 | ctxt_a = HE.encrypt(15) # implicit encoding 95 | ctxt_b = HE.encrypt(25) 96 | ctxt_a.save("ctxt_a.ctxt") 97 | ctxt_b.save("ctxt_b.ctxt") 98 | 99 | ##### SERVER 100 | HE_server = Pyfhel() 101 | HE_server.load_context("mycontext.con") 102 | HE_server.load_public_key("mypk.pk") # no secret key 103 | # Load ciphertexts 104 | ca = PyCtxt(pyfhel=HE_server, fileName="ctxt_a.ctxt") 105 | cb = PyCtxt(pyfhel=HE_server, fileName="ctxt_b.ctxt") 106 | # Compute homomorphically and send result 107 | cr = (ca + cb) * 2 108 | cr.save("cr.ctxt") 109 | 110 | ##### CLIENT 111 | # Load and decrypt result 112 | c_res = PyCtxt(pyfhel=HE, fileName="cr.ctxt") 113 | print("-----------------------------------------------------") 114 | print("Client_server result:", c_res.decrypt()) 115 | for f in ["mypk.pk", "mycontext.con", "ctxt_a.ctxt", "ctxt_b.ctxt", "cr.ctxt"]: 116 | os.remove(f) 117 | 118 | 119 | 120 | # %% -------------------------------------------------- 121 | # 5. Exploring CKKS pitfalls (Sec. 5.1) 122 | # ----------------------------------------------------- 123 | print("-----------------------------------------------------") 124 | from Pyfhel import PyCtxt, Pyfhel, PyPtxt 125 | HE = Pyfhel() 126 | HE.contextGen(scheme='CKKS', n=16384, qi_sizes=[30,30,30,30,30], scale=2 ** 30) 127 | HE.keyGen() 128 | ctxt_x = HE.encrypt(3.1, scale=2 ** 30) # implicit encode 129 | ctxt_y = HE.encrypt(4.1, scale=2 ** 30) 130 | ctxt_z = HE.encrypt(5.9, scale=2 ** 30) 131 | 132 | ctxtSum = HE.add(ctxt_x, ctxt_y) 133 | ctxtProd = HE.multiply_plain(ctxt_z, HE.encode(5)) 134 | ctxt_t = HE.multiply(ctxtSum, ctxtProd) 135 | 136 | ptxt_ten = HE.encode(10, scale=2 ** 30) 137 | try: 138 | ctxt_result = HE.add_plain(ctxt_t, ptxt_ten) #error: mismatched scales 139 | except ValueError as e: 140 | assert str(e) == "scale mismatch" 141 | print("CKKS: Mismatched scales detected!") 142 | 143 | ptxt_d = HE.encode(10, 2 ** 30) 144 | ctxt_d = HE.encrypt(ptxt_d) 145 | HE.rescale_to_next(ctxt_t) # 2^90 -> 2^60 146 | HE.rescale_to_next(ctxt_t) # 2^60 -> 2^30 147 | 148 | HE.mod_switch_to_next(ctxt_d) # match first rescale 149 | HE.mod_switch_to_next(ctxt_d) # match second rescale 150 | 151 | ctxt_t.set_scale(2**30) 152 | ctxt_result = HE.add(ctxt_t, ctxt_d) # final result 153 | 154 | # NOTE: The original code (substituting `HE.multiply` and `HE.add` 155 | # by `+` and `*`) no longer generates an error: scales are 156 | # automatically aligned using HE.align_mod_n_scale when using 157 | # operator overloads `+`, `-`, `*` and `/`. 158 | 159 | 160 | # %% -------------------------------------------------- 161 | # 6. Implementing Key-Recovery for CKKS (Sec. 5.2) 162 | # ----------------------------------------------------- 163 | # Setup: Encrypt, Decrypt, Decode 164 | ctxt = HE.encrypt(0, scale=2**40) 165 | ptxt_dec = HE.decryptPtxt(ctxt) 166 | values = HE.decodeComplex(ptxt_dec) 167 | 168 | # Attack 169 | ptxt_re = HE.encode(values, scale=2**40) 170 | a = HE.poly_from_ciphertext(ctxt,1) # PyPoly 171 | b = HE.poly_from_ciphertext(ctxt,0) # or b = ctxt[0] 172 | m = HE.poly_from_plaintext(ctxt, ptxt_re) # PyPoly 173 | s = (m - b) * ~a # ~a = inverse of a 174 | print("-----------------------------------------------------") 175 | 176 | # sphinx_gallery_thumbnail_path = 'static/thumbnails/helloworld.png' -------------------------------------------------------------------------------- /examples/README.rst: -------------------------------------------------------------------------------- 1 | Tutorials 2 | =================== 3 | 4 | This is the compendium of Demos showcasing the main functionalities of Pyfhel -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # This file contains the project metadata as defined in PEP621, as well as 2 | # custom compilation and linking flags. Non-expert users should only modify 3 | # the following sections: 4 | # - [cpplibraries.SEAL.cmake-opts] 5 | 6 | #=============================================================================== 7 | #============================== PROJECT METADATA =============================== 8 | #=============================================================================== 9 | # Pyfhel's core metadata 10 | [project] 11 | name = "Pyfhel" 12 | version = "3.4.2" 13 | description = "Python for Homomorphic Encryption Libraries" 14 | readme = "README.md" 15 | requires-python = ">=3.7" 16 | license = {text = "GNU GPLv3"} 17 | keywords = ["homomorphic" , "encryption", "cython", "cryptography"] 18 | authors = [ 19 | {name = "Alberto Ibarrondo", email = "ibarrond@eurecom.fr"}, 20 | {name = "Alexander Viand", email = "alexander.viand@inf.ethz.ch"}, 21 | ] 22 | classifiers = [ 23 | "Programming Language :: C++", 24 | "Programming Language :: Cython", 25 | "Programming Language :: Python :: 3", 26 | "Programming Language :: Python :: 3.7", 27 | "Programming Language :: Python :: 3.8", 28 | "Programming Language :: Python :: 3.9", 29 | "Programming Language :: Python :: 3.10", 30 | "Programming Language :: Python :: Implementation :: CPython", 31 | "Development Status :: 5 - Production/Stable", 32 | "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", 33 | "Operating System :: Unix", 34 | "Operating System :: POSIX", 35 | "Operating System :: Microsoft :: Windows", 36 | "Topic :: Security", 37 | "Topic :: Security :: Cryptography", 38 | ] 39 | 40 | dependencies = [ 41 | "numpy>=1.21" 42 | ] 43 | 44 | [project.optional-dependencies] 45 | test = [ 46 | "pytest >= 6", 47 | "coverage >= 6.4", 48 | "pytest-cov >= 3.0", 49 | ] 50 | 51 | [project.urls] 52 | homepage = "https://pyfhel.readthedocs.io" 53 | documentation = "https://pyfhel.readthedocs.io" 54 | repository = "https://github.com/ibarrond/github" 55 | 56 | 57 | # Minimum requirements for the build system to execute. 58 | [build-system] 59 | requires = [ 60 | "setuptools<=60.9", 61 | "wheel", 62 | "cython>=3.0.0", 63 | "numpy>=1.21", 64 | "cmake>=3.15", 65 | "toml>=0.10" 66 | ] 67 | build-backend = "setuptools.build_meta" 68 | 69 | # Tests config 70 | [tool.pytest.ini_options] 71 | testpaths = ["Pyfhel/test"] 72 | 73 | [tool.coverage.run] 74 | plugins = ["Cython.Coverage"] 75 | source = ["Pyfhel"] 76 | omit =[ 77 | "setup.py", 78 | "Pyfhel/Afhel", 79 | "Pyfhel/backend", 80 | "Pyfhel/test/*", 81 | "examples/*", 82 | "*__init__*", 83 | "*.pxi", 84 | "Pyfhel/utils/*", 85 | "Pyfhel/PyPoly.pyx", 86 | ] 87 | 88 | [platforms] 89 | platforms = ["Linux", "Windows", "Darwin"] 90 | 91 | #=============================================================================== 92 | #========================= COMPILATION CONFIGURATION =========================== 93 | #=============================================================================== 94 | # Compile & link configuration for C/C++ libraries and extensions 95 | # - Libs in mode 'cmake' are compiled running `cmake` inside source_dir. 96 | # - Libs in mode 'standard' are compiled using the default platform compiler. 97 | #------------------------------ C/C++ LIBRARIES -------------------------------- 98 | [cpplibraries] 99 | 100 | #--> SEAL 101 | [cpplibraries.SEAL] 102 | mode = 'cmake' # standard/cmake 103 | lib_type = 'static' # static/shared 104 | # source_dir: dir with the top CMakeLists.txt. 105 | source_dir = 'Pyfhel/backend/SEAL' 106 | include_dirs = [ 107 | 'Pyfhel/backend/SEAL/native/src', 108 | 'Pyfhel/backend/SEAL/thirdparty/msgsl-src/include' 109 | ] 110 | 111 | ## Build-relative dirs: 112 | # -> paths are relative to build directory. 113 | # built_library_dir: output dir containing the cmake-built libs 114 | built_library_dir='lib' 115 | # built_include_dirs: dirs with cmake-built headers. 116 | built_include_dirs=['native/src', 'thirdparty/msgsl-src/include'] 117 | 118 | ## CMake options: https://github.com/microsoft/SEAL/#basic-cmake-options 119 | [cpplibraries.SEAL.cmake_opts] 120 | CMAKE_BUILD_TYPE = 'Release' 121 | SEAL_USE_INTEL_HEXL ='OFF' # ON/OFF, use Intel HEXL for low-level kernels 122 | SEAL_USE_ALIGNED_ALLOC ='OFF'# ON/OFF, 64B aligned memory allocations, better perf with Intel HEXL. 123 | BUILD_SHARED_LIBS ='OFF' # ON/OFF, build shared and not static library 124 | SEAL_THROW_ON_TRANSPARENT_CIPHERTEXT='ON' # ON/OFF, runtime error when multiplying a ctxt with a zeroed plaintext. 125 | SEAL_USE_MSGSL = 'ON' # ON/OFF, Build with Microsoft GSL support. 126 | SEAL_USE_ZLIB = 'ON' # ON/OFF, Build with ZLIB support. 127 | SEAL_USE_ZSTD = 'ON' # ON/OFF, Build with Zstandard support. 128 | 129 | #--> Afhel 130 | [cpplibraries.Afhel] 131 | mode = 'standard' # standard/cmake 132 | lib_type = 'shared' # static/shared 133 | sources = ['Pyfhel/Afhel/Afseal.cpp',] 134 | include_dirs = ['Pyfhel/Afhel',] 135 | define_macros = [ 136 | # ["SEAL_USE_ZLIB","0"], 137 | # ["SEAL_USE_ZSTD","0"], 138 | ] 139 | extra_compile_args = [ 140 | {Windows = ["/O2","/std:c++17","/openmp"]}, 141 | {Darwin = ["-std=c++17","-O3","-Xpreprocessor","-fopenmp"]}, 142 | {Linux = ["-std=c++17","-O3","-fopenmp"]} ] 143 | extra_link_args = [ 144 | {Windows = []}, 145 | {Darwin = ["-Wl,-rpath,@loader_path/.", "-fopenmp","-Wl,-no_compact_unwind"]}, 146 | {Linux = ["-Wl,-rpath=$ORIGIN/.", "-fopenmp"]} ] 147 | libraries = ['SEAL'] 148 | 149 | 150 | #----------------------------- CYTHON EXTENSIONS ------------------------------- 151 | [extensions.config] # Common compilation config for all extensions 152 | include_dirs = [ 153 | {Darwin = ["/usr/local/include"]}, 154 | ] 155 | define_macros = [ 156 | ["NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION"], 157 | ["__PYX_ENUM_CLASS_DECL", "enum"], # Support enums in cython 158 | ] 159 | extra_compile_args = [ 160 | {Windows = ["/O2","/openmp","/std:c++17"]}, 161 | {Darwin = ["-std=c++17","-O3","-Xpreprocessor","-fopenmp","-Wno-unreachable-code","-Wno-unqualified-std-cast-call"]}, 162 | {Linux = ["-std=c++17","-O3","-fopenmp"]}, 163 | ] 164 | extra_link_args = [ 165 | {Windows = []}, 166 | {Darwin = ["-Wl,-rpath,@loader_path/.", "-fopenmp","-Wl,-no_compact_unwind"]}, 167 | {Linux = ["-Wl,-rpath=$ORIGIN/.", "-fopenmp"]}, 168 | ] 169 | libraries = [] # libraries to link with, cpplibraries above are added by default 170 | 171 | # List of extensions to compile. Custom compilation config can be defined for each 172 | [extensions.Pyfhel] 173 | fullname='Pyfhel.Pyfhel' 174 | sources=['Pyfhel/Pyfhel.pyx'] 175 | 176 | [extensions.PyCtxt] 177 | fullname='Pyfhel.PyCtxt' 178 | sources=['Pyfhel/PyCtxt.pyx'] 179 | 180 | [extensions.PyPtxt] 181 | fullname='Pyfhel.PyPtxt' 182 | sources=['Pyfhel/PyPtxt.pyx'] 183 | 184 | [extensions.PyPoly] 185 | fullname='Pyfhel.PyPoly' 186 | sources=['Pyfhel/PyPoly.pyx'] 187 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [build_ext] 2 | inplace=1 --------------------------------------------------------------------------------