lightphe 0.0.11__tar.gz → 0.0.12__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.11 → lightphe-0.0.12}/PKG-INFO +3 -2
- {lightphe-0.0.11 → lightphe-0.0.12}/README.md +2 -1
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/__init__.py +117 -40
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/cryptosystems/EllipticCurveElGamal.py +2 -2
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/models/Tensor.py +107 -7
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe.egg-info/PKG-INFO +3 -2
- lightphe-0.0.12/lightphe.egg-info/requires.txt +3 -0
- lightphe-0.0.11/lightphe.egg-info/requires.txt → lightphe-0.0.12/requirements.txt +1 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/setup.py +1 -1
- {lightphe-0.0.11 → lightphe-0.0.12}/tests/test_tensors.py +95 -8
- lightphe-0.0.11/requirements.txt +0 -2
- {lightphe-0.0.11 → lightphe-0.0.12}/LICENSE +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/commons/__init__.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/commons/binary_operations.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/commons/logger.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/commons/phe_utils.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/cryptosystems/Benaloh.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/cryptosystems/DamgardJurik.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/cryptosystems/ElGamal.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/cryptosystems/GoldwasserMicali.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/cryptosystems/NaccacheStern.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/cryptosystems/OkamotoUchiyama.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/cryptosystems/Paillier.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/cryptosystems/RSA.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/cryptosystems/__init__.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/elliptic_curve_forms/__init__.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/elliptic_curve_forms/edwards.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/elliptic_curve_forms/koblitz.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/elliptic_curve_forms/weierstrass.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/models/Algorithm.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/models/Ciphertext.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/models/Curve.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/models/EllipticCurve.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/models/Homomorphic.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/models/__init__.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe.egg-info/SOURCES.txt +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe.egg-info/dependency_links.txt +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/lightphe.egg-info/top_level.txt +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/setup.cfg +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/tests/__init__.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/tests/test_benaloh.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/tests/test_cloud.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/tests/test_damgard.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/tests/test_elgamal.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/tests/test_ellipticcurve.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/tests/test_ellipticcurveelgamal.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/tests/test_exporting_keys.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/tests/test_goldwasser.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/tests/test_naccache.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/tests/test_okamoto.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/tests/test_paillier.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/tests/test_rsa.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.12}/tests/test_salary.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.12
|
|
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
|
|
@@ -23,6 +23,7 @@ License-File: LICENSE
|
|
|
23
23
|
[](https://github.com/serengil/LightPHE/actions/workflows/tests.yml)
|
|
24
24
|
[](https://github.com/serengil/LightPHE/blob/master/LICENSE)
|
|
25
25
|
[](https://arxiv.org/abs/2408.05219)
|
|
26
|
+
[](https://news.ycombinator.com/item?id=43024283)
|
|
26
27
|
<!-- [](https://arxiv.org/abs/2408.05219) -->
|
|
27
28
|
|
|
28
29
|
[](https://sefiks.com)
|
|
@@ -267,7 +268,7 @@ _2G = G + G
|
|
|
267
268
|
_3G = _2G + G
|
|
268
269
|
_5G = _3G + _2G
|
|
269
270
|
|
|
270
|
-
_20G = 4 *
|
|
271
|
+
_20G = 4 * _5G
|
|
271
272
|
_19G = _20G - G
|
|
272
273
|
```
|
|
273
274
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
[](https://github.com/serengil/LightPHE/actions/workflows/tests.yml)
|
|
8
8
|
[](https://github.com/serengil/LightPHE/blob/master/LICENSE)
|
|
9
9
|
[](https://arxiv.org/abs/2408.05219)
|
|
10
|
+
[](https://news.ycombinator.com/item?id=43024283)
|
|
10
11
|
<!-- [](https://arxiv.org/abs/2408.05219) -->
|
|
11
12
|
|
|
12
13
|
[](https://sefiks.com)
|
|
@@ -251,7 +252,7 @@ _2G = G + G
|
|
|
251
252
|
_3G = _2G + G
|
|
252
253
|
_5G = _3G + _2G
|
|
253
254
|
|
|
254
|
-
_20G = 4 *
|
|
255
|
+
_20G = 4 * _5G
|
|
255
256
|
_19G = _20G - G
|
|
256
257
|
```
|
|
257
258
|
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# built-in dependencies
|
|
2
|
+
import time
|
|
2
3
|
import json
|
|
3
4
|
from typing import Optional, Union, List
|
|
5
|
+
import multiprocessing
|
|
6
|
+
from contextlib import closing
|
|
7
|
+
|
|
8
|
+
# 3rd party dependencies
|
|
9
|
+
from tqdm import tqdm
|
|
4
10
|
|
|
5
11
|
# project dependencies
|
|
6
12
|
from lightphe.models.Homomorphic import Homomorphic
|
|
@@ -30,7 +36,7 @@ from lightphe.elliptic_curve_forms.koblitz import Koblitz
|
|
|
30
36
|
|
|
31
37
|
logger = Logger(module="lightphe/__init__.py")
|
|
32
38
|
|
|
33
|
-
VERSION = "0.0.
|
|
39
|
+
VERSION = "0.0.12"
|
|
34
40
|
|
|
35
41
|
|
|
36
42
|
class LightPHE:
|
|
@@ -209,46 +215,49 @@ class LightPHE:
|
|
|
209
215
|
encrypted tensor (list of encrypted tensor object)
|
|
210
216
|
"""
|
|
211
217
|
encrypted_tensor: List[Fraction] = []
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
abs_dividend, _ = phe_utils.fractionize(
|
|
235
|
-
value=(abs(m) % self.cs.plaintext_modulo),
|
|
236
|
-
modulo=self.cs.plaintext_modulo,
|
|
237
|
-
precision=self.precision,
|
|
238
|
-
)
|
|
239
|
-
dividend_encrypted = self.cs.encrypt(plaintext=dividend)
|
|
240
|
-
abs_dividend_encrypted = self.cs.encrypt(plaintext=abs_dividend)
|
|
241
|
-
divisor_encrypted = self.cs.encrypt(plaintext=divisor)
|
|
242
|
-
c = Fraction(
|
|
243
|
-
dividend=dividend_encrypted,
|
|
244
|
-
divisor=divisor_encrypted,
|
|
245
|
-
abs_dividend=abs_dividend_encrypted,
|
|
246
|
-
sign=1 if m >= 0 else -1,
|
|
218
|
+
|
|
219
|
+
encrypted_zero = self.cs.encrypt(plaintext=0)
|
|
220
|
+
divisor_encrypted = self.cs.encrypt(plaintext=10**self.precision)
|
|
221
|
+
is_positive_tensor = all(m >= 0 for m in tensor)
|
|
222
|
+
|
|
223
|
+
num_workers = min(len(tensor), 2 * multiprocessing.cpu_count())
|
|
224
|
+
logger.debug(f"encrypting tensors in {num_workers} parallel")
|
|
225
|
+
|
|
226
|
+
with closing(multiprocessing.Pool(num_workers)) as pool:
|
|
227
|
+
funclist = []
|
|
228
|
+
|
|
229
|
+
for m in tensor:
|
|
230
|
+
f = pool.apply_async(
|
|
231
|
+
encrypt_something,
|
|
232
|
+
(
|
|
233
|
+
m,
|
|
234
|
+
divisor_encrypted,
|
|
235
|
+
self.cs,
|
|
236
|
+
self.precision,
|
|
237
|
+
encrypted_zero,
|
|
238
|
+
is_positive_tensor,
|
|
239
|
+
),
|
|
247
240
|
)
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
241
|
+
funclist.append(f)
|
|
242
|
+
|
|
243
|
+
tic = time.time()
|
|
244
|
+
encrypted_tensor = []
|
|
245
|
+
for f in tqdm(
|
|
246
|
+
funclist,
|
|
247
|
+
desc="Encrypting tensors",
|
|
248
|
+
disable=True if len(tensor) < 100 else False,
|
|
249
|
+
):
|
|
250
|
+
result = f.get(timeout=10)
|
|
251
|
+
encrypted_tensor.append(result)
|
|
252
|
+
|
|
253
|
+
toc = time.time()
|
|
254
|
+
logger.debug(f"encryption took {toc - tic} seconds")
|
|
255
|
+
|
|
256
|
+
return EncryptedTensor(
|
|
257
|
+
fractions=encrypted_tensor,
|
|
258
|
+
cs=self.cs,
|
|
259
|
+
precision=self.precision,
|
|
260
|
+
)
|
|
252
261
|
|
|
253
262
|
def __decrypt_tensors(
|
|
254
263
|
self, encrypted_tensor: EncryptedTensor
|
|
@@ -356,6 +365,74 @@ class LightPHE:
|
|
|
356
365
|
)
|
|
357
366
|
|
|
358
367
|
|
|
368
|
+
def encrypt_something(
|
|
369
|
+
m: Union[int, float],
|
|
370
|
+
divisor_encrypted,
|
|
371
|
+
cs: Homomorphic,
|
|
372
|
+
precision: int,
|
|
373
|
+
encrypted_zero: int,
|
|
374
|
+
is_positive_tensor: bool,
|
|
375
|
+
) -> Fraction:
|
|
376
|
+
if m == 0:
|
|
377
|
+
# this is very common in VGG-Face embeddings
|
|
378
|
+
c = Fraction(
|
|
379
|
+
dividend=encrypted_zero,
|
|
380
|
+
divisor=divisor_encrypted,
|
|
381
|
+
abs_dividend=encrypted_zero,
|
|
382
|
+
sign=0,
|
|
383
|
+
)
|
|
384
|
+
elif isinstance(m, int):
|
|
385
|
+
dividend_encrypted = cs.encrypt(
|
|
386
|
+
plaintext=(m % cs.plaintext_modulo) * pow(10, precision)
|
|
387
|
+
)
|
|
388
|
+
abs_dividend_encrypted = (
|
|
389
|
+
dividend_encrypted
|
|
390
|
+
if is_positive_tensor
|
|
391
|
+
else cs.encrypt(
|
|
392
|
+
plaintext=(abs(m) % cs.plaintext_modulo) * pow(10, precision)
|
|
393
|
+
)
|
|
394
|
+
)
|
|
395
|
+
# divisor_encrypted = self.cs.encrypt(plaintext=pow(10, self.precision))
|
|
396
|
+
c = Fraction(
|
|
397
|
+
dividend=dividend_encrypted,
|
|
398
|
+
divisor=divisor_encrypted,
|
|
399
|
+
abs_dividend=abs_dividend_encrypted,
|
|
400
|
+
sign=1 if m >= 0 else -1,
|
|
401
|
+
)
|
|
402
|
+
elif isinstance(m, float):
|
|
403
|
+
dividend, _ = phe_utils.fractionize(
|
|
404
|
+
value=(m % cs.plaintext_modulo),
|
|
405
|
+
modulo=cs.plaintext_modulo,
|
|
406
|
+
precision=precision,
|
|
407
|
+
)
|
|
408
|
+
abs_dividend = (
|
|
409
|
+
dividend
|
|
410
|
+
if is_positive_tensor
|
|
411
|
+
else phe_utils.fractionize(
|
|
412
|
+
value=(abs(m) % cs.plaintext_modulo),
|
|
413
|
+
modulo=cs.plaintext_modulo,
|
|
414
|
+
precision=precision,
|
|
415
|
+
)[0]
|
|
416
|
+
)
|
|
417
|
+
dividend_encrypted = cs.encrypt(plaintext=dividend)
|
|
418
|
+
abs_dividend_encrypted = (
|
|
419
|
+
dividend_encrypted
|
|
420
|
+
if is_positive_tensor
|
|
421
|
+
else cs.encrypt(plaintext=abs_dividend)
|
|
422
|
+
)
|
|
423
|
+
# divisor_encrypted = self.cs.encrypt(plaintext=_divisor)
|
|
424
|
+
c = Fraction(
|
|
425
|
+
dividend=dividend_encrypted,
|
|
426
|
+
divisor=divisor_encrypted,
|
|
427
|
+
abs_dividend=abs_dividend_encrypted,
|
|
428
|
+
sign=1 if m >= 0 else -1,
|
|
429
|
+
)
|
|
430
|
+
else:
|
|
431
|
+
raise ValueError(f"unimplemented type - {type(m)}")
|
|
432
|
+
|
|
433
|
+
return c
|
|
434
|
+
|
|
435
|
+
|
|
359
436
|
class ECC:
|
|
360
437
|
__version__ = VERSION
|
|
361
438
|
|
|
@@ -46,8 +46,8 @@ class EllipticCurveElGamal(Homomorphic):
|
|
|
46
46
|
self.ecc = ECC(form_name=form, curve_name=curve)
|
|
47
47
|
|
|
48
48
|
self.keys = keys or self.generate_keys(key_size or self.ecc.n.bit_length())
|
|
49
|
-
self.keys["
|
|
50
|
-
self.keys["
|
|
49
|
+
self.keys["curve"] = curve
|
|
50
|
+
self.keys["form"] = form
|
|
51
51
|
self.plaintext_modulo = self.ecc.modulo
|
|
52
52
|
self.ciphertext_modulo = self.ecc.modulo
|
|
53
53
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from typing import Union, List
|
|
2
2
|
from lightphe.models.Homomorphic import Homomorphic
|
|
3
3
|
from lightphe.commons import phe_utils
|
|
4
|
+
from lightphe.models.Ciphertext import Ciphertext
|
|
4
5
|
from lightphe.commons.logger import Logger
|
|
5
6
|
|
|
6
7
|
logger = Logger(module="lightphe/models/Tensor.py")
|
|
@@ -43,15 +44,22 @@ class EncryptedTensor:
|
|
|
43
44
|
Class to store encrypted tensor objects
|
|
44
45
|
"""
|
|
45
46
|
|
|
46
|
-
def __init__(
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
fractions: List[Fraction],
|
|
50
|
+
cs: Homomorphic,
|
|
51
|
+
precision: int = 5,
|
|
52
|
+
):
|
|
47
53
|
"""
|
|
48
54
|
Initialization method
|
|
49
55
|
Args:
|
|
50
56
|
fractions (list): list of fractions storing individual encrypted tensor items
|
|
51
|
-
cs: cryptosystem
|
|
57
|
+
cs (cryptosystem): built cryptosystem
|
|
58
|
+
precision (int): precision of the tensor
|
|
52
59
|
"""
|
|
53
60
|
self.fractions = fractions
|
|
54
61
|
self.cs = cs
|
|
62
|
+
self.precision = precision
|
|
55
63
|
|
|
56
64
|
def __str__(self):
|
|
57
65
|
"""
|
|
@@ -68,7 +76,41 @@ class EncryptedTensor:
|
|
|
68
76
|
"""
|
|
69
77
|
return self.__str__()
|
|
70
78
|
|
|
71
|
-
def
|
|
79
|
+
def __matmul__(self, other: Union["EncryptedTensor", list]):
|
|
80
|
+
"""
|
|
81
|
+
Perform dot product of two encrypted tensors
|
|
82
|
+
"""
|
|
83
|
+
if not (
|
|
84
|
+
(isinstance(other, EncryptedTensor) and isinstance(self, list))
|
|
85
|
+
or (isinstance(other, list) and isinstance(self, EncryptedTensor))
|
|
86
|
+
):
|
|
87
|
+
raise ValueError(
|
|
88
|
+
"Dot product can be run for EncryptedTensor and List of float / int"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
encrypted_tensor = self.__mul__(other=other)
|
|
92
|
+
|
|
93
|
+
sum_dividend = cast_ciphertext(
|
|
94
|
+
cs=self.cs, value=encrypted_tensor.fractions[0].abs_dividend
|
|
95
|
+
)
|
|
96
|
+
divisor = cast_ciphertext(
|
|
97
|
+
cs=self.cs, value=encrypted_tensor.fractions[0].divisor
|
|
98
|
+
)
|
|
99
|
+
for fraction in encrypted_tensor.fractions[1:]:
|
|
100
|
+
sum_dividend += cast_ciphertext(value=fraction.abs_dividend, cs=self.cs)
|
|
101
|
+
|
|
102
|
+
fraction = Fraction(
|
|
103
|
+
dividend=sum_dividend.value,
|
|
104
|
+
abs_dividend=sum_dividend.value,
|
|
105
|
+
divisor=divisor.value,
|
|
106
|
+
sign=1,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
return EncryptedTensor(fractions=[fraction], cs=self.cs)
|
|
110
|
+
|
|
111
|
+
def __mul__(
|
|
112
|
+
self, other: Union["EncryptedTensor", int, float, list]
|
|
113
|
+
) -> "EncryptedTensor":
|
|
72
114
|
"""
|
|
73
115
|
Perform homomorphic element-wise multipliction on tensors
|
|
74
116
|
or multiplication of an encrypted tensor with a constant
|
|
@@ -112,6 +154,52 @@ class EncryptedTensor:
|
|
|
112
154
|
fractions.append(fraction)
|
|
113
155
|
|
|
114
156
|
return EncryptedTensor(fractions=fractions, cs=self.cs)
|
|
157
|
+
elif isinstance(other, list):
|
|
158
|
+
# perform element-wise multiplication of encrypted tensor with plain tensor
|
|
159
|
+
if len(self.fractions) != len(other):
|
|
160
|
+
raise ValueError(
|
|
161
|
+
"Tensor sizes must be equal in homomorphic multiplication"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
if any(i < 0 for i in other) or any(
|
|
165
|
+
fraction.sign < 0 for fraction in self.fractions
|
|
166
|
+
):
|
|
167
|
+
raise ValueError(
|
|
168
|
+
"all items in the plain and encrypted tensor must be positive"
|
|
169
|
+
" to perform element wise multiplication"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
dividends = []
|
|
173
|
+
divisor = None
|
|
174
|
+
for alpha, beta in zip(self.fractions, other):
|
|
175
|
+
c_abs_dividend, c_divisor = phe_utils.fractionize(
|
|
176
|
+
value=(abs(beta) % self.cs.plaintext_modulo),
|
|
177
|
+
modulo=self.cs.plaintext_modulo,
|
|
178
|
+
precision=self.precision,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
dividend = (
|
|
182
|
+
cast_ciphertext(cs=self.cs, value=alpha.abs_dividend)
|
|
183
|
+
* c_abs_dividend
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
if divisor is None:
|
|
187
|
+
divisor = (
|
|
188
|
+
cast_ciphertext(cs=self.cs, value=alpha.divisor) * c_divisor
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# dividends.append(dividend)
|
|
192
|
+
dividends.append(
|
|
193
|
+
Fraction(
|
|
194
|
+
dividend=dividend.value,
|
|
195
|
+
abs_dividend=dividend.value,
|
|
196
|
+
divisor=divisor.value,
|
|
197
|
+
sign=1,
|
|
198
|
+
)
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
return EncryptedTensor(fractions=dividends, cs=self.cs)
|
|
202
|
+
|
|
115
203
|
elif isinstance(other, (int, float)):
|
|
116
204
|
constant_sign = 1 if other >= 0 else -1
|
|
117
205
|
other = abs(other)
|
|
@@ -142,15 +230,17 @@ class EncryptedTensor:
|
|
|
142
230
|
"Encrypted tensor can be multiplied by an encrypted tensor or constant"
|
|
143
231
|
)
|
|
144
232
|
|
|
145
|
-
def __rmul__(
|
|
233
|
+
def __rmul__(
|
|
234
|
+
self, multiplier: Union[int, float, "EncryptedTensor"]
|
|
235
|
+
) -> "EncryptedTensor":
|
|
146
236
|
"""
|
|
147
|
-
Perform multiplication of encrypted tensor with a constant
|
|
237
|
+
Perform multiplication of encrypted tensor with a constant or plain tensor (element-wise)
|
|
148
238
|
Args:
|
|
149
|
-
|
|
239
|
+
multiplier: scalar value
|
|
150
240
|
Returns:
|
|
151
241
|
encrypted tensor
|
|
152
242
|
"""
|
|
153
|
-
return self.__mul__(other=
|
|
243
|
+
return self.__mul__(other=multiplier)
|
|
154
244
|
|
|
155
245
|
def __add__(self, other: "EncryptedTensor") -> "EncryptedTensor":
|
|
156
246
|
"""
|
|
@@ -201,3 +291,13 @@ class EncryptedTensor:
|
|
|
201
291
|
current_tensors.append(current_tensor)
|
|
202
292
|
|
|
203
293
|
return EncryptedTensor(fractions=current_tensors, cs=self.cs)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def cast_ciphertext(cs: Homomorphic, value: int) -> Ciphertext:
|
|
297
|
+
return Ciphertext(
|
|
298
|
+
algorithm_name=cs.__class__.__name__,
|
|
299
|
+
keys=cs.keys,
|
|
300
|
+
value=value,
|
|
301
|
+
form=cs.keys.get("form"),
|
|
302
|
+
curve=cs.keys.get("curve"),
|
|
303
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: lightphe
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.12
|
|
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
|
|
@@ -23,6 +23,7 @@ License-File: LICENSE
|
|
|
23
23
|
[](https://github.com/serengil/LightPHE/actions/workflows/tests.yml)
|
|
24
24
|
[](https://github.com/serengil/LightPHE/blob/master/LICENSE)
|
|
25
25
|
[](https://arxiv.org/abs/2408.05219)
|
|
26
|
+
[](https://news.ycombinator.com/item?id=43024283)
|
|
26
27
|
<!-- [](https://arxiv.org/abs/2408.05219) -->
|
|
27
28
|
|
|
28
29
|
[](https://sefiks.com)
|
|
@@ -267,7 +268,7 @@ _2G = G + G
|
|
|
267
268
|
_3G = _2G + G
|
|
268
269
|
_5G = _3G + _2G
|
|
269
270
|
|
|
270
|
-
_20G = 4 *
|
|
271
|
+
_20G = 4 * _5G
|
|
271
272
|
_19G = _20G - G
|
|
272
273
|
```
|
|
273
274
|
|
|
@@ -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.12",
|
|
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,9 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
# built-in dependencies
|
|
2
2
|
from typing import List
|
|
3
|
+
import random
|
|
4
|
+
|
|
5
|
+
# 3rd party dependencies
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
# project dependencies
|
|
3
9
|
from lightphe.commons import phe_utils
|
|
4
|
-
from lightphe.models.Tensor import EncryptedTensor
|
|
10
|
+
from lightphe.models.Tensor import EncryptedTensor
|
|
5
11
|
from lightphe import LightPHE
|
|
6
|
-
|
|
7
12
|
from lightphe.commons.logger import Logger
|
|
8
13
|
|
|
9
14
|
logger = Logger(module="tests/test_tensors.py")
|
|
@@ -11,6 +16,7 @@ logger = Logger(module="tests/test_tensors.py")
|
|
|
11
16
|
THRESHOLD = 1
|
|
12
17
|
|
|
13
18
|
|
|
19
|
+
# pylint: disable=consider-using-enumerate
|
|
14
20
|
def convert_negative_float_to_int(value: float, modulo: int) -> float:
|
|
15
21
|
x, y = phe_utils.fractionize(
|
|
16
22
|
value=value % modulo,
|
|
@@ -109,7 +115,9 @@ def test_homomorphic_multiply_with_int_constant():
|
|
|
109
115
|
for i, restored_tensor in enumerate(t2):
|
|
110
116
|
assert abs((t1[i] * constant) - restored_tensor) < THRESHOLD
|
|
111
117
|
|
|
112
|
-
logger.info(
|
|
118
|
+
logger.info(
|
|
119
|
+
"✅ Homomorphic multiplication with an integer constant tests succeeded"
|
|
120
|
+
)
|
|
113
121
|
|
|
114
122
|
|
|
115
123
|
def test_homomorphic_multiply_with_positive_float_constant():
|
|
@@ -126,7 +134,9 @@ def test_homomorphic_multiply_with_positive_float_constant():
|
|
|
126
134
|
for i, restored_tensor in enumerate(t2):
|
|
127
135
|
assert abs((t1[i] * constant) - restored_tensor) < THRESHOLD
|
|
128
136
|
|
|
129
|
-
logger.info(
|
|
137
|
+
logger.info(
|
|
138
|
+
"✅ Homomorphic multiplication with a positive float constant tests succeeded"
|
|
139
|
+
)
|
|
130
140
|
|
|
131
141
|
|
|
132
142
|
def test_homomorphic_multiply_with_negative_float_constant():
|
|
@@ -143,7 +153,9 @@ def test_homomorphic_multiply_with_negative_float_constant():
|
|
|
143
153
|
for i, restored_tensor in enumerate(t2):
|
|
144
154
|
assert abs((t1[i] * constant) - restored_tensor) < THRESHOLD
|
|
145
155
|
|
|
146
|
-
logger.info(
|
|
156
|
+
logger.info(
|
|
157
|
+
"✅ Homomorphic multiplication with a positive float constant tests succeeded"
|
|
158
|
+
)
|
|
147
159
|
|
|
148
160
|
|
|
149
161
|
def test_homomorphic_addition():
|
|
@@ -160,10 +172,16 @@ def test_homomorphic_addition():
|
|
|
160
172
|
restored_tensors = cs.decrypt(c3)
|
|
161
173
|
|
|
162
174
|
for i, restored_tensor in enumerate(restored_tensors):
|
|
163
|
-
if (
|
|
175
|
+
if (
|
|
176
|
+
(t1[i] >= 0 and t2[i] >= 0)
|
|
177
|
+
or (t1[i] < 0 and t2[i] < 0)
|
|
178
|
+
or (t1[i] + t2[i] >= 0)
|
|
179
|
+
):
|
|
164
180
|
assert abs((t1[i] + t2[i]) - restored_tensor) < THRESHOLD
|
|
165
181
|
elif t1[i] + t2[i] < 0:
|
|
166
|
-
expected = convert_negative_float_to_int(
|
|
182
|
+
expected = convert_negative_float_to_int(
|
|
183
|
+
t1[i] + t2[i], cs.cs.plaintext_modulo
|
|
184
|
+
)
|
|
167
185
|
assert abs(expected - restored_tensor) < THRESHOLD
|
|
168
186
|
else:
|
|
169
187
|
raise ValueError("else must not be called at all")
|
|
@@ -172,3 +190,72 @@ def test_homomorphic_addition():
|
|
|
172
190
|
_ = c1 * c2
|
|
173
191
|
|
|
174
192
|
logger.info("✅ Homomorphic addition tests succeeded")
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def test_for_integer_tensor():
|
|
196
|
+
cs = LightPHE(algorithm_name="Paillier")
|
|
197
|
+
|
|
198
|
+
# suppose that these are normalized vectors
|
|
199
|
+
a = [7.1, 5.2, 5.3, 2.4, 3.5, 4.6]
|
|
200
|
+
b = [5.6, 3.7, 2.8, 4, 0, 5.9]
|
|
201
|
+
|
|
202
|
+
expected_similarity = sum(x * y for x, y in zip(a, b))
|
|
203
|
+
|
|
204
|
+
enc_a = cs.encrypt(a)
|
|
205
|
+
|
|
206
|
+
fractions = enc_a.fractions
|
|
207
|
+
|
|
208
|
+
# we expect to have same divisor for all items
|
|
209
|
+
for fraction in fractions[1:]:
|
|
210
|
+
assert fractions[0].divisor == fraction.divisor
|
|
211
|
+
|
|
212
|
+
enc_a_times_b = enc_a * b
|
|
213
|
+
|
|
214
|
+
a_times_b = cs.decrypt(enc_a_times_b)
|
|
215
|
+
|
|
216
|
+
for i in range(0, len(a)):
|
|
217
|
+
assert (
|
|
218
|
+
abs(a[i] * b[i] - a_times_b[i]) < 0.1
|
|
219
|
+
), f"Expected {a[i] * b[i]}, got {a_times_b[i]}"
|
|
220
|
+
|
|
221
|
+
# dot product
|
|
222
|
+
encrypted_similarity = enc_a @ b
|
|
223
|
+
decrypted_similarity = cs.decrypt(encrypted_similarity)[0]
|
|
224
|
+
|
|
225
|
+
assert (
|
|
226
|
+
abs(decrypted_similarity - expected_similarity) < 0.1
|
|
227
|
+
), f"expected {expected_similarity} but got {decrypted_similarity}"
|
|
228
|
+
|
|
229
|
+
logger.info("✅ Integer tensor tests succeeded")
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def test_real_world_embedding():
|
|
233
|
+
cs = LightPHE(algorithm_name="Paillier")
|
|
234
|
+
|
|
235
|
+
# suppose that source and target embeddings are normalized vectors
|
|
236
|
+
|
|
237
|
+
source_embedding = [float(format(random.uniform(1, 2), ".17f")) for _ in range(128)]
|
|
238
|
+
logger.info(f"source image's embedding found - {len(source_embedding)}D")
|
|
239
|
+
|
|
240
|
+
source_embedding_encrypted = cs.encrypt(source_embedding)
|
|
241
|
+
logger.info("source embedding encrypted")
|
|
242
|
+
|
|
243
|
+
target_embedding = [float(format(random.uniform(1, 2), ".17f")) for _ in range(128)]
|
|
244
|
+
logger.info(f"target image's embedding found - {len(target_embedding)}D")
|
|
245
|
+
|
|
246
|
+
# dot product to calculate encrypted similarity
|
|
247
|
+
encrypted_similarity = source_embedding_encrypted @ target_embedding
|
|
248
|
+
|
|
249
|
+
decrypted_similarity = cs.decrypt(encrypted_similarity)[0]
|
|
250
|
+
|
|
251
|
+
expected_similarity = sum(x * y for x, y in zip(source_embedding, target_embedding))
|
|
252
|
+
|
|
253
|
+
logger.info(
|
|
254
|
+
f"ℹ️ expected similarity: {expected_similarity}, got {decrypted_similarity}"
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
assert (
|
|
258
|
+
abs(expected_similarity - decrypted_similarity) < 0.1
|
|
259
|
+
), f"expected {expected_similarity} but got {decrypted_similarity}"
|
|
260
|
+
|
|
261
|
+
logger.info("✅ Real world embedding test succeeded")
|
lightphe-0.0.11/requirements.txt
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|