├── .clang-format ├── .github └── workflows │ └── github-ci.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .typos.toml ├── CMakeLists.txt ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── SECURITY.md ├── bench └── bench_ipcl_python.py ├── example └── ipclpy_example.py ├── lib ├── ipcl.cmake └── pybind11.cmake ├── requirements.txt ├── setup.py ├── src └── ipcl_python │ ├── __init__.py │ ├── bindings │ ├── __init__.py │ ├── baseconverter.cpp │ ├── fixedpoint.py │ ├── include │ │ ├── baseconverter.hpp │ │ └── ipcl_bindings.hpp │ ├── ipcl_bindings.cpp │ └── ipcl_bindings_classes.cpp │ └── ipcl_python.py └── tests ├── __init__.py └── ipcl_python_test.py /.clang-format: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | BasedOnStyle: Google 5 | Language: Cpp 6 | DerivePointerAlignment: false 7 | PointerAlignment: Left 8 | -------------------------------------------------------------------------------- /.github/workflows/github-ci.yml: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | name: ipcl-python_internal 5 | on: 6 | # By default this will run when the activity type is "opened", "synchronize", 7 | # or "reopened". 8 | pull_request: 9 | branches: 10 | - main 11 | - development 12 | push: 13 | branches: 14 | - development 15 | 16 | # Manually run this workflow on any specified branch. 17 | workflow_dispatch: 18 | 19 | ############## 20 | # IceLake CI # 21 | ############## 22 | permissions: 23 | contents: read 24 | 25 | jobs: 26 | format: 27 | name: Format check 28 | runs-on: [self-hosted, linux, x64, icx, debian] 29 | # Use environment protection (require review) 30 | environment: intel_workflow 31 | steps: 32 | - uses: actions/checkout@v2 33 | # Add local bin for cpplint 34 | - name: Setup 35 | run: echo "$HOME/.local/bin" >> $GITHUB_PATH 36 | - name: pre-commit 37 | run: pre-commit run --all-files 38 | 39 | build-and-test: 40 | name: Build and test IPCL-Python extension 41 | needs: [format] 42 | runs-on: [self-hosted, linux, x64, icx, debian] 43 | # Use environment protection (require review) 44 | environment: intel_workflow 45 | defaults: 46 | run: 47 | shell: bash 48 | working-directory: . 49 | steps: 50 | - uses: actions/checkout@v2 51 | - name: Validate paths 52 | run: | 53 | whoami 54 | echo $HOME 55 | echo $GITHUB_WORKSPACE 56 | echo "Testing from branch:" 57 | echo $GITHUB_REF 58 | pwd 59 | - name: Install IPCL-Python extension 60 | run: python3 setup.py install --user 61 | - name: Run the unit test 62 | run: python3 setup.py test 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | .vscode/ 5 | .vs/ 6 | build*/ 7 | dev_*.py 8 | 9 | .DS_Store 10 | 11 | # Doxygen files 12 | docs/doxygen/ 13 | Doxyfile 14 | 15 | # Sphinx files 16 | _build/ 17 | 18 | # Generated files 19 | *.log 20 | dist/ 21 | 22 | # Python files 23 | venv/ 24 | __pycache__/ 25 | .ipynb_checkpoints/ 26 | .mypy_cache/ 27 | .env 28 | *.egg-info/ 29 | *.cpython*.so 30 | .eggs/ 31 | 32 | # vim 33 | **.swp 34 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | exclude: 'docs/doxygen/' 5 | repos: 6 | - repo: https://github.com/pre-commit/pre-commit-hooks 7 | rev: v4.0.1 8 | hooks: 9 | - id: trailing-whitespace 10 | - id: end-of-file-fixer 11 | - id: check-merge-conflict 12 | - id: mixed-line-ending 13 | - id: check-byte-order-marker 14 | - id: check-yaml 15 | - repo: https://github.com/crate-ci/typos 16 | rev: typos-v0.8.2 17 | hooks: 18 | - id: typos 19 | - repo: https://github.com/psf/black 20 | rev: 22.3.0 21 | hooks: 22 | - id: black 23 | files: \.(py|ipynb)$ 24 | exclude: __pycache__|build|__init__.py|fixedpoint.py|dev_*.py 25 | args: 26 | - --line-length 27 | - '80' 28 | - repo: https://github.com/PyCQA/flake8 29 | rev: 5.0.4 30 | hooks: 31 | - id: flake8 32 | files: \.py$ 33 | exclude: __pycache__|build|__init__.py|fixedpoint.py|dev_*.py 34 | args: 35 | - --max-line-length 36 | - '80' 37 | - --ignore 38 | - E203,W503 39 | - repo: local 40 | hooks: 41 | - id: clang-format 42 | name: clang-format 43 | entry: clang-format 44 | language: system 45 | files: \.(c|cc|cxx|cpp|h|hpp|hxx|js|proto)$ 46 | args: 47 | - -i 48 | - id: shfmt 49 | name: shfmt 50 | entry: shfmt 51 | language: system 52 | files: \.sh$ 53 | args: 54 | - -d 55 | - -i 56 | - '2' 57 | - -ci 58 | - -sr 59 | - -bn 60 | - -w 61 | - id: cpplint 62 | name: cpplint 63 | entry: cpplint 64 | language: system 65 | files: \.(cxx|cpp|hpp|hxx)$ 66 | args: 67 | - --recursive 68 | - --filter=-runtime/references,-whitespace/comments,-whitespace/indent 69 | - id: cpplint-c 70 | name: cpplint-c 71 | entry: cpplint 72 | language: system 73 | files: \.(c|cc|h)$ 74 | args: 75 | - --recursive 76 | - --filter=-runtime/references,-whitespace/comments,-whitespace/indent,-readability/casting,-runtime/int 77 | - id: shellcheck 78 | name: shellcheck 79 | entry: shellcheck 80 | language: system 81 | files: \.sh$ 82 | args: 83 | - -s 84 | - bash 85 | - -e 86 | - SC1091 87 | -------------------------------------------------------------------------------- /.typos.toml: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | [default.extend-identifiers] 5 | 6 | [default.extend-words] 7 | # variation of params 8 | parms = "parms" 9 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | cmake_minimum_required(VERSION 3.15.1) 5 | 6 | project(IPCL_PYTHON VERSION 2.0.0 LANGUAGES C CXX) 7 | 8 | if(CMAKE_BUILD_TYPE) 9 | set(RELEASE_TYPES 10 | Debug 11 | Release 12 | RelWithDebInfo 13 | MinSizeRel) 14 | list(FIND RELEASE_TYPES ${CMAKE_BUILD_TYPE} INDEX_FOUND) 15 | if(${INDEX_FOUND} EQUAL -1) 16 | message( 17 | FATAL_ERROR 18 | "CMAKE_BUILD_TYPE must be one of Debug, Release, RelWithDebInfo, or MinSizeRel" 19 | ) 20 | endif() 21 | else() 22 | set(CMAKE_BUILD_TYPE Release) 23 | endif() 24 | 25 | # TODO(skmono): Assess if C++17 is necessary 26 | set(CMAKE_CXX_STANDARD 14) 27 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 28 | set(CMAKE_CXX_EXTENSIONS OFF) 29 | 30 | # Create compilation database compile_commands.json 31 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 32 | 33 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 34 | 35 | set(CMAKE_C_FLAGS "-O2") 36 | set(CMAKE_CXX_FLAGS "-O2 -fpermissive -Wno-as-needed") 37 | 38 | #--------------------------------------------------- 39 | option(IPCL_PYTHON_TEST "Enable testing" ON) 40 | option(IPCL_PYTHON_DOCS "Enable document building" OFF) 41 | option(IPCL_PYTHON_ENABLE_OMP "Use OpenMP for IPCL backend" OFF) 42 | option(IPCL_PYTHON_DETECT_CPU_RUNTIME "Use CPU runtime detection" OFF) 43 | option(IPCL_PYTHON_ENABLE_QAT "Use QAT for IPCL backend" OFF) 44 | 45 | # Set IPCL flags 46 | if(IPCL_PYTHON_ENABLE_QAT) 47 | add_compile_definitions(IPCL_PYTHON_USE_QAT) 48 | endif() 49 | 50 | if(IPCL_PYTHON_ENABLE_OMP) 51 | find_package(OpenMP) 52 | if(NOT OpenMP_FOUND) 53 | set(IPCL_PYTHON_ENABLE_OMP OFF) 54 | message(STATUS "OpenMP not found - disabling for build") 55 | endif() 56 | endif() 57 | 58 | message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") 59 | message(STATUS "CMAKE_C_COMPILER: ${CMAKE_C_COMPILER}") 60 | message(STATUS "CMAKE_CXX_COMPILER: ${CMAKE_CXX_COMPILER}") 61 | message(STATUS "IPCL_PYTHON_TEST: ${IPCL_PYTHON_TEST}") 62 | message(STATUS "IPCL_PYTHON_DOCS: ${IPCL_PYTHON_DOCS}") 63 | message(STATUS "IPCL_PYTHON_DETECT_CPU_RUNTIME: ${IPCL_PYTHON_DETECT_CPU_RUNTIME}") 64 | message(STATUS "IPCL_PYTHON_ENABLE_OMP: ${IPCL_PYTHON_ENABLE_OMP}") 65 | message(STATUS "IPCL_PYTHON_ENABLE_QAT: ${IPCL_PYTHON_ENABLE_QAT}") 66 | 67 | 68 | set(IPCL_PYTHON_FORWARD_CMAKE_ARGS 69 | -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} 70 | -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} 71 | -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} 72 | -DCMAKE_CXX_STANDARD_REQUIRED=${CMAKE_CXX_STANDARD_REQUIRED} 73 | -DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS} 74 | -DCMAKE_EXPORT_COMPILE_COMMANDS=${CMAKE_EXPORT_COMPILE_COMMANDS} 75 | -DCMAKE_POSITION_INDEPENDENT_CODE=${CMAKE_POSITION_INDEPENDENT_CODE} 76 | -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} 77 | -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} 78 | ) 79 | 80 | set(IPCL_BINDINGS_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src/ipcl_python/bindings) 81 | set(IPCL_BINDINGS_INC_DIR ${IPCL_BINDINGS_SRC_DIR}/include) 82 | set(IPCL_BINDINGS_BIN_DIR ${CMAKE_CURRENT_BINARY_DIR}) 83 | message(STATUS "IPCL_BINDINGS_SRCS_DIR: ${IPCL_BINDINGS_SRC_DIR}") 84 | 85 | include(lib/ipcl.cmake) 86 | include(lib/pybind11.cmake) 87 | 88 | FetchContent_GetProperties(pybind11) 89 | 90 | set(IPCL_BINDINGS_SRCS ${IPCL_BINDINGS_SRC_DIR}/ipcl_bindings.cpp 91 | ${IPCL_BINDINGS_SRC_DIR}/baseconverter.cpp 92 | ${IPCL_BINDINGS_SRC_DIR}/ipcl_bindings_classes.cpp) 93 | 94 | pybind11_add_module(ipcl_bindings ${IPCL_BINDINGS_SRCS}) 95 | 96 | target_include_directories(ipcl_bindings PUBLIC ${PYTHON_INCLUDE_DIRS} ${IPCL_BINDINGS_INC_DIR} ${IPCL_BINDINGS_SRC_DIR}) 97 | target_link_libraries(ipcl_bindings PUBLIC IPCL::ipcl) 98 | 99 | set_target_properties(ipcl_bindings PROPERTIES 100 | BUILD_WITH_INSTALL_RPATH FALSE 101 | LINK_FLAGS "-Wl,-rpath,'$ORIGIN' -Wl,-rpath,'$ORIGIN'/ippcrypto -Wl,-rpath,'$ORIGIN'/cpufeatures" 102 | ) 103 | 104 | if(IPCL_PYTHON_ENABLE_OMP) 105 | find_package(OpenMP REQUIRED) 106 | target_link_libraries(ipcl_bindings PUBLIC OpenMP::OpenMP_CXX) 107 | endif() 108 | target_link_libraries(ipcl_bindings INTERFACE ext_fate_fixedpoint) 109 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # Default codeowner for all files 5 | * @intel/pailliercryptolib-maintain 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | webadmin@linux.intel.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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 2021-2022 Intel Corporation 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python bindings and wrapper for Intel Paillier Cryptosystem Library 2 | [Intel Paillier Cryptosystem Library](https://github.com/intel/pailliercryptolib) is an open-source library which provides accelerated performance of a partial homomorphic encryption (HE), named Paillier cryptosystem, by utilizing Intel® [IPP-Crypto](https://github.com/intel/ipp-crypto) technologies on Intel CPUs supporting the AVX512IFMA instructions and Intel® [Quickassist Technology](https://01.org/intel-quickassist-technology). The library is written in modern standard C++ and provides the essential API for the Paillier cryptosystem scheme. 3 | Intel Paillier Cryptosystem Library - Python is a Python extension package intended for Python based privacy preserving machine learning solutions which utilizes the partial HE scheme for increased data and model protection. Intel Paillier Cryptosystem Library - Python is certified for ISO compliance. 4 | 5 | ## Contents 6 | - [Python bindings and wrapper for Intel Paillier Cryptosystem Library](#python-bindings-and-wrapper-for-intel-paillier-cryptosystem-library) 7 | - [Contents](#contents) 8 | - [Introduction](#introduction) 9 | - [Installing the package](#installing-the-package) 10 | - [Prerequisites](#prerequisites) 11 | - [Dependencies](#dependencies) 12 | - [Installation](#installation) 13 | - [Usage](#usage) 14 | - [General](#general) 15 | - [Using with QAT](#using-with-qat) 16 | - [Note: For more information of using the module, please refer to the example code available in the example folder.](#note-for-more-information-of-using-the-module-please-refer-to-the-example-code-available-in-the-example-folder) 17 | - [Benchmark](#benchmark) 18 | - [Standardization](#standardization) 19 | - [Contributors](#contributors) 20 | 21 | ## Introduction 22 | Paillier cryptosystem is a probabilistic asymmetric algorithm for public key cryptography and a partial homomorphic encryption scheme which allows two types of computation: 23 | - addition of two ciphertexts 24 | - addition and multiplication of a ciphertext by a plaintext number 25 | 26 | As a public key encryption scheme, Paillier cryptosystem has three stages: 27 | 28 | - Generate public-private key pair 29 | - Encryption with public key 30 | - Decryption with private key 31 | 32 | For increased security, typically the key length is at least 1024 bits, but recommendation is 2048 bits or larger. To handle such large size integers, conventional implementations of the Paillier cryptosystem utilizes the GNU Multiple Precision Arithmetic Library (GMP). The essential computation of the scheme relies on the modular exponentiation, and our library takes advantage of the multi-buffer modular exponentiation function (```mbx_exp_mb8```) of IPP-Crypto library, which is enabled in AVX512IFMA instruction sets supporting SKUs, such as Intel Icelake Xeon CPUs. 33 | 34 | The Python extension package allows ease of use of the C++ backend library. The extension provides seamless conversion from Python integer and floating point objects, which are theoretically infinite precision limited by memory size, to C++ [BigNumber type](https://www.intel.com/content/www/us/en/develop/documentation/ipp-crypto-reference/top/public-key-cryptography-functions/big-number-arithmetic.html). It also allows easier handling of arrays in ```numpy.ndarray``` or ```list``` format, for encryption, decryption and HE computations. 35 | 36 | ## Installing the package 37 | ### Prerequisites 38 | For best performance, especially due to the multi-buffer modular exponentiation function, the Python extension is to be used on AVX512IFMA enabled systems, as listed below in Intel CPU codenames: 39 | - Intel Cannon Lake 40 | - Intel Ice Lake 41 | 42 | The extension module can be used without AVX512IFMA - if the instruction set is not detected on the system, it will automatically switch to non multi-buffer modular exponentiation. 43 | 44 | The following operating systems have been tested and deemed to be fully functional. 45 | 46 | - Ubuntu 18.04 and higher 47 | - Red Hat Enterprise Linux 8.1 and higher 48 | 49 | We will keep working on adding more supported operating systems. 50 | 51 | 52 | ### Dependencies 53 | Must have dependencies include: 54 | ``` 55 | cmake >=3.15.1 56 | git 57 | pthread 58 | g++ >= 7.0 or clang >= 10.0 59 | ``` 60 | 61 | The following libraries and tools are also required, 62 | ``` 63 | Python >= 3.8 64 | pip >= 22.0.1 65 | OpenSSL >= 1.1.0 66 | numa >= 2.0.12 67 | gmp >= 5.0.0 68 | mpfr >= 3.1.0 69 | mpc >= 1.1.0 70 | ``` 71 | 72 | which can be installed by: 73 | ```bash 74 | # Ubuntu 20.04 or higher 75 | $ sudo apt install python3-dev python3-pip libssl-dev libnuma-dev libgmp-dev libmpfr-dev libmpc-dev 76 | 77 | # Fedora (RHEL 8, CentOS 8) 78 | $ sudo dnf install python3-devel python3-pip openssl-devel numactl-devel gmp-devel mpfr-devel libmpc-devel 79 | ``` 80 | 81 | The following is also required 82 | ``` 83 | nasm >= 2.15 84 | ``` 85 | For Ubuntu 20.04 or lower and RHEL/CentOS, please refer to the [Netwide Assembler webpage](https://nasm.us/) for download and installation details. 86 | 87 | For more details regarding the C++ backend, refer to the [Intel Paillier Cryptosystem Library](https://github.com/intel-sandbox/libraries.security.cryptography.homomorphic-encryption.glade.pailliercryptolib). 88 | 89 | ### Installation 90 | Compiling and installing the package can be done by: 91 | ```bash 92 | python setup.py install 93 | ``` 94 | or 95 | ```bash 96 | pip install . 97 | ``` 98 | 99 | For building a distributable wheel package of the Intel Paillier Cryptosystem Library - Python, 100 | ```bash 101 | python setup.py bdist_wheel 102 | ``` 103 | and the wheel package can be found under ```{PROJECT_ROOT}/dist```. 104 | 105 | To test the installed module, 106 | ```bash 107 | python setup.py test 108 | ``` 109 | and the unit-test will be executed. 110 | 111 | ## Usage 112 | ### General 113 | The module can be imported by: 114 | ```python 115 | import ipcl_python 116 | ``` 117 | 118 | First, the key pair needs to be generated - public key for encryption and private key for decryption. 119 | ```python 120 | from ipcl_python import PaillierKeypair 121 | pubkey, prikey = PaillierKeypair.generate_keypair(2048, True) 122 | ``` 123 | 124 | For encryption and decryption, and the result verification: 125 | ```python 126 | a = np.random.rand(100) 127 | ct_a = pubkey.encrypt(a) 128 | 129 | de_a = prikey.decrypt(ct_a) 130 | print(np.allclose(a, de_a)) 131 | ``` 132 | 133 | Paillier cryptosystem supports ciphertext addition and plaintext addition/multiplcation. 134 | 135 | ```python 136 | # ciphertext addition 137 | b = np.random.rand(100) 138 | ct_b = pubkey.encrypt(b) 139 | ct_c = ct_a + ct_b 140 | 141 | de_c = prikey.decrypt(ct_c) 142 | print(np.allclose(a + b, de_c)) 143 | ``` 144 | 145 | ```python 146 | # plaintext addition 147 | ct_c = ct_a + b 148 | 149 | de_c = prikey.decrypt(ct_c) 150 | print(np.allclose(a + b, de_c)) 151 | ``` 152 | 153 | ```python 154 | # plaintext multiplication 155 | ct_d = ct_a * b 156 | 157 | de_d = prikey.decrypt(ct_d) 158 | print(np.allclose(a * b, de_d)) 159 | ``` 160 | 161 | ### Using with QAT 162 | Before running the Python module with Quickassist Technology enabled, it is essential to trigger the QAT engine before running any workload and release it upon completion. Below is a simple code piece including how to initialize and release the QAT engine. 163 | ```python 164 | from ipcl_python import context, PaillierKeypair 165 | 166 | # Reserve QAT engine 167 | context.initializeContext("QAT") 168 | 169 | # Sample Paillier HE operations 170 | pubkey, prikey = PaillierKeypair.generate_keypair(2048, True) 171 | a = np.random.rand(100) 172 | ct_a = pubkey.encrypt(a) 173 | de_a = prikey.decrypt(ct_a) 174 | print(np.allclose(a, de_a)) 175 | 176 | # On completion - release QAT engine 177 | context.terminateContext() 178 | ``` 179 | 180 | ### Note: For more information of using the module, please refer to the example code available in the [example](./example) folder. 181 | ### Benchmark 182 | We provide a benchmark tool, located under the folder [bench](bench/bench_ipcl_python.py). In order to run the benchmark, please install the [Google Benchmark](https://github.com/google/benchmark) via, 183 | ```bash 184 | pip install google-benchmark>=1.6.1 185 | ``` 186 | 187 | # Standardization 188 | This library is certified for ISO compliance with the homomorphic encryption standards [ISO/IEC 18033-6](https://www.iso.org/standard/67740.html) by Dekra. 189 | 190 | # Contributors 191 | Main contributors to this project, sorted by alphabetical order of last name are: 192 | - [Xiaojun Huang](https://github.com/xhuan28) 193 | - [Sejun Kim](https://github.com/skmono) (lead) 194 | - [Bin Wang](https://github.com/bwang30) 195 | - [Pengfei Zhao](https://github.com/justalittlenoob) 196 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | Intel is committed to rapidly addressing security vulnerabilities affecting our customers and providing clear guidance on the solution, impact, severity and mitigation. 3 | 4 | ## Reporting a Vulnerability 5 | Please report any security vulnerabilities in this project utilizing the guidelines [here](https://www.intel.com/content/www/us/en/security-center/vulnerability-handling-guidelines.html). 6 | -------------------------------------------------------------------------------- /bench/bench_ipcl_python.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | # Copyright (C) 2022 Intel Corporation 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | import numpy as np 7 | from ipcl_python import PaillierKeypair 8 | import ipcl_python as ipcl 9 | from ipcl_python import context 10 | import google_benchmark as benchmark 11 | 12 | 13 | @benchmark.register 14 | @benchmark.option.unit(benchmark.kMicrosecond) 15 | @benchmark.option.arg(1024) 16 | @benchmark.option.arg(2048) 17 | def BM_KeyGen(state): 18 | while state: 19 | _ = PaillierKeypair.generate_keypair(state.range(0)) 20 | 21 | 22 | @benchmark.register 23 | @benchmark.option.unit(benchmark.kMicrosecond) 24 | @benchmark.option.arg(16) 25 | @benchmark.option.arg(64) 26 | def BM_Encrypt(state): 27 | x = (np.arange(state.range(0)) + 11) * 1234.5678 28 | while state: 29 | _ = pk.encrypt(x) 30 | 31 | 32 | @benchmark.register 33 | @benchmark.option.unit(benchmark.kMicrosecond) 34 | @benchmark.option.arg(16) 35 | @benchmark.option.arg(64) 36 | def BM_Decrypt(state): 37 | x = (np.arange(state.range(0)) + 1) * 1234.5678 38 | ct_x = pk.encrypt(x) 39 | while state: 40 | _ = sk.decrypt(ct_x) 41 | 42 | 43 | @benchmark.register 44 | @benchmark.option.unit(benchmark.kMicrosecond) 45 | @benchmark.option.arg(16) 46 | @benchmark.option.arg(64) 47 | def BM_Add_CTCT(state): 48 | x = (np.arange(state.range(0)) + 11) * 5111.2834 49 | y = (32768 - np.arange(state.range(0))) * 1.3872 50 | ct_x = pk.encrypt(x) 51 | ct_y = pk.encrypt(y) 52 | while state: 53 | _ = ct_x + ct_y 54 | 55 | 56 | @benchmark.register 57 | @benchmark.option.unit(benchmark.kMicrosecond) 58 | @benchmark.option.arg(16) 59 | @benchmark.option.arg(64) 60 | def BM_Add_CTPT(state): 61 | x = (np.arange(state.range(0)) + 11) * 5111.2834 62 | y = (32768 - np.arange(state.range(0))) * 1.3872 63 | ct_x = pk.encrypt(x) 64 | ct_x = ct_x * x 65 | while state: 66 | _ = ct_x + y 67 | 68 | 69 | @benchmark.register 70 | @benchmark.option.unit(benchmark.kMicrosecond) 71 | @benchmark.option.arg(16) 72 | @benchmark.option.arg(64) 73 | def BM_Mul_CTPT(state): 74 | x = (np.arange(state.range(0)) + 11) * 5111.2834 75 | y = (32768 - np.arange(state.range(0))) * 1.3872 76 | ct_x = pk.encrypt(x) 77 | while state: 78 | _ = ct_x * y 79 | 80 | 81 | if __name__ == "__main__": 82 | # preset values 83 | P = int( 84 | "17907722236348068892950089903191692955407412936775759886364595" 85 | "52735277384518331167761570138552647970967958807251538217623805" 86 | "88199893129274771549316901998509025503556766712439571067562061" 87 | "82758501008605649830815202920954024506122402034968011655978902" 88 | "1149844414656481106116277049053335145991958168290159067444243" 89 | ) 90 | Q = int( 91 | "15364074494048192090239748141292366255531269713338718185264182" 92 | "86675686268115568620066283414819003320683895025898634379074026" 93 | "89773240679814850328978260611055592547225724264355875488478904" 94 | "93257704058129319548913255512313204302948601763310613641989076" 95 | "0822812194551465180127077927138009701322446602892596555566791" 96 | ) 97 | N = P * Q 98 | 99 | context.initializeContext("QAT") 100 | 101 | pk = ipcl.PaillierPublicKey(N, N.bit_length(), True) 102 | sk = ipcl.PaillierPrivateKey(pk, P, Q) 103 | 104 | benchmark.main() 105 | context.terminateContext() 106 | -------------------------------------------------------------------------------- /example/ipclpy_example.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from ipcl_python import PaillierKeypair, context, hybridControl, hybridMode 5 | import pickle as pkl 6 | import timeit 7 | import sys 8 | import numpy as np 9 | from collections import OrderedDict 10 | 11 | 12 | def test_encrypt_decrypt(sz=128): 13 | a = np.random.rand(sz) * 1000 14 | 15 | # encrypt a 16 | ct_a = pk.encrypt(a) 17 | 18 | # decrypt ct_a 19 | de_a = sk.decrypt(ct_a) 20 | 21 | # verify result 22 | print( 23 | f"{sys._getframe().f_code.co_name:<25} :: result = " 24 | f"{np.allclose(a, de_a)}" 25 | ) 26 | 27 | 28 | def test_addCTCT(sz=128): 29 | # test CT+CT and verify result 30 | a = np.random.rand(sz) * 1000 31 | b = np.random.rand(sz) * 1000 32 | 33 | # encrypt a, b 34 | ct_a = pk.encrypt(a) 35 | ct_b = pk.encrypt(b) 36 | 37 | # HE add (ct_a, ct_b) 38 | ct_sum_ab = ct_a + ct_b 39 | 40 | # decrypt ct_sum_ab 41 | de_sum_ab = sk.decrypt(ct_sum_ab) 42 | 43 | # verify result 44 | print( 45 | f"{sys._getframe().f_code.co_name:<25} :: result = " 46 | f"{np.allclose(a + b, de_sum_ab)}" 47 | ) 48 | 49 | 50 | def test_addCTPT(sz=128): 51 | # test CT+PT and verify result 52 | a = np.random.rand(sz) * 1000 53 | b = np.random.rand(sz) * 1000 54 | 55 | # encrypt a 56 | ct_a = pk.encrypt(a) 57 | 58 | # HE add (ct_a, b) 59 | ct_sum_ab = ct_a + b 60 | 61 | # decrypt ct_sum_ab 62 | de_sum_ab = sk.decrypt(ct_sum_ab) 63 | 64 | # verify result 65 | print( 66 | f"{sys._getframe().f_code.co_name:<25} :: result = " 67 | f"{np.allclose(a + b, de_sum_ab)}" 68 | ) 69 | 70 | 71 | def test_mulCTPT(sz=128): 72 | # test CT*PT and verify result 73 | a = np.random.rand(sz) * 1000 74 | b = np.random.rand(sz) * 1000 75 | 76 | # encrypt a 77 | ct_a = pk.encrypt(a) 78 | 79 | # HE multiply (ct_a, b) 80 | ct_mul_ab = ct_a * b 81 | 82 | # decrypt ct_sum_ab 83 | de_mul_ab = sk.decrypt(ct_mul_ab) 84 | 85 | # verify result 86 | print( 87 | f"{sys._getframe().f_code.co_name:<25} :: result = " 88 | f"{np.allclose(a * b, de_mul_ab)}" 89 | ) 90 | 91 | 92 | def test_HE_ops(sz=128): 93 | # test combination of HE ops 94 | 95 | a = np.random.rand(sz) * 1000 96 | b = np.random.rand(sz) * 1000 97 | c = np.random.rand(sz) * 1000 98 | 99 | # d is for broadcasting 100 | res = a - b * c 101 | 102 | ct_a = pk.encrypt(a) 103 | ct_b = pk.encrypt(b) 104 | 105 | ct_res = ct_a - ct_b * c 106 | 107 | is_allpass = True 108 | 109 | for i in range(5): 110 | # broadcasting test 111 | res = res + i 112 | 113 | ct_i = pk.encrypt(i) 114 | ct_res = ct_res + ct_i 115 | de_res = sk.decrypt(ct_res) 116 | 117 | if not np.allclose(res, de_res): 118 | is_allpass = True 119 | break 120 | 121 | print(f"{sys._getframe().f_code.co_name:<25} :: result = {is_allpass}") 122 | 123 | 124 | def test_serialize(sz=128): 125 | # test pickle serialization of IPCL objects 126 | 127 | a = np.random.rand(sz) * 1000 128 | ct_a = pk.encrypt(a) 129 | 130 | # serialize pub key 131 | pkl_pk = pkl.dumps(pk) 132 | _pk = pkl.loads(pkl_pk) 133 | 134 | # serialize sec key 135 | pkl_sk = pkl.dumps(sk) 136 | _sk = pkl.loads(pkl_sk) 137 | 138 | # serialize PaillierEncryptedNumber 139 | pkl_ct_a = pkl.dumps(ct_a) 140 | deserialized_ct_a = pkl.loads(pkl_ct_a) 141 | deserialized_de_a = sk.decrypt(deserialized_ct_a) 142 | 143 | if not np.allclose(a, deserialized_de_a): 144 | print("a == deserialized_de_a FAIL!") 145 | return 146 | 147 | # encrypt with deserialized key 148 | _ct_a = _pk.encrypt(a) 149 | 150 | # decrypt with deserialized key 151 | _de_a = _sk.decrypt(_ct_a) 152 | if not np.allclose(a, _de_a): 153 | print("a == _de_a FAIL!") 154 | return 155 | 156 | print(f"{sys._getframe().f_code.co_name:<25} :: result = {True}") 157 | 158 | 159 | def test_hybridMode(sz=64): 160 | # test hybridControl 161 | a = np.random.rand(sz) * 1000 162 | b = np.random.rand(sz) * 1000 163 | 164 | ct_a = pk.encrypt(a) 165 | 166 | def run_benchmark(num_iter=100): 167 | def bench_encrypt(x): 168 | _ = pk.encrypt(x) 169 | 170 | def bench_decrypt(ct_x): 171 | _ = sk.decrypt(ct_x) 172 | 173 | def bench_multiply(ct_x, y): 174 | _ = ct_x * y 175 | 176 | t_enc = timeit.timeit(lambda: bench_encrypt(a), number=num_iter) 177 | t_dec = timeit.timeit(lambda: bench_decrypt(ct_a), number=num_iter) 178 | t_mul = timeit.timeit(lambda: bench_multiply(ct_a, b), number=num_iter) 179 | 180 | return [t_enc * 1000.0, t_dec * 1000.0, t_mul * 1000.0] 181 | 182 | t_res = OrderedDict() 183 | 184 | for i in [ 185 | hybridMode.OPTIMAL, 186 | hybridMode.IPP, 187 | hybridMode.HALF, 188 | hybridMode.QAT, 189 | ]: 190 | hybridControl.setHybridMode(i) 191 | t_res["hybridMode." + i.name] = run_benchmark(10) 192 | 193 | print() 194 | print("-- HybridMode benchmark --") 195 | print( 196 | f"{'hybridMode':<20} {'Encrypt':<11} {'Decrypt':<11}" 197 | f" {'Multiply':<11}" 198 | ) 199 | for k, v in t_res.items(): 200 | t_enc, t_dec, t_mul = v 201 | print( 202 | f"{k:<20} {t_enc:>3.4f} ms {t_dec:>3.4f} ms" 203 | f" {t_mul:>3.4f} ms" 204 | ) 205 | 206 | print() 207 | print( 208 | "* Note: Benchmark result may not reflect full performance. For" 209 | " better representation, please run" 210 | " {PROJECT_ROOT}/bench/bench_ipcl_python.py" 211 | ) 212 | 213 | 214 | if __name__ == "__main__": 215 | print("====== IPCL-Python examples ======") 216 | # generate Paillier scheme key pair 217 | pk, sk = PaillierKeypair.generate_keypair(2048) 218 | 219 | # Acquire QAT engine control 220 | context.initializeContext("QAT") 221 | 222 | test_encrypt_decrypt() 223 | test_addCTCT() 224 | test_addCTPT() 225 | test_mulCTPT() 226 | test_HE_ops() 227 | test_serialize() 228 | test_hybridMode() 229 | 230 | # Release QAT engine control 231 | context.terminateContext() 232 | -------------------------------------------------------------------------------- /lib/ipcl.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | include(FetchContent) 5 | MESSAGE(STATUS "Configuring Intel Paillier Cryptosystem Library") 6 | set(IPCL_GIT_REPO_URL https://github.com/intel/pailliercryptolib.git) 7 | set(IPCL_GIT_LABEL development) 8 | 9 | # set IPCL flags 10 | if(IPCL_PYTHON_ENABLE_QAT) 11 | set(IPCL_ENABLE_QAT ON) 12 | endif() 13 | 14 | if(IPCL_PYTHON_DETECT_CPU_RUNTIME) 15 | set(IPCL_DETECT_CPU_RUNTIME ON) 16 | endif() 17 | 18 | set(IPCL_ENABLE_OMP OFF) 19 | if(IPCL_PYTHON_ENABLE_OMP) 20 | if(OpenMP_FOUND) 21 | set(IPCL_ENABLE_OMP ON) 22 | endif() 23 | endif() 24 | 25 | set(IPCL_SHARED ON) 26 | set(IPCL_TEST OFF) 27 | set(IPCL_BENCHMARK OFF) 28 | set(IPCL_INTERNAL_PYTHON_BUILD ON) 29 | 30 | FetchContent_Declare( 31 | ipcl 32 | GIT_REPOSITORY ${IPCL_GIT_REPO_URL} 33 | GIT_TAG ${IPCL_GIT_LABEL} 34 | ) 35 | FetchContent_MakeAvailable(ipcl) 36 | -------------------------------------------------------------------------------- /lib/pybind11.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | include(FetchContent) 5 | find_package(Git) 6 | 7 | FetchContent_Declare(pybind11 8 | GIT_REPOSITORY https://github.com/pybind/pybind11.git 9 | ) 10 | 11 | FetchContent_MakeAvailable(pybind11) 12 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | wheel 5 | numpy>=1.23.1, <=1.23.2 6 | pycryptodomex==3.19.1 7 | gmpy2==2.1.5 8 | cachetools==3.0.0 9 | ruamel-yaml==0.16.10 10 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | # Copyright (C) 2021 Intel Corporation 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | import os 7 | import sys 8 | import subprocess 9 | 10 | from setuptools import setup, find_packages, Extension 11 | from setuptools.command.build_ext import build_ext 12 | from pathlib import Path 13 | 14 | 15 | class CMakeExtension(Extension): 16 | def __init__(self, name, sourcedir=""): 17 | Extension.__init__(self, name, sources=[]) 18 | self.sourcedir = os.path.abspath(sourcedir) 19 | 20 | 21 | class CMakeBuild(build_ext): 22 | def run(self): 23 | try: 24 | _ = subprocess.check_output(["cmake", "--version"]) 25 | except OSError: 26 | raise RuntimeError( 27 | "CMake must be installed to build the following extensions: " 28 | + ", ".join(e.name for e in self.extensions) 29 | ) 30 | 31 | for ext in self.extensions: 32 | self.build_extension(ext) 33 | 34 | def build_extension(self, ext): 35 | 36 | ext_fullpath = Path.cwd() / self.get_ext_fullpath(ext.name) 37 | extdir = ext_fullpath.parent.resolve() 38 | 39 | cmake_args = [ 40 | f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={extdir}{os.sep}", 41 | "-DPYTHON_EXECUTABLE=" + sys.executable, 42 | "-DIPCL_PYTHON_ENABLE_QAT=ON", 43 | "-DIPCL_PYTHON_DETECT_CPU_RUNTIME=ON", 44 | "-DIPCL_PYTHON_ENABLE_OMP=OFF", 45 | ] 46 | 47 | cfg = "Debug" if self.debug else "Release" 48 | build_args = ["--config", cfg] 49 | 50 | cmake_args += ["-DCMAKE_BUILD_TYPE=" + cfg] 51 | cpu_count = os.cpu_count() - 1 if os.cpu_count() > 1 else 1 52 | build_args += ["--", "-j" + str(cpu_count)] 53 | 54 | env = os.environ.copy() 55 | env["CXXFLAGS"] = '{} -DVERSION_INFO=\\"{}\\"'.format( 56 | env.get("CXXFLAGS", ""), self.distribution.get_version() 57 | ) 58 | if not os.path.exists(self.build_temp): 59 | os.makedirs(self.build_temp) 60 | subprocess.check_call( 61 | ["cmake", ext.sourcedir] + cmake_args, cwd=self.build_temp, env=env 62 | ) 63 | subprocess.check_call( 64 | ["cmake", "--build", "."] + build_args, cwd=self.build_temp 65 | ) 66 | 67 | 68 | setup( 69 | name="ipcl-python", 70 | version="2.0.0", 71 | author="Sejun Kim", 72 | author_email="sejun.kim@intel.com", 73 | description="Python wrapper for Intel Paillier Cryptosystem Library", 74 | long_description="", 75 | packages=find_packages("src"), 76 | package_dir={"": "src"}, 77 | ext_modules=[CMakeExtension("ipcl_python/bindings/ipcl_bindings")], 78 | cmdclass={"build_ext": CMakeBuild}, 79 | test_suite="tests", 80 | license="Apache-2.0", 81 | classifiers=[ 82 | "Programming Language :: Python :: 3", 83 | "Operating System :: POSIX :: Linux", 84 | ], 85 | install_requires=[ 86 | "wheel", 87 | "numpy>=1.23.1, <=1.23.2", 88 | "pycryptodomex==3.19.1", 89 | "gmpy2==2.1.5", 90 | "cachetools==3.0.0", 91 | "ruamel.yaml==0.16.10", 92 | ], 93 | zip_safe=False, 94 | ) 95 | -------------------------------------------------------------------------------- /src/ipcl_python/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from .ipcl_python import ( 5 | PaillierKeypair, 6 | PaillierPublicKey, 7 | PaillierPrivateKey, 8 | PaillierEncryptedNumber, 9 | ) 10 | 11 | from .bindings.ipcl_bindings import context, hybridControl, hybridMode 12 | -------------------------------------------------------------------------------- /src/ipcl_python/bindings/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /src/ipcl_python/bindings/baseconverter.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | #include "include/baseconverter.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace baseconverter { 11 | std::string hex2dec(std::string val) { 12 | std::string res; 13 | do { 14 | unsigned int remainder = divide(hexSet, val, 10); 15 | res.push_back(decSet[remainder]); 16 | } while (!val.empty() && !(val.length() == 1 && val[0] == hexSet[0])); 17 | std::reverse(res.begin(), res.end()); 18 | return res; 19 | } 20 | std::string dec2hex(std::string val) { 21 | std::string res; 22 | 23 | do { 24 | unsigned int remainder = divide(decSet, val, 16); 25 | res.push_back(hexSet[remainder]); 26 | } while (!val.empty() && !(val.length() == 1 && val[0] == decSet[0])); 27 | std::reverse(res.begin(), res.end()); 28 | return res; 29 | } 30 | 31 | unsigned int divide(const std::string& baseSet, std::string& x, 32 | unsigned int y) { 33 | std::string quotient; 34 | size_t length = x.length(); 35 | 36 | for (size_t i = 0; i < length; ++i) { 37 | size_t j = i + 1 + x.length() - length; 38 | if (x.length() < j) break; 39 | 40 | unsigned int value = getdec(baseSet, x.substr(0, j)); 41 | 42 | quotient.push_back(baseSet[value / y]); 43 | x = getbase(baseSet, value % y) + x.substr(j); 44 | } 45 | 46 | unsigned int remainder = getdec(baseSet, x); 47 | size_t n = quotient.find_first_not_of(baseSet[0]); 48 | if (n != std::string::npos) 49 | x = quotient.substr(n); 50 | else 51 | x.clear(); 52 | 53 | return remainder; 54 | } 55 | 56 | std::string getbase(const std::string& baseSet, unsigned int val) { 57 | unsigned int n = static_cast(baseSet.length()); 58 | std::string res; 59 | 60 | do { 61 | res.push_back(baseSet[val % n]); 62 | val /= n; 63 | } while (val > 0); 64 | 65 | std::reverse(res.begin(), res.end()); 66 | return res; 67 | } 68 | unsigned int getdec(const std::string& baseSet, const std::string& val) { 69 | unsigned int n = static_cast(baseSet.length()); 70 | unsigned int res = 0; 71 | for (size_t i = 0; i < val.length(); ++i) { 72 | res *= n; 73 | int c = baseSet.find(val[i]); 74 | if (c == std::string::npos) throw std::runtime_error("Invalid character"); 75 | 76 | res += static_cast(c); 77 | } 78 | return res; 79 | } 80 | 81 | std::string BN2dec(const BigNumber& bn) { 82 | std::string s_hex; 83 | bn.num2hex(s_hex); 84 | size_t start = s_hex.find_first_not_of(" \n\r\t\f\b"); 85 | s_hex = s_hex.substr(start + 2); 86 | 87 | return hex2dec(s_hex); 88 | } 89 | 90 | }; // namespace baseconverter 91 | -------------------------------------------------------------------------------- /src/ipcl_python/bindings/fixedpoint.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019 The FATE Authors. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | # Modifications Copyright (C) 2021 Intel Corporation 17 | # 18 | 19 | import functools 20 | import math 21 | import sys 22 | 23 | import numpy as np 24 | 25 | 26 | class FixedPointNumber(object): 27 | """Represents a float or int fixedpoint encoding;. 28 | """ 29 | BASE = 2 30 | LOG2_BASE = math.log(BASE, 2) 31 | FLOAT_MANTISSA_BITS = sys.float_info.mant_dig 32 | 33 | Q = 293973345475167247070445277780365744413 ** 2 34 | 35 | def __init__(self, encoding, exponent, n=None, max_int=None): 36 | if n is None: 37 | self.n = FixedPointNumber.Q 38 | self.max_int = self.n // 2 39 | else: 40 | self.n = n 41 | if max_int is None: 42 | self.max_int = self.n // 2 43 | else: 44 | self.max_int = max_int 45 | 46 | self.encoding = encoding 47 | self.exponent = exponent 48 | 49 | @classmethod 50 | def calculate_exponent_from_precision(cls, precision): 51 | exponent = math.floor(math.log(precision, cls.BASE)) 52 | return exponent 53 | 54 | @classmethod 55 | def encode(cls, scalar, n=None, max_int=None, precision=None, max_exponent=None): 56 | """return an encoding of an int or float. 57 | """ 58 | # Calculate the maximum exponent for desired precision 59 | exponent = None 60 | 61 | # Too low value preprocess; 62 | # avoid "OverflowError: int too large to convert to float" 63 | 64 | if np.abs(scalar) < 1e-200: 65 | scalar = 0 66 | 67 | if n is None: 68 | n = cls.Q 69 | max_int = n // 2 70 | 71 | if precision is None: 72 | if isinstance(scalar, int) or isinstance(scalar, np.int16) or \ 73 | isinstance(scalar, np.int32) or isinstance(scalar, np.int64): 74 | exponent = 0 75 | elif isinstance(scalar, float) or isinstance(scalar, np.float16) \ 76 | or isinstance(scalar, np.float32) or isinstance(scalar, np.float64): 77 | flt_exponent = math.frexp(scalar)[1] 78 | lsb_exponent = cls.FLOAT_MANTISSA_BITS - flt_exponent 79 | exponent = math.floor(lsb_exponent / cls.LOG2_BASE) 80 | else: 81 | raise TypeError("Don't know the precision of type %s." 82 | % type(scalar)) 83 | else: 84 | exponent = cls.calculate_exponent_from_precision(precision) 85 | 86 | if max_exponent is not None: 87 | exponent = max(max_exponent, exponent) 88 | 89 | int_fixpoint = int(round(scalar * pow(cls.BASE, exponent))) 90 | 91 | if abs(int_fixpoint) > max_int: 92 | raise ValueError(f"Integer needs to be within +/- {max_int},but got {int_fixpoint}," 93 | f"basic info, scalar={scalar}, base={cls.BASE}, exponent={exponent}" 94 | ) 95 | 96 | return cls(int_fixpoint % n, exponent, n, max_int) 97 | 98 | def decode(self): 99 | """return decode plaintext. 100 | """ 101 | if self.encoding >= self.n: 102 | # Should be mod n 103 | raise ValueError('Attempted to decode corrupted number') 104 | elif self.encoding <= self.max_int: 105 | # Positive 106 | mantissa = self.encoding 107 | elif self.encoding >= self.n - self.max_int: 108 | # Negative 109 | mantissa = self.encoding - self.n 110 | else: 111 | raise OverflowError(f'Overflow detected in decode number, encoding: {self.encoding}, ' 112 | f'{self.exponent}' 113 | f' {self.n}') 114 | 115 | return mantissa * pow(self.BASE, -self.exponent) 116 | 117 | def increase_exponent_to(self, new_exponent): 118 | """return FixedPointNumber: new encoding with same value but having great exponent. 119 | """ 120 | if new_exponent < self.exponent: 121 | raise ValueError('New exponent %i should be greater than' 122 | 'old exponent %i' % (new_exponent, self.exponent)) 123 | 124 | factor = pow(self.BASE, new_exponent - self.exponent) 125 | new_encoding = self.encoding * factor % self.n 126 | 127 | return FixedPointNumber(new_encoding, new_exponent, self.n, self.max_int) 128 | 129 | def __align_exponent(self, x, y): 130 | """return x,y with same exponent 131 | """ 132 | if x.exponent < y.exponent: 133 | x = x.increase_exponent_to(y.exponent) 134 | elif x.exponent > y.exponent: 135 | y = y.increase_exponent_to(x.exponent) 136 | 137 | return x, y 138 | 139 | def __truncate(self, a): 140 | scalar = a.decode() 141 | return FixedPointNumber.encode(scalar, n=self.n, max_int=self.max_int) 142 | 143 | def __add__(self, other): 144 | if isinstance(other, FixedPointNumber): 145 | return self.__add_fixedpointnumber(other) 146 | elif type(other).__name__ == "PaillierEncryptedNumber": 147 | return other + self.decode() 148 | else: 149 | return self.__add_scalar(other) 150 | 151 | def __radd__(self, other): 152 | return self.__add__(other) 153 | 154 | def __sub__(self, other): 155 | if isinstance(other, FixedPointNumber): 156 | return self.__sub_fixedpointnumber(other) 157 | elif type(other).__name__ == "PaillierEncryptedNumber": 158 | return (other - self.decode()) * -1 159 | else: 160 | return self.__sub_scalar(other) 161 | 162 | def __rsub__(self, other): 163 | if type(other).__name__ == "PaillierEncryptedNumber": 164 | return other - self.decode() 165 | 166 | x = self.__sub__(other) 167 | x = -1 * x.decode() 168 | return self.encode(x, n=self.n, max_int=self.max_int) 169 | 170 | def __rmul__(self, other): 171 | return self.__mul__(other) 172 | 173 | def __mul__(self, other): 174 | if isinstance(other, FixedPointNumber): 175 | return self.__mul_fixedpointnumber(other) 176 | elif type(other).__name__ == "PaillierEncryptedNumber": 177 | return other * self.decode() 178 | else: 179 | return self.__mul_scalar(other) 180 | 181 | def __truediv__(self, other): 182 | if isinstance(other, FixedPointNumber): 183 | scalar = other.decode() 184 | else: 185 | scalar = other 186 | 187 | return self.__mul__(1 / scalar) 188 | 189 | def __rtruediv__(self, other): 190 | res = 1.0 / self.__truediv__(other).decode() 191 | return FixedPointNumber.encode(res, n=self.n, max_int=self.max_int) 192 | 193 | def __lt__(self, other): 194 | x = self.decode() 195 | if isinstance(other, FixedPointNumber): 196 | y = other.decode() 197 | else: 198 | y = other 199 | if x < y: 200 | return True 201 | else: 202 | return False 203 | 204 | def __gt__(self, other): 205 | x = self.decode() 206 | if isinstance(other, FixedPointNumber): 207 | y = other.decode() 208 | else: 209 | y = other 210 | if x > y: 211 | return True 212 | else: 213 | return False 214 | 215 | def __le__(self, other): 216 | x = self.decode() 217 | if isinstance(other, FixedPointNumber): 218 | y = other.decode() 219 | else: 220 | y = other 221 | if x <= y: 222 | return True 223 | else: 224 | return False 225 | 226 | def __ge__(self, other): 227 | x = self.decode() 228 | if isinstance(other, FixedPointNumber): 229 | y = other.decode() 230 | else: 231 | y = other 232 | 233 | if x >= y: 234 | return True 235 | else: 236 | return False 237 | 238 | def __eq__(self, other): 239 | x = self.decode() 240 | if isinstance(other, FixedPointNumber): 241 | y = other.decode() 242 | else: 243 | y = other 244 | if x == y: 245 | return True 246 | else: 247 | return False 248 | 249 | def __ne__(self, other): 250 | x = self.decode() 251 | if isinstance(other, FixedPointNumber): 252 | y = other.decode() 253 | else: 254 | y = other 255 | if x != y: 256 | return True 257 | else: 258 | return False 259 | 260 | def __add_fixedpointnumber(self, other): 261 | if self.n != other.n: 262 | other = self.encode(other.decode(), n=self.n, max_int=self.max_int) 263 | x, y = self.__align_exponent(self, other) 264 | encoding = (x.encoding + y.encoding) % self.n 265 | return FixedPointNumber(encoding, x.exponent, n=self.n, max_int=self.max_int) 266 | 267 | def __add_scalar(self, scalar): 268 | encoded = self.encode(scalar, n=self.n, max_int=self.max_int) 269 | return self.__add_fixedpointnumber(encoded) 270 | 271 | def __sub_fixedpointnumber(self, other): 272 | if self.n != other.n: 273 | other = self.encode(other.decode(), n=self.n, max_int=self.max_int) 274 | x, y = self.__align_exponent(self, other) 275 | encoding = (x.encoding - y.encoding) % self.n 276 | 277 | return FixedPointNumber(encoding, x.exponent, n=self.n, max_int=self.max_int) 278 | 279 | def __sub_scalar(self, scalar): 280 | scalar = -1 * scalar 281 | return self.__add_scalar(scalar) 282 | 283 | def __mul_fixedpointnumber(self, other): 284 | return self.__mul_scalar(other.decode()) 285 | 286 | def __mul_scalar(self, scalar): 287 | val = self.decode() 288 | z = val * scalar 289 | z_encode = FixedPointNumber.encode(z, n=self.n, max_int=self.max_int) 290 | return z_encode 291 | 292 | def __abs__(self): 293 | if self.encoding <= self.max_int: 294 | # Positive 295 | return self 296 | elif self.encoding >= self.n - self.max_int: 297 | # Negative 298 | return self * -1 299 | 300 | def __mod__(self, other): 301 | return FixedPointNumber(self.encoding % other, self.exponent, n=self.n, max_int=self.max_int) 302 | 303 | 304 | class FixedPointEndec(object): 305 | 306 | def __init__(self, n=None, max_int=None, precision=None, *args, **kwargs): 307 | if n is None: 308 | self.n = FixedPointNumber.Q 309 | self.max_int = self.n // 2 310 | else: 311 | self.n = n 312 | if max_int is None: 313 | self.max_int = self.n // 2 314 | else: 315 | self.max_int = max_int 316 | 317 | self.precision = precision 318 | 319 | @classmethod 320 | def _transform_op(cls, tensor, op): 321 | from fate_arch.session import is_table 322 | 323 | def _transform(x): 324 | arr = np.zeros(shape=x.shape, dtype=object) 325 | view = arr.view().reshape(-1) 326 | x_array = x.view().reshape(-1) 327 | for i in range(arr.size): 328 | view[i] = op(x_array[i]) 329 | 330 | return arr 331 | 332 | if isinstance(tensor, (int, np.int16, np.int32, np.int64, 333 | float, np.float16, np.float32, np.float64, 334 | FixedPointNumber)): 335 | return op(tensor) 336 | 337 | if isinstance(tensor, np.ndarray): 338 | z = _transform(tensor) 339 | return z 340 | 341 | elif is_table(tensor): 342 | f = functools.partial(_transform) 343 | return tensor.mapValues(f) 344 | else: 345 | raise ValueError(f"unsupported type: {type(tensor)}") 346 | 347 | def _encode(self, scalar): 348 | return FixedPointNumber.encode(scalar, 349 | n=self.n, 350 | max_int=self.max_int, 351 | precision=self.precision) 352 | 353 | def _decode(self, number): 354 | return number.decode() 355 | 356 | def _truncate(self, number): 357 | scalar = number.decode() 358 | return FixedPointNumber.encode(scalar, n=self.n, max_int=self.max_int) 359 | 360 | def encode(self, float_tensor): 361 | return self._transform_op(float_tensor, op=self._encode) 362 | 363 | def decode(self, integer_tensor): 364 | return self._transform_op(integer_tensor, op=self._decode) 365 | 366 | def truncate(self, integer_tensor, *args, **kwargs): 367 | return self._transform_op(integer_tensor, op=self._truncate) 368 | -------------------------------------------------------------------------------- /src/ipcl_python/bindings/include/baseconverter.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | #include 5 | 6 | #include "ipcl/ipcl.hpp" 7 | 8 | #ifndef SRC_IPCL_PYTHON_BINDINGS_INCLUDE_BASECONVERTER_HPP_ 9 | #define SRC_IPCL_PYTHON_BINDINGS_INCLUDE_BASECONVERTER_HPP_ 10 | 11 | namespace baseconverter { 12 | std::string hex2dec(std::string val); 13 | std::string dec2hex(std::string val); 14 | 15 | unsigned int divide(const std::string& baseSet, std::string& x, unsigned int y); 16 | const char decSet[] = "0123456789"; 17 | const char hexSet[] = "0123456789abcdef"; 18 | 19 | std::string getbase(const std::string& baseSet, unsigned int val); 20 | unsigned int getdec(const std::string& baseSet, const std::string& val); 21 | std::string BN2dec(const BigNumber& bn); 22 | }; // namespace baseconverter 23 | 24 | #endif // SRC_IPCL_PYTHON_BINDINGS_INCLUDE_BASECONVERTER_HPP_ 25 | -------------------------------------------------------------------------------- /src/ipcl_python/bindings/include/ipcl_bindings.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | // #include "ipcl/context.hpp" 17 | #include "ipcl/ipcl.hpp" 18 | 19 | #ifndef SRC_IPCL_PYTHON_BINDINGS_INCLUDE_IPCL_BINDINGS_HPP_ 20 | #define SRC_IPCL_PYTHON_BINDINGS_INCLUDE_IPCL_BINDINGS_HPP_ 21 | class py_ipclKeyPair { 22 | public: 23 | static pybind11::tuple generate_keypair(int64_t n_length = 1024, 24 | bool enable_DJN = true); 25 | }; 26 | 27 | class py_ipclContext { 28 | public: 29 | static bool initializeContext(std::string runtime_choice) { 30 | return ipcl::initializeContext(runtime_choice); 31 | } 32 | static bool terminateContext() { return ipcl::terminateContext(); } 33 | static bool isQATRunning() { return ipcl::isQATRunning(); } 34 | static bool isQATActive() { return ipcl::isQATActive(); } 35 | }; 36 | 37 | class py_ipclHybridControl { 38 | public: 39 | static void setHybridMode(ipcl::HybridMode mode); 40 | static void setHybridOff() { ipcl::setHybridOff(); } 41 | static ipcl::HybridMode getHybridMode() { return ipcl::getHybridMode(); } 42 | }; 43 | 44 | void def_ipclPublicKey(pybind11::module&); 45 | void def_ipclPrivateKey(pybind11::module&); 46 | void def_BigNumber(pybind11::module&); 47 | void def_ipclPlainText(pybind11::module&); 48 | void def_ipclCipherText(pybind11::module&); 49 | 50 | namespace ipclPythonUtils { 51 | pybind11::tuple getTupleIpclPubKey(const ipcl::PublicKey& pk); 52 | ipcl::PublicKey setIpclPubKey(const pybind11::tuple& t_pk); 53 | BigNumber pyByte2BN(const pybind11::bytes& data); 54 | pybind11::bytes BN2bytes(const BigNumber& bn); 55 | pybind11::bytes BN2bytes(const std::shared_ptr& bn); 56 | }; // namespace ipclPythonUtils 57 | 58 | #endif // SRC_IPCL_PYTHON_BINDINGS_INCLUDE_IPCL_BINDINGS_HPP_ 59 | -------------------------------------------------------------------------------- /src/ipcl_python/bindings/ipcl_bindings.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | #include "include/ipcl_bindings.hpp" 5 | 6 | #include 7 | 8 | namespace py = pybind11; 9 | 10 | PYBIND11_MAKE_OPAQUE(BigNumber); 11 | 12 | py::tuple py_ipclKeyPair::generate_keypair(int64_t n_length, bool enable_DJN) { 13 | ipcl::KeyPair keys = ipcl::generateKeypair(n_length, enable_DJN); 14 | return py::make_tuple(keys.pub_key, keys.priv_key); 15 | } 16 | 17 | void py_ipclHybridControl::setHybridMode(ipcl::HybridMode mode) { 18 | ipcl::setHybridMode(mode); 19 | } 20 | 21 | PYBIND11_MODULE(ipcl_bindings, m) { 22 | m.doc() = "Python wrapper for Intel ipp-crypto Paillier cryptosystem"; 23 | 24 | // PaillierKeyPair and generate_keypair pymodule 25 | py::class_(m, "ipclKeypair") 26 | .def_static("generate_keypair", &py_ipclKeyPair::generate_keypair) 27 | .def_static("generate_keypair", [](int64_t n_length, bool enable_DJN) { 28 | return py_ipclKeyPair::generate_keypair(n_length, enable_DJN); 29 | }); 30 | 31 | py::class_(m, "context") 32 | .def_static("initializeContext", &py_ipclContext::initializeContext) 33 | .def_static("terminateContext", &py_ipclContext::terminateContext) 34 | .def_static("isQATRunning", &py_ipclContext::isQATRunning) 35 | .def_static("isQATActive", &py_ipclContext::isQATActive); 36 | 37 | py::enum_(m, "hybridMode") 38 | .value("OPTIMAL", ipcl::HybridMode::OPTIMAL) 39 | .value("QAT", ipcl::HybridMode::QAT) 40 | .value("PREF_QAT90", ipcl::HybridMode::PREF_QAT90) 41 | .value("PREF_QAT80", ipcl::HybridMode::PREF_QAT80) 42 | .value("PREF_QAT70", ipcl::HybridMode::PREF_QAT70) 43 | .value("PREF_QAT60", ipcl::HybridMode::PREF_QAT60) 44 | .value("HALF", ipcl::HybridMode::HALF) 45 | .value("PREF_IPP60", ipcl::HybridMode::PREF_IPP60) 46 | .value("PREF_IPP70", ipcl::HybridMode::PREF_IPP70) 47 | .value("PREF_IPP80", ipcl::HybridMode::PREF_IPP80) 48 | .value("PREF_IPP90", ipcl::HybridMode::PREF_IPP90) 49 | .value("IPP", ipcl::HybridMode::IPP) 50 | .value("UNDEFINED", ipcl::HybridMode::UNDEFINED) 51 | .export_values(); 52 | 53 | py::class_(m, "hybridControl") 54 | .def_static("setHybridMode", &py_ipclHybridControl::setHybridMode) 55 | .def_static("setHybridOff", &py_ipclHybridControl::setHybridOff) 56 | .def_static("getHybridMode", &py_ipclHybridControl::getHybridMode); 57 | 58 | def_ipclPublicKey(m); 59 | def_ipclPrivateKey(m); 60 | def_ipclPlainText(m); 61 | def_ipclCipherText(m); 62 | def_BigNumber(m); 63 | } 64 | 65 | namespace ipclPythonUtils { 66 | py::tuple getTupleIpclPubKey(const ipcl::PublicKey& pk) { 67 | py::tuple ret; 68 | int pk_length = pk.getBits(); 69 | bool isDJN = pk.isDJN(); 70 | if (isDJN) { // DJN scheme 71 | auto l_n = BN2bytes(pk.getN()); 72 | auto l_hs = BN2bytes(pk.getHS()); 73 | int randbits = pk.getRandBits(); 74 | ret = py::make_tuple(1, l_n, pk_length, l_hs, randbits); 75 | } else { // Paillier scheme 76 | auto l_n = BN2bytes(pk.getN()); 77 | ret = py::make_tuple(0, l_n, pk_length, 0, 0); 78 | } 79 | return ret; 80 | } 81 | 82 | ipcl::PublicKey setIpclPubKey(const py::tuple& t_pk) { 83 | ipcl::PublicKey ret; 84 | int scheme = py::cast(t_pk[0]); 85 | py::bytes l_n = t_pk[1]; 86 | BigNumber n = pyByte2BN(l_n); 87 | int pk_length = py::cast(t_pk[2]); 88 | 89 | if (scheme == 0) { // Paillier scheme 90 | ret.create(n, pk_length); 91 | } else { // DJN scheme 92 | py::bytes l_hs = t_pk[3]; 93 | BigNumber hs = pyByte2BN(l_hs); 94 | int randbits = py::cast(t_pk[4]); 95 | ret.create(n, pk_length, hs, randbits); 96 | } 97 | return ret; 98 | } 99 | 100 | BigNumber pyByte2BN(const py::bytes& data) { 101 | py::buffer_info buffer_info(py::buffer(data).request()); 102 | unsigned char* chardata = reinterpret_cast(buffer_info.ptr); 103 | size_t length = static_cast(buffer_info.size); 104 | 105 | size_t new_length = (length + 3) >> 2; 106 | Ipp32u* data32 = reinterpret_cast(chardata); 107 | 108 | // check MSB if length % 4 != 0 109 | size_t rmdr = length & 3; 110 | if (rmdr) { // length % 4 != 0 111 | size_t st = length - rmdr; 112 | Ipp32u tmp = chardata[st] & 0xFF; 113 | for (size_t j = 1; j < rmdr; ++j) 114 | tmp += (chardata[j + st] & 0xFF) << (8 * j); 115 | *(data32 + new_length - 1) = tmp; 116 | } 117 | 118 | return BigNumber(data32, new_length); 119 | } 120 | 121 | py::bytes BN2bytes(const BigNumber& bn) { 122 | int bnBitLen; 123 | Ipp32u* bnData = nullptr; 124 | ippsRef_BN(nullptr, &bnBitLen, &bnData, bn); 125 | int length = BITSIZE_WORD(bnBitLen) * 4; 126 | unsigned char* bytesData = reinterpret_cast(bnData); 127 | std::string str(reinterpret_cast(bytesData), length); 128 | return py::bytes(str); 129 | } 130 | py::bytes BN2bytes(const std::shared_ptr& bn) { 131 | int bnBitLen; 132 | Ipp32u* bnData = nullptr; 133 | ippsRef_BN(nullptr, &bnBitLen, &bnData, *bn); 134 | int length = BITSIZE_WORD(bnBitLen) * 4; 135 | unsigned char* bytesData = reinterpret_cast(bnData); 136 | std::string str(reinterpret_cast(bytesData), length); 137 | return py::bytes(str); 138 | } 139 | }; // namespace ipclPythonUtils 140 | -------------------------------------------------------------------------------- /src/ipcl_python/bindings/ipcl_bindings_classes.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | #include 5 | #include 6 | 7 | #include "include/baseconverter.hpp" 8 | #include "include/ipcl_bindings.hpp" 9 | 10 | namespace py = pybind11; 11 | 12 | void def_ipclPublicKey(py::module& m) { 13 | // Paillier publickey module 14 | py::class_>(m, 15 | "ipclPublicKey") 16 | .def(py::init([](const BigNumber& n) { 17 | return std::make_shared(ipcl::PublicKey(n, 1024)); 18 | }), 19 | "ipclPublicKey constructor") 20 | .def(py::init([](const BigNumber& n, int bits) { 21 | return std::make_shared(ipcl::PublicKey(n, bits)); 22 | }), 23 | "ipclPublicKey constructor") 24 | .def(py::init([](const BigNumber& n, int bits, bool enable_DJN) { 25 | return std::make_shared( 26 | ipcl::PublicKey(n, bits, enable_DJN)); 27 | })) 28 | .def("__repr__", 29 | [](const ipcl::PublicKey& self) { 30 | std::stringstream ss; 31 | ss << self.addr; 32 | std::size_t hashcode = std::hash{}(ss.str()); 33 | return std::string(""); 35 | }) 36 | .def("__eq__", 37 | [](const ipcl::PublicKey& self, const ipcl::PublicKey& other) { 38 | return self.getN() == other.getN(); 39 | }) 40 | .def("__hash__", 41 | [](const ipcl::PublicKey& self) { 42 | std::stringstream ss; 43 | ss << self.getN(); 44 | std::size_t hashcode = std::hash{}(ss.str()); 45 | return hashcode; 46 | }) 47 | .def_property_readonly("n", &ipcl::PublicKey::getN, 48 | "ipclPublicKey n in ipclBigNumber") 49 | .def_property_readonly("length", &ipcl::PublicKey::getBits, 50 | "ipclPublicKey length") 51 | .def_property_readonly("nsquare", &ipcl::PublicKey::getNSQ, 52 | "square of ipclPublicKey n in ipclBigNumber") 53 | .def( 54 | "encrypt", // encrypt plaintext 55 | [](const ipcl::PublicKey& self, const ipcl::PlainText& pt, 56 | bool make_secure) { 57 | ipcl::CipherText ct = self.encrypt(pt, make_secure); 58 | return ct; 59 | }, 60 | "encrypt ipcl::PlainText and returns ipcl::CipherText") 61 | .def( 62 | "encrypt_tolist", // encrypt plaintext and return container of 63 | // Ciphertext 64 | [](const ipcl::PublicKey& self, const ipcl::PlainText& pt, 65 | bool make_secure) { 66 | ipcl::CipherText ct = self.encrypt(pt, make_secure); 67 | py::list l_container = py::cast(ct.getTexts()); 68 | return l_container; 69 | }, 70 | "encrypt ipcl::PlainText and returns container of ipcl::CipherText") 71 | .def("apply_obfuscator", // ciphertext obfuscator 72 | [](const ipcl::PublicKey& self, const BigNumber& ct) { 73 | std::vector ret(1, ct); 74 | self.applyObfuscator(ret); 75 | return ret[0]; 76 | }) 77 | .def("apply_obfuscator", // overloaded ciphertext obfuscator 78 | [](const ipcl::PublicKey& self, const ipcl::CipherText& ct) { 79 | std::vector ret = ct.getTexts(); 80 | self.applyObfuscator(ret); 81 | py::list l_ret = py::cast(ret); 82 | return l_ret; 83 | }) 84 | .def(py::pickle( 85 | [](const ipcl::PublicKey& self) { // __getstate__ 86 | return ipclPythonUtils::getTupleIpclPubKey(self); 87 | }, 88 | [](py::tuple t) { // __setstate__ 89 | return ipclPythonUtils::setIpclPubKey(t); 90 | })); 91 | } 92 | 93 | void def_ipclPrivateKey(py::module& m) { 94 | py::class_>( 95 | m, "ipclPrivateKey") 96 | .def(py::init([](const ipcl::PublicKey& pubkey, const BigNumber& p, 97 | const BigNumber& q) { 98 | return std::make_shared( 99 | ipcl::PrivateKey(pubkey, p, q)); 100 | }), 101 | "ipclPrivateKey constructor") 102 | .def("__repr__", 103 | [](const ipcl::PrivateKey& self) { 104 | std::stringstream ss; 105 | ss << self.addr; 106 | std::size_t hashcode = std::hash{}(ss.str()); 107 | return std::string(""); 109 | }) 110 | .def("__eq__", 111 | [](const ipcl::PrivateKey& self, const ipcl::PrivateKey& other) { 112 | return self.getQ() == other.getQ(); 113 | }) 114 | .def("__hash__", 115 | [](const ipcl::PrivateKey& self) { 116 | std::stringstream ss; 117 | ss << self.addr; 118 | std::size_t hashcode = std::hash{}(ss.str()); 119 | return hashcode; 120 | }) 121 | .def_property_readonly("n", &ipcl::PrivateKey::getN, 122 | "get ipclPublicKey n property") 123 | .def_property_readonly("p", &ipcl::PrivateKey::getP, 124 | "get ipclPrivateKey p property") 125 | .def_property_readonly("q", &ipcl::PrivateKey::getQ, 126 | "get ipclPrivateKey q property") 127 | .def( 128 | "decrypt", // decrypt ipcl::CipherText 129 | [](ipcl::PrivateKey& self, const ipcl::CipherText& ct) { 130 | ipcl::PlainText pt = self.decrypt(ct); 131 | return pt; 132 | }, 133 | "decrypt ipcl::CipherText into ipcl::PlainText") 134 | .def( 135 | "decrypt_tolist", // decrypt ipcl::CipherText and return container 136 | [](ipcl::PrivateKey& self, const ipcl::CipherText& ct) { 137 | ipcl::PlainText pt = self.decrypt(ct); 138 | py::list l_container = py::cast(pt.getTexts()); 139 | return l_container; 140 | }, 141 | "decrypt ipcl::CipherText into container of ipcl::PlainText") 142 | .def(py::pickle( 143 | [](const ipcl::PrivateKey& self) { // __getstate__ 144 | std::shared_ptr n = self.getN(); 145 | auto _n = ipclPythonUtils::BN2bytes(n); 146 | std::shared_ptr p = self.getP(); 147 | auto _p = ipclPythonUtils::BN2bytes(p); 148 | std::shared_ptr q = self.getQ(); 149 | auto _q = ipclPythonUtils::BN2bytes(q); 150 | 151 | return py::make_tuple(_n, _p, _q); 152 | }, 153 | [](py::tuple t) { // __setstate__ 154 | py::bytes l_n = t[0]; 155 | BigNumber n = ipclPythonUtils::pyByte2BN(l_n); 156 | py::bytes l_p = t[1]; 157 | BigNumber p = ipclPythonUtils::pyByte2BN(l_p); 158 | py::bytes l_q = t[2]; 159 | BigNumber q = ipclPythonUtils::pyByte2BN(l_q); 160 | return std::unique_ptr( 161 | new ipcl::PrivateKey(n, p, q)); 162 | })); 163 | } 164 | 165 | void def_ipclPlainText(py::module& m) { 166 | // ipcl::PlainText 167 | py::class_(m, "ipclPlainText") 168 | .def(py::init(), "ipclPlainText constructor") 169 | .def(py::init(), "ipclPlainText constructor") 170 | .def(py::init(), "ipclPlainText constructor") 171 | .def(py::init([](py::list data) { 172 | std::vector pData = 173 | py::cast>(data); 174 | return ipcl::PlainText(pData); 175 | }), 176 | "ipclPlainText constructor with list of BigNumbers") 177 | .def(py::init([](py::array_t data) { 178 | py::buffer_info buffer_info = data.request(); 179 | size_t length = buffer_info.shape[0]; 180 | Ipp32u* _data = static_cast(buffer_info.ptr); 181 | std::vector pData(_data, _data + length); 182 | delete[] _data; 183 | return ipcl::PlainText(pData); 184 | }), 185 | "ipclPlainText constructor with numpy array of unsigned integers " 186 | "(32bit)") 187 | .def("__repr__", 188 | [](const ipcl::PlainText& self) { 189 | std::stringstream ss; 190 | ss << self.addr; 191 | std::size_t hashcode = std::hash{}(ss.str()); 192 | return std::string(""); 194 | }) 195 | .def("__str__", 196 | [](const ipcl::PlainText& self) { 197 | std::stringstream ss; 198 | ss << self.addr; 199 | std::size_t hashcode = std::hash{}(ss.str()); 200 | return std::string(""); 202 | }) 203 | .def("__eq__", 204 | [](const ipcl::PlainText& self, const ipcl::PlainText& other) { 205 | if (self.getSize() != other.getSize()) 206 | throw std::runtime_error("Size mismatch"); 207 | size_t length = self.getSize(); 208 | std::vector selfText = self.getTexts(); 209 | std::vector otherText = other.getTexts(); 210 | for (size_t i = 0; i < length; ++i) 211 | if (selfText[i] != otherText[i]) 212 | throw std::runtime_error("PlainText mismatch"); 213 | 214 | return true; 215 | }) 216 | .def("__getitem__", &ipcl::PlainText::getElement) 217 | .def("__getitem__", 218 | [](const ipcl::PlainText& self, py::slice slice) { 219 | size_t start, stop, step, slicelength; 220 | if (!slice.compute(self.getSize(), &start, &stop, &step, 221 | &slicelength)) 222 | throw py::error_already_set(); 223 | if (step != 1) throw std::runtime_error("Step size not supported"); 224 | return self.getChunk(start, slicelength); 225 | }) 226 | .def("__len__", &ipcl::PlainText::getSize) 227 | .def("rotate", &ipcl::PlainText::rotate, "Rotate ipclPlainText container") 228 | .def( 229 | "getElementVec", 230 | [](const ipcl::PlainText& self, const size_t& n) { 231 | std::vector vec = self.getElementVec(n); 232 | py::list l_vec = py::cast(vec); 233 | return l_vec; 234 | }, 235 | "Get element in little endian vector") 236 | .def("getElementHex", &ipcl::PlainText::getElementHex, 237 | "Get element in hexadecimal string") 238 | .def( 239 | "getTexts", 240 | [](const ipcl::PlainText& self) { 241 | std::vector vec = self.getTexts(); 242 | py::list l_vec = py::cast(vec); 243 | return l_vec; 244 | }, 245 | "Get container in BigNumber vector") 246 | .def("getSize", &ipcl::PlainText::getSize, 247 | "Get size of container") 248 | .def(py::pickle( 249 | [](const ipcl::PlainText& self) { // __getstate__ 250 | std::vector vec = self.getTexts(); 251 | size_t length = self.getSize(); 252 | py::list l_bn; 253 | for (auto it : vec) l_bn.append(ipclPythonUtils::BN2bytes(it)); 254 | return py::make_tuple(length, l_bn); 255 | }, 256 | [](const py::tuple& t) { // __setstate__ 257 | size_t length = static_cast(py::cast(t[0])); 258 | std::vector vec(length); 259 | py::list l_lbn = t[1]; 260 | for (size_t i = 0; i < length; ++i) { 261 | py::bytes lbn = l_lbn[i]; 262 | vec[i] = ipclPythonUtils::pyByte2BN(lbn); 263 | } 264 | return ipcl::PlainText(vec); 265 | })); 266 | } 267 | 268 | void def_ipclCipherText(py::module& m) { 269 | // ipcl::CipherText 270 | py::class_>( 271 | m, "ipclCipherText") 272 | .def(py::init(), 273 | "ipclCipherText constructor") 274 | .def(py::init(), 275 | "ipclCipherText constructor") 276 | .def(py::init([](const ipcl::PublicKey& pk, py::list data) { 277 | std::vector pData = 278 | py::cast>(data); 279 | return ipcl::CipherText(pk, pData); 280 | }), 281 | "ipclCipherText constructor with list of BigNumbers") 282 | .def(py::init([](const ipcl::PublicKey& pk, py::array_t data) { 283 | py::buffer_info buffer_info = data.request(); 284 | size_t length = buffer_info.shape[0]; 285 | Ipp32u* _data = static_cast(buffer_info.ptr); 286 | std::vector pData(_data, _data + length); 287 | delete[] _data; 288 | return ipcl::CipherText(pk, pData); 289 | }), 290 | "ipclCipherText constructor with numpy array of unsigned integers " 291 | "(32bit)") 292 | .def("__repr__", 293 | [](const ipcl::CipherText& self) { 294 | std::stringstream ss; 295 | ss << self.addr; 296 | std::size_t hashcode = std::hash{}(ss.str()); 297 | return std::string(""); 299 | }) 300 | .def("__str__", 301 | [](const ipcl::CipherText& self) { 302 | std::stringstream ss; 303 | ss << self.addr; 304 | std::size_t hashcode = std::hash{}(ss.str()); 305 | return std::string(""); 307 | }) 308 | .def("__getitem__", &ipcl::CipherText::getElement) 309 | .def("__getitem__", 310 | [](const ipcl::CipherText& self, py::slice slice) { 311 | size_t start, stop, step, slicelength; 312 | if (!slice.compute(self.getSize(), &start, &stop, &step, 313 | &slicelength)) 314 | throw py::error_already_set(); 315 | if (step != 1) throw std::runtime_error("Step size not supported"); 316 | return self.getChunk(start, slicelength); 317 | }) 318 | .def("__add__", 319 | [](const ipcl::CipherText& self, const ipcl::CipherText& other) { 320 | return self + other; 321 | }) 322 | .def("__add__", [](const ipcl::CipherText& self, 323 | const ipcl::PlainText& other) { return self + other; }) 324 | .def("__mul__", [](const ipcl::CipherText& self, 325 | const ipcl::PlainText& other) { return self * other; }) 326 | .def("__len__", &ipcl::CipherText::getSize) 327 | .def("getCipherText", &ipcl::CipherText::getCipherText) 328 | .def("rotate", &ipcl::CipherText::rotate, 329 | "Rotate ipclCipherText container") 330 | .def( 331 | "getElementVec", 332 | [](const ipcl::CipherText& self, const size_t& n) { 333 | std::vector vec = self.getElementVec(n); 334 | py::list l_vec = py::cast(vec); 335 | return l_vec; 336 | }, 337 | "Get element in little endian vector") 338 | .def_property_readonly("public_key", 339 | [](const ipcl::CipherText& self) { 340 | std::shared_ptr pk = 341 | self.getPubKey(); 342 | return pk; 343 | }) 344 | .def("getElementHex", &ipcl::CipherText::getElementHex, 345 | "Get element in hexadecimal string") 346 | .def( 347 | "getTexts", 348 | [](const ipcl::CipherText& self) { 349 | std::vector vec = self.getTexts(); 350 | py::list l_vec = py::cast(vec); 351 | return l_vec; 352 | }, 353 | "Get container in BigNumber vector") 354 | .def("getSize", &ipcl::CipherText::getSize, 355 | "Get size of container") 356 | .def(py::pickle( 357 | [](const ipcl::CipherText& self) { // __getstate__ 358 | std::vector vec = self.getTexts(); 359 | size_t length = self.getSize(); 360 | py::list l_bn; 361 | for (auto it : vec) l_bn.append(ipclPythonUtils::BN2bytes(it)); 362 | std::shared_ptr _pk = self.getPubKey(); 363 | py::tuple t_pubkey = ipclPythonUtils::getTupleIpclPubKey(*_pk); 364 | return py::make_tuple(length, l_bn, t_pubkey); 365 | }, 366 | [](const py::tuple& t) { // __setstate__ 367 | size_t length = static_cast(py::cast(t[0])); 368 | std::vector vec(length); 369 | py::list l_lbn = t[1]; 370 | for (size_t i = 0; i < length; ++i) { 371 | py::bytes lbn = l_lbn[i]; 372 | vec[i] = ipclPythonUtils::pyByte2BN(lbn); 373 | } 374 | py::tuple t_pubkey = t[2]; 375 | ipcl::PublicKey pubkey = ipclPythonUtils::setIpclPubKey(t_pubkey); 376 | return ipcl::CipherText(pubkey, vec); 377 | })); 378 | } 379 | 380 | void def_BigNumber(py::module& m) { 381 | py::class_>(m, "ipclBigNumber") 382 | .def(py::init(), "ipclBigNumber constructor") 383 | .def(py::init( 384 | [](Ipp32u data) { return std::make_shared(data); }), 385 | "ipclBigNumber constructor") 386 | .def(py::init([](const py::list& data) { 387 | size_t length = data.size(); 388 | std::vector pData = py::cast>(data); 389 | return std::shared_ptr( 390 | new BigNumber(pData.data(), pData.size())); 391 | }), 392 | "ipclBigNumber constructor with list of integers - little endian " 393 | "format") 394 | .def(py::init([](const py::array_t& data) { 395 | py::buffer_info buffer_info = data.request(); 396 | 397 | Ipp32u* pData = static_cast(buffer_info.ptr); 398 | std::vector shape = buffer_info.shape; 399 | return std::shared_ptr(new BigNumber(pData, shape[0])); 400 | }), 401 | "ipclBigNumber constructor with array of integers - little endian " 402 | "format") 403 | .def(py::init([](const py::bytes& data) { 404 | return std::shared_ptr( 405 | new BigNumber(ipclPythonUtils::pyByte2BN(data))); 406 | })) 407 | .def("__repr__", 408 | [](BigNumber const& self) { 409 | std::stringstream ss_hash; 410 | ss_hash << self.addr; 411 | std::size_t hashcode = std::hash{}(ss_hash.str()); 412 | std::string s_dec = baseconverter::BN2dec(self); 413 | return std::string(""); 416 | }) 417 | .def("__str__", 418 | [](BigNumber const& self) { 419 | std::string s_dec = baseconverter::BN2dec(self); 420 | return s_dec; 421 | }) 422 | .def("__getitem__", 423 | [](BigNumber const& self, size_t n) { 424 | int bnBitLen; 425 | Ipp32u* bnData; 426 | ippsRef_BN(nullptr, &bnBitLen, &bnData, self); 427 | int length = BITSIZE_WORD(bnBitLen); 428 | if (n >= length) 429 | throw std::out_of_range("Index is larger than size: " + 430 | std::to_string(length)); 431 | return bnData[n]; 432 | }) 433 | .def("__eq__", [](BigNumber const& self, 434 | BigNumber const& other) { return self == other; }) 435 | .def("__ne__", [](BigNumber const& self, 436 | BigNumber const& other) { return self != other; }) 437 | .def("__add__", [](BigNumber const& self, 438 | BigNumber const& other) { return self + other; }) 439 | .def("__sub__", [](BigNumber const& self, 440 | BigNumber const& other) { return self - other; }) 441 | .def("__iadd__", 442 | [](BigNumber& self, BigNumber const& other) { 443 | self += other; 444 | return self; 445 | }) 446 | .def("__mul__", [](BigNumber const& self, 447 | BigNumber const& other) { return self * other; }) 448 | .def("__mul__", 449 | [](BigNumber const& self, Ipp32u other) { return self * other; }) 450 | .def("__lt__", [](BigNumber const& self, 451 | BigNumber const& other) { return self < other; }) 452 | .def("__le__", [](BigNumber const& self, 453 | BigNumber const& other) { return self <= other; }) 454 | .def("__gt__", [](BigNumber const& self, 455 | BigNumber const& other) { return self > other; }) 456 | .def("__ge__", [](BigNumber const& self, 457 | BigNumber const& other) { return self >= other; }) 458 | .def("DwordSize", &BigNumber::DwordSize) 459 | .def("BitSize", &BigNumber::BitSize) 460 | .def( 461 | "data", 462 | [](BigNumber const& self) { 463 | int bnBitLen; 464 | Ipp32u* bnData = nullptr; 465 | ippsRef_BN(nullptr, &bnBitLen, &bnData, self); 466 | int length = BITSIZE_WORD(bnBitLen); 467 | py::list l_dt; 468 | for (int i = 0; i < length; i++) l_dt.append(bnData[i]); 469 | return py::make_tuple(length, l_dt); 470 | }, 471 | "return ipclBigNumber in size and list of 32bit unsigned integers") 472 | .def( 473 | "to_bytes", 474 | [](BigNumber const& self) { return ipclPythonUtils::BN2bytes(self); }) 475 | .def_property_readonly_static( 476 | "Zero", [](const py::object&) { return BigNumber::Zero(); }) 477 | .def_property_readonly_static( 478 | "One", [](const py::object&) { return BigNumber::One(); }) 479 | .def_property_readonly_static( 480 | "Two", [](const py::object&) { return BigNumber::Two(); }) 481 | .def(py::pickle( 482 | [](const BigNumber& self) { // __getstate__ 483 | auto btl = ipclPythonUtils::BN2bytes(self); 484 | return py::make_tuple(btl); 485 | }, 486 | [](py::tuple t) { // __setstate__ 487 | py::bytes l_dt = t[0]; 488 | return std::shared_ptr( 489 | new BigNumber(ipclPythonUtils::pyByte2BN(l_dt))); 490 | })); 491 | } 492 | -------------------------------------------------------------------------------- /src/ipcl_python/ipcl_python.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from .bindings.fixedpoint import FixedPointNumber 5 | from .bindings.ipcl_bindings import ( 6 | ipclKeypair, 7 | ipclPublicKey, 8 | ipclPrivateKey, 9 | ipclPlainText, 10 | ipclCipherText, 11 | ipclBigNumber, 12 | ) 13 | 14 | import numpy as np 15 | import gmpy2 16 | 17 | from typing import Callable, Union, Optional, Tuple 18 | 19 | 20 | class PaillierKeypair: 21 | @staticmethod 22 | def generate_keypair( 23 | n_length: int = 1024, enable_DJN: bool = True 24 | ) -> Tuple["PaillierPublicKey", "PaillierPrivateKey"]: 25 | """ 26 | Invokes IPCL keypair generation 27 | 28 | Args: 29 | n_length: key length to generate public and private key pair. 30 | Supports up to 2048 bits 31 | enable_DJN: Enables faster encrypt/decrypt scheme by 32 | Damgård-Jurik-Nielsen (DJN) 33 | 34 | Returns: 35 | (PaillierPublicKey, PaillierPrivateKey): Tuple of public and 36 | private key 37 | """ 38 | 39 | pub, pri = ipclKeypair.generate_keypair(n_length, enable_DJN) 40 | return PaillierPublicKey(pub), PaillierPrivateKey(pri) 41 | 42 | 43 | class PaillierPublicKey: 44 | def __init__( 45 | self, 46 | key: Union[ipclPublicKey, "PaillierPublicKey", int], 47 | n_length: Optional[int] = None, 48 | enable_DJN: Optional[bool] = None, 49 | ): 50 | """ 51 | PaillierPublicKey constructor 52 | 53 | Args: 54 | key: ipcl_bindings.PaillierPublicKey or PaillierPublicKey 55 | n_length: (default=None) Needed when constructing w/ arbitrary key 56 | """ 57 | if isinstance(key, ipclPublicKey): 58 | self.n = BNUtils.BN2int(key.n) 59 | self.pubkey = key 60 | elif isinstance(key, PaillierPublicKey): 61 | self = key 62 | elif ( 63 | isinstance(key, int) 64 | and n_length is not None 65 | and enable_DJN is not None 66 | ): 67 | self.n = key 68 | self.pubkey = ipclPublicKey( 69 | BNUtils.int2BN(self.n), n_length, enable_DJN 70 | ) 71 | else: 72 | raise ValueError( 73 | "PaillierPublicKey: PubKey should be either key value (n)," 74 | "PaillierPublicKey or IPP-PaillierPublicKey object" 75 | ) 76 | self.max_int = self.n // 3 - 1 77 | self.nsquare = self.n * self.n 78 | 79 | def __getstate__(self): 80 | return self.pubkey 81 | 82 | def __setstate__(self, state): 83 | self.pubkey = state 84 | self.n = BNUtils.BN2int(self.pubkey.n) 85 | self.max_int = self.n // 3 - 1 86 | self.nsquare = self.n * self.n 87 | 88 | def __repr__(self): 89 | return repr(self.pubkey) 90 | 91 | def __eq__(self, other): 92 | return self.n == other.n 93 | 94 | def __hash__(self): 95 | return hash(self.pubkey) 96 | 97 | def apply_obfuscator(self, x: Union[int, ipclBigNumber]): 98 | # apply_obfuscator function is embedded in encrypt 99 | if isinstance(x, int): 100 | return self.pubkey.apply_obfuscator(BNUtils.int2BN(x)) 101 | return self.pubkey.apply_obfuscator(x) 102 | 103 | def raw_encrypt( 104 | self, plaintext: Union[np.ndarray, list, int, float] 105 | ) -> "PaillierEncryptedNumber": 106 | return self.encrypt(plaintext, apply_obfuscator=False) 107 | 108 | def encrypt( 109 | self, 110 | values: Union[np.ndarray, list, int, float], 111 | apply_obfuscator: bool = True, 112 | ) -> "PaillierEncryptedNumber": 113 | """ 114 | Encrypts scalar or list/array of scalars 115 | 116 | Args: 117 | value: integer/float scalar of list/array of integers/floats 118 | apply_obfuscator: (default=True) Applies obfuscator to ciphertext. 119 | 120 | Returns: 121 | A single PaillierEncryptedNumber (scalar value) or numpy.ndarray 122 | of PaillierEncryptedNumber (list/array of integer/floats) 123 | """ 124 | if np.isscalar(values): 125 | values = [values] 126 | 127 | if not all( 128 | isinstance(value, (int, float, np.integer, np.floating)) 129 | for value in values 130 | ): 131 | raise ValueError( 132 | "PaillierPublicKey.encrypt: input value(s) should be " 133 | "integer or float" 134 | ) 135 | 136 | encodings = [ 137 | FixedPointNumber.encode(value, self.n, self.max_int) 138 | for value in values 139 | ] 140 | encs = [BNUtils.int2BN(encoding.encoding) for encoding in encodings] 141 | expos = [encoding.exponent for encoding in encodings] 142 | 143 | plaintext = ipclPlainText(encs) 144 | ct = self.pubkey.encrypt(plaintext, apply_obfuscator) 145 | return PaillierEncryptedNumber( 146 | self, ct, exponents=expos, length=len(values) 147 | ) 148 | 149 | 150 | class PaillierPrivateKey: 151 | def __init__( 152 | self, 153 | key: Union[ipclPrivateKey, ipclPublicKey, PaillierPublicKey], 154 | p: Optional[int] = None, 155 | q: Optional[int] = None, 156 | ): 157 | """ 158 | PaillierPrivateKey constructor 159 | 160 | Args: 161 | key: ipcl_bindings.PaillierPrivateKey or 162 | ipcl_bindings.PaillierPublicKey with p and q (private keys) 163 | """ 164 | if isinstance(key, ipclPrivateKey): 165 | self.prikey = key 166 | self.__n = BNUtils.BN2int(key.n) 167 | self.__max_int = self.__n // 3 - 1 168 | elif isinstance(key, ipclPublicKey) and p is not None and q is not None: 169 | self.prikey = ipclPrivateKey( 170 | key, BNUtils.int2BN(p), BNUtils.int2BN(q) 171 | ) 172 | self.__n = BNUtils.BN2int(key.n) 173 | self.__max_int = self.__n // 3 - 1 174 | elif ( 175 | isinstance(key, PaillierPublicKey) 176 | and p is not None 177 | and q is not None 178 | ): 179 | self.prikey = ipclPrivateKey( 180 | key.pubkey, BNUtils.int2BN(p), BNUtils.int2BN(q) 181 | ) 182 | self.__n = key.n 183 | self.__max_int = key.max_int 184 | else: 185 | raise KeyError( 186 | "PaillierPrivateKey: key should be either Private key or" 187 | " Public key (with p and q)" 188 | ) 189 | 190 | def __getstate__(self): 191 | return (self.prikey, self.__n, self.__max_int) 192 | 193 | def __setstate__(self, state): 194 | (self.prikey, self.__n, self.__max_int) = state 195 | 196 | def __eq__(self, other: "PaillierPrivateKey"): 197 | return (self.prikey.p == other.prikey.p) and ( 198 | self.prikey.q == other.prikey.q 199 | ) 200 | 201 | def __hash__(self): 202 | return hash(self.prikey) 203 | 204 | def __repr__(self): 205 | return repr(self.prikey) 206 | 207 | def raw_decrypt(self, ciphertext: "PaillierEncryptedNumber") -> int: 208 | if ciphertext.public_key.n != self.__n: 209 | raise ValueError( 210 | "PaillierPrivateKey.raw_decrypt: Public key mismatch" 211 | ) 212 | 213 | decrypted = self.prikey.decrypt(ciphertext.ciphertext()) 214 | l_pt = decrypted.getTexts() 215 | ret = [BNUtils.BN2int(i) for i in l_pt] 216 | 217 | return ret if len(ciphertext) > 1 else ret[0] 218 | 219 | def decrypt( 220 | self, 221 | encrypted_number: "PaillierEncryptedNumber", 222 | ): 223 | """ 224 | Decrypts PaillierEncryptedNumber 225 | 226 | Args: 227 | encrypted_number: PaillierEncryptedNumber 228 | 229 | Returns: 230 | array or single integer of decrypted encrypted number 231 | """ 232 | if encrypted_number.public_key.n != self.__n: 233 | raise ValueError("PailierPrivateKey.decrypt: Public key mismatch") 234 | 235 | decrypted = self.prikey.decrypt(encrypted_number.ciphertext()) 236 | l_pt, l_expo = decrypted.getTexts(), encrypted_number.exponent() 237 | 238 | ret = [ 239 | FixedPointNumber( 240 | BNUtils.BN2int(pt), expo, self.__n, self.__max_int 241 | ).decode() 242 | for pt, expo in zip(l_pt, l_expo) 243 | ] 244 | 245 | return ret if len(encrypted_number) > 1 else ret[0] 246 | 247 | 248 | class PaillierEncryptedNumber: 249 | def __init__( 250 | self, 251 | public_key: PaillierPublicKey, 252 | ciphertext: ipclCipherText, 253 | exponents: list, 254 | length: int, 255 | ): 256 | """ 257 | PaillierEncryptedNumber constructor 258 | 259 | Args: 260 | public_key: PaillierPublicKey 261 | ciphertext: ipcl_bindings.PaillierEncryptedNumber 262 | exponent: exponent of ciphertext 263 | """ 264 | 265 | if ciphertext.public_key != public_key.pubkey: 266 | raise ValueError("PaillierEncryptedNumber: public key mismatch") 267 | self.__exponents = exponents 268 | self.public_key = public_key 269 | self.__ipclCipherText = ciphertext 270 | self.__length = length 271 | 272 | def __invert_ct(self, ct): 273 | int_ct = BNUtils.BN2int(ct) 274 | return BNUtils.int2BN( 275 | int(gmpy2.invert(int_ct, self.public_key.nsquare)) 276 | ) 277 | 278 | def __repr__(self): 279 | return repr(self.__ipclCipherText) 280 | 281 | def __getstate__(self) -> tuple: 282 | return ( 283 | self.public_key, 284 | len(self), 285 | self.exponent(), 286 | [BNUtils.BN2int(i) for i in self.ciphertextBN()], 287 | ) 288 | 289 | def __setstate__(self, state: tuple): 290 | ( 291 | self.public_key, 292 | self.__length, 293 | self.__exponents, 294 | ciphertextPyInt, 295 | ) = state 296 | self.__ipclCipherText = ipclCipherText( 297 | self.public_key.pubkey, [BNUtils.int2BN(i) for i in ciphertextPyInt] 298 | ) 299 | 300 | def __len__(self) -> int: 301 | return self.__length 302 | 303 | def ciphertext(self) -> ipclCipherText: 304 | return self.__ipclCipherText 305 | 306 | def ciphertextBN(self, idx: Optional[int] = None): 307 | """ 308 | Getter function for obfuscated ciphertext 309 | Args:= 310 | idx: index in the ciphertext array. If None, returns 311 | list of entire ciphertexts 312 | Returns: 313 | if idx is None, returns entire ciphertexts in lists, otherwise 314 | returns ciphertext of index 315 | """ 316 | if idx is None: 317 | return self.__ipclCipherText.getTexts() 318 | 319 | if not 0 <= idx < self.__length: 320 | raise IndexError("ciphertext: idx out of range") 321 | 322 | return self.__ipclCipherText[idx] 323 | 324 | def exponent(self, idx: Optional[int] = None): 325 | """ 326 | Getter function for exponents 327 | Args: 328 | idx: index in the list of exponents. If None, returns 329 | entire exponents list 330 | Returns: 331 | if idx is None, returns entire exponents list, otherwise 332 | returns exponent in index 333 | """ 334 | if idx is None: 335 | return self.__exponents 336 | 337 | if not 0 <= idx < self.__length: 338 | raise IndexError("exponent: idx out of range") 339 | 340 | return self.__exponents[idx] 341 | 342 | def apply_obfuscator(self): 343 | self.__ipclCipherText = ipclCipherText( 344 | self.public_key.pubkey, 345 | self.public_key.pubkey.apply_obfuscator(self.__ipclCipherText), 346 | ) 347 | 348 | def __getitem__(self, key: Union[int, slice]) -> "PaillierEncryptedNumber": 349 | if isinstance(key, int): 350 | key = slice(key, key + 1) 351 | 352 | if not 0 <= key.stop <= len(self) or not 0 <= key.start < len(self): 353 | raise IndexError("__getitem__: key out of range") 354 | 355 | ciphertextBN = self.__ipclCipherText[key] 356 | newCT = ipclCipherText(self.public_key.pubkey, ciphertextBN) 357 | 358 | return PaillierEncryptedNumber( 359 | self.public_key, newCT, self.__exponents[key], len(ciphertextBN) 360 | ) 361 | 362 | def __iter__(self) -> "PaillierEncryptedNumber": 363 | return (self[i] for i in range(len(self))) 364 | 365 | def __add__( 366 | self, 367 | other: Union["PaillierEncryptedNumber", np.ndarray, list, int, float], 368 | ) -> "PaillierEncryptedNumber": 369 | if ( 370 | self.__length == 1 371 | and isinstance(other, PaillierEncryptedNumber) 372 | and len(other) > 1 373 | ): 374 | return other.__raw_add(self) 375 | return self.__raw_add(other) 376 | 377 | def __radd__( 378 | self, 379 | other: Union["PaillierEncryptedNumber", np.ndarray, list, int, float], 380 | ) -> "PaillierEncryptedNumber": 381 | return self + other 382 | 383 | def __sub__( 384 | self, 385 | other: Union["PaillierEncryptedNumber", np.ndarray, list, int, float], 386 | ) -> "PaillierEncryptedNumber": 387 | if isinstance(other, list): 388 | other = np.array(other) 389 | return self.__raw_add(other * -1.0) 390 | 391 | def __rsub__( 392 | self, 393 | other: Union["PaillierEncryptedNumber", np.ndarray, list, int, float], 394 | ) -> "PaillierEncryptedNumber": 395 | if isinstance(other, PaillierEncryptedNumber): 396 | return other - self 397 | return (self * (-1.0)).__raw_add(other) 398 | 399 | def __rmul__( 400 | self, other: Union[np.ndarray, list, int, float] 401 | ) -> "PaillierEncryptedNumber": 402 | return self * other 403 | 404 | def __truediv__( 405 | self, other: Union[np.ndarray, list, int, float] 406 | ) -> "PaillierEncryptedNumber": 407 | if isinstance(other, list): 408 | other = np.array(other) 409 | inv_other = 1.0 / other 410 | return self * inv_other 411 | 412 | def __mul__( 413 | self, other: Union[np.ndarray, list, float, int] 414 | ) -> "PaillierEncryptedNumber": 415 | if np.isscalar(other): # scalar - do broadcast 416 | encode = FixedPointNumber.encode( 417 | other, self.public_key.n, self.public_key.max_int 418 | ) 419 | pt = encode.encoding 420 | pt_exponent = encode.exponent 421 | if not 0 <= pt < self.public_key.n: 422 | raise ValueError( 423 | f"PaillierEncryptedNumber.__mul__: Scalar out of" 424 | f"bounds: {pt}" 425 | ) 426 | if pt >= self.public_key.n - self.public_key.max_int: 427 | # invert all ciphertext 428 | neg_ct = [self.__invert_ct(ct) for ct in self.ciphertextBN()] 429 | res_expo = [expo + pt_exponent for expo in self.exponent()] 430 | 431 | neg_ct_ipclCipherText = ipclCipherText( 432 | self.public_key.pubkey, neg_ct 433 | ) 434 | neg_pt = BNUtils.int2BN(self.public_key.n - pt) 435 | neg_pt_ipclPlainText = ipclPlainText(neg_pt) 436 | 437 | res_ct = neg_ct_ipclCipherText * neg_pt_ipclPlainText 438 | 439 | return PaillierEncryptedNumber( 440 | self.public_key, res_ct, res_expo, self.__length 441 | ) 442 | 443 | res_expo = [expo + pt_exponent for expo in self.exponent()] 444 | ct_ipclCipherText = self.ciphertext() 445 | pt_ipclPlainText = ipclPlainText(BNUtils.int2BN(pt)) 446 | 447 | else: # non scalar 448 | if len(other) != self.__length: 449 | raise ValueError( 450 | "PaillierEncryptedNumber.__mul__: Multiply size mismatch" 451 | ) 452 | l_bn = self.ciphertextBN() 453 | 454 | encodes = [ 455 | FixedPointNumber.encode( 456 | pt, self.public_key.n, self.public_key.max_int 457 | ) 458 | for pt in other 459 | ] 460 | res_expo = [ 461 | ct_expo + encode.exponent 462 | for ct_expo, encode in zip(self.exponent(), encodes) 463 | ] 464 | 465 | pts = [encode.encoding for encode in encodes] 466 | for pt in pts: 467 | if not 0 <= pt < self.public_key.n: 468 | raise ValueError(f"Scalar out of bounds: {pt}") 469 | 470 | cond = self.public_key.n - self.public_key.max_int 471 | # invert corresponding ciphertext if less than above condition 472 | this_pt = [ 473 | BNUtils.int2BN(pt if pt < cond else self.public_key.n - pt) 474 | for ct, pt in zip(l_bn, pts) 475 | ] 476 | this_ct = [ 477 | ct if pt < cond else self.__invert_ct(ct) 478 | for ct, pt in zip(l_bn, pts) 479 | ] 480 | 481 | ct_ipclCipherText = ipclCipherText(self.public_key.pubkey, this_ct) 482 | pt_ipclPlainText = ipclPlainText(this_pt) 483 | 484 | res_ct = ct_ipclCipherText * pt_ipclPlainText 485 | 486 | return PaillierEncryptedNumber( 487 | self.public_key, res_ct, res_expo, self.__length 488 | ) 489 | 490 | def __raw_add( 491 | self, 492 | other: Union["PaillierEncryptedNumber", int, float, np.ndarray, list], 493 | ) -> "PaillierEncryptedNumber": 494 | # PlainText array or list 495 | if isinstance(other, (np.ndarray, list)): 496 | if self.__length != len(other): 497 | raise ValueError( 498 | "PaillierEncryptedNumber.__raw_add: array(list) size" 499 | " mismatch with PaillierEncryptedNumber" 500 | ) 501 | other = self.public_key.encrypt(other, apply_obfuscator=False) 502 | # PlainText scalar - broadcasting 503 | elif np.isscalar(other) and isinstance(other, (int, float)): 504 | other = self.public_key.encrypt(other, apply_obfuscator=False) 505 | elif isinstance(other, PaillierEncryptedNumber): 506 | if self.public_key != other.public_key: 507 | raise ValueError( 508 | "PaillierEncryptedNumber.__raw_add: PublicKey mismatch" 509 | ) 510 | if self.__length != len(other) and len(other) > 1: 511 | raise ValueError( 512 | "PaillierEncryptedNumber.__raw_add: CipherText size" 513 | " mismatch with PaillierEncryptedNumber" 514 | ) 515 | 516 | self_ct_aligned, other_aligned, res_expo = self.__align_exponent( 517 | self.ciphertext(), 518 | self.exponent(), 519 | other.ciphertext(), 520 | other.exponent(), 521 | ) 522 | 523 | res_ct = self_ct_aligned + other_aligned 524 | return PaillierEncryptedNumber( 525 | self.public_key, res_ct, res_expo, self.__length 526 | ) 527 | 528 | def increase_exponent_to( 529 | self, 530 | x_ct: ipclCipherText, 531 | x_expo: Union[np.ndarray, list], 532 | exponent: int, 533 | ) -> ipclCipherText: 534 | """ 535 | Increases exponent of py_ipp_paillier.PaillierEncryptedNumber 536 | to target exponent 537 | Args: 538 | x_ct: ipclCipherText 539 | x_expo: list of exponents of x 540 | exponent: target exponent. Needs to be larger than current exponent 541 | Returns: 542 | Updated encrypted number with increased exponent 543 | """ 544 | 545 | expo_diff = exponent - np.fromiter(x_expo, np.int32) 546 | idx_to_multiply = np.asarray(expo_diff > 0).nonzero()[0] 547 | 548 | if idx_to_multiply.size > 0: 549 | x_to_multiply = [x_ct[i] for i in idx_to_multiply] 550 | x_factor = [ 551 | BNUtils.int2BN(pow(FixedPointNumber.BASE, expo_diff[i].item())) 552 | for i in idx_to_multiply 553 | ] 554 | 555 | x_to_multiply_ipclCipherText = ipclCipherText( 556 | self.public_key.pubkey, x_to_multiply 557 | ) 558 | x_factor_ipclPlainText = ipclPlainText(x_factor) 559 | x_ipclCipherText_factored = ( 560 | x_to_multiply_ipclCipherText * x_factor_ipclPlainText 561 | ) 562 | 563 | ret = np.fromiter(x_ct.getTexts(), ipclBigNumber) 564 | ret[idx_to_multiply] = x_ipclCipherText_factored.getTexts() 565 | 566 | return ipclCipherText(self.public_key.pubkey, ret.tolist()) 567 | 568 | return x_ct 569 | 570 | def __align_exponent( 571 | self, 572 | x_ct: ipclCipherText, 573 | x_expo: Union[list, np.ndarray], 574 | y_ct: ipclCipherText, 575 | y_expo: Union[list, np.ndarray], 576 | ) -> Tuple[ipclCipherText, ipclCipherText, list]: 577 | """ 578 | Aligns exponent of self and other ipclCipherText 579 | Args: 580 | ct: target ipclCipherText 581 | expo: list of exponents of the target ciphertext 582 | Returns: 583 | tuple of two exponent matching ipclCipherTexts (self, target) and 584 | list of matched exponents 585 | """ 586 | 587 | x_factor = [] 588 | 589 | x_to_multiply = [] 590 | x_idx_to_multiply = [] 591 | 592 | y_factor = [] 593 | 594 | y_idx_to_multiply = [] 595 | 596 | ret_exponent = list(x_expo) 597 | 598 | # if broadcasting 599 | if len(y_ct) == 1: 600 | for i, _x_expo in enumerate(x_expo): 601 | if _x_expo > y_expo[0]: 602 | y_factor.append( 603 | BNUtils.int2BN( 604 | pow(FixedPointNumber.BASE, _x_expo - y_expo[0]) 605 | ) 606 | ) 607 | y_idx_to_multiply.append(i) 608 | elif _x_expo < y_expo[0]: 609 | x_factor.append( 610 | BNUtils.int2BN( 611 | pow(FixedPointNumber.BASE, y_expo[0] - _x_expo) 612 | ) 613 | ) 614 | x_idx_to_multiply.append(i) 615 | x_to_multiply.append(x_ct[i]) 616 | ret_exponent[i] = y_expo[0] 617 | 618 | x_factored_CipherText = None 619 | if len(x_idx_to_multiply) > 0: 620 | x_to_multiply_ipclCipherText = ipclCipherText( 621 | self.public_key.pubkey, x_to_multiply 622 | ) 623 | x_factor_PlainText = ipclPlainText(x_factor) 624 | 625 | x_factored_CipherText_tmp = ( 626 | x_to_multiply_ipclCipherText * x_factor_PlainText 627 | ) 628 | x_factored_CipherText = np.fromiter( 629 | x_ct.getTexts(), ipclBigNumber 630 | ) 631 | x_factored_CipherText[ 632 | x_idx_to_multiply 633 | ] = x_factored_CipherText_tmp.getTexts() 634 | 635 | y_factored_CipherText = None 636 | if len(y_idx_to_multiply) > 0: 637 | y_to_multiply_ipclCipherText = ipclCipherText( 638 | self.public_key.pubkey, 639 | y_ct.getTexts() * len(y_idx_to_multiply), 640 | ) 641 | y_factor_PlainText = ipclPlainText(y_factor) 642 | y_factored_CipherText_tmp = ( 643 | y_to_multiply_ipclCipherText * y_factor_PlainText 644 | ) 645 | 646 | y_factored_CipherText = np.repeat(y_ct.getTexts(), len(x_ct)) 647 | y_factored_CipherText[ 648 | y_idx_to_multiply 649 | ] = y_factored_CipherText_tmp.getTexts() 650 | 651 | return ( 652 | ( 653 | x_ct 654 | if x_factored_CipherText is None 655 | else ipclCipherText( 656 | self.public_key.pubkey, x_factored_CipherText.tolist() 657 | ) 658 | ), 659 | ( 660 | y_ct 661 | if y_factored_CipherText is None 662 | else ipclCipherText( 663 | self.public_key.pubkey, y_factored_CipherText.tolist() 664 | ) 665 | ), 666 | ret_exponent, 667 | ) 668 | 669 | else: 670 | y_to_multiply = [] 671 | 672 | for i, (_x_expo, _y_expo) in enumerate(zip(x_expo, y_expo)): 673 | if _x_expo > _y_expo: 674 | y_factor.append( 675 | BNUtils.int2BN( 676 | int(pow(FixedPointNumber.BASE, _x_expo - _y_expo)) 677 | ) 678 | ) 679 | y_idx_to_multiply.append(i) 680 | y_to_multiply.append(y_ct[i]) 681 | elif _x_expo < _y_expo: 682 | x_factor.append( 683 | BNUtils.int2BN( 684 | int(pow(FixedPointNumber.BASE, _y_expo - _x_expo)) 685 | ) 686 | ) 687 | x_idx_to_multiply.append(i) 688 | x_to_multiply.append(x_ct[i]) 689 | ret_exponent[i] = _y_expo 690 | 691 | x_factored_CipherText = None 692 | if len(x_idx_to_multiply) > 0: 693 | x_to_multiply_ipclCipherText = ipclCipherText( 694 | self.public_key.pubkey, x_to_multiply 695 | ) 696 | x_factor_PlainText = ipclPlainText(x_factor) 697 | 698 | x_factored_CipherText_tmp = ( 699 | x_to_multiply_ipclCipherText * x_factor_PlainText 700 | ) 701 | 702 | x_factored_CipherText = np.fromiter( 703 | x_ct.getTexts(), ipclBigNumber 704 | ) 705 | x_factored_CipherText[ 706 | x_idx_to_multiply 707 | ] = x_factored_CipherText_tmp.getTexts() 708 | 709 | y_factored_CipherText = None 710 | if len(y_idx_to_multiply) > 0: 711 | y_to_multiply_ipclCipherText = ipclCipherText( 712 | self.public_key.pubkey, y_to_multiply 713 | ) 714 | y_factor_PlainText = ipclPlainText(y_factor) 715 | y_factored_CipherText_tmp = ( 716 | y_to_multiply_ipclCipherText * y_factor_PlainText 717 | ) 718 | y_factored_CipherText = np.fromiter( 719 | y_ct.getTexts(), ipclBigNumber 720 | ) 721 | y_factored_CipherText[ 722 | y_idx_to_multiply 723 | ] = y_factored_CipherText_tmp.getTexts() 724 | 725 | return ( 726 | ( 727 | x_ct 728 | if x_factored_CipherText is None 729 | else ipclCipherText( 730 | self.public_key.pubkey, x_factored_CipherText.tolist() 731 | ) 732 | ), 733 | ( 734 | y_ct 735 | if y_factored_CipherText is None 736 | else ipclCipherText( 737 | self.public_key.pubkey, y_factored_CipherText.tolist() 738 | ) 739 | ), 740 | ret_exponent, 741 | ) 742 | 743 | def length(self) -> int: 744 | return self.__length 745 | 746 | def sum(self) -> "PaillierEncryptedNumber": 747 | max_exponent = max(self.exponent()) 748 | ct_aligned_ipclCipherText = self.increase_exponent_to( 749 | self.__ipclCipherText, self.exponent(), max_exponent 750 | ) 751 | 752 | temp_ct = ct_aligned_ipclCipherText.getTexts() 753 | res_ipclCipherText = ipclCipherText( 754 | self.public_key.pubkey, self.__padded_ct(temp_ct, len(self)) 755 | ) 756 | 757 | return PaillierEncryptedNumber( 758 | self.public_key, 759 | res_ipclCipherText, 760 | exponents=[max_exponent], 761 | length=1, 762 | ) 763 | 764 | def mean(self) -> "PaillierEncryptedNumber": 765 | return self.sum() / len(self) 766 | 767 | def dot(self, other: Union[np.ndarray, list]) -> "PaillierEncryptedNumber": 768 | if len(other) != len(self): 769 | raise ValueError( 770 | "PaillierEncryptedNumber.dot: input size mismatch with" 771 | " ciphertext" 772 | ) 773 | 774 | elemul = self * other 775 | return elemul.sum() 776 | 777 | def __matmul_idx_pt( 778 | self, other: np.ndarray, m: int, n: int, k: int, rhs: bool = False 779 | ) -> Callable: 780 | def r_iteration(i: int) -> Tuple[int, int, PaillierEncryptedNumber]: 781 | idx_self = i % n * k + i // n % k 782 | idx_other_x = i // (n * k) 783 | idx_other_y = i % n 784 | 785 | pt = ( 786 | other[idx_other_x][idx_other_y] 787 | if other.ndim == 2 788 | else other[idx_other_y] 789 | ) 790 | 791 | return i, idx_self, pt 792 | 793 | def iteration(i: int) -> Tuple[int, int, PaillierEncryptedNumber]: 794 | idx_self = i // (n * k) * n + i % n 795 | idx_other_x = i % n 796 | idx_other_y = i // n % k 797 | 798 | pt = ( 799 | other[idx_other_x][idx_other_y] 800 | if other.ndim == 2 801 | else other[idx_other_x] 802 | ) 803 | 804 | return i, idx_self, pt 805 | 806 | if rhs is True: 807 | return (r_iteration(i) for i in range(m * n * k)) 808 | return (iteration(i) for i in range(m * n * k)) 809 | 810 | def __padded_ct(self, temp_ct, n: int): 811 | max_step = 2 ** ((n - 1).bit_length()) 812 | if max_step > n: 813 | zero_ct = self.public_key.encrypt(0, apply_obfuscator=False) 814 | padded_list = temp_ct.getTexts() + zero_ct.ciphertextBN() * ( 815 | max_step - n 816 | ) 817 | padded_ct = ipclCipherText(self.public_key.pubkey, padded_list) 818 | else: 819 | padded_ct = temp_ct 820 | 821 | step = 1 822 | while step < max_step: 823 | rotated = padded_ct.rotate(step) 824 | padded_ct = padded_ct + rotated 825 | step = step << 1 826 | 827 | return padded_ct[0] 828 | 829 | def __matmul( 830 | self, other: np.ndarray, m: int, n: int, k: int, rhs: bool = False 831 | ) -> "PaillierEncryptedNumber": 832 | res_ct, res_expo = [], [] 833 | this_ct, this_pt = [], [] 834 | temp_expo = [] 835 | 836 | l_bn = self.ciphertextBN() 837 | 838 | for i, idx_self, _pt in self.__matmul_idx_pt(other, m, n, k, rhs): 839 | _ct = l_bn[idx_self] 840 | _ct_expo = self.exponent()[idx_self] 841 | 842 | encode = FixedPointNumber.encode( 843 | _pt, self.public_key.n, self.public_key.max_int 844 | ) 845 | pt = encode.encoding 846 | pt_expo = encode.exponent 847 | 848 | if not 0 <= pt < self.public_key.n: 849 | raise ValueError(f"Scalar out of bounds: {pt}") 850 | 851 | if pt >= self.public_key.n - self.public_key.max_int: 852 | # invert corresponding ciphertext 853 | this_pt.append(BNUtils.int2BN(self.public_key.n - pt)) 854 | this_ct.append(self.__invert_ct(_ct)) 855 | else: 856 | this_pt.append(BNUtils.int2BN(pt)) 857 | this_ct.append(_ct) 858 | 859 | temp_expo.append(_ct_expo + pt_expo) 860 | 861 | if (i + 1) % n == 0: 862 | ct_ipclCipherText = ipclCipherText( 863 | self.public_key.pubkey, this_ct 864 | ) 865 | pt_ipclPlainText = ipclPlainText(this_pt) 866 | 867 | temp_ct = ct_ipclCipherText * pt_ipclPlainText 868 | temp_ct = self.increase_exponent_to( 869 | temp_ct, temp_expo, max(temp_expo) 870 | ) 871 | 872 | res_ct.append(self.__padded_ct(temp_ct, n)) 873 | res_expo.append(max(temp_expo)) 874 | 875 | this_ct.clear() 876 | this_pt.clear() 877 | temp_expo.clear() 878 | 879 | res_ct = ipclCipherText(self.public_key.pubkey, res_ct) 880 | return PaillierEncryptedNumber(self.public_key, res_ct, res_expo, m * k) 881 | 882 | def __matmul__( 883 | self, other: Union[np.ndarray, list] 884 | ) -> "PaillierEncryptedNumber": 885 | if len(self) % len(other) != 0: 886 | raise ValueError( 887 | "PaillierEncryptedNumber.__matmul__: " 888 | "matrix multiply size mismatch" 889 | ) 890 | 891 | other = np.array(other) 892 | if other.ndim not in (1, 2): 893 | raise NotImplementedError( 894 | f"PaillierEncryptedNumber.__matmul__: input ndim {other.ndim}" 895 | f"not supported" 896 | ) 897 | 898 | # self.shape: (m x n), other.shape: (n x k), k could be none 899 | n = other.shape[0] 900 | k = other.shape[1] if other.ndim == 2 else 1 901 | m = len(self) // n 902 | 903 | return self.__matmul(other, m, n, k) 904 | 905 | def __rmatmul__( 906 | self, other: Union[np.ndarray, list] 907 | ) -> "PaillierEncryptedNumber": 908 | other = np.array(other) 909 | if other.ndim not in (1, 2): 910 | raise NotImplementedError( 911 | f"PaillierEncryptedNumber.__rmatmul__: input ndim {other.ndim} " 912 | f"not supported" 913 | ) 914 | 915 | # other.shape: (m x n), self.shape: (n x k) 916 | m = other.shape[0] if other.ndim == 2 else 1 917 | n = other.shape[1] if other.ndim == 2 else other.shape[0] 918 | if len(self) % n != 0: 919 | raise ValueError( 920 | "PaillierEncryptedNumber.__rmatmul__: matrix multiply" 921 | "size mismatch" 922 | ) 923 | k = len(self) // n 924 | 925 | return self.__matmul(other, m, n, k, rhs=True) 926 | 927 | def __imatmul__( 928 | self, other: Union[np.ndarray, list] 929 | ) -> "PaillierEncryptedNumber": 930 | return self @ other 931 | 932 | 933 | class BNUtils: 934 | # slice first then send array 935 | @staticmethod 936 | def int2Bytes(val: int) -> bytes: 937 | return val.to_bytes((val.bit_length() + 7) // 8, byteorder="little") 938 | 939 | @staticmethod 940 | def bytes2Int(val: bytes) -> int: 941 | return int.from_bytes(val, "little") 942 | 943 | @staticmethod 944 | def int2BN(val: int) -> ipclBigNumber: 945 | """ 946 | Convert Python integer to BigNumber 947 | 948 | Args: 949 | val: integer 950 | 951 | Returns: 952 | BigNumber representation of val 953 | """ 954 | 955 | if val == 0: 956 | return ipclBigNumber.Zero 957 | 958 | if val == 1: 959 | return ipclBigNumber.One 960 | 961 | if val == 2: 962 | return ipclBigNumber.Two 963 | 964 | return ipclBigNumber(BNUtils.int2Bytes(val)) 965 | 966 | @staticmethod 967 | def BN2int(val: ipclBigNumber) -> int: 968 | """ 969 | Convert BigNumber to Python integer 970 | 971 | Args: 972 | val: BigNumber 973 | 974 | Returns: 975 | Python integer representation of BigNumber 976 | """ 977 | return BNUtils.bytes2Int(val.to_bytes()) 978 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /tests/ipcl_python_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | import numpy as np 5 | import unittest 6 | from ipcl_python.ipcl_python import PaillierKeypair 7 | import time 8 | 9 | 10 | class TestPaillierEncryptedNumber(unittest.TestCase): 11 | def setUp(self): 12 | self.public_key, self.private_key = PaillierKeypair.generate_keypair( 13 | 2048 14 | ) 15 | self.startTime = time.perf_counter() 16 | 17 | def tearDown(self): 18 | t = time.perf_counter() - self.startTime 19 | print("%s: %.3f" % (self.id(), t)) 20 | 21 | def test_add(self): 22 | np.set_printoptions(suppress=True) 23 | x_li = np.ones(100) * np.random.randint(100) 24 | y_li = np.ones(100) * np.random.randint(1000) 25 | z_li = np.ones(100) * np.random.rand() 26 | t_li = list(range(100)) 27 | en_x_li = self.public_key.encrypt(x_li) 28 | en_y_li = self.public_key.encrypt(y_li) 29 | en_z_li = self.public_key.encrypt(z_li) 30 | en_t_li = self.public_key.encrypt(t_li) 31 | 32 | en_res = en_x_li + en_y_li + en_z_li + en_t_li 33 | 34 | res = x_li + y_li + z_li + t_li 35 | de_en_res = self.private_key.decrypt(en_res) 36 | 37 | for i in range(x_li.shape[0]): 38 | self.assertAlmostEqual(de_en_res[i], res[i]) 39 | 40 | def test_mul(self): 41 | np.set_printoptions(suppress=True) 42 | x_li = np.ones(100) * np.random.randint(100) 43 | y_li = np.ones(100) * np.random.randint(1000) * -1 44 | z_li = np.ones(100) * np.random.rand() 45 | t_li = list(range(100)) 46 | 47 | en_x_li = self.public_key.encrypt(x_li) 48 | en_res = (en_x_li * y_li + z_li) * t_li 49 | de_en_res = self.private_key.decrypt(en_res) 50 | 51 | res = (x_li * y_li + z_li) * t_li 52 | 53 | for i in range(x_li.shape[0]): 54 | self.assertAlmostEqual(de_en_res[i], res[i]) 55 | 56 | x = 9 57 | en_x = self.public_key.encrypt(x) 58 | 59 | for i in range(100): 60 | en_x = en_x + 5000 61 | en_x = en_x - 0.2 62 | x = x + 5000 - 0.2 63 | 64 | de_en_x = self.private_key.decrypt(en_x) 65 | 66 | self.assertAlmostEqual(de_en_x, x) 67 | 68 | def test_matmul(self): 69 | np.set_printoptions(suppress=True) 70 | for _ in range(10): 71 | m = np.random.randint(1, 9) 72 | n = np.random.randint(1, 9) 73 | k = np.random.randint(1, 9) 74 | x_li = np.random.rand(m, n) 75 | y_li = np.random.rand(n, k) 76 | 77 | res = x_li @ y_li 78 | 79 | en_x_li = self.public_key.encrypt(x_li.flatten()) 80 | en_res = en_x_li @ y_li 81 | de_en_res = self.private_key.decrypt(en_res) 82 | de_en_res = np.array(de_en_res).reshape([m, k]) 83 | 84 | np.allclose(de_en_res, res) 85 | 86 | def test_rmatmul(self): 87 | np.set_printoptions(suppress=True) 88 | for _ in range(10): 89 | m = np.random.randint(1, 9) 90 | n = np.random.randint(1, 9) 91 | k = np.random.randint(1, 9) 92 | x_li = np.random.rand(m, n).tolist() 93 | y_li = np.random.rand(n, k) 94 | 95 | res = x_li @ y_li 96 | 97 | en_y_li = self.public_key.encrypt(y_li.flatten()) 98 | en_res = x_li @ en_y_li 99 | de_en_res = self.private_key.decrypt(en_res) 100 | de_en_res = np.array(de_en_res).reshape([m, k]) 101 | 102 | np.allclose(de_en_res, res) 103 | 104 | def test_imatmul(self): 105 | np.set_printoptions(suppress=True) 106 | for _ in range(10): 107 | m = np.random.randint(1, 9) 108 | n = np.random.randint(1, 9) 109 | k = np.random.randint(1, 9) 110 | x_li = np.random.rand(m, n) 111 | y_li = np.random.rand(n, k) 112 | 113 | en_x_li = self.public_key.encrypt(x_li.flatten()) 114 | en_x_li @= y_li 115 | de_en_res = self.private_key.decrypt(en_x_li) 116 | de_en_res = np.array(de_en_res).reshape([m, k]) 117 | 118 | x_li = x_li @ y_li 119 | np.allclose(de_en_res, x_li) 120 | 121 | 122 | if __name__ == "__main__": 123 | # unittest.main() 124 | suite = unittest.TestLoader().loadTestsFromTestCase( 125 | TestPaillierEncryptedNumber 126 | ) 127 | unittest.TextTestRunner(verbosity=0).run(suite) 128 | --------------------------------------------------------------------------------