lightphe 0.0.11__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 (56) hide show
  1. {lightphe-0.0.11 → lightphe-0.0.13}/PKG-INFO +100 -23
  2. {lightphe-0.0.11 → lightphe-0.0.13}/README.md +99 -22
  3. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/__init__.py +154 -44
  4. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/cryptosystems/EllipticCurveElGamal.py +11 -18
  5. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/cryptosystems/GoldwasserMicali.py +38 -11
  6. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/cryptosystems/OkamotoUchiyama.py +10 -4
  7. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/models/EllipticCurve.py +36 -0
  8. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/models/Tensor.py +193 -7
  9. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe.egg-info/PKG-INFO +100 -23
  10. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe.egg-info/SOURCES.txt +2 -0
  11. lightphe-0.0.13/lightphe.egg-info/requires.txt +3 -0
  12. lightphe-0.0.11/lightphe.egg-info/requires.txt → lightphe-0.0.13/requirements.txt +1 -0
  13. {lightphe-0.0.11 → lightphe-0.0.13}/setup.py +1 -1
  14. lightphe-0.0.13/tests/test_ciphertexts.py +33 -0
  15. {lightphe-0.0.11 → lightphe-0.0.13}/tests/test_goldwasser.py +19 -16
  16. lightphe-0.0.13/tests/test_keys.py +60 -0
  17. lightphe-0.0.13/tests/test_tensors.py +337 -0
  18. lightphe-0.0.11/requirements.txt +0 -2
  19. lightphe-0.0.11/tests/test_tensors.py +0 -174
  20. {lightphe-0.0.11 → lightphe-0.0.13}/LICENSE +0 -0
  21. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/commons/__init__.py +0 -0
  22. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/commons/binary_operations.py +0 -0
  23. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/commons/logger.py +0 -0
  24. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/commons/phe_utils.py +0 -0
  25. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/cryptosystems/Benaloh.py +0 -0
  26. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/cryptosystems/DamgardJurik.py +0 -0
  27. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/cryptosystems/ElGamal.py +0 -0
  28. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/cryptosystems/NaccacheStern.py +0 -0
  29. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/cryptosystems/Paillier.py +0 -0
  30. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/cryptosystems/RSA.py +0 -0
  31. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/cryptosystems/__init__.py +0 -0
  32. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/elliptic_curve_forms/__init__.py +0 -0
  33. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/elliptic_curve_forms/edwards.py +0 -0
  34. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/elliptic_curve_forms/koblitz.py +0 -0
  35. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/elliptic_curve_forms/weierstrass.py +0 -0
  36. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/models/Algorithm.py +0 -0
  37. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/models/Ciphertext.py +0 -0
  38. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/models/Curve.py +0 -0
  39. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/models/Homomorphic.py +0 -0
  40. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/models/__init__.py +0 -0
  41. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe.egg-info/dependency_links.txt +0 -0
  42. {lightphe-0.0.11 → lightphe-0.0.13}/lightphe.egg-info/top_level.txt +0 -0
  43. {lightphe-0.0.11 → lightphe-0.0.13}/setup.cfg +0 -0
  44. {lightphe-0.0.11 → lightphe-0.0.13}/tests/__init__.py +0 -0
  45. {lightphe-0.0.11 → lightphe-0.0.13}/tests/test_benaloh.py +0 -0
  46. {lightphe-0.0.11 → lightphe-0.0.13}/tests/test_cloud.py +0 -0
  47. {lightphe-0.0.11 → lightphe-0.0.13}/tests/test_damgard.py +0 -0
  48. {lightphe-0.0.11 → lightphe-0.0.13}/tests/test_elgamal.py +0 -0
  49. {lightphe-0.0.11 → lightphe-0.0.13}/tests/test_ellipticcurve.py +0 -0
  50. {lightphe-0.0.11 → lightphe-0.0.13}/tests/test_ellipticcurveelgamal.py +0 -0
  51. {lightphe-0.0.11 → lightphe-0.0.13}/tests/test_exporting_keys.py +0 -0
  52. {lightphe-0.0.11 → lightphe-0.0.13}/tests/test_naccache.py +0 -0
  53. {lightphe-0.0.11 → lightphe-0.0.13}/tests/test_okamoto.py +0 -0
  54. {lightphe-0.0.11 → lightphe-0.0.13}/tests/test_paillier.py +0 -0
  55. {lightphe-0.0.11 → lightphe-0.0.13}/tests/test_rsa.py +0 -0
  56. {lightphe-0.0.11 → 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.11
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
@@ -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)
@@ -250,27 +251,6 @@ However, if you tried to multiply ciphertexts with RSA, or xor ciphertexts with
250
251
 
