lightphe 0.0.21__tar.gz → 0.0.23__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.21 → lightphe-0.0.23}/PKG-INFO +2 -2
- {lightphe-0.0.21 → lightphe-0.0.23}/README.md +1 -1
- {lightphe-0.0.21 → lightphe-0.0.23}/lightphe/__init__.py +1 -1
- {lightphe-0.0.21 → lightphe-0.0.23}/lightphe/cryptosystems/BonehGohNissim.py +64 -16
- {lightphe-0.0.21 → lightphe-0.0.23}/lightphe/cryptosystems/SanderYoungYung.py +2 -2
- {lightphe-0.0.21 → lightphe-0.0.23}/lightphe.egg-info/PKG-INFO +2 -2
- {lightphe-0.0.21 → lightphe-0.0.23}/setup.py +1 -1
- {lightphe-0.0.21 → lightphe-0.0.23}/tests/test_benaloh.py +1 -32
- lightphe-0.0.23/tests/test_bonehgohnissim.py +251 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/tests/test_ciphertexts.py +2 -4
- lightphe-0.0.23/tests/test_damgard.py +34 -0
- lightphe-0.0.23/tests/test_elgamal.py +64 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/tests/test_ellipticcurveelgamal.py +2 -1
- {lightphe-0.0.21 → lightphe-0.0.23}/tests/test_exporting_keys.py +1 -1
- lightphe-0.0.23/tests/test_goldwasser.py +40 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/tests/test_keys.py +2 -5
- {lightphe-0.0.21 → lightphe-0.0.23}/tests/test_naccache.py +0 -95
- lightphe-0.0.23/tests/test_okamoto.py +34 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/tests/test_paillier.py +2 -36
- {lightphe-0.0.21 → lightphe-0.0.23}/tests/test_rsa.py +1 -31
- lightphe-0.0.23/tests/test_salary.py +24 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/tests/test_sanderyoungyung.py +1 -1
- {lightphe-0.0.21 → lightphe-0.0.23}/tests/test_tensors.py +25 -11
- lightphe-0.0.21/tests/test_bonehgohnissim.py +0 -143
- lightphe-0.0.21/tests/test_damgard.py +0 -63
- lightphe-0.0.21/tests/test_elgamal.py +0 -139
- lightphe-0.0.21/tests/test_goldwasser.py +0 -83
- lightphe-0.0.21/tests/test_okamoto.py +0 -61
- lightphe-0.0.21/tests/test_salary.py +0 -53
- {lightphe-0.0.21 → lightphe-0.0.23}/LICENSE +0 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/lightphe/commons/__init__.py +0 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/lightphe/commons/logger.py +0 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/lightphe/commons/phe_utils.py +0 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/lightphe/cryptosystems/Benaloh.py +0 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/lightphe/cryptosystems/DamgardJurik.py +0 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/lightphe/cryptosystems/ElGamal.py +0 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/lightphe/cryptosystems/EllipticCurveElGamal.py +0 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/lightphe/cryptosystems/GoldwasserMicali.py +0 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/lightphe/cryptosystems/NaccacheStern.py +0 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/lightphe/cryptosystems/OkamotoUchiyama.py +0 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/lightphe/cryptosystems/Paillier.py +0 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/lightphe/cryptosystems/RSA.py +0 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/lightphe/cryptosystems/__init__.py +0 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/lightphe/models/Algorithm.py +0 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/lightphe/models/Ciphertext.py +0 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/lightphe/models/Homomorphic.py +0 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/lightphe/models/Tensor.py +0 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/lightphe/models/__init__.py +0 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/lightphe.egg-info/SOURCES.txt +0 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/lightphe.egg-info/dependency_links.txt +0 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/lightphe.egg-info/requires.txt +0 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/lightphe.egg-info/top_level.txt +0 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/requirements.txt +0 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/setup.cfg +0 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/tests/__init__.py +0 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/tests/test_cloud.py +0 -0
- {lightphe-0.0.21 → lightphe-0.0.23}/tests/test_large_keys.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: lightphe
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.23
|
|
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
|
|
@@ -36,7 +36,7 @@ License-File: LICENSE
|
|
|
36
36
|
|
|
37
37
|
<p align="center"><img src="https://raw.githubusercontent.com/serengil/LightPHE/master/icons/phi.png" width="200" height="240"></p>
|
|
38
38
|
|
|
39
|
-
LightPHE is a lightweight
|
|
39
|
+
LightPHE is a lightweight homomorphic encryption library for python supporting various partially and somewhat homomorphic encryptions schemes such as [`RSA`](https://sefiks.com/2023/03/06/a-step-by-step-partially-homomorphic-encryption-example-with-rsa-in-python/), [`ElGamal`](https://sefiks.com/2023/03/27/a-step-by-step-partially-homomorphic-encryption-example-with-elgamal-in-python/), [`Exponential ElGamal`](https://sefiks.com/2023/03/27/a-step-by-step-partially-homomorphic-encryption-example-with-elgamal-in-python/), [`Elliptic Curve ElGamal`](https://sefiks.com/2018/08/21/elliptic-curve-elgamal-encryption/) ([`Weierstrass`](https://sefiks.com/2016/03/13/the-math-behind-elliptic-curve-cryptography/), [`Koblitz`](sefiks.com/2016/03/13/the-math-behind-elliptic-curves-over-binary-field/) and [`Edwards`](https://sefiks.com/2018/12/19/a-gentle-introduction-to-edwards-curves/) forms), [`Paillier`](https://sefiks.com/2023/04/03/a-step-by-step-partially-homomorphic-encryption-example-with-paillier-in-python/), [`Damgard-Jurik`](https://sefiks.com/2023/10/20/a-step-by-step-partially-homomorphic-encryption-example-with-damgard-jurik-in-python/), [`Okamoto–Uchiyama`](https://sefiks.com/2023/10/20/a-step-by-step-partially-homomorphic-encryption-example-with-okamoto-uchiyama-in-python/), [`Benaloh`](https://sefiks.com/2023/10/06/a-step-by-step-partially-homomorphic-encryption-example-with-benaloh-in-python-from-scratch/), [`Naccache–Stern`](https://sefiks.com/2023/10/26/a-step-by-step-partially-homomorphic-encryption-example-with-naccache-stern-in-python/), [`Goldwasser–Micali`](https://sefiks.com/2023/10/27/a-step-by-step-partially-homomorphic-encryption-example-with-goldwasser-micali-in-python/), [`Sander-Young-Yung`](https://sefiks.com/2026/04/02/a-step-by-step-partially-homomorphic-sander-young-yung-example-in-python/), [`Boneh-Goh-Nissim`](https://sefiks.com/2026/04/02/a-step-by-step-somewhat-homomorphic-encryption-example-with-boneh-goh-nissim-in-python/).
|
|
40
40
|
|
|
41
41
|
# Partially vs Fully Homomorphic Encryption
|
|
42
42
|
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
<p align="center"><img src="https://raw.githubusercontent.com/serengil/LightPHE/master/icons/phi.png" width="200" height="240"></p>
|
|
22
22
|
|
|
23
|
-
LightPHE is a lightweight
|
|
23
|
+
LightPHE is a lightweight homomorphic encryption library for python supporting various partially and somewhat homomorphic encryptions schemes such as [`RSA`](https://sefiks.com/2023/03/06/a-step-by-step-partially-homomorphic-encryption-example-with-rsa-in-python/), [`ElGamal`](https://sefiks.com/2023/03/27/a-step-by-step-partially-homomorphic-encryption-example-with-elgamal-in-python/), [`Exponential ElGamal`](https://sefiks.com/2023/03/27/a-step-by-step-partially-homomorphic-encryption-example-with-elgamal-in-python/), [`Elliptic Curve ElGamal`](https://sefiks.com/2018/08/21/elliptic-curve-elgamal-encryption/) ([`Weierstrass`](https://sefiks.com/2016/03/13/the-math-behind-elliptic-curve-cryptography/), [`Koblitz`](sefiks.com/2016/03/13/the-math-behind-elliptic-curves-over-binary-field/) and [`Edwards`](https://sefiks.com/2018/12/19/a-gentle-introduction-to-edwards-curves/) forms), [`Paillier`](https://sefiks.com/2023/04/03/a-step-by-step-partially-homomorphic-encryption-example-with-paillier-in-python/), [`Damgard-Jurik`](https://sefiks.com/2023/10/20/a-step-by-step-partially-homomorphic-encryption-example-with-damgard-jurik-in-python/), [`Okamoto–Uchiyama`](https://sefiks.com/2023/10/20/a-step-by-step-partially-homomorphic-encryption-example-with-okamoto-uchiyama-in-python/), [`Benaloh`](https://sefiks.com/2023/10/06/a-step-by-step-partially-homomorphic-encryption-example-with-benaloh-in-python-from-scratch/), [`Naccache–Stern`](https://sefiks.com/2023/10/26/a-step-by-step-partially-homomorphic-encryption-example-with-naccache-stern-in-python/), [`Goldwasser–Micali`](https://sefiks.com/2023/10/27/a-step-by-step-partially-homomorphic-encryption-example-with-goldwasser-micali-in-python/), [`Sander-Young-Yung`](https://sefiks.com/2026/04/02/a-step-by-step-partially-homomorphic-sander-young-yung-example-in-python/), [`Boneh-Goh-Nissim`](https://sefiks.com/2026/04/02/a-step-by-step-somewhat-homomorphic-encryption-example-with-boneh-goh-nissim-in-python/).
|
|
24
24
|
|
|
25
25
|
# Partially vs Fully Homomorphic Encryption
|
|
26
26
|
|
|
@@ -21,8 +21,8 @@ class BonehGohNissim(Homomorphic):
|
|
|
21
21
|
"""
|
|
22
22
|
Boneh-Goh-Nissim algorithm is homomorphic with respect to the addition.
|
|
23
23
|
It's also somehow homomorphic with respect to the multiplication
|
|
24
|
-
only for one multiplication per ciphertext
|
|
25
|
-
|
|
24
|
+
only for one multiplication per ciphertext.
|
|
25
|
+
Ref: https://sefiks.com/2026/04/02/a-step-by-step-somewhat-homomorphic-encryption-example-with-boneh-goh-nissim-in-python/
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
28
|
def __init__(
|
|
@@ -54,7 +54,10 @@ class BonehGohNissim(Homomorphic):
|
|
|
54
54
|
},
|
|
55
55
|
)
|
|
56
56
|
|
|
57
|
-
self.
|
|
57
|
+
if "private_key" in self.keys and "q2" in self.keys["private_key"]:
|
|
58
|
+
self.plaintext_modulo = self.keys["private_key"]["q2"]
|
|
59
|
+
else:
|
|
60
|
+
self.plaintext_modulo = self.keys["public_key"]["curve"]["n"]
|
|
58
61
|
self.ciphertext_modulo = self.ec.modulo
|
|
59
62
|
|
|
60
63
|
def generate_keys(
|
|
@@ -160,14 +163,28 @@ class BonehGohNissim(Homomorphic):
|
|
|
160
163
|
|
|
161
164
|
raise Exception(f"Failed to find a generator after {max_tries} attempts")
|
|
162
165
|
|
|
166
|
+
def _has_private_key(self) -> bool:
|
|
167
|
+
return "private_key" in self.keys and "q2" in self.keys["private_key"]
|
|
168
|
+
|
|
163
169
|
def generate_random_key(self) -> int:
|
|
164
170
|
"""
|
|
165
171
|
Boneh-Goh-Nissim requires to generate one-time random key per encryption
|
|
166
172
|
Returns:
|
|
167
173
|
random key (int): one time random key for encryption
|
|
168
174
|
"""
|
|
169
|
-
|
|
170
|
-
|
|
175
|
+
if self._has_private_key():
|
|
176
|
+
q2 = self.keys["private_key"]["q2"]
|
|
177
|
+
return random.randint(1, q2 - 1)
|
|
178
|
+
|
|
179
|
+
n = self.keys["public_key"]["curve"]["n"]
|
|
180
|
+
logger.warn(
|
|
181
|
+
"Private key is not available. Random key will be bounded by n"
|
|
182
|
+
" instead of q2. Encryption will work for small plaintexts,"
|
|
183
|
+
" but negative number encoding via plaintext_modulo will not"
|
|
184
|
+
" round-trip correctly because plaintext_modulo is set to n"
|
|
185
|
+
" (public) instead of q2 (private)."
|
|
186
|
+
)
|
|
187
|
+
return random.randint(1, n - 1)
|
|
171
188
|
|
|
172
189
|
def encrypt(
|
|
173
190
|
self, plaintext: int, random_key: Optional[int] = None
|
|
@@ -200,23 +217,28 @@ class BonehGohNissim(Homomorphic):
|
|
|
200
217
|
plaintext (int): restored message
|
|
201
218
|
"""
|
|
202
219
|
q1 = self.keys["private_key"]["q1"]
|
|
220
|
+
q2 = self.keys["private_key"]["q2"]
|
|
203
221
|
|
|
204
222
|
if isinstance(ciphertext, tuple):
|
|
205
|
-
return self._decrypt_gt(ciphertext, q1)
|
|
223
|
+
return self._decrypt_gt(ciphertext, q1, q2)
|
|
206
224
|
|
|
207
225
|
G = self.ec.G
|
|
208
226
|
|
|
209
227
|
mP = ciphertext * q1
|
|
210
228
|
P = G * q1
|
|
211
229
|
|
|
212
|
-
|
|
230
|
+
# P has order q2, so DLP result is in [0, q2)
|
|
231
|
+
if mP == self.ec.O:
|
|
232
|
+
return 0
|
|
233
|
+
|
|
234
|
+
for i in range(1, q2):
|
|
213
235
|
iP = P * i
|
|
214
236
|
if iP == mP:
|
|
215
237
|
return i
|
|
216
238
|
|
|
217
239
|
raise Exception("Decryption failed")
|
|
218
240
|
|
|
219
|
-
def _decrypt_gt(self, ciphertext: tuple, q1: int) -> int:
|
|
241
|
+
def _decrypt_gt(self, ciphertext: tuple, q1: int, q2: int) -> int:
|
|
220
242
|
"""
|
|
221
243
|
Decrypt a G_T ciphertext (result of homomorphic multiplication).
|
|
222
244
|
|
|
@@ -241,9 +263,12 @@ class BonehGohNissim(Homomorphic):
|
|
|
241
263
|
base = _fp2_pow(e_gg, q1, p)
|
|
242
264
|
target = _fp2_pow(ciphertext, q1, p)
|
|
243
265
|
|
|
244
|
-
# brute-force DLP in G_T
|
|
266
|
+
# brute-force DLP in G_T — result is in [0, q2)
|
|
267
|
+
if target == (1, 0):
|
|
268
|
+
return 0
|
|
269
|
+
|
|
245
270
|
acc = (1, 0)
|
|
246
|
-
for i in range(1,
|
|
271
|
+
for i in range(1, q2):
|
|
247
272
|
acc = _fp2_mul(acc, base, p)
|
|
248
273
|
if acc == target:
|
|
249
274
|
return i
|
|
@@ -251,8 +276,10 @@ class BonehGohNissim(Homomorphic):
|
|
|
251
276
|
raise Exception("Decryption of G_T ciphertext failed")
|
|
252
277
|
|
|
253
278
|
def add(
|
|
254
|
-
self,
|
|
255
|
-
|
|
279
|
+
self,
|
|
280
|
+
ciphertext1: Union[EllipticCurvePoint, Tuple[int, int]],
|
|
281
|
+
ciphertext2: Union[EllipticCurvePoint, Tuple[int, int]],
|
|
282
|
+
) -> Union[EllipticCurvePoint, Tuple[int, int]]:
|
|
256
283
|
"""
|
|
257
284
|
Add two ciphertexts with Boneh-Goh-Nissim
|
|
258
285
|
Args:
|
|
@@ -261,7 +288,15 @@ class BonehGohNissim(Homomorphic):
|
|
|
261
288
|
Returns:
|
|
262
289
|
result (tuple): resulting ciphertext after addition
|
|
263
290
|
"""
|
|
264
|
-
|
|
291
|
+
if isinstance(ciphertext1, EllipticCurvePoint) and isinstance(
|
|
292
|
+
ciphertext2, EllipticCurvePoint
|
|
293
|
+
):
|
|
294
|
+
return ciphertext1 + ciphertext2
|
|
295
|
+
|
|
296
|
+
if isinstance(ciphertext1, tuple) and isinstance(ciphertext2, tuple):
|
|
297
|
+
return _fp2_mul(ciphertext1, ciphertext2, self.ec.modulo)
|
|
298
|
+
|
|
299
|
+
raise ValueError("Both ciphertexts must be of the same type for addition.")
|
|
265
300
|
|
|
266
301
|
def multiply(
|
|
267
302
|
self,
|
|
@@ -300,8 +335,8 @@ class BonehGohNissim(Homomorphic):
|
|
|
300
335
|
return result
|
|
301
336
|
|
|
302
337
|
def multiply_by_constant(
|
|
303
|
-
self, ciphertext: EllipticCurvePoint, constant: int
|
|
304
|
-
) -> EllipticCurvePoint:
|
|
338
|
+
self, ciphertext: Union[EllipticCurvePoint, Tuple[int, int]], constant: int
|
|
339
|
+
) -> Union[EllipticCurvePoint, Tuple[int, int]]:
|
|
305
340
|
"""
|
|
306
341
|
Multiply a ciphertext by a constant with Boneh-Goh-Nissim
|
|
307
342
|
Args:
|
|
@@ -310,7 +345,20 @@ class BonehGohNissim(Homomorphic):
|
|
|
310
345
|
Returns:
|
|
311
346
|
result (tuple): resulting ciphertext after multiplication
|
|
312
347
|
"""
|
|
313
|
-
|
|
348
|
+
if isinstance(ciphertext, EllipticCurvePoint):
|
|
349
|
+
return ciphertext * constant
|
|
350
|
+
if isinstance(ciphertext, tuple):
|
|
351
|
+
# For GT elements, we can use exponentiation to achieve scalar multiplication
|
|
352
|
+
base = ciphertext
|
|
353
|
+
result = (1, 0) # identity element in F_{p^2}*
|
|
354
|
+
for _ in range(constant):
|
|
355
|
+
result = _fp2_mul(result, base, self.ec.modulo)
|
|
356
|
+
return result
|
|
357
|
+
|
|
358
|
+
raise ValueError(
|
|
359
|
+
"Ciphertext must be either an EllipticCurvePoint or a tuple "
|
|
360
|
+
"representing an F_{p^2} element for multiplication by constant."
|
|
361
|
+
)
|
|
314
362
|
|
|
315
363
|
def reencrypt(self, ciphertext: EllipticCurvePoint) -> EllipticCurvePoint:
|
|
316
364
|
"""
|
|
@@ -17,8 +17,8 @@ logger = Logger(module="lightphe/cryptosystems/SanderYoungYung.py")
|
|
|
17
17
|
|
|
18
18
|
class SanderYoungYung(Homomorphic):
|
|
19
19
|
"""
|
|
20
|
-
Sander-Young-Yung algorithm is homomorphic with respect to
|
|
21
|
-
|
|
20
|
+
Sander-Young-Yung algorithm is homomorphic with respect to bitwise AND.
|
|
21
|
+
Ref: https://sefiks.com/2026/04/02/a-step-by-step-partially-homomorphic-sander-young-yung-example-in-python/
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
24
|
def __init__(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: lightphe
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.23
|
|
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
|
|
@@ -36,7 +36,7 @@ License-File: LICENSE
|
|
|
36
36
|
|
|
37
37
|
<p align="center"><img src="https://raw.githubusercontent.com/serengil/LightPHE/master/icons/phi.png" width="200" height="240"></p>
|
|
38
38
|
|
|
39
|
-
LightPHE is a lightweight
|
|
39
|
+
LightPHE is a lightweight homomorphic encryption library for python supporting various partially and somewhat homomorphic encryptions schemes such as [`RSA`](https://sefiks.com/2023/03/06/a-step-by-step-partially-homomorphic-encryption-example-with-rsa-in-python/), [`ElGamal`](https://sefiks.com/2023/03/27/a-step-by-step-partially-homomorphic-encryption-example-with-elgamal-in-python/), [`Exponential ElGamal`](https://sefiks.com/2023/03/27/a-step-by-step-partially-homomorphic-encryption-example-with-elgamal-in-python/), [`Elliptic Curve ElGamal`](https://sefiks.com/2018/08/21/elliptic-curve-elgamal-encryption/) ([`Weierstrass`](https://sefiks.com/2016/03/13/the-math-behind-elliptic-curve-cryptography/), [`Koblitz`](sefiks.com/2016/03/13/the-math-behind-elliptic-curves-over-binary-field/) and [`Edwards`](https://sefiks.com/2018/12/19/a-gentle-introduction-to-edwards-curves/) forms), [`Paillier`](https://sefiks.com/2023/04/03/a-step-by-step-partially-homomorphic-encryption-example-with-paillier-in-python/), [`Damgard-Jurik`](https://sefiks.com/2023/10/20/a-step-by-step-partially-homomorphic-encryption-example-with-damgard-jurik-in-python/), [`Okamoto–Uchiyama`](https://sefiks.com/2023/10/20/a-step-by-step-partially-homomorphic-encryption-example-with-okamoto-uchiyama-in-python/), [`Benaloh`](https://sefiks.com/2023/10/06/a-step-by-step-partially-homomorphic-encryption-example-with-benaloh-in-python-from-scratch/), [`Naccache–Stern`](https://sefiks.com/2023/10/26/a-step-by-step-partially-homomorphic-encryption-example-with-naccache-stern-in-python/), [`Goldwasser–Micali`](https://sefiks.com/2023/10/27/a-step-by-step-partially-homomorphic-encryption-example-with-goldwasser-micali-in-python/), [`Sander-Young-Yung`](https://sefiks.com/2026/04/02/a-step-by-step-partially-homomorphic-sander-young-yung-example-in-python/), [`Boneh-Goh-Nissim`](https://sefiks.com/2026/04/02/a-step-by-step-somewhat-homomorphic-encryption-example-with-boneh-goh-nissim-in-python/).
|
|
40
40
|
|
|
41
41
|
# Partially vs Fully Homomorphic Encryption
|
|
42
42
|
|
|
@@ -8,7 +8,7 @@ with open("requirements.txt", "r", encoding="utf-8") as f:
|
|
|
8
8
|
|
|
9
9
|
setuptools.setup(
|
|
10
10
|
name="lightphe",
|
|
11
|
-
version="0.0.
|
|
11
|
+
version="0.0.23",
|
|
12
12
|
author="Sefik Ilkin Serengil",
|
|
13
13
|
author_email="serengil@gmail.com",
|
|
14
14
|
description="A Lightweight Partially Homomorphic Encryption Library for Python",
|
|
@@ -1,44 +1,13 @@
|
|
|
1
1
|
import pytest
|
|
2
|
-
from lightphe.cryptosystems.Benaloh import Benaloh
|
|
3
2
|
from lightphe.commons.logger import Logger
|
|
4
3
|
|
|
5
4
|
logger = Logger(module="tests/test_benaloh.py")
|
|
6
5
|
|
|
7
6
|
|
|
8
|
-
def test_benaloh():
|
|
9
|
-
bn = Benaloh()
|
|
10
|
-
|
|
11
|
-
m1 = bn.plaintext_modulo + 18
|
|
12
|
-
m2 = bn.plaintext_modulo + 22
|
|
13
|
-
|
|
14
|
-
c1 = bn.encrypt(m1)
|
|
15
|
-
c2 = bn.encrypt(m2)
|
|
16
|
-
|
|
17
|
-
# supported homomorphic operations
|
|
18
|
-
assert bn.decrypt(bn.add(c1, c2)) == (m1 + m2) % bn.plaintext_modulo
|
|
19
|
-
assert (
|
|
20
|
-
bn.decrypt(bn.multiply_by_constant(c1, m2)) == (m1 * m2) % bn.plaintext_modulo
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
# unsupported homomorphic operations
|
|
24
|
-
with pytest.raises(ValueError):
|
|
25
|
-
bn.multiply(c1, m2)
|
|
26
|
-
|
|
27
|
-
with pytest.raises(ValueError):
|
|
28
|
-
bn.xor(c1, c2)
|
|
29
|
-
|
|
30
|
-
# re-encryption
|
|
31
|
-
c1_prime = bn.reencrypt(c1)
|
|
32
|
-
assert c1_prime != c1
|
|
33
|
-
assert bn.decrypt(c1_prime) == m1 % bn.plaintext_modulo
|
|
34
|
-
|
|
35
|
-
logger.info("✅ Benaloh test succeeded")
|
|
36
|
-
|
|
37
|
-
|
|
38
7
|
def test_api():
|
|
39
8
|
from lightphe import LightPHE
|
|
40
9
|
|
|
41
|
-
cs = LightPHE(algorithm_name="Benaloh"
|
|
10
|
+
cs = LightPHE(algorithm_name="Benaloh", key_size=50)
|
|
42
11
|
|
|
43
12
|
n = cs.cs.keys["public_key"]["n"]
|
|
44
13
|
r = cs.cs.keys["public_key"]["r"]
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from lightphe import LightPHE
|
|
6
|
+
from lightphe.commons.logger import Logger
|
|
7
|
+
|
|
8
|
+
logger = Logger(module="tests/test_bonehgodnissim.py")
|
|
9
|
+
|
|
10
|
+
cs = LightPHE(
|
|
11
|
+
algorithm_name="Boneh-Goh-Nissim",
|
|
12
|
+
key_size=50,
|
|
13
|
+
max_tries=10000,
|
|
14
|
+
)
|
|
15
|
+
security_level = cs.cs.ec.n.bit_length()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_api():
|
|
19
|
+
tic = time.time()
|
|
20
|
+
|
|
21
|
+
m1 = 83
|
|
22
|
+
m2 = 31
|
|
23
|
+
|
|
24
|
+
c1 = cs.encrypt(plaintext=m1)
|
|
25
|
+
c2 = cs.encrypt(plaintext=m2)
|
|
26
|
+
|
|
27
|
+
# proof of decryption
|
|
28
|
+
assert cs.decrypt(c1) == m1
|
|
29
|
+
assert cs.decrypt(c2) == m2
|
|
30
|
+
|
|
31
|
+
# homomorphic addition
|
|
32
|
+
assert cs.decrypt(c1 + c2) == (m1 + m2) % cs.cs.plaintext_modulo
|
|
33
|
+
|
|
34
|
+
# homomorphic multiplication (once)
|
|
35
|
+
c1_times_c2 = c1 * c2
|
|
36
|
+
assert cs.decrypt(c1_times_c2) == (m1 * m2) % cs.cs.plaintext_modulo
|
|
37
|
+
|
|
38
|
+
# scalar multiplication
|
|
39
|
+
k = 2
|
|
40
|
+
assert cs.decrypt(c1 * k) == (m1 * k) % cs.cs.plaintext_modulo
|
|
41
|
+
|
|
42
|
+
# unsupported homomorphic operations
|
|
43
|
+
with pytest.raises(
|
|
44
|
+
ValueError,
|
|
45
|
+
match="Boneh-Goh-Nissim only supports multiplication ciphertexts once!",
|
|
46
|
+
):
|
|
47
|
+
_ = c1_times_c2 * c2
|
|
48
|
+
|
|
49
|
+
with pytest.raises(ValueError):
|
|
50
|
+
_ = c1 & c2
|
|
51
|
+
|
|
52
|
+
with pytest.raises(ValueError):
|
|
53
|
+
_ = c1 ^ c2
|
|
54
|
+
|
|
55
|
+
# re-randomization
|
|
56
|
+
c1_prime = cs.cs.reencrypt(c1.value)
|
|
57
|
+
c2_prime = cs.cs.reencrypt(c2.value)
|
|
58
|
+
# assert c1.value != c1_prime
|
|
59
|
+
# assert c2.value != c2_prime
|
|
60
|
+
assert cs.cs.decrypt(c1_prime) == m1
|
|
61
|
+
assert cs.cs.decrypt(c2_prime) == m2
|
|
62
|
+
|
|
63
|
+
logger.info(
|
|
64
|
+
f"✅ Boneh-God-Nissim test succeeded ({security_level} bit ECC security level)"
|
|
65
|
+
f" in {time.time() - tic:.2f} seconds"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_linear_regression():
|
|
70
|
+
tic = time.time()
|
|
71
|
+
|
|
72
|
+
x1 = 5
|
|
73
|
+
w1 = 7
|
|
74
|
+
|
|
75
|
+
x2 = 3
|
|
76
|
+
w2 = 4
|
|
77
|
+
|
|
78
|
+
x1_enc = cs.encrypt(plaintext=x1)
|
|
79
|
+
w1_enc = cs.encrypt(plaintext=w1)
|
|
80
|
+
|
|
81
|
+
x2_enc = cs.encrypt(plaintext=x2)
|
|
82
|
+
w2_enc = cs.encrypt(plaintext=w2)
|
|
83
|
+
|
|
84
|
+
x1w1_enc = x1_enc * w1_enc
|
|
85
|
+
x2w2_enc = x2_enc * w2_enc
|
|
86
|
+
|
|
87
|
+
sum_enc = x1w1_enc + x2w2_enc
|
|
88
|
+
|
|
89
|
+
sum_decrypted = cs.decrypt(sum_enc)
|
|
90
|
+
|
|
91
|
+
assert sum_decrypted == (x1 * w1 + x2 * w2)
|
|
92
|
+
|
|
93
|
+
# multiplication by constant
|
|
94
|
+
_5x1w1_enc = cs.decrypt(x1w1_enc * 5)
|
|
95
|
+
assert _5x1w1_enc == (5 * x1 * w1)
|
|
96
|
+
|
|
97
|
+
# unsupported homomorphic operations
|
|
98
|
+
with pytest.raises(
|
|
99
|
+
ValueError,
|
|
100
|
+
match="Boneh-Goh-Nissim only supports multiplication ciphertexts once!",
|
|
101
|
+
):
|
|
102
|
+
_ = x1w1_enc * w2_enc
|
|
103
|
+
|
|
104
|
+
logger.info(
|
|
105
|
+
f"✅ Boneh-God-Nissim linear regression test succeeded "
|
|
106
|
+
f"({security_level} bit ECC security level)"
|
|
107
|
+
f" in {time.time() - tic:.2f} seconds"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_cosine_similarity():
|
|
112
|
+
tic = time.time()
|
|
113
|
+
i1 = 5
|
|
114
|
+
i2 = 2
|
|
115
|
+
i3 = 8
|
|
116
|
+
|
|
117
|
+
# v2 = [1, 1, 2]
|
|
118
|
+
j1 = 1
|
|
119
|
+
j2 = 1
|
|
120
|
+
j3 = 2
|
|
121
|
+
|
|
122
|
+
i1_enc = cs.encrypt(i1)
|
|
123
|
+
i2_enc = cs.encrypt(i2)
|
|
124
|
+
i3_enc = cs.encrypt(i3)
|
|
125
|
+
|
|
126
|
+
j1_enc = cs.encrypt(j1)
|
|
127
|
+
j2_enc = cs.encrypt(j2)
|
|
128
|
+
j3_enc = cs.encrypt(j3)
|
|
129
|
+
|
|
130
|
+
# homomorphic operation
|
|
131
|
+
cosine_sim_enc = i1_enc * j1_enc + i2_enc * j2_enc + i3_enc * j3_enc
|
|
132
|
+
|
|
133
|
+
# proof of work
|
|
134
|
+
assert cs.decrypt(cosine_sim_enc) == i1 * j1 + i2 * j2 + i3 * j3
|
|
135
|
+
|
|
136
|
+
logger.info(
|
|
137
|
+
f"✅ Boneh-God-Nissim cosine similarity test succeeded "
|
|
138
|
+
f"({security_level} bit ECC security level)"
|
|
139
|
+
f" in {time.time() - tic:.2f} seconds"
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def test_euclidean_distance():
|
|
144
|
+
tic = time.time()
|
|
145
|
+
|
|
146
|
+
# v1 = [5, 2, 3]
|
|
147
|
+
i1 = 5
|
|
148
|
+
i2 = 2
|
|
149
|
+
i3 = 8
|
|
150
|
+
|
|
151
|
+
# v2 = [1, 1, 2]
|
|
152
|
+
j1 = 1
|
|
153
|
+
j2 = 1
|
|
154
|
+
j3 = 2
|
|
155
|
+
|
|
156
|
+
i1_enc = cs.encrypt(i1)
|
|
157
|
+
i2_enc = cs.encrypt(i2)
|
|
158
|
+
i3_enc = cs.encrypt(i3)
|
|
159
|
+
|
|
160
|
+
j1_enc = cs.encrypt(-j1)
|
|
161
|
+
j2_enc = cs.encrypt(-j2)
|
|
162
|
+
j3_enc = cs.encrypt(-j3)
|
|
163
|
+
|
|
164
|
+
euclidean_sqrt_enc = (
|
|
165
|
+
(i1_enc + j1_enc) * (i1_enc + j1_enc)
|
|
166
|
+
+ (i2_enc + j2_enc) * (i2_enc + j2_enc)
|
|
167
|
+
+ (i3_enc + j3_enc) * (i3_enc + j3_enc)
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
assert (
|
|
171
|
+
cs.decrypt(euclidean_sqrt_enc)
|
|
172
|
+
== (i1 - j1) ** 2 + (i2 - j2) ** 2 + (i3 - j3) ** 2
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
logger.info(
|
|
176
|
+
f"✅ Boneh-God-Nissim euclidean distance test succeeded "
|
|
177
|
+
f"({security_level} bit ECC security level)"
|
|
178
|
+
f" in {time.time() - tic:.2f} seconds"
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def test_api_with_predefined_keys():
|
|
183
|
+
keys = {
|
|
184
|
+
"private_key": {"q1": 1260048562698661, "q2": 1495813357973021},
|
|
185
|
+
"public_key": {
|
|
186
|
+
"curve": {
|
|
187
|
+
"a": 1,
|
|
188
|
+
"b": 0,
|
|
189
|
+
"p": 1477681217875020437035023206706703,
|
|
190
|
+
"G": (
|
|
191
|
+
404050258967758769169929337984186,
|
|
192
|
+
548422986186523785500578682099939,
|
|
193
|
+
),
|
|
194
|
+
"n": 1884797471779362802340590824881,
|
|
195
|
+
},
|
|
196
|
+
"G": (404050258967758769169929337984186, 548422986186523785500578682099939),
|
|
197
|
+
"u": (
|
|
198
|
+
496699107331579016081758964967239,
|
|
199
|
+
1209427529257066171815901459972102,
|
|
200
|
+
),
|
|
201
|
+
"h": (557434130026147587760748115479880, 109859910481881270030204038710204),
|
|
202
|
+
"l": 784,
|
|
203
|
+
},
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
bgn_cs = LightPHE(
|
|
207
|
+
algorithm_name="Boneh-Goh-Nissim",
|
|
208
|
+
keys=keys,
|
|
209
|
+
)
|
|
210
|
+
security_level = bgn_cs.cs.ec.n.bit_length()
|
|
211
|
+
|
|
212
|
+
m1 = 3
|
|
213
|
+
m2 = 2
|
|
214
|
+
|
|
215
|
+
c1 = bgn_cs.encrypt(plaintext=m1)
|
|
216
|
+
c2 = bgn_cs.encrypt(plaintext=m2)
|
|
217
|
+
|
|
218
|
+
# proof of decryption
|
|
219
|
+
assert bgn_cs.decrypt(c1) == m1
|
|
220
|
+
assert bgn_cs.decrypt(c2) == m2
|
|
221
|
+
|
|
222
|
+
# homomorphic addition
|
|
223
|
+
assert bgn_cs.decrypt(c1 + c2) == (m1 + m2) % bgn_cs.cs.plaintext_modulo
|
|
224
|
+
|
|
225
|
+
# homomorphic multiplication
|
|
226
|
+
c1_times_c2 = c1 * c2
|
|
227
|
+
assert bgn_cs.decrypt(c1_times_c2) == (m1 * m2) % bgn_cs.cs.plaintext_modulo
|
|
228
|
+
|
|
229
|
+
# scalar multiplication
|
|
230
|
+
k = 2
|
|
231
|
+
assert bgn_cs.decrypt(c1 * k) == (m1 * k) % bgn_cs.cs.plaintext_modulo
|
|
232
|
+
|
|
233
|
+
# unsupported homomorphic operations
|
|
234
|
+
|
|
235
|
+
with pytest.raises(ValueError):
|
|
236
|
+
_ = c1 & c2
|
|
237
|
+
|
|
238
|
+
with pytest.raises(ValueError):
|
|
239
|
+
_ = c1 ^ c2
|
|
240
|
+
|
|
241
|
+
# re-randomization
|
|
242
|
+
c1_prime = bgn_cs.cs.reencrypt(c1.value)
|
|
243
|
+
c2_prime = bgn_cs.cs.reencrypt(c2.value)
|
|
244
|
+
# assert c1.value != c1_prime
|
|
245
|
+
# assert c2.value != c2_prime
|
|
246
|
+
assert bgn_cs.cs.decrypt(c1_prime) == m1
|
|
247
|
+
assert bgn_cs.cs.decrypt(c2_prime) == m2
|
|
248
|
+
|
|
249
|
+
logger.info(
|
|
250
|
+
f"✅ Boneh-God-Nissim test succeeded ({security_level} bit ECC security level)"
|
|
251
|
+
)
|
|
@@ -6,10 +6,10 @@ from lightphe.commons.logger import Logger
|
|
|
6
6
|
|
|
7
7
|
logger = Logger(module="tests/test_ciphertexts.py")
|
|
8
8
|
|
|
9
|
+
onprem_cs = LightPHE(algorithm_name="Paillier", key_size=50)
|
|
9
10
|
|
|
10
|
-
def test_private_key_not_available_in_ciphertext():
|
|
11
|
-
onprem_cs = LightPHE(algorithm_name="Paillier")
|
|
12
11
|
|
|
12
|
+
def test_private_key_not_available_in_ciphertext():
|
|
13
13
|
m = 17
|
|
14
14
|
|
|
15
15
|
c = onprem_cs.encrypt(m)
|
|
@@ -21,8 +21,6 @@ def test_private_key_not_available_in_ciphertext():
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def test_private_key_not_available_in_encrypted_tensor():
|
|
24
|
-
onprem_cs = LightPHE(algorithm_name="Paillier")
|
|
25
|
-
|
|
26
24
|
t = [1.5, 2.4, 3.3, 4.2, 5.1]
|
|
27
25
|
|
|
28
26
|
c = onprem_cs.encrypt(t, silent=True)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from lightphe.commons.logger import Logger
|
|
3
|
+
|
|
4
|
+
logger = Logger(module="tests/test_damgard.py")
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_api():
|
|
8
|
+
from lightphe import LightPHE
|
|
9
|
+
|
|
10
|
+
cs = LightPHE(algorithm_name="Damgard-Jurik", key_size=50)
|
|
11
|
+
|
|
12
|
+
m1 = 17
|
|
13
|
+
m2 = 21
|
|
14
|
+
|
|
15
|
+
c1 = cs.encrypt(plaintext=m1)
|
|
16
|
+
c2 = cs.encrypt(plaintext=m2)
|
|
17
|
+
|
|
18
|
+
# homomorphic addition
|
|
19
|
+
assert cs.decrypt(c1 + c2) == m1 + m2
|
|
20
|
+
|
|
21
|
+
# homomorphic scalar multiplication
|
|
22
|
+
assert cs.decrypt(c1 * m2) == m1 * m2
|
|
23
|
+
assert cs.decrypt(c2 * m1) == m1 * m2
|
|
24
|
+
assert cs.decrypt(m2 * c1) == m1 * m2
|
|
25
|
+
assert cs.decrypt(m1 * c2) == m1 * m2
|
|
26
|
+
|
|
27
|
+
# unsupported homomorphic operations
|
|
28
|
+
with pytest.raises(ValueError):
|
|
29
|
+
_ = c1 * c2
|
|
30
|
+
|
|
31
|
+
with pytest.raises(ValueError):
|
|
32
|
+
_ = c1 ^ c2
|
|
33
|
+
|
|
34
|
+
logger.info("✅ Damgard-Jurik api test succeeded")
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from lightphe.commons.logger import Logger
|
|
3
|
+
|
|
4
|
+
logger = Logger(module="tests/test_elgamal.py")
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_api_for_multiplicative():
|
|
8
|
+
from lightphe import LightPHE
|
|
9
|
+
|
|
10
|
+
cs = LightPHE(algorithm_name="ElGamal", key_size=50)
|
|
11
|
+
|
|
12
|
+
m1 = 17
|
|
13
|
+
m2 = 21
|
|
14
|
+
|
|
15
|
+
c1 = cs.encrypt(plaintext=m1)
|
|
16
|
+
c2 = cs.encrypt(plaintext=m2)
|
|
17
|
+
|
|
18
|
+
# homomorphic addition
|
|
19
|
+
assert cs.decrypt(c1 * c2) == m1 * m2
|
|
20
|
+
|
|
21
|
+
# unsupported homomorphic operations
|
|
22
|
+
with pytest.raises(ValueError):
|
|
23
|
+
_ = c1 + c2
|
|
24
|
+
|
|
25
|
+
with pytest.raises(ValueError):
|
|
26
|
+
_ = c1 ^ c2
|
|
27
|
+
|
|
28
|
+
with pytest.raises(ValueError):
|
|
29
|
+
_ = c1 * 5
|
|
30
|
+
|
|
31
|
+
with pytest.raises(ValueError):
|
|
32
|
+
_ = 5 * c1
|
|
33
|
+
|
|
34
|
+
logger.info("✅ ElGamal api test succeeded")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_api_for_additive():
|
|
38
|
+
from lightphe import LightPHE
|
|
39
|
+
|
|
40
|
+
cs = LightPHE(algorithm_name="Exponential-ElGamal", key_size=50)
|
|
41
|
+
|
|
42
|
+
m1 = 17
|
|
43
|
+
m2 = 21
|
|
44
|
+
|
|
45
|
+
c1 = cs.encrypt(plaintext=m1)
|
|
46
|
+
c2 = cs.encrypt(plaintext=m2)
|
|
47
|
+
|
|
48
|
+
# homomorphic addition
|
|
49
|
+
assert cs.decrypt(c1 + c2) == m1 + m2
|
|
50
|
+
|
|
51
|
+
# scalar multiplication
|
|
52
|
+
assert cs.decrypt(c1 * m2) == m1 * m2
|
|
53
|
+
assert cs.decrypt(c2 * m1) == m1 * m2
|
|
54
|
+
assert cs.decrypt(m2 * c1) == m1 * m2
|
|
55
|
+
assert cs.decrypt(m1 * c2) == m1 * m2
|
|
56
|
+
|
|
57
|
+
# unsupported homomorphic operations
|
|
58
|
+
with pytest.raises(ValueError):
|
|
59
|
+
_ = c1 * c2
|
|
60
|
+
|
|
61
|
+
with pytest.raises(ValueError):
|
|
62
|
+
_ = c1 ^ c2
|
|
63
|
+
|
|
64
|
+
logger.info("✅ Exponential ElGamal api test succeeded")
|