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.
Files changed (53) hide show
  1. {lightphe-0.0.11 → lightphe-0.0.12}/PKG-INFO +3 -2
  2. {lightphe-0.0.11 → lightphe-0.0.12}/README.md +2 -1
  3. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/__init__.py +117 -40
  4. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/cryptosystems/EllipticCurveElGamal.py +2 -2
  5. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/models/Tensor.py +107 -7
  6. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe.egg-info/PKG-INFO +3 -2
  7. lightphe-0.0.12/lightphe.egg-info/requires.txt +3 -0
  8. lightphe-0.0.11/lightphe.egg-info/requires.txt → lightphe-0.0.12/requirements.txt +1 -0
  9. {lightphe-0.0.11 → lightphe-0.0.12}/setup.py +1 -1
  10. {lightphe-0.0.11 → lightphe-0.0.12}/tests/test_tensors.py +95 -8
  11. lightphe-0.0.11/requirements.txt +0 -2
  12. {lightphe-0.0.11 → lightphe-0.0.12}/LICENSE +0 -0
  13. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/commons/__init__.py +0 -0
  14. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/commons/binary_operations.py +0 -0
  15. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/commons/logger.py +0 -0
  16. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/commons/phe_utils.py +0 -0
  17. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/cryptosystems/Benaloh.py +0 -0
  18. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/cryptosystems/DamgardJurik.py +0 -0
  19. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/cryptosystems/ElGamal.py +0 -0
  20. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/cryptosystems/GoldwasserMicali.py +0 -0
  21. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/cryptosystems/NaccacheStern.py +0 -0
  22. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/cryptosystems/OkamotoUchiyama.py +0 -0
  23. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/cryptosystems/Paillier.py +0 -0
  24. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/cryptosystems/RSA.py +0 -0
  25. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/cryptosystems/__init__.py +0 -0
  26. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/elliptic_curve_forms/__init__.py +0 -0
  27. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/elliptic_curve_forms/edwards.py +0 -0
  28. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/elliptic_curve_forms/koblitz.py +0 -0
  29. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/elliptic_curve_forms/weierstrass.py +0 -0
  30. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/models/Algorithm.py +0 -0
  31. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/models/Ciphertext.py +0 -0
  32. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/models/Curve.py +0 -0
  33. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/models/EllipticCurve.py +0 -0
  34. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/models/Homomorphic.py +0 -0
  35. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe/models/__init__.py +0 -0
  36. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe.egg-info/SOURCES.txt +0 -0
  37. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe.egg-info/dependency_links.txt +0 -0
  38. {lightphe-0.0.11 → lightphe-0.0.12}/lightphe.egg-info/top_level.txt +0 -0
  39. {lightphe-0.0.11 → lightphe-0.0.12}/setup.cfg +0 -0
  40. {lightphe-0.0.11 → lightphe-0.0.12}/tests/__init__.py +0 -0
  41. {lightphe-0.0.11 → lightphe-0.0.12}/tests/test_benaloh.py +0 -0
  42. {lightphe-0.0.11 → lightphe-0.0.12}/tests/test_cloud.py +0 -0
  43. {lightphe-0.0.11 → lightphe-0.0.12}/tests/test_damgard.py +0 -0
  44. {lightphe-0.0.11 → lightphe-0.0.12}/tests/test_elgamal.py +0 -0
  45. {lightphe-0.0.11 → lightphe-0.0.12}/tests/test_ellipticcurve.py +0 -0
  46. {lightphe-0.0.11 → lightphe-0.0.12}/tests/test_ellipticcurveelgamal.py +0 -0
  47. {lightphe-0.0.11 → lightphe-0.0.12}/tests/test_exporting_keys.py +0 -0
  48. {lightphe-0.0.11 → lightphe-0.0.12}/tests/test_goldwasser.py +0 -0
  49. {lightphe-0.0.11 → lightphe-0.0.12}/tests/test_naccache.py +0 -0
  50. {lightphe-0.0.11 → lightphe-0.0.12}/tests/test_okamoto.py +0 -0
  51. {lightphe-0.0.11 → lightphe-0.0.12}/tests/test_paillier.py +0 -0
  52. {lightphe-0.0.11 → lightphe-0.0.12}/tests/test_rsa.py +0 -0
  53. {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.11
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
  [![Tests](https://github.com/serengil/LightPHE/actions/workflows/tests.yml/badge.svg)](https://github.com/serengil/LightPHE/actions/workflows/tests.yml)
24
24
  [![License](http://img.shields.io/:license-MIT-green.svg?style=flat)](https://github.com/serengil/LightPHE/blob/master/LICENSE)
25
25
  [![arXiv](https://img.shields.io/badge/arXiv-2408.05219-b31b1b.svg?logo=arXiv)](https://arxiv.org/abs/2408.05219)
26
+ [![Hacker News](https://img.shields.io/badge/dynamic/json?color=orange&label=Hacker%20News&query=score&url=https%3A%2F%2Fhacker-news.firebaseio.com%2Fv0%2Fitem%2F43024283.json&logo=y-combinator)](https://news.ycombinator.com/item?id=43024283)
26
27
  <!-- [![DOI](http://img.shields.io/:DOI-10.48550/arXiv.2408.05219-blue.svg?style=flat)](https://arxiv.org/abs/2408.05219) -->
27
28
 
28
29
  [![Blog](https://img.shields.io/:blog-sefiks.com-blue.svg?style=flat&logo=wordpress)](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 * G
271
+ _20G = 4 * _5G
271
272
  _19G = _20G - G
272
273
  ```
273
274
 
@@ -7,6 +7,7 @@
7
7
  [![Tests](https://github.com/serengil/LightPHE/actions/workflows/tests.yml/badge.svg)](https://github.com/serengil/LightPHE/actions/workflows/tests.yml)
8
8
  [![License](http://img.shields.io/:license-MIT-green.svg?style=flat)](https://github.com/serengil/LightPHE/blob/master/LICENSE)
9
9
  [![arXiv](https://img.shields.io/badge/arXiv-2408.05219-b31b1b.svg?logo=arXiv)](https://arxiv.org/abs/2408.05219)
10
+ [![Hacker News](https://img.shields.io/badge/dynamic/json?color=orange&label=Hacker%20News&query=score&url=https%3A%2F%2Fhacker-news.firebaseio.com%2Fv0%2Fitem%2F43024283.json&logo=y-combinator)](https://news.ycombinator.com/item?id=43024283)
10
11
  <!-- [![DOI](http://img.shields.io/:DOI-10.48550/arXiv.2408.05219-blue.svg?style=flat)](https://arxiv.org/abs/2408.05219) -->
11
12
 
12
13
  [![Blog](https://img.shields.io/:blog-sefiks.com-blue.svg?style=flat&logo=wordpress)](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 * G
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.11"
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
- for m in tensor:
213
- if isinstance(m, int):
214
- dividend_encrypted = self.cs.encrypt(
215
- plaintext=(m % self.cs.plaintext_modulo) * pow(10, self.precision)
216
- )
217
- abs_dividend_encrypted = self.cs.encrypt(
218
- plaintext=(abs(m) % self.cs.plaintext_modulo)
219
- * pow(10, self.precision)
220
- )
221
- divisor_encrypted = self.cs.encrypt(plaintext=pow(10, self.precision))
222
- c = Fraction(
223
- dividend=dividend_encrypted,
224
- divisor=divisor_encrypted,
225
- abs_dividend=abs_dividend_encrypted,
226
- sign=1 if m >= 0 else -1,
227
- )
228
- elif isinstance(m, float):
229
- dividend, divisor = phe_utils.fractionize(
230
- value=(m % self.cs.plaintext_modulo),
231
- modulo=self.cs.plaintext_modulo,
232
- precision=self.precision,
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
- else:
249
- raise ValueError(f"unimplemented type - {type(m)}")
250
- encrypted_tensor.append(c)
251
- return EncryptedTensor(fractions=encrypted_tensor, cs=self.cs)
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["public_key"]["form"] = form
50
- self.keys["private_key"]["form"] = form
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__(self, fractions: List[Fraction], cs: Homomorphic):
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 __mul__(self, other: Union["EncryptedTensor", int, float]) -> "EncryptedTensor":
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__(self, constant: Union[int, float]) -> "EncryptedTensor":
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
- constant: scalar value
239
+ multiplier: scalar value
150
240
  Returns:
151
241
  encrypted tensor
152
242
  """
153
- return self.__mul__(other=constant)
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.11
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
  [![Tests](https://github.com/serengil/LightPHE/actions/workflows/tests.yml/badge.svg)](https://github.com/serengil/LightPHE/actions/workflows/tests.yml)
24
24
  [![License](http://img.shields.io/:license-MIT-green.svg?style=flat)](https://github.com/serengil/LightPHE/blob/master/LICENSE)
25
25
  [![arXiv](https://img.shields.io/badge/arXiv-2408.05219-b31b1b.svg?logo=arXiv)](https://arxiv.org/abs/2408.05219)
26
+ [![Hacker News](https://img.shields.io/badge/dynamic/json?color=orange&label=Hacker%20News&query=score&url=https%3A%2F%2Fhacker-news.firebaseio.com%2Fv0%2Fitem%2F43024283.json&logo=y-combinator)](https://news.ycombinator.com/item?id=43024283)
26
27
  <!-- [![DOI](http://img.shields.io/:DOI-10.48550/arXiv.2408.05219-blue.svg?style=flat)](https://arxiv.org/abs/2408.05219) -->
27
28
 
28
29
  [![Blog](https://img.shields.io/:blog-sefiks.com-blue.svg?style=flat&logo=wordpress)](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 * G
271
+ _20G = 4 * _5G
271
272
  _19G = _20G - G
272
273
  ```
273
274
 
@@ -0,0 +1,3 @@
1
+ sympy==1.12
2
+ pytest==7.1.2
3
+ tqdm
@@ -1,2 +1,3 @@
1
1
  sympy==1.12
2
2
  pytest==7.1.2
3
+ tqdm
@@ -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",
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
- import pytest
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, Fraction
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("✅ Homomorphic multiplication with an integer constant tests succeeded")
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("✅ Homomorphic multiplication with a positive float constant tests succeeded")
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("✅ Homomorphic multiplication with a positive float constant tests succeeded")
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 (t1[i] >= 0 and t2[i] >= 0) or (t1[i] < 0 and t2[i] < 0) or (t1[i] + t2[i] >= 0):
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(t1[i] + t2[i], cs.cs.plaintext_modulo)
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")
@@ -1,2 +0,0 @@
1
- sympy==1.12
2
- pytest==7.1.2
File without changes
File without changes
File without changes
File without changes
File without changes