lightphe 0.0.12__tar.gz → 0.0.13__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 (54) hide show
  1. {lightphe-0.0.12 → lightphe-0.0.13}/PKG-INFO +99 -23
  2. {lightphe-0.0.12 → lightphe-0.0.13}/README.md +98 -22
  3. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe/__init__.py +100 -67
  4. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe/cryptosystems/EllipticCurveElGamal.py +11 -18
  5. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe/cryptosystems/GoldwasserMicali.py +38 -11
  6. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe/cryptosystems/OkamotoUchiyama.py +10 -4
  7. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe/models/EllipticCurve.py +36 -0
  8. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe/models/Tensor.py +100 -14
  9. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe.egg-info/PKG-INFO +99 -23
  10. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe.egg-info/SOURCES.txt +2 -0
  11. {lightphe-0.0.12 → lightphe-0.0.13}/setup.py +1 -1
  12. lightphe-0.0.13/tests/test_ciphertexts.py +33 -0
  13. {lightphe-0.0.12 → lightphe-0.0.13}/tests/test_goldwasser.py +19 -16
  14. lightphe-0.0.13/tests/test_keys.py +60 -0
  15. {lightphe-0.0.12 → lightphe-0.0.13}/tests/test_tensors.py +107 -31
  16. {lightphe-0.0.12 → lightphe-0.0.13}/LICENSE +0 -0
  17. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe/commons/__init__.py +0 -0
  18. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe/commons/binary_operations.py +0 -0
  19. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe/commons/logger.py +0 -0
  20. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe/commons/phe_utils.py +0 -0
  21. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe/cryptosystems/Benaloh.py +0 -0
  22. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe/cryptosystems/DamgardJurik.py +0 -0
  23. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe/cryptosystems/ElGamal.py +0 -0
  24. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe/cryptosystems/NaccacheStern.py +0 -0
  25. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe/cryptosystems/Paillier.py +0 -0
  26. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe/cryptosystems/RSA.py +0 -0
  27. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe/cryptosystems/__init__.py +0 -0
  28. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe/elliptic_curve_forms/__init__.py +0 -0
  29. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe/elliptic_curve_forms/edwards.py +0 -0
  30. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe/elliptic_curve_forms/koblitz.py +0 -0
  31. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe/elliptic_curve_forms/weierstrass.py +0 -0
  32. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe/models/Algorithm.py +0 -0
  33. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe/models/Ciphertext.py +0 -0
  34. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe/models/Curve.py +0 -0
  35. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe/models/Homomorphic.py +0 -0
  36. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe/models/__init__.py +0 -0
  37. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe.egg-info/dependency_links.txt +0 -0
  38. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe.egg-info/requires.txt +0 -0
  39. {lightphe-0.0.12 → lightphe-0.0.13}/lightphe.egg-info/top_level.txt +0 -0
  40. {lightphe-0.0.12 → lightphe-0.0.13}/requirements.txt +0 -0
  41. {lightphe-0.0.12 → lightphe-0.0.13}/setup.cfg +0 -0
  42. {lightphe-0.0.12 → lightphe-0.0.13}/tests/__init__.py +0 -0
  43. {lightphe-0.0.12 → lightphe-0.0.13}/tests/test_benaloh.py +0 -0
  44. {lightphe-0.0.12 → lightphe-0.0.13}/tests/test_cloud.py +0 -0
  45. {lightphe-0.0.12 → lightphe-0.0.13}/tests/test_damgard.py +0 -0
  46. {lightphe-0.0.12 → lightphe-0.0.13}/tests/test_elgamal.py +0 -0
  47. {lightphe-0.0.12 → lightphe-0.0.13}/tests/test_ellipticcurve.py +0 -0
  48. {lightphe-0.0.12 → lightphe-0.0.13}/tests/test_ellipticcurveelgamal.py +0 -0
  49. {lightphe-0.0.12 → lightphe-0.0.13}/tests/test_exporting_keys.py +0 -0
  50. {lightphe-0.0.12 → lightphe-0.0.13}/tests/test_naccache.py +0 -0
  51. {lightphe-0.0.12 → lightphe-0.0.13}/tests/test_okamoto.py +0 -0
  52. {lightphe-0.0.12 → lightphe-0.0.13}/tests/test_paillier.py +0 -0
  53. {lightphe-0.0.12 → lightphe-0.0.13}/tests/test_rsa.py +0 -0
  54. {lightphe-0.0.12 → lightphe-0.0.13}/tests/test_salary.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lightphe