251
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.
252
253
 
253
- 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.
254
-
255
- ```python
256
- from lightphe import ECC
257
-
258
- forms = ["weierstrass", "edwards", "koblitz"]
259
-
260
- # construct an elliptic curve
261
- ed = ECC(form_name="edwards")
262
-
263
- # Base Point
264
- G = ed.G
265
-
266
- _2G = G + G
267
- _3G = _2G + G
268
- _5G = _3G + _2G
269
-
270
- _20G = 4 * G
271
- _19G = _20G - G
272
- ```
273
-
274
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.
275
255
 
276
256
  ```python
@@ -288,11 +268,108 @@ Each curve in LightPHE has a specific order, which is carefully chosen to balanc
288
268
 
289
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.
290
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
+
291
368
  # Contributing
292
369
 
293
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.
294
371
 
295
- 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.
296
373
 
297
374
  # Support
298
375
 
@@ -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)
@@ -234,27 +235,6 @@ However, if you tried to multiply ciphertexts with RSA, or xor ciphertexts with
234
235
 
235
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.
236
237
 
237
- 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.
238
-
239
- ```python
240
- from lightphe import ECC
241
-
242
- forms = ["weierstrass", "edwards", "koblitz"]
243
-
244
- # construct an elliptic curve
245
- ed = ECC(form_name="edwards")
246
-
247
- # Base Point
248
- G = ed.G
249
-
250
- _2G = G + G
251
- _3G = _2G + G
252
- _5G = _3G + _2G
253
-
254
- _20G = 4 * G
255
- _19G = _20G - G
256
- ```
257
-
258
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.
259
239
 
260
240
  ```python
@@ -272,11 +252,108 @@ Each curve in LightPHE has a specific order, which is carefully chosen to balanc
272
252
 
273
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.
274
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
+
275
352
  # Contributing
276
353
 
277
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.
278
355
 
279
- 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.
280
357
 
281
358
  # Support
282
359
 
@@ -1,6 +1,14 @@
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
+ import traceback
8
+ import copy
9
+
10
+ # 3rd party dependencies
11
+ from tqdm import tqdm
4
12
 
5
13
  # project dependencies
6
14
  from lightphe.models.Homomorphic import Homomorphic
@@ -30,7 +38,7 @@ from lightphe.elliptic_curve_forms.koblitz import Koblitz
30
38
 
31
39
  logger = Logger(module="lightphe/__init__.py")
32
40
 
33
- VERSION = "0.0.11"
41
+ VERSION = "0.0.13"
34
42
 
35
43
 
36
44
  class LightPHE:
@@ -149,12 +157,13 @@ class LightPHE:
149
157
  return cs
150
158
 
151
159
  def encrypt(
152
- self, plaintext: Union[int, float, list]
160
+ self, plaintext: Union[int, float, list], silent: bool = False
153
161
  ) -> Union[Ciphertext, EncryptedTensor]:
154
162
  """
155
163
  Encrypt a plaintext with a built cryptosystem
156
164
  Args:
157
165
  plaintext (int, float or tensor): message
166
+ silent (bool): set this to True if you do not want to see progress bar
158
167
  Returns
159
168
  ciphertext (from lightphe.models.Ciphertext import Ciphertext): encrypted message
160
169
  """
@@ -163,16 +172,21 @@ class LightPHE:
163
172
 
164
173
  if isinstance(plaintext, list):
165
174
  # then encrypt tensors
166
- return self.__encrypt_tensors(tensor=plaintext)
175
+ return self.__encrypt_tensors(tensor=plaintext, silent=silent)
167
176
 
168
177
  ciphertext = self.cs.encrypt(
169
178
  plaintext=phe_utils.normalize_input(
170
179
  value=plaintext, modulo=self.cs.plaintext_modulo
171
180
  )
172
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
+
173
187
  return Ciphertext(
174
188
  algorithm_name=self.algorithm_name,
175
- keys=self.cs.keys,
189
+ keys=public_keys,
176
190
  value=ciphertext,
177
191
  form=self.form,
178
192
  curve=self.curve,
@@ -200,55 +214,61 @@ class LightPHE:
200
214
 
201
215
  return self.cs.decrypt(ciphertext=ciphertext.value)
202
216
 
203
- def __encrypt_tensors(self, tensor: list) -> EncryptedTensor:
217
+ def __encrypt_tensors(self, tensor: list, silent: bool = False) -> EncryptedTensor:
204
218
  """
205
219
  Encrypt a given tensor
206
220
  Args:
207
221
  tensor (list of int or float)
222
+ silent (bool): set this to True if you do not want to see progress bar
208
223
  Returns
209
224
  encrypted tensor (list of encrypted tensor object)
210
225
  """
211
226
  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,
227
+
228
+ encrypted_zero = self.cs.encrypt(plaintext=0)
229
+ divisor_encrypted = self.cs.encrypt(plaintext=10**self.precision)
230
+
231
+ num_workers = min(len(tensor), 2 * multiprocessing.cpu_count())
232
+ logger.debug(f"encrypting tensors in {num_workers} parallel")
233
+
234
+ with closing(multiprocessing.Pool(num_workers)) as pool:
235
+ funclist = []
236
+
237
+ for m in tensor:
238
+ f = pool.apply_async(
239
+ encrypt_float,
240
+ (
241
+ m,
242
+ divisor_encrypted,
243
+ self.cs,
244
+ self.precision,
245
+ encrypted_zero,
246
+ ),
247
247
  )
248
- else:
249
- raise ValueError(f"unimplemented type - {type(m)}")
250
- encrypted_tensor.append(c)
251
- return EncryptedTensor(fractions=encrypted_tensor, cs=self.cs)
248
+ funclist.append(f)
249
+
250
+ tic = time.time()
251
+ encrypted_tensor = []
252
+ for f in tqdm(
253
+ funclist,
254
+ desc="Encrypting tensors",
255
+ disable=silent,
256
+ ):
257
+ result = f.get(timeout=10)
258
+ encrypted_tensor.append(result)
259
+
260
+ toc = time.time()
261
+ logger.debug(f"encryption took {toc - tic} seconds")
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
+
267
+ return EncryptedTensor(
268
+ fractions=encrypted_tensor,
269
+ cs=public_cs,
270
+ precision=self.precision,
271
+ )
252
272
 
253
273
  def __decrypt_tensors(
254
274
  self, encrypted_tensor: EncryptedTensor
@@ -268,7 +288,10 @@ class LightPHE:
268
288
  sign = c.sign
269
289
  abs_dividend = self.cs.decrypt(ciphertext=c.abs_dividend)
270
290
  # dividend = self.cs.decrypt(ciphertext=c.dividend)
291
+
292
+ # TODO: do I really need encrypted divisor? cannot I store current_precision
271
293
  divisor = self.cs.decrypt(ciphertext=c.divisor)
294
+
272
295
  m = sign * abs_dividend / divisor
273
296
 
274
297
  plain_tensor.append(m)
@@ -356,6 +379,93 @@ class LightPHE:
356
379
  )
357
380
 
358
381
 
382
+ def encrypt_float(
383
+ m: Union[int, float],
384
+ divisor_encrypted: int,
385
+ cs: Homomorphic,
386
+ precision: int,
387
+ encrypted_zero: int,
388
+ ) -> Fraction:
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,
408
+ )
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),
432
+ modulo=cs.plaintext_modulo,
433
+ precision=precision,
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)}")
461
+
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
467
+
468
+
359
469
  class ECC:
360
470
  __version__ = VERSION
361
471
 
@@ -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["public_key"]["form"] = form
50
- self.keys["private_key"]["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(