lightphe 0.0.4__tar.gz → 0.0.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lightphe-0.0.4 → lightphe-0.0.6}/PKG-INFO +49 -3
- {lightphe-0.0.4 → lightphe-0.0.6}/README.md +48 -2
- {lightphe-0.0.4 → lightphe-0.0.6}/lightphe/__init__.py +114 -12
- {lightphe-0.0.4 → lightphe-0.0.6}/lightphe/commons/logger.py +2 -1
- lightphe-0.0.6/lightphe/commons/phe_utils.py +58 -0
- {lightphe-0.0.4 → lightphe-0.0.6}/lightphe/cryptosystems/Benaloh.py +1 -1
- {lightphe-0.0.4 → lightphe-0.0.6}/lightphe/cryptosystems/DamgardJurik.py +1 -1
- {lightphe-0.0.4 → lightphe-0.0.6}/lightphe/cryptosystems/ElGamal.py +3 -2
- {lightphe-0.0.4 → lightphe-0.0.6}/lightphe/cryptosystems/EllipticCurveElGamal.py +3 -2
- {lightphe-0.0.4 → lightphe-0.0.6}/lightphe/cryptosystems/GoldwasserMicali.py +3 -1
- {lightphe-0.0.4 → lightphe-0.0.6}/lightphe/cryptosystems/NaccacheStern.py +1 -1
- {lightphe-0.0.4 → lightphe-0.0.6}/lightphe/cryptosystems/OkamotoUchiyama.py +11 -8
- {lightphe-0.0.4 → lightphe-0.0.6}/lightphe/cryptosystems/Paillier.py +1 -1
- {lightphe-0.0.4 → lightphe-0.0.6}/lightphe/cryptosystems/RSA.py +3 -2
- {lightphe-0.0.4 → lightphe-0.0.6}/lightphe/elliptic/Weierstrass.py +1 -0
- {lightphe-0.0.4 → lightphe-0.0.6}/lightphe/models/Ciphertext.py +13 -13
- {lightphe-0.0.4 → lightphe-0.0.6}/lightphe/models/Homomorphic.py +5 -6
- lightphe-0.0.6/lightphe/models/Tensor.py +186 -0
- {lightphe-0.0.4 → lightphe-0.0.6}/lightphe.egg-info/PKG-INFO +49 -3
- {lightphe-0.0.4 → lightphe-0.0.6}/lightphe.egg-info/SOURCES.txt +6 -2
- lightphe-0.0.6/requirements.txt +2 -0
- {lightphe-0.0.4 → lightphe-0.0.6}/setup.py +2 -1
- {lightphe-0.0.4 → lightphe-0.0.6}/tests/__init__.py +1 -1
- {lightphe-0.0.4 → lightphe-0.0.6}/tests/test_benaloh.py +1 -1
- {lightphe-0.0.4 → lightphe-0.0.6}/tests/test_cloud.py +19 -10
- {lightphe-0.0.4 → lightphe-0.0.6}/tests/test_damgard.py +1 -1
- {lightphe-0.0.4 → lightphe-0.0.6}/tests/test_elgamal.py +8 -8
- {lightphe-0.0.4 → lightphe-0.0.6}/tests/test_ellipticcurveelgamal.py +1 -1
- lightphe-0.0.6/tests/test_exporting_keys.py +26 -0
- {lightphe-0.0.4 → lightphe-0.0.6}/tests/test_goldwasser.py +1 -1
- {lightphe-0.0.4 → lightphe-0.0.6}/tests/test_naccache.py +1 -1
- {lightphe-0.0.4 → lightphe-0.0.6}/tests/test_okamoto.py +1 -1
- {lightphe-0.0.4 → lightphe-0.0.6}/tests/test_paillier.py +1 -4
- {lightphe-0.0.4 → lightphe-0.0.6}/tests/test_rsa.py +4 -4
- {lightphe-0.0.4 → lightphe-0.0.6}/tests/test_salary.py +1 -1
- lightphe-0.0.6/tests/test_tensors.py +174 -0
- lightphe-0.0.4/lightphe/commons/calculations.py +0 -23
- {lightphe-0.0.4 → lightphe-0.0.6}/LICENSE +0 -0
- {lightphe-0.0.4 → lightphe-0.0.6}/lightphe/commons/__init__.py +0 -0
- {lightphe-0.0.4 → lightphe-0.0.6}/lightphe/cryptosystems/__init__.py +0 -0
- {lightphe-0.0.4 → lightphe-0.0.6}/lightphe/elliptic/__init__.py +0 -0
- {lightphe-0.0.4 → lightphe-0.0.6}/lightphe/models/Algorithm.py +0 -0
- {lightphe-0.0.4 → lightphe-0.0.6}/lightphe/models/EllipticCurve.py +0 -0
- {lightphe-0.0.4 → lightphe-0.0.6}/lightphe/models/__init__.py +0 -0
- {lightphe-0.0.4 → lightphe-0.0.6}/lightphe.egg-info/dependency_links.txt +0 -0
- {lightphe-0.0.4 → lightphe-0.0.6}/lightphe.egg-info/requires.txt +0 -0
- {lightphe-0.0.4 → lightphe-0.0.6}/lightphe.egg-info/top_level.txt +0 -0
- {lightphe-0.0.4 → lightphe-0.0.6}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: lightphe
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.6
|
|
4
4
|
Summary: A Lightweight Partially Homomorphic Encryption Library for Python
|
|
5
5
|
Home-page: https://github.com/serengil/LightPHE
|
|
6
6
|
Author: Sefik Ilkin Serengil
|
|
@@ -19,7 +19,7 @@ License-File: LICENSE
|
|
|
19
19
|
<div align="center">
|
|
20
20
|
|
|
21
21
|
[](https://pepy.tech/project/lightphe)
|
|
22
|
-
[](https://github.com/serengil/LightPHE/stargazers)
|
|
22
|
+
[](https://github.com/serengil/LightPHE/stargazers)
|
|
23
23
|
[](https://github.com/serengil/LightPHE/actions/workflows/tests.yml)
|
|
24
24
|
[](https://github.com/serengil/LightPHE/blob/master/LICENSE)
|
|
25
25
|
[](https://www.patreon.com/serengil?repo=lightphe)
|
|
@@ -41,7 +41,8 @@ Even though fully homomorphic encryption (FHE) has become available in recent ti
|
|
|
41
41
|
|
|
42
42
|
- 🏎️ Notably faster
|
|
43
43
|
- 💻 Demands fewer computational resources
|
|
44
|
-
- 📏 Generating smaller ciphertexts
|
|
44
|
+
- 📏 Generating much smaller ciphertexts
|
|
45
|
+
- 🔑 Distributing much smaller keys
|
|
45
46
|
- 🧠 Well-suited for memory-constrained environments
|
|
46
47
|
- ⚖️ Strikes a favorable balance for practical use cases
|
|
47
48
|
|
|
@@ -173,6 +174,51 @@ with pytest.raises(ValueError, match="Paillier is not homomorphic with respect t
|
|
|
173
174
|
|
|
174
175
|
However, if you tried to multiply ciphertexts with RSA, or xor ciphertexts with Goldwasser-Micali, these will be succeeded because those cryptosystems support those homomorphic operations.
|
|
175
176
|
|
|
177
|
+
# Working with vectors
|
|
178
|
+
|
|
179
|
+
You can encrypt the output vectors of machine learning models with LightPHE. These encrypted tensors come with homomorphic operation support including homomorphic addition, element-wise multiplication and scalar multiplication.
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
# build an additively homomorphic cryptosystem
|
|
183
|
+
cs = LightPHE(algorithm_name="Paillier")
|
|
184
|
+
|
|
185
|
+
# define plain tensors
|
|
186
|
+
t1 = [1.005, 2.05, 3.5, 4]
|
|
187
|
+
t2 = [5, 6.2, 7.002, 8.02]
|
|
188
|
+
|
|
189
|
+
# encrypt tensors
|
|
190
|
+
c1 = cs.encrypt(t1)
|
|
191
|
+
c2 = cs.encrypt(t2)
|
|
192
|
+
|
|
193
|
+
# perform homomorphic addition
|
|
194
|
+
c3 = c1 + c2
|
|
195
|
+
|
|
196
|
+
# perform homomorphic element-wise multiplication
|
|
197
|
+
c4 = c1 * c2
|
|
198
|
+
|
|
199
|
+
# perform homomorphic scalar multiplication
|
|
200
|
+
k = 5
|
|
201
|
+
c5 = k * c1
|
|
202
|
+
|
|
203
|
+
# decrypt the addition tensor
|
|
204
|
+
t3 = cs.decrypt(c3)
|
|
205
|
+
|
|
206
|
+
# decrypt the element-wise multiplied tensor
|
|
207
|
+
t4 = cs.decrypt(c4)
|
|
208
|
+
|
|
209
|
+
# decrypt the scalar multiplied tensor
|
|
210
|
+
t5 = cs.decrypt(c5)
|
|
211
|
+
|
|
212
|
+
# data validations
|
|
213
|
+
threshold = 0.5
|
|
214
|
+
for i in range(0, len(t1)):
|
|
215
|
+
assert abs((t1[i] + t2[i]) - t3[i]) < threshold
|
|
216
|
+
assert abs((t1[i] * t2[i]) - t4[i]) < threshold
|
|
217
|
+
assert abs((t1[i] * k) - t5[i]) < threshold
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Unfortunately, vector multiplication (dot product) requires both homomorphic addition and homomorphic multiplication and this cannot be done with partially homomorphic encryption algorithms.
|
|
221
|
+
|
|
176
222
|
# Contributing
|
|
177
223
|
|
|
178
224
|
All PRs are more than welcome! If you are planning to contribute a large patch, please create an issue first to get any upfront questions or design decisions out of the way first.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
|
|
5
5
|
[](https://pepy.tech/project/lightphe)
|
|
6
|
-
[](https://github.com/serengil/LightPHE/stargazers)
|
|
6
|
+
[](https://github.com/serengil/LightPHE/stargazers)
|
|
7
7
|
[](https://github.com/serengil/LightPHE/actions/workflows/tests.yml)
|
|
8
8
|
[](https://github.com/serengil/LightPHE/blob/master/LICENSE)
|
|
9
9
|
[](https://www.patreon.com/serengil?repo=lightphe)
|
|
@@ -25,7 +25,8 @@ Even though fully homomorphic encryption (FHE) has become available in recent ti
|
|
|
25
25
|
|
|
26
26
|
- 🏎️ Notably faster
|
|
27
27
|
- 💻 Demands fewer computational resources
|
|
28
|
-
- 📏 Generating smaller ciphertexts
|
|
28
|
+
- 📏 Generating much smaller ciphertexts
|
|
29
|
+
- 🔑 Distributing much smaller keys
|
|
29
30
|
- 🧠 Well-suited for memory-constrained environments
|
|
30
31
|
- ⚖️ Strikes a favorable balance for practical use cases
|
|
31
32
|
|
|
@@ -157,6 +158,51 @@ with pytest.raises(ValueError, match="Paillier is not homomorphic with respect t
|
|
|
157
158
|
|
|
158
159
|
However, if you tried to multiply ciphertexts with RSA, or xor ciphertexts with Goldwasser-Micali, these will be succeeded because those cryptosystems support those homomorphic operations.
|
|
159
160
|
|
|
161
|
+
# Working with vectors
|
|
162
|
+
|
|
163
|
+
You can encrypt the output vectors of machine learning models with LightPHE. These encrypted tensors come with homomorphic operation support including homomorphic addition, element-wise multiplication and scalar multiplication.
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
# build an additively homomorphic cryptosystem
|
|
167
|
+
cs = LightPHE(algorithm_name="Paillier")
|
|
168
|
+
|
|
169
|
+
# define plain tensors
|
|
170
|
+
t1 = [1.005, 2.05, 3.5, 4]
|
|
171
|
+
t2 = [5, 6.2, 7.002, 8.02]
|
|
172
|
+
|
|
173
|
+
# encrypt tensors
|
|
174
|
+
c1 = cs.encrypt(t1)
|
|
175
|
+
c2 = cs.encrypt(t2)
|
|
176
|
+
|
|
177
|
+
# perform homomorphic addition
|
|
178
|
+
c3 = c1 + c2
|
|
179
|
+
|
|
180
|
+
# perform homomorphic element-wise multiplication
|
|
181
|
+
c4 = c1 * c2
|
|
182
|
+
|
|
183
|
+
# perform homomorphic scalar multiplication
|
|
184
|
+
k = 5
|
|
185
|
+
c5 = k * c1
|
|
186
|
+
|
|
187
|
+
# decrypt the addition tensor
|
|
188
|
+
t3 = cs.decrypt(c3)
|
|
189
|
+
|
|
190
|
+
# decrypt the element-wise multiplied tensor
|
|
191
|
+
t4 = cs.decrypt(c4)
|
|
192
|
+
|
|
193
|
+
# decrypt the scalar multiplied tensor
|
|
194
|
+
t5 = cs.decrypt(c5)
|
|
195
|
+
|
|
196
|
+
# data validations
|
|
197
|
+
threshold = 0.5
|
|
198
|
+
for i in range(0, len(t1)):
|
|
199
|
+
assert abs((t1[i] + t2[i]) - t3[i]) < threshold
|
|
200
|
+
assert abs((t1[i] * t2[i]) - t4[i]) < threshold
|
|
201
|
+
assert abs((t1[i] * k) - t5[i]) < threshold
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Unfortunately, vector multiplication (dot product) requires both homomorphic addition and homomorphic multiplication and this cannot be done with partially homomorphic encryption algorithms.
|
|
205
|
+
|
|
160
206
|
# Contributing
|
|
161
207
|
|
|
162
208
|
All PRs are more than welcome! If you are planning to contribute a large patch, please create an issue first to get any upfront questions or design decisions out of the way first.
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from typing import Optional, Union
|
|
2
|
+
from typing import Optional, Union, List
|
|
3
3
|
|
|
4
|
+
from lightphe.models.Homomorphic import Homomorphic
|
|
4
5
|
from lightphe.models.Ciphertext import Ciphertext
|
|
5
6
|
from lightphe.models.Algorithm import Algorithm
|
|
7
|
+
from lightphe.models.Tensor import Fraction, EncryptedTensor
|
|
6
8
|
from lightphe.cryptosystems.RSA import RSA
|
|
7
9
|
from lightphe.cryptosystems.ElGamal import ElGamal
|
|
8
10
|
from lightphe.cryptosystems.Paillier import Paillier
|
|
@@ -12,12 +14,12 @@ from lightphe.cryptosystems.Benaloh import Benaloh
|
|
|
12
14
|
from lightphe.cryptosystems.NaccacheStern import NaccacheStern
|
|
13
15
|
from lightphe.cryptosystems.GoldwasserMicali import GoldwasserMicali
|
|
14
16
|
from lightphe.cryptosystems.EllipticCurveElGamal import EllipticCurveElGamal
|
|
15
|
-
from lightphe.commons import
|
|
17
|
+
from lightphe.commons import phe_utils
|
|
16
18
|
from lightphe.commons.logger import Logger
|
|
17
19
|
|
|
18
|
-
# pylint: disable=eval-used
|
|
20
|
+
# pylint: disable=eval-used, simplifiable-if-expression
|
|
19
21
|
|
|
20
|
-
logger = Logger()
|
|
22
|
+
logger = Logger(module="lightphe/__init__.py")
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
class LightPHE:
|
|
@@ -27,6 +29,7 @@ class LightPHE:
|
|
|
27
29
|
keys: Optional[dict] = None,
|
|
28
30
|
key_file: Optional[str] = None,
|
|
29
31
|
key_size: Optional[int] = None,
|
|
32
|
+
precision: int = 5,
|
|
30
33
|
):
|
|
31
34
|
"""
|
|
32
35
|
Build LightPHE class
|
|
@@ -37,17 +40,19 @@ class LightPHE:
|
|
|
37
40
|
keys (dict): optional private-public key pair
|
|
38
41
|
key_file (str): if keys are exported, you can load them into cryptosystem
|
|
39
42
|
key_size (int): key size in bits
|
|
43
|
+
precision (int): precision for homomorphic operations on tensors
|
|
40
44
|
"""
|
|
41
45
|
self.algorithm_name = algorithm_name
|
|
46
|
+
self.precision = precision
|
|
42
47
|
|
|
43
48
|
if key_file is not None:
|
|
44
49
|
keys = self.restore_keys(target_file=key_file)
|
|
45
50
|
|
|
46
|
-
self.cs = self.
|
|
51
|
+
self.cs: Homomorphic = self.__build_cryptosystem(
|
|
47
52
|
algorithm_name=algorithm_name, keys=keys, key_size=key_size
|
|
48
53
|
)
|
|
49
54
|
|
|
50
|
-
def
|
|
55
|
+
def __build_cryptosystem(
|
|
51
56
|
self,
|
|
52
57
|
algorithm_name: str,
|
|
53
58
|
keys: Optional[dict] = None,
|
|
@@ -103,22 +108,29 @@ class LightPHE:
|
|
|
103
108
|
raise ValueError(f"unimplemented algorithm - {algorithm_name}")
|
|
104
109
|
return cs
|
|
105
110
|
|
|
106
|
-
def encrypt(self, plaintext: Union[int, float]) -> Ciphertext:
|
|
111
|
+
def encrypt(self, plaintext: Union[int, float, list]) -> Union[Ciphertext, EncryptedTensor]:
|
|
107
112
|
"""
|
|
108
113
|
Encrypt a plaintext with a built cryptosystem
|
|
109
114
|
Args:
|
|
110
|
-
plaintext (int or
|
|
115
|
+
plaintext (int, float or tensor): message
|
|
111
116
|
Returns
|
|
112
117
|
ciphertext (from lightphe.models.Ciphertext import Ciphertext): encrypted message
|
|
113
118
|
"""
|
|
119
|
+
if self.cs.keys.get("public_key") is None:
|
|
120
|
+
raise ValueError("You must have public key to perform encryption")
|
|
121
|
+
|
|
122
|
+
if isinstance(plaintext, list):
|
|
123
|
+
# then encrypt tensors
|
|
124
|
+
return self.__encrypt_tensors(tensor=plaintext)
|
|
125
|
+
|
|
114
126
|
ciphertext = self.cs.encrypt(
|
|
115
|
-
plaintext=
|
|
116
|
-
value=plaintext, modulo=self.cs.modulo or self.cs.plaintext_modulo
|
|
117
|
-
)
|
|
127
|
+
plaintext=phe_utils.parse_int(value=plaintext, modulo=self.cs.plaintext_modulo)
|
|
118
128
|
)
|
|
119
129
|
return Ciphertext(algorithm_name=self.algorithm_name, keys=self.cs.keys, value=ciphertext)
|
|
120
130
|
|
|
121
|
-
def decrypt(
|
|
131
|
+
def decrypt(
|
|
132
|
+
self, ciphertext: Union[Ciphertext, EncryptedTensor]
|
|
133
|
+
) -> Union[int, List[int], List[float]]:
|
|
122
134
|
"""
|
|
123
135
|
Decrypt a ciphertext with a buit cryptosystem
|
|
124
136
|
Args:
|
|
@@ -126,8 +138,89 @@ class LightPHE:
|
|
|
126
138
|
Returns:
|
|
127
139
|
plaintext (int): restored message
|
|
128
140
|
"""
|
|
141
|
+
if self.cs.keys.get("private_key") is None:
|
|
142
|
+
raise ValueError("You must have private key to perform decryption")
|
|
143
|
+
|
|
144
|
+
if self.cs.keys.get("public_key") is None:
|
|
145
|
+
raise ValueError("You must have public key to perform decryption")
|
|
146
|
+
|
|
147
|
+
if isinstance(ciphertext, EncryptedTensor):
|
|
148
|
+
# then this is encrypted tensor
|
|
149
|
+
return self.__decrypt_tensors(encrypted_tensor=ciphertext)
|
|
150
|
+
|
|
129
151
|
return self.cs.decrypt(ciphertext=ciphertext.value)
|
|
130
152
|
|
|
153
|
+
def __encrypt_tensors(self, tensor: list) -> EncryptedTensor:
|
|
154
|
+
"""
|
|
155
|
+
Encrypt a given tensor
|
|
156
|
+
Args:
|
|
157
|
+
tensor (list of int or float)
|
|
158
|
+
Returns
|
|
159
|
+
encrypted tensor (list of encrypted tensor object)
|
|
160
|
+
"""
|
|
161
|
+
encrypted_tensor: List[Fraction] = []
|
|
162
|
+
for m in tensor:
|
|
163
|
+
if isinstance(m, int):
|
|
164
|
+
dividend_encrypted = self.cs.encrypt(
|
|
165
|
+
plaintext=(m % self.cs.plaintext_modulo) * pow(10, self.precision)
|
|
166
|
+
)
|
|
167
|
+
abs_dividend_encrypted = self.cs.encrypt(
|
|
168
|
+
plaintext=(abs(m) % self.cs.plaintext_modulo) * pow(10, self.precision)
|
|
169
|
+
)
|
|
170
|
+
divisor_encrypted = self.cs.encrypt(plaintext=pow(10, self.precision))
|
|
171
|
+
c = Fraction(
|
|
172
|
+
dividend=dividend_encrypted,
|
|
173
|
+
divisor=divisor_encrypted,
|
|
174
|
+
abs_dividend=abs_dividend_encrypted,
|
|
175
|
+
sign=1 if m >= 0 else -1,
|
|
176
|
+
)
|
|
177
|
+
elif isinstance(m, float):
|
|
178
|
+
dividend, divisor = phe_utils.fractionize(
|
|
179
|
+
value=(m % self.cs.plaintext_modulo),
|
|
180
|
+
modulo=self.cs.plaintext_modulo,
|
|
181
|
+
precision=self.precision,
|
|
182
|
+
)
|
|
183
|
+
abs_dividend, _ = phe_utils.fractionize(
|
|
184
|
+
value=(abs(m) % self.cs.plaintext_modulo),
|
|
185
|
+
modulo=self.cs.plaintext_modulo,
|
|
186
|
+
precision=self.precision,
|
|
187
|
+
)
|
|
188
|
+
dividend_encrypted = self.cs.encrypt(plaintext=dividend)
|
|
189
|
+
abs_dividend_encrypted = self.cs.encrypt(plaintext=abs_dividend)
|
|
190
|
+
divisor_encrypted = self.cs.encrypt(plaintext=divisor)
|
|
191
|
+
c = Fraction(
|
|
192
|
+
dividend=dividend_encrypted,
|
|
193
|
+
divisor=divisor_encrypted,
|
|
194
|
+
abs_dividend=abs_dividend_encrypted,
|
|
195
|
+
sign=1 if m >= 0 else -1,
|
|
196
|
+
)
|
|
197
|
+
else:
|
|
198
|
+
raise ValueError(f"unimplemented type - {type(m)}")
|
|
199
|
+
encrypted_tensor.append(c)
|
|
200
|
+
return EncryptedTensor(fractions=encrypted_tensor, cs=self.cs)
|
|
201
|
+
|
|
202
|
+
def __decrypt_tensors(self, encrypted_tensor: EncryptedTensor) -> Union[List[int], List[float]]:
|
|
203
|
+
"""
|
|
204
|
+
Decrypt a given encrypted tensor
|
|
205
|
+
Args:
|
|
206
|
+
encrypted_tensor (list of encrypted tensor)
|
|
207
|
+
Returns:
|
|
208
|
+
List of plain tensors
|
|
209
|
+
"""
|
|
210
|
+
plain_tensor = []
|
|
211
|
+
for c in encrypted_tensor.fractions:
|
|
212
|
+
if isinstance(c, Fraction) is False:
|
|
213
|
+
raise ValueError("Ciphertext items must be type of Fraction")
|
|
214
|
+
|
|
215
|
+
sign = c.sign
|
|
216
|
+
abs_dividend = self.cs.decrypt(ciphertext=c.abs_dividend)
|
|
217
|
+
# dividend = self.cs.decrypt(ciphertext=c.dividend)
|
|
218
|
+
divisor = self.cs.decrypt(ciphertext=c.divisor)
|
|
219
|
+
m = sign * abs_dividend / divisor
|
|
220
|
+
|
|
221
|
+
plain_tensor.append(m)
|
|
222
|
+
return plain_tensor
|
|
223
|
+
|
|
131
224
|
def regenerate_ciphertext(self, ciphertext: Ciphertext) -> Ciphertext:
|
|
132
225
|
"""
|
|
133
226
|
Generate a different ciphertext belonging to same plaintext
|
|
@@ -136,6 +229,9 @@ class LightPHE:
|
|
|
136
229
|
Returns:
|
|
137
230
|
ciphertext (from lightphe.models.Ciphertext import Ciphertext): encrypted message
|
|
138
231
|
"""
|
|
232
|
+
if self.cs.keys.get("private_key") is None:
|
|
233
|
+
raise ValueError("You must have private key to perform decryption")
|
|
234
|
+
|
|
139
235
|
ciphertext_new = self.cs.reencrypt(ciphertext=ciphertext.value)
|
|
140
236
|
return Ciphertext(
|
|
141
237
|
algorithm_name=self.algorithm_name, keys=self.cs.keys, value=ciphertext_new
|
|
@@ -150,7 +246,9 @@ class LightPHE:
|
|
|
150
246
|
to publicly.
|
|
151
247
|
"""
|
|
152
248
|
keys = self.cs.keys
|
|
249
|
+
private_key = None
|
|
153
250
|
if public is True and keys.get("private_key") is not None:
|
|
251
|
+
private_key = keys["private_key"]
|
|
154
252
|
del keys["private_key"]
|
|
155
253
|
|
|
156
254
|
if public is False:
|
|
@@ -162,6 +260,10 @@ class LightPHE:
|
|
|
162
260
|
with open(target_file, "w", encoding="UTF-8") as file:
|
|
163
261
|
file.write(json.dumps(keys))
|
|
164
262
|
|
|
263
|
+
# restore private key if you dropped
|
|
264
|
+
if private_key is not None:
|
|
265
|
+
self.cs.keys["private_key"] = private_key
|
|
266
|
+
|
|
165
267
|
def restore_keys(self, target_file: str) -> dict:
|
|
166
268
|
"""
|
|
167
269
|
Restore keys from a file
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from typing import Union, Tuple, Optional
|
|
2
|
+
from decimal import Decimal, getcontext
|
|
3
|
+
from lightphe.commons.logger import Logger
|
|
4
|
+
|
|
5
|
+
logger = Logger(module="lightphe/commons/phe_utils.py")
|
|
6
|
+
|
|
7
|
+
# pylint: disable=no-else-return, no-else-break
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def parse_int(value: Union[int, float], modulo: int) -> int:
|
|
11
|
+
if isinstance(value, int):
|
|
12
|
+
result = value % modulo
|
|
13
|
+
elif isinstance(value, float) and value >= 0:
|
|
14
|
+
dividend, divisor = fractionize(value=value, modulo=modulo)
|
|
15
|
+
logger.debug(f"{dividend}*{divisor}^-1 mod {modulo}")
|
|
16
|
+
result = (dividend * pow(divisor, -1, modulo)) % modulo
|
|
17
|
+
elif isinstance(value, float) and value < 0:
|
|
18
|
+
# TODO: think and implement this later
|
|
19
|
+
raise ValueError("Case constant float and negative not implemented yet")
|
|
20
|
+
else:
|
|
21
|
+
raise ValueError(f"Unimplemented case for constant type {type(value)}")
|
|
22
|
+
|
|
23
|
+
return result
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def fractionize(value: float, modulo: int, precision: Optional[int] = None) -> Tuple[int, int]:
|
|
27
|
+
getcontext().prec = 50
|
|
28
|
+
|
|
29
|
+
if precision is None:
|
|
30
|
+
decimal_places = len(str(value).split(".")[1])
|
|
31
|
+
scaling_factor = 10**decimal_places
|
|
32
|
+
else:
|
|
33
|
+
scaling_factor = 10**precision
|
|
34
|
+
|
|
35
|
+
while True:
|
|
36
|
+
integer_value = int(Decimal(value) * Decimal(scaling_factor)) % modulo
|
|
37
|
+
|
|
38
|
+
if precision is None:
|
|
39
|
+
break
|
|
40
|
+
|
|
41
|
+
if scaling_factor > 10**precision:
|
|
42
|
+
# If scaling factor is too large, discard excess part of integer value
|
|
43
|
+
integer_value = int(integer_value / (10 ** (scaling_factor - 10**precision)))
|
|
44
|
+
break
|
|
45
|
+
elif scaling_factor < 10 ** (precision - 1):
|
|
46
|
+
# If scaling factor is too small, multiply dividend and divisor 10 times
|
|
47
|
+
value *= 10
|
|
48
|
+
scaling_factor *= 10
|
|
49
|
+
else:
|
|
50
|
+
break
|
|
51
|
+
|
|
52
|
+
logger.debug(f"{integer_value}*{scaling_factor}^-1 mod {modulo}")
|
|
53
|
+
return integer_value, scaling_factor
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def solve_dlp():
|
|
57
|
+
# TODO: implement this later
|
|
58
|
+
pass
|
|
@@ -5,7 +5,7 @@ import sympy
|
|
|
5
5
|
from lightphe.models.Homomorphic import Homomorphic
|
|
6
6
|
from lightphe.commons.logger import Logger
|
|
7
7
|
|
|
8
|
-
logger = Logger()
|
|
8
|
+
logger = Logger(module="lightphe/cryptosystems/ElGamal.py")
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class ElGamal(Homomorphic):
|
|
@@ -26,7 +26,8 @@ class ElGamal(Homomorphic):
|
|
|
26
26
|
"""
|
|
27
27
|
self.exponential = exponential
|
|
28
28
|
self.keys = keys or self.generate_keys(key_size)
|
|
29
|
-
self.
|
|
29
|
+
self.plaintext_modulo = self.keys["public_key"]["p"]
|
|
30
|
+
self.ciphertext_modulo = self.keys["public_key"]["p"]
|
|
30
31
|
|
|
31
32
|
def generate_keys(self, key_size: int):
|
|
32
33
|
"""
|
|
@@ -4,7 +4,7 @@ from lightphe.models.Homomorphic import Homomorphic
|
|
|
4
4
|
from lightphe.elliptic.Weierstrass import Weierstrass
|
|
5
5
|
from lightphe.commons.logger import Logger
|
|
6
6
|
|
|
7
|
-
logger = Logger()
|
|
7
|
+
logger = Logger(module="lightphe/cryptosystems/EllipticCurveElGamal.py")
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class EllipticCurveElGamal(Homomorphic):
|
|
@@ -27,7 +27,8 @@ class EllipticCurveElGamal(Homomorphic):
|
|
|
27
27
|
# TODO: add different forms and curves. e.g. Koblitz, Edwards (Ed25519)
|
|
28
28
|
self.curve = Weierstrass()
|
|
29
29
|
self.keys = keys or self.generate_keys(key_size)
|
|
30
|
-
self.
|
|
30
|
+
self.plaintext_modulo = self.curve.p
|
|
31
|
+
self.ciphertext_modulo = self.curve.p
|
|
31
32
|
|
|
32
33
|
def generate_keys(self, key_size: int):
|
|
33
34
|
"""
|
|
@@ -6,7 +6,7 @@ from sympy import jacobi_symbol
|
|
|
6
6
|
from lightphe.models.Homomorphic import Homomorphic
|
|
7
7
|
from lightphe.commons.logger import Logger
|
|
8
8
|
|
|
9
|
-
logger = Logger()
|
|
9
|
+
logger = Logger(module="lightphe/cryptosystems/GoldwasserMicali.py")
|
|
10
10
|
|
|
11
11
|
# pylint:disable=consider-using-enumerate
|
|
12
12
|
|
|
@@ -26,6 +26,8 @@ class GoldwasserMicali(Homomorphic):
|
|
|
26
26
|
"""
|
|
27
27
|
self.keys = keys or self.generate_keys(key_size)
|
|
28
28
|
self.ciphertext_modulo = self.keys["public_key"]["n"]
|
|
29
|
+
# TODO: not sure about the plaintext modulo
|
|
30
|
+
self.plaintext_modulo = self.keys["public_key"]["n"]
|
|
29
31
|
|
|
30
32
|
def generate_keys(self, key_size: int) -> dict:
|
|
31
33
|
"""
|
|
@@ -6,7 +6,7 @@ from sympy.ntheory.modular import solve_congruence
|
|
|
6
6
|
from lightphe.models.Homomorphic import Homomorphic
|
|
7
7
|
from lightphe.commons.logger import Logger
|
|
8
8
|
|
|
9
|
-
logger = Logger()
|
|
9
|
+
logger = Logger(module="lightphe/cryptosystems/NaccacheStern.py")
|
|
10
10
|
|
|
11
11
|
# pylint: disable=simplifiable-if-expression, consider-using-enumerate
|
|
12
12
|
|
|
@@ -5,7 +5,7 @@ import sympy
|
|
|
5
5
|
from lightphe.models.Homomorphic import Homomorphic
|
|
6
6
|
from lightphe.commons.logger import Logger
|
|
7
7
|
|
|
8
|
-
logger = Logger()
|
|
8
|
+
logger = Logger(module="lightphe/cryptosystems/OkamotoUchiyama.py")
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class OkamotoUchiyama(Homomorphic):
|
|
@@ -81,18 +81,21 @@ class OkamotoUchiyama(Homomorphic):
|
|
|
81
81
|
Returns:
|
|
82
82
|
ciphertext (int): encrypted message
|
|
83
83
|
"""
|
|
84
|
-
|
|
84
|
+
|
|
85
85
|
g = self.keys["public_key"]["g"]
|
|
86
86
|
n = self.keys["public_key"]["n"]
|
|
87
87
|
h = self.keys["public_key"]["h"]
|
|
88
88
|
r = random_key or self.generate_random_key()
|
|
89
89
|
|
|
90
|
-
if
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
# having private key is not a must to encrypt but still if you have
|
|
91
|
+
if self.keys.get("private_key") is not None:
|
|
92
|
+
p = self.keys["private_key"]["p"]
|
|
93
|
+
if plaintext > p:
|
|
94
|
+
plaintext = plaintext % p
|
|
95
|
+
logger.debug(
|
|
96
|
+
f"plaintext must be in scale [0, {p=}] but this is exceeded."
|
|
97
|
+
"New plaintext is {plaintext}"
|
|
98
|
+
)
|
|
96
99
|
return (pow(g, plaintext, n) * pow(h, r, n)) % n
|
|
97
100
|
|
|
98
101
|
def decrypt(self, ciphertext: int):
|
|
@@ -5,7 +5,7 @@ import sympy
|
|
|
5
5
|
from lightphe.models.Homomorphic import Homomorphic
|
|
6
6
|
from lightphe.commons.logger import Logger
|
|
7
7
|
|
|
8
|
-
logger = Logger()
|
|
8
|
+
logger = Logger(module="lightphe/cryptosystems/RSA.py")
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class RSA(Homomorphic):
|
|
@@ -28,7 +28,8 @@ class RSA(Homomorphic):
|
|
|
28
28
|
and do decryption with private key d.
|
|
29
29
|
"""
|
|
30
30
|
self.keys = keys or self.generate_keys(key_size)
|
|
31
|
-
self.
|
|
31
|
+
self.plaintext_modulo = self.keys["public_key"]["n"]
|
|
32
|
+
self.ciphertext_modulo = self.keys["public_key"]["n"]
|
|
32
33
|
self.encrypt_with_public = encrypt_with_public
|
|
33
34
|
|
|
34
35
|
def generate_keys(self, key_size: int) -> dict:
|
|
@@ -10,10 +10,10 @@ from lightphe.cryptosystems.Benaloh import Benaloh
|
|
|
10
10
|
from lightphe.cryptosystems.NaccacheStern import NaccacheStern
|
|
11
11
|
from lightphe.cryptosystems.GoldwasserMicali import GoldwasserMicali
|
|
12
12
|
from lightphe.cryptosystems.EllipticCurveElGamal import EllipticCurveElGamal
|
|
13
|
-
from lightphe.commons import
|
|
13
|
+
from lightphe.commons import phe_utils
|
|
14
14
|
from lightphe.commons.logger import Logger
|
|
15
15
|
|
|
16
|
-
logger = Logger()
|
|
16
|
+
logger = Logger(module="lightphe/models/Ciphertext.py")
|
|
17
17
|
|
|
18
18
|
# pylint: disable=too-few-public-methods, no-else-return
|
|
19
19
|
|
|
@@ -63,6 +63,9 @@ class Ciphertext:
|
|
|
63
63
|
Returns:
|
|
64
64
|
ciphertext (Ciphertext): homomorphic addition of ciphertext
|
|
65
65
|
"""
|
|
66
|
+
if self.cs.keys.get("public_key") is None:
|
|
67
|
+
raise ValueError("You must have public key to perform homomorphic addition")
|
|
68
|
+
|
|
66
69
|
result = self.cs.add(ciphertext1=self.value, ciphertext2=other.value)
|
|
67
70
|
return Ciphertext(algorithm_name=self.algorithm_name, keys=self.keys, value=result)
|
|
68
71
|
|
|
@@ -74,15 +77,16 @@ class Ciphertext:
|
|
|
74
77
|
Returns
|
|
75
78
|
homomorphic multiplication of ciphertexts | scalar multiplication of ciphertext
|
|
76
79
|
"""
|
|
80
|
+
if self.cs.keys.get("public_key") is None:
|
|
81
|
+
raise ValueError("You must have public key to perform homomorphic multiplication")
|
|
82
|
+
|
|
77
83
|
if isinstance(other, Ciphertext):
|
|
78
84
|
# Handle multiplication with another EncryptedObject
|
|
79
85
|
result = self.cs.multiply(ciphertext1=self.value, ciphertext2=other.value)
|
|
80
86
|
elif isinstance(other, int):
|
|
81
87
|
result = self.cs.multiply_by_contant(ciphertext=self.value, constant=other)
|
|
82
88
|
elif isinstance(other, float):
|
|
83
|
-
constant =
|
|
84
|
-
value=other, modulo=self.cs.modulo or self.cs.plaintext_modulo
|
|
85
|
-
)
|
|
89
|
+
constant = phe_utils.parse_int(value=other, modulo=self.cs.plaintext_modulo)
|
|
86
90
|
result = self.cs.multiply_by_contant(ciphertext=self.value, constant=constant)
|
|
87
91
|
else:
|
|
88
92
|
raise ValueError(
|
|
@@ -98,14 +102,7 @@ class Ciphertext:
|
|
|
98
102
|
Returns
|
|
99
103
|
scalar multiplication of ciphertext
|
|
100
104
|
"""
|
|
101
|
-
|
|
102
|
-
constant = calculations.parse_int(
|
|
103
|
-
value=constant, modulo=self.cs.modulo or self.cs.plaintext_modulo
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
# Handle multiplication with a constant on the right
|
|
107
|
-
result = self.cs.multiply_by_contant(ciphertext=self.value, constant=constant)
|
|
108
|
-
return Ciphertext(algorithm_name=self.algorithm_name, keys=self.keys, value=result)
|
|
105
|
+
return self.__mul__(other=constant)
|
|
109
106
|
|
|
110
107
|
def __xor__(self, other: "Ciphertext") -> "Ciphertext":
|
|
111
108
|
"""
|
|
@@ -115,5 +112,8 @@ class Ciphertext:
|
|
|
115
112
|
Returns
|
|
116
113
|
homomorphic xor of ciphertexts
|
|
117
114
|
"""
|
|
115
|
+
if self.cs.keys.get("public_key") is None:
|
|
116
|
+
raise ValueError("You must have public key to perform homomorphic xor")
|
|
117
|
+
|
|
118
118
|
result = self.cs.xor(ciphertext1=self.value, ciphertext2=other.value)
|
|
119
119
|
return Ciphertext(algorithm_name=self.algorithm_name, keys=self.keys, value=result)
|