3
- Version: 0.0.12
3
+ Version: 0.0.13
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
@@ -251,27 +251,6 @@ However, if you tried to multiply ciphertexts with RSA, or xor ciphertexts with
251
251
 
252
252
  ECC is a powerful public-key cryptosystem based on the algebraic structure of elliptic curves over finite fields. The library supports 3 elliptic curve forms (weierstrass, edwards and koblitz) and 100+ standard elliptic curve configurations.
253
253
 
254
- Building an elliptic curve with LightPHE is very straightforward. Once the curve is initialized, you can perform operations such as point addition and scalar multiplication.
255
-
256
- ```python
257
- from lightphe import ECC
258
-
259
- forms = ["weierstrass", "edwards", "koblitz"]
260
-
261
- # construct an elliptic curve
262
- ed = ECC(form_name="edwards")
263
-
264
- # Base Point
265
- G = ed.G
266
-
267
- _2G = G + G
268
- _3G = _2G + G
269
- _5G = _3G + _2G
270
-
271
- _20G = 4 * _5G
272
- _19G = _20G - G
273
- ```
274
-
275
254
  In LightPHE, the [Elliptic Curve ElGamal](https://sefiks.com/2018/08/21/elliptic-curve-elgamal-encryption/) scheme is implemented, offering a secure and efficient homomorphic encryption option.
276
255
 
277
256
  ```python
@@ -289,11 +268,108 @@ Each curve in LightPHE has a specific order, which is carefully chosen to balanc
289
268
 
290
269
  See [`curves`](https://github.com/serengil/LightPHE/tree/master/lightphe/elliptic_curve_forms) page for a list of all supported forms, curves and their details.
291
270
 
271
+ ### Vector Embeddings and Tensors
272
+
273
+ LightPHE supports homomorphic encryption on vector embeddings and tensors. This is useful in privacy-preserving machine learning, secure aggregation, and confidential data processing.
274
+
275
+ ```python
276
+ # build a cryptosystem
277
+ cs = LightPHE(algorithm_name="Paillier")
278
+
279
+ # define plain embedding
280
+ tensor = [1.005, 2.05, 3.005, 4.005, -5.05, 6, 7.003, 7.002]
281
+
282
+ # encrypt vector embedding
283
+ encrypted_tensors = cs.encrypt(tensor)
284
+
285
+ # restore embedding
286
+ decrypted_tensors = cs.decrypt(encrypted_tensors)
287
+
288
+ # proof of work
289
+ assert all(abs(original - decrypted) < 1e-2 for original, decrypted in zip(tensor, decrypted_tensors))
290
+ ```
291
+
292
+ Encrypted embeddings retain their homomorphic properties, enabling secure computations on encrypted data without decryption. For example, two embeddings encrypted using a multiplicatively homomorphic algorithm can be multiplied element-wise.
293
+
294
+ ```python
295
+ # build a multiplicatively homomorphic cryptosystem (e.g. RSA)
296
+ cs = LightPHE("RSA")
297
+
298
+ # define plain embeddings
299
+ t1 = [1.005, 2.05, -3.5, 3.1, -4]
300
+ t2 = [5, 6.2, -7.002, -7.1, 8.02]
301
+
302
+ # encrypt embeddings
303
+ c1, c2 = cs.encrypt(t1), cs.encrypt(t2)
304
+
305
+ # perform element-wise homomorphic multiplication
306
+ c3 = c1 * c2
307
+
308
+ # proof of work
309
+ assert np.allclose(cs.decrypt(c3), [a * b for a, b in zip(t1, t2)], rtol=1e-2)
310
+ ```
311
+
312
+ Similarly, two embeddings encrypted with an additively homomorphic algorithm can be added. Additionally, an encrypted embedding can be multiplied by a constant or a plain embedding.
313
+
314
+ ```python
315
+ # build an additively homomorphic cryptosystem (e.g. Paillier)
316
+ cs = LightPHE("Paillier")
317
+
318
+ # define plain embeddings
319
+ t1 = [1.005, 2.05, 3.6, 4, 4.02, 3.5]
320
+ t2 = [5, 6.2, 7.5, 8.02, 8.02, 4.5]
321
+ t3 = [1.03, 2.04, 3.05, 7.02, 2.01, 1.06]
322
+
323
+ # encrypt embeddings
324
+ c1, c2 = cs.encrypt(t1), cs.encrypt(t2)
325
+
326
+ # perform addition of two encrypted embeddings
327
+ c4 = c1 + c2
328
+
329
+ # perform scalar multiplication on an embedding
330
+ c5 = 3 * c1
331
+
332
+ # perform element-wise multiplication between an encrypted embedding and plain embedding
333
+ c6 = c1 * t3 # Encrypted-plaintext multiplication
334
+
335
+ # proof of work
336
+ assert np.allclose(cs.decrypt(c4), [a + b for a, b in zip(t1, t2)], rtol=1e-2)
337
+ assert np.allclose(cs.decrypt(c5), [a * 3 for a in t1], rtol=1e-2)
338
+ assert np.allclose(cs.decrypt(c6), [a * b for a, b in zip(t1, t3)], rtol=1e-2)
339
+ ```
340
+
341
+ ### Vector Similarity Search with PHE
342
+
343
+ Many machine learning models rely on a two-tower architecture, including facial recognition, reverse image search, recommendation engines, large language models, and more. In this setup, user and item inputs are separately mapped to vector embeddings.
344
+
345
+ For example, suppose all facial embeddings in your database are encrypted on-prem in advance. When verifying an identity, the attempted facial embedding is generated on an edge device or in the cloud. You can compute the encrypted similarity by performing a dot product between the encrypted vector and the plain vector, ensuring secure comparison without decrypting sensitive data. Only additively homomorphic cryptosystems offer encrypted similarity calculation.
346
+
347
+ ```python
348
+ # build an additively homomorphic cryptosystem (e.g. Paillier)
349
+ cs = LightPHE("Paillier")
350
+
351
+ # define plain vectors - suppose those are l2 normalized already
352
+ alpha = [7.1, 5.2, 5.3, 2.4, 3.5, 4.6] # On-prem vector (user tower)
353
+ beta = [5.6, 3.7, 2.8, 4, 0, 5.9] # Cloud vector (item tower)
354
+
355
+ # encrypt embedding
356
+ encrypted_alpha = cs.encrypt(alpha)
357
+
358
+ # dot product of encrypted embedding and plain embedding
359
+ encrypted_cosine_similarity = encrypted_alpha @ beta
360
+
361
+ # decrypt similarity
362
+ cosine_similarity = cs.decrypt(encrypted_cosine_similarity)[0]
363
+
364
+ # proof of work
365
+ assert abs(cosine_similarity - sum(x * y for x, y in zip(alpha, beta))) < 1e-2
366
+ ```
367
+
292
368
  # Contributing
293
369
 
294
370
  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.
295
371
 
296
- You should be able run `make test` and `make lint` commands successfully before committing. Once a PR is created, GitHub test workflow will be run automatically and unit test results will be available in [GitHub actions](https://github.com/serengil/LightPHE/actions/workflows/tests.yml) before approval. Besides, workflow will evaluate the code with pylint as well.
372
+ You should be able run `make test` and `make lint` commands successfully before committing. Once a PR is created, GitHub test workflow will be run automatically and unit test results will be available in [GitHub actions](https://github.com/serengil/LightPHE/actions/workflows/tests.yml) before approval.
297
373
 
298
374
  # Support
299
375
 
@@ -235,27 +235,6 @@ However, if you tried to multiply ciphertexts with RSA, or xor ciphertexts with
235
235
 
236
236
  ECC is a powerful public-key cryptosystem based on the algebraic structure of elliptic curves over finite fields. The library supports 3 elliptic curve forms (weierstrass, edwards and koblitz) and 100+ standard elliptic curve configurations.
237
237
 
238
- Building an elliptic curve with LightPHE is very straightforward. Once the curve is initialized, you can perform operations such as point addition and scalar multiplication.
239
-
240
- ```python
241
- from lightphe import ECC
242
-
243
- forms = ["weierstrass", "edwards", "koblitz"]
244
-
245
- # construct an elliptic curve
246
- ed = ECC(form_name="edwards")
247
-
248
- # Base Point
249
- G = ed.G
250
-
251
- _2G = G + G
252
- _3G = _2G + G
253
- _5G = _3G + _2G
254
-
255
- _20G = 4 * _5G
256
- _19G = _20G - G
257
- ```
258
-
259
238
  In LightPHE, the [Elliptic Curve ElGamal](https://sefiks.com/2018/08/21/elliptic-curve-elgamal-encryption/) scheme is implemented, offering a secure and efficient homomorphic encryption option.
260
239
 
261
240
  ```python
@@ -273,11 +252,108 @@ Each curve in LightPHE has a specific order, which is carefully chosen to balanc
273
252
 
274
253
  See [`curves`](https://github.com/serengil/LightPHE/tree/master/lightphe/elliptic_curve_forms) page for a list of all supported forms, curves and their details.
275
254
 
255
+ ### Vector Embeddings and Tensors
256
+
257
+ LightPHE supports homomorphic encryption on vector embeddings and tensors. This is useful in privacy-preserving machine learning, secure aggregation, and confidential data processing.
258
+
259
+ ```python
260
+ # build a cryptosystem
261
+ cs = LightPHE(algorithm_name="Paillier")
262
+
263
+ # define plain embedding
264
+ tensor = [1.005, 2.05, 3.005, 4.005, -5.05, 6, 7.003, 7.002]
265
+
266
+ # encrypt vector embedding
267
+ encrypted_tensors = cs.encrypt(tensor)
268
+
269
+ # restore embedding
270
+ decrypted_tensors = cs.decrypt(encrypted_tensors)
271
+
272
+ # proof of work
273
+ assert all(abs(original - decrypted) < 1e-2 for original, decrypted in zip(tensor, decrypted_tensors))
274
+ ```
275
+
276
+ Encrypted embeddings retain their homomorphic properties, enabling secure computations on encrypted data without decryption. For example, two embeddings encrypted using a multiplicatively homomorphic algorithm can be multiplied element-wise.
277
+
278
+ ```python
279
+ # build a multiplicatively homomorphic cryptosystem (e.g. RSA)
280
+ cs = LightPHE("RSA")
281
+
282
+ # define plain embeddings
283
+ t1 = [1.005, 2.05, -3.5, 3.1, -4]
284
+ t2 = [5, 6.2, -7.002, -7.1, 8.02]
285
+
286
+ # encrypt embeddings
287
+ c1, c2 = cs.encrypt(t1), cs.encrypt(t2)
288
+
289
+ # perform element-wise homomorphic multiplication
290
+ c3 = c1 * c2
291
+
292
+ # proof of work
293
+ assert np.allclose(cs.decrypt(c3), [a * b for a, b in zip(t1, t2)], rtol=1e-2)
294
+ ```
295
+
296
+ Similarly, two embeddings encrypted with an additively homomorphic algorithm can be added. Additionally, an encrypted embedding can be multiplied by a constant or a plain embedding.
297
+
298
+ ```python
299
+ # build an additively homomorphic cryptosystem (e.g. Paillier)
300
+ cs = LightPHE("Paillier")
301
+
302
+ # define plain embeddings
303
+ t1 = [1.005, 2.05, 3.6, 4, 4.02, 3.5]
304
+ t2 = [5, 6.2, 7.5, 8.02, 8.02, 4.5]
305
+ t3 = [1.03, 2.04, 3.05, 7.02, 2.01, 1.06]
306
+
307
+ # encrypt embeddings
308
+ c1, c2 = cs.encrypt(t1), cs.encrypt(t2)
309
+
310
+ # perform addition of two encrypted embeddings
311
+ c4 = c1 + c2
312
+
313
+ # perform scalar multiplication on an embedding
314
+ c5 = 3 * c1
315
+
316
+ # perform element-wise multiplication between an encrypted embedding and plain embedding
317
+ c6 = c1 * t3 # Encrypted-plaintext multiplication
318
+
319
+ # proof of work
320
+ assert np.allclose(cs.decrypt(c4), [a + b for a, b in zip(t1, t2)], rtol=1e-2)
321
+ assert np.allclose(cs.decrypt(c5), [a * 3 for a in t1], rtol=1e-2)
322
+ assert np.allclose(cs.decrypt(c6), [a * b for a, b in zip(t1, t3)], rtol=1e-2)
323
+ ```
324
+
325
+ ### Vector Similarity Search with PHE
326
+
327
+ Many machine learning models rely on a two-tower architecture, including facial recognition, reverse image search, recommendation engines, large language models, and more. In this setup, user and item inputs are separately mapped to vector embeddings.
328
+
329
+ For example, suppose all facial embeddings in your database are encrypted on-prem in advance. When verifying an identity, the attempted facial embedding is generated on an edge device or in the cloud. You can compute the encrypted similarity by performing a dot product between the encrypted vector and the plain vector, ensuring secure comparison without decrypting sensitive data. Only additively homomorphic cryptosystems offer encrypted similarity calculation.
330
+
331
+ ```python
332
+ # build an additively homomorphic cryptosystem (e.g. Paillier)
333
+ cs = LightPHE("Paillier")
334
+
335
+ # define plain vectors - suppose those are l2 normalized already
336
+ alpha = [7.1, 5.2, 5.3, 2.4, 3.5, 4.6] # On-prem vector (user tower)
337
+ beta = [5.6, 3.7, 2.8, 4, 0, 5.9] # Cloud vector (item tower)
338
+
339
+ # encrypt embedding
340
+ encrypted_alpha = cs.encrypt(alpha)
341
+
342
+ # dot product of encrypted embedding and plain embedding
343
+ encrypted_cosine_similarity = encrypted_alpha @ beta
344
+
345
+ # decrypt similarity
346
+ cosine_similarity = cs.decrypt(encrypted_cosine_similarity)[0]
347
+
348
+ # proof of work
349
+ assert abs(cosine_similarity - sum(x * y for x, y in zip(alpha, beta))) < 1e-2
350
+ ```
351
+
276
352
  # Contributing
277
353
 
278
354
  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.
279
355
 
280
- You should be able run `make test` and `make lint` commands successfully before committing. Once a PR is created, GitHub test workflow will be run automatically and unit test results will be available in [GitHub actions](https://github.com/serengil/LightPHE/actions/workflows/tests.yml) before approval. Besides, workflow will evaluate the code with pylint as well.
356
+ You should be able run `make test` and `make lint` commands successfully before committing. Once a PR is created, GitHub test workflow will be run automatically and unit test results will be available in [GitHub actions](https://github.com/serengil/LightPHE/actions/workflows/tests.yml) before approval.
281
357
 
282
358
  # Support
283
359
 
@@ -4,6 +4,8 @@ import json
4
4
  from typing import Optional, Union, List
5
5
  import multiprocessing
6
6
  from contextlib import closing
7
+ import traceback
8
+ import copy
7
9
 
8
10
  # 3rd party dependencies
9
11
  from tqdm import tqdm
@@ -36,7 +38,7 @@ from lightphe.elliptic_curve_forms.koblitz import Koblitz
36
38
 
37
39
  logger = Logger(module="lightphe/__init__.py")
38
40
 
39
- VERSION = "0.0.12"
41
+ VERSION = "0.0.13"
40
42
 
41
43
 
42
44
  class LightPHE:
@@ -155,12 +157,13 @@ class LightPHE:
155
157
  return cs
156
158
 
157
159
  def encrypt(
158
- self, plaintext: Union[int, float, list]
160
+ self, plaintext: Union[int, float, list], silent: bool = False
159
161
  ) -> Union[Ciphertext, EncryptedTensor]:
160
162
  """
161
163
  Encrypt a plaintext with a built cryptosystem
162
164
  Args:
163
165
  plaintext (int, float or tensor): message
166
+ silent (bool): set this to True if you do not want to see progress bar
164
167
  Returns
165
168
  ciphertext (from lightphe.models.Ciphertext import Ciphertext): encrypted message
166
169
  """
@@ -169,16 +172,21 @@ class LightPHE:
169
172
 
170
173
  if isinstance(plaintext, list):
171
174
  # then encrypt tensors
172
- return self.__encrypt_tensors(tensor=plaintext)
175
+ return self.__encrypt_tensors(tensor=plaintext, silent=silent)
173
176
 
174
177
  ciphertext = self.cs.encrypt(
175
178
  plaintext=phe_utils.normalize_input(
176
179
  value=plaintext, modulo=self.cs.plaintext_modulo
177
180
  )
178
181
  )
182
+
183
+ public_keys = self.cs.keys.copy()
184
+ if public_keys.get("private_key") is not None:
185
+ del public_keys["private_key"]
186
+
179
187
  return Ciphertext(
180
188
  algorithm_name=self.algorithm_name,
181
- keys=self.cs.keys,
189
+ keys=public_keys,
182
190
  value=ciphertext,
183
191
  form=self.form,
184
192
  curve=self.curve,
@@ -206,11 +214,12 @@ class LightPHE:
206
214
 
207
215
  return self.cs.decrypt(ciphertext=ciphertext.value)
208
216
 
209
- def __encrypt_tensors(self, tensor: list) -> EncryptedTensor:
217
+ def __encrypt_tensors(self, tensor: list, silent: bool = False) -> EncryptedTensor:
210
218
  """
211
219
  Encrypt a given tensor
212
220
  Args:
213
221
  tensor (list of int or float)
222
+ silent (bool): set this to True if you do not want to see progress bar
214
223
  Returns
215
224
  encrypted tensor (list of encrypted tensor object)
216
225
  """
@@ -218,7 +227,6 @@ class LightPHE:
218
227
 
219
228
  encrypted_zero = self.cs.encrypt(plaintext=0)
220
229
  divisor_encrypted = self.cs.encrypt(plaintext=10**self.precision)
221
- is_positive_tensor = all(m >= 0 for m in tensor)
222
230
 
223
231
  num_workers = min(len(tensor), 2 * multiprocessing.cpu_count())
224
232
  logger.debug(f"encrypting tensors in {num_workers} parallel")
@@ -228,14 +236,13 @@ class LightPHE:
228
236
 
229
237
  for m in tensor:
230
238
  f = pool.apply_async(
231
- encrypt_something,
239
+ encrypt_float,
232
240
  (
233
241
  m,
234
242
  divisor_encrypted,
235
243
  self.cs,
236
244
  self.precision,
237
245
  encrypted_zero,
238
- is_positive_tensor,
239
246
  ),
240
247
  )
241
248
  funclist.append(f)
@@ -245,7 +252,7 @@ class LightPHE:
245
252
  for f in tqdm(
246
253
  funclist,
247
254
  desc="Encrypting tensors",
248
- disable=True if len(tensor) < 100 else False,
255
+ disable=silent,
249
256
  ):
250
257
  result = f.get(timeout=10)
251
258
  encrypted_tensor.append(result)
@@ -253,9 +260,13 @@ class LightPHE:
253
260
  toc = time.time()
254
261
  logger.debug(f"encryption took {toc - tic} seconds")
255
262
 
263
+ public_cs = copy.deepcopy(self.cs)
264
+ if public_cs.keys.get("private_key") is not None:
265
+ del public_cs.keys["private_key"]
266
+
256
267
  return EncryptedTensor(
257
268
  fractions=encrypted_tensor,
258
- cs=self.cs,
269
+ cs=public_cs,
259
270
  precision=self.precision,
260
271
  )
261
272
 
@@ -277,7 +288,10 @@ class LightPHE:
277
288
  sign = c.sign
278
289
  abs_dividend = self.cs.decrypt(ciphertext=c.abs_dividend)
279
290
  # dividend = self.cs.decrypt(ciphertext=c.dividend)
291
+
292
+ # TODO: do I really need encrypted divisor? cannot I store current_precision
280
293
  divisor = self.cs.decrypt(ciphertext=c.divisor)
294
+
281
295
  m = sign * abs_dividend / divisor
282
296
 
283
297
  plain_tensor.append(m)
@@ -365,72 +379,91 @@ class LightPHE:
365
379
  )
366
380
 
367
381
 
368
- def encrypt_something(
382
+ def encrypt_float(
369
383
  m: Union[int, float],
370
- divisor_encrypted,
384
+ divisor_encrypted: int,
371
385
  cs: Homomorphic,
372
386
  precision: int,
373
387
  encrypted_zero: int,
374
- is_positive_tensor: bool,
375
388
  ) -> 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)
389
+ """
390
+ Encrypt a float value
391
+ Args:
392
+ m (int or float): message to encrypt
393
+ divisor_encrypted (int): pre-calculated encrypted divisor
394
+ cs (Homomorphic): cryptosystem itself
395
+ precision (int): define how many digits after dot
396
+ encrypted_zero (int): pre-calculated encrypted value of 0
397
+ Returns:
398
+ result (Fraction): encrypted float value
399
+ """
400
+ try:
401
+ if m == 0:
402
+ # this is very common in VGG-Face embeddings
403
+ c = Fraction(
404
+ dividend=encrypted_zero,
405
+ divisor=divisor_encrypted,
406
+ abs_dividend=encrypted_zero,
407
+ sign=1,
393
408
  )
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),
409
+ elif isinstance(m, int):
410
+ dividend_encrypted = cs.encrypt(
411
+ plaintext=(m % cs.plaintext_modulo) * pow(10, precision)
412
+ )
413
+ abs_dividend_encrypted = (
414
+ dividend_encrypted
415
+ if m > 0
416
+ else cs.encrypt(
417
+ plaintext=(abs(m) % cs.plaintext_modulo) * pow(10, precision)
418
+ )
419
+ )
420
+ # divisor_encrypted = self.cs.encrypt(plaintext=pow(10, self.precision))
421
+ c = Fraction(
422
+ dividend=dividend_encrypted,
423
+ divisor=divisor_encrypted,
424
+ abs_dividend=abs_dividend_encrypted,
425
+ sign=1 if m >= 0 else -1,
426
+ )
427
+ elif isinstance(m, float):
428
+ # got `int too large to convert float` while m mod plaintext modulo
429
+ # when security level is set to 128
430
+ dividend, _ = phe_utils.fractionize(
431
+ value=(m % cs.plaintext_modulo if m > cs.plaintext_modulo else m),
413
432
  modulo=cs.plaintext_modulo,
414
433
  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)}")
434
+ )
435
+ abs_dividend = (
436
+ dividend
437
+ if m > 0
438
+ else phe_utils.fractionize(
439
+ value=(
440
+ abs(m) % cs.plaintext_modulo
441
+ if abs(m) > cs.plaintext_modulo
442
+ else abs(m)
443
+ ),
444
+ modulo=cs.plaintext_modulo,
445
+ precision=precision,
446
+ )[0]
447
+ )
448
+ dividend_encrypted = cs.encrypt(plaintext=dividend)
449
+ abs_dividend_encrypted = (
450
+ dividend_encrypted if m > 0 else cs.encrypt(plaintext=abs_dividend)
451
+ )
452
+ # divisor_encrypted = self.cs.encrypt(plaintext=_divisor)
453
+ c = Fraction(
454
+ dividend=dividend_encrypted,
455
+ divisor=divisor_encrypted,
456
+ abs_dividend=abs_dividend_encrypted,
457
+ sign=1 if m >= 0 else -1,
458
+ )
459
+ else:
460
+ raise ValueError(f"unimplemented type - {type(m)}")
432
461
 
433
- return c
462
+ return c
463
+ except Exception as err:
464
+ logger.error(f"Exception while running encrypt_float: {str(err)}")
465
+ logger.error(traceback.format_exc())
466
+ raise err
434
467
 
435
468
 
436
469
  class ECC:
@@ -46,8 +46,13 @@ 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["curve"] = curve
50
- self.keys["form"] = form
49
+
50
+ if curve is not None:
51
+ self.keys["curve"] = curve
52
+
53
+ if form is not None:
54
+ self.keys["form"] = form
55
+
51
56
  self.plaintext_modulo = self.ecc.modulo
52
57
  self.ciphertext_modulo = self.ecc.modulo
53
58
 
@@ -74,7 +79,7 @@ class EllipticCurveElGamal(Homomorphic):
74
79
  # public key
75
80
  Qa = self.ecc.G * ka
76
81
 
77
- keys["public_key"]["Qa"] = Qa
82
+ keys["public_key"]["Qa"] = Qa.get_point()
78
83
  keys["private_key"]["ka"] = ka
79
84
 
80
85
  return keys
@@ -98,7 +103,8 @@ class EllipticCurveElGamal(Homomorphic):
98
103
  ciphertext (tuple): c1 and c2
99
104
  """
100
105
  # public key
101
- Qa = self.keys["public_key"]["Qa"]
106
+ x, y = self.keys["public_key"]["Qa"]
107
+ Qa = EllipticCurvePoint(x=x, y=y, curve=self.ecc.curve)
102
108
 
103
109
  # random key
104
110
  r = random_key or self.generate_random_key()
@@ -134,20 +140,7 @@ class EllipticCurveElGamal(Homomorphic):
134
140
  # we need to find k from known s_prime and G
135
141
  # this requires to solve ECDLP
136
142
 
137
- # base point
138
- G = self.ecc.G
139
- k = 2
140
- while True:
141
- G = G + self.ecc.G
142
-
143
- if G == s_prime:
144
- return k
145
-
146
- k = k + 1
147
- if k > self.ecc.n:
148
- raise ValueError(
149
- f"Cannot restore scalar from {s_prime} = k x {self.ecc.G}"
150
- )
143
+ return s_prime / self.ecc.G
151
144
 
152
145
  def multiply(self, ciphertext1: tuple, ciphertext2: tuple) -> tuple:
153
146
  raise ValueError(