lightphe 0.0.12__tar.gz → 0.0.14__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 (59) hide show
  1. {lightphe-0.0.12 → lightphe-0.0.14}/PKG-INFO +99 -25
  2. {lightphe-0.0.12 → lightphe-0.0.14}/README.md +98 -24
  3. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe/__init__.py +100 -67
  4. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe/cryptosystems/EllipticCurveElGamal.py +11 -18
  5. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe/cryptosystems/GoldwasserMicali.py +38 -11
  6. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe/cryptosystems/OkamotoUchiyama.py +10 -4
  7. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe/models/EllipticCurve.py +36 -0
  8. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe/models/Tensor.py +100 -14
  9. lightphe-0.0.14/lightphe/standard_curves/edwards.py +144 -0
  10. lightphe-0.0.14/lightphe/standard_curves/inventory.py +83 -0
  11. lightphe-0.0.14/lightphe/standard_curves/koblitz.py +433 -0
  12. lightphe-0.0.14/lightphe/standard_curves/weierstrass.py +936 -0
  13. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe.egg-info/PKG-INFO +99 -25
  14. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe.egg-info/SOURCES.txt +7 -0
  15. {lightphe-0.0.12 → lightphe-0.0.14}/setup.py +1 -1
  16. lightphe-0.0.14/tests/__init__.py +0 -0
  17. lightphe-0.0.14/tests/test_ciphertexts.py +33 -0
  18. {lightphe-0.0.12 → lightphe-0.0.14}/tests/test_goldwasser.py +19 -16
  19. lightphe-0.0.14/tests/test_keys.py +60 -0
  20. {lightphe-0.0.12 → lightphe-0.0.14}/tests/test_tensors.py +107 -31
  21. {lightphe-0.0.12 → lightphe-0.0.14}/LICENSE +0 -0
  22. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe/commons/__init__.py +0 -0
  23. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe/commons/binary_operations.py +0 -0
  24. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe/commons/logger.py +0 -0
  25. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe/commons/phe_utils.py +0 -0
  26. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe/cryptosystems/Benaloh.py +0 -0
  27. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe/cryptosystems/DamgardJurik.py +0 -0
  28. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe/cryptosystems/ElGamal.py +0 -0
  29. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe/cryptosystems/NaccacheStern.py +0 -0
  30. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe/cryptosystems/Paillier.py +0 -0
  31. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe/cryptosystems/RSA.py +0 -0
  32. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe/cryptosystems/__init__.py +0 -0
  33. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe/elliptic_curve_forms/__init__.py +0 -0
  34. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe/elliptic_curve_forms/edwards.py +0 -0
  35. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe/elliptic_curve_forms/koblitz.py +0 -0
  36. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe/elliptic_curve_forms/weierstrass.py +0 -0
  37. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe/models/Algorithm.py +0 -0
  38. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe/models/Ciphertext.py +0 -0
  39. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe/models/Curve.py +0 -0
  40. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe/models/Homomorphic.py +0 -0
  41. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe/models/__init__.py +0 -0
  42. {lightphe-0.0.12/tests → lightphe-0.0.14/lightphe/standard_curves}/__init__.py +0 -0
  43. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe.egg-info/dependency_links.txt +0 -0
  44. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe.egg-info/requires.txt +0 -0
  45. {lightphe-0.0.12 → lightphe-0.0.14}/lightphe.egg-info/top_level.txt +0 -0
  46. {lightphe-0.0.12 → lightphe-0.0.14}/requirements.txt +0 -0
  47. {lightphe-0.0.12 → lightphe-0.0.14}/setup.cfg +0 -0
  48. {lightphe-0.0.12 → lightphe-0.0.14}/tests/test_benaloh.py +0 -0
  49. {lightphe-0.0.12 → lightphe-0.0.14}/tests/test_cloud.py +0 -0
  50. {lightphe-0.0.12 → lightphe-0.0.14}/tests/test_damgard.py +0 -0
  51. {lightphe-0.0.12 → lightphe-0.0.14}/tests/test_elgamal.py +0 -0
  52. {lightphe-0.0.12 → lightphe-0.0.14}/tests/test_ellipticcurve.py +0 -0
  53. {lightphe-0.0.12 → lightphe-0.0.14}/tests/test_ellipticcurveelgamal.py +0 -0
  54. {lightphe-0.0.12 → lightphe-0.0.14}/tests/test_exporting_keys.py +0 -0
  55. {lightphe-0.0.12 → lightphe-0.0.14}/tests/test_naccache.py +0 -0
  56. {lightphe-0.0.12 → lightphe-0.0.14}/tests/test_okamoto.py +0 -0
  57. {lightphe-0.0.12 → lightphe-0.0.14}/tests/test_paillier.py +0 -0
  58. {lightphe-0.0.12 → lightphe-0.0.14}/tests/test_rsa.py +0 -0
  59. {lightphe-0.0.12 → lightphe-0.0.14}/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.14
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,8 +23,6 @@ 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)
27
- <!-- [![DOI](http://img.shields.io/:DOI-10.48550/arXiv.2408.05219-blue.svg?style=flat)](https://arxiv.org/abs/2408.05219) -->
28
26
 
29
27
  [![Blog](https://img.shields.io/:blog-sefiks.com-blue.svg?style=flat&logo=wordpress)](https://sefiks.com)
30
28
  [![YouTube](https://img.shields.io/:youtube-@sefiks-red.svg?style=flat&logo=youtube)](https://www.youtube.com/@sefiks?sub_confirmation=1)
@@ -251,27 +249,6 @@ However, if you tried to multiply ciphertexts with RSA, or xor ciphertexts with
251
249
 
252
250
  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
251
 
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
252
  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
253
 
277
254
  ```python
@@ -289,11 +266,108 @@ Each curve in LightPHE has a specific order, which is carefully chosen to balanc
289
266
 
290
267
  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
268
 
269
+ ### Vector Embeddings and Tensors
270
+
271
+ LightPHE supports homomorphic encryption on vector embeddings and tensors. This is useful in privacy-preserving machine learning, secure aggregation, and confidential data processing.
272
+
273
+ ```python
274
+ # build a cryptosystem
275
+ cs = LightPHE(algorithm_name="Paillier")
276
+
277
+ # define plain embedding
278
+ tensor = [1.005, 2.05, 3.005, 4.005, -5.05, 6, 7.003, 7.002]
279
+
280
+ # encrypt vector embedding
281
+ encrypted_tensors = cs.encrypt(tensor)
282
+
283
+ # restore embedding
284
+ decrypted_tensors = cs.decrypt(encrypted_tensors)
285
+
286
+ # proof of work
287
+ assert all(abs(original - decrypted) < 1e-2 for original, decrypted in zip(tensor, decrypted_tensors))
288
+ ```
289
+
290
+ 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.
291
+
292
+ ```python
293
+ # build a multiplicatively homomorphic cryptosystem (e.g. RSA)
294
+ cs = LightPHE("RSA")
295
+
296
+ # define plain embeddings
297
+ t1 = [1.005, 2.05, -3.5, 3.1, -4]
298
+ t2 = [5, 6.2, -7.002, -7.1, 8.02]
299
+
300
+ # encrypt embeddings
301
+ c1, c2 = cs.encrypt(t1), cs.encrypt(t2)
302
+
303
+ # perform element-wise homomorphic multiplication
304
+ c3 = c1 * c2
305
+
306
+ # proof of work
307
+ assert np.allclose(cs.decrypt(c3), [a * b for a, b in zip(t1, t2)], rtol=1e-2)
308
+ ```
309
+
310
+ 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.
311
+
312
+ ```python
313
+ # build an additively homomorphic cryptosystem (e.g. Paillier)
314
+ cs = LightPHE("Paillier")
315
+
316
+ # define plain embeddings
317
+ t1 = [1.005, 2.05, 3.6, 4, 4.02, 3.5]
318
+ t2 = [5, 6.2, 7.5, 8.02, 8.02, 4.5]
319
+ t3 = [1.03, 2.04, 3.05, 7.02, 2.01, 1.06]
320
+
321
+ # encrypt embeddings
322
+ c1, c2 = cs.encrypt(t1), cs.encrypt(t2)
323
+
324
+ # perform addition of two encrypted embeddings
325
+ c4 = c1 + c2
326
+
327
+ # perform scalar multiplication on an embedding
328
+ c5 = 3 * c1
329
+
330
+ # perform element-wise multiplication between an encrypted embedding and plain embedding
331
+ c6 = c1 * t3 # Encrypted-plaintext multiplication
332
+
333
+ # proof of work
334
+ assert np.allclose(cs.decrypt(c4), [a + b for a, b in zip(t1, t2)], rtol=1e-2)
335
+ assert np.allclose(cs.decrypt(c5), [a * 3 for a in t1], rtol=1e-2)
336
+ assert np.allclose(cs.decrypt(c6), [a * b for a, b in zip(t1, t3)], rtol=1e-2)
337
+ ```
338
+
339
+ ### Vector Similarity Search with PHE
340
+
341
+ 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.
342
+
343
+ 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.
344
+
345
+ ```python
346
+ # build an additively homomorphic cryptosystem (e.g. Paillier)
347
+ cs = LightPHE("Paillier")
348
+
349
+ # define plain vectors - suppose those are l2 normalized already
350
+ alpha = [7.1, 5.2, 5.3, 2.4, 3.5, 4.6] # On-prem vector (user tower)
351
+ beta = [5.6, 3.7, 2.8, 4, 0, 5.9] # Cloud vector (item tower)
352
+
353
+ # encrypt embedding
354
+ encrypted_alpha = cs.encrypt(alpha)
355
+
356
+ # dot product of encrypted embedding and plain embedding
357
+ encrypted_cosine_similarity = encrypted_alpha @ beta
358
+
359
+ # decrypt similarity
360
+ cosine_similarity = cs.decrypt(encrypted_cosine_similarity)[0]
361
+
362
+ # proof of work
363
+ assert abs(cosine_similarity - sum(x * y for x, y in zip(alpha, beta))) < 1e-2
364
+ ```
365
+
292
366
  # Contributing
293
367
 
294
368
  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
369
 
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.
370
+ 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
371
 
298
372
  # Support
299
373
 
@@ -7,8 +7,6 @@
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)
11
- <!-- [![DOI](http://img.shields.io/:DOI-10.48550/arXiv.2408.05219-blue.svg?style=flat)](https://arxiv.org/abs/2408.05219) -->
12
10
 
13
11
  [![Blog](https://img.shields.io/:blog-sefiks.com-blue.svg?style=flat&logo=wordpress)](https://sefiks.com)
14
12
  [![YouTube](https://img.shields.io/:youtube-@sefiks-red.svg?style=flat&logo=youtube)](https://www.youtube.com/@sefiks?sub_confirmation=1)
@@ -235,27 +233,6 @@ However, if you tried to multiply ciphertexts with RSA, or xor ciphertexts with
235
233
 
236
234
  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
235
 
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
236
  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
237
 
261
238
  ```python
@@ -273,11 +250,108 @@ Each curve in LightPHE has a specific order, which is carefully chosen to balanc
273
250
 
274
251
  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
252
 
253
+ ### Vector Embeddings and Tensors
254
+
255
+ LightPHE supports homomorphic encryption on vector embeddings and tensors. This is useful in privacy-preserving machine learning, secure aggregation, and confidential data processing.
256
+
257
+ ```python
258
+ # build a cryptosystem
259
+ cs = LightPHE(algorithm_name="Paillier")
260
+
261
+ # define plain embedding
262
+ tensor = [1.005, 2.05, 3.005, 4.005, -5.05, 6, 7.003, 7.002]
263
+
264
+ # encrypt vector embedding
265
+ encrypted_tensors = cs.encrypt(tensor)
266
+
267
+ # restore embedding
268
+ decrypted_tensors = cs.decrypt(encrypted_tensors)
269
+
270
+ # proof of work
271
+ assert all(abs(original - decrypted) < 1e-2 for original, decrypted in zip(tensor, decrypted_tensors))
272
+ ```
273
+
274
+ 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.
275
+
276
+ ```python
277
+ # build a multiplicatively homomorphic cryptosystem (e.g. RSA)
278
+ cs = LightPHE("RSA")
279
+
280
+ # define plain embeddings
281
+ t1 = [1.005, 2.05, -3.5, 3.1, -4]
282
+ t2 = [5, 6.2, -7.002, -7.1, 8.02]
283
+
284
+ # encrypt embeddings
285
+ c1, c2 = cs.encrypt(t1), cs.encrypt(t2)
286
+
287
+ # perform element-wise homomorphic multiplication
288
+ c3 = c1 * c2
289
+
290
+ # proof of work
291
+ assert np.allclose(cs.decrypt(c3), [a * b for a, b in zip(t1, t2)], rtol=1e-2)
292
+ ```
293
+
294
+ 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.
295
+
296
+ ```python
297
+ # build an additively homomorphic cryptosystem (e.g. Paillier)
298
+ cs = LightPHE("Paillier")
299
+
300
+ # define plain embeddings
301
+ t1 = [1.005, 2.05, 3.6, 4, 4.02, 3.5]
302
+ t2 = [5, 6.2, 7.5, 8.02, 8.02, 4.5]
303
+ t3 = [1.03, 2.04, 3.05, 7.02, 2.01, 1.06]
304
+
305
+ # encrypt embeddings
306
+ c1, c2 = cs.encrypt(t1), cs.encrypt(t2)
307
+
308
+ # perform addition of two encrypted embeddings
309
+ c4 = c1 + c2
310
+
311
+ # perform scalar multiplication on an embedding
312
+ c5 = 3 * c1
313
+
314
+ # perform element-wise multiplication between an encrypted embedding and plain embedding
315
+ c6 = c1 * t3 # Encrypted-plaintext multiplication
316
+
317
+ # proof of work
318
+ assert np.allclose(cs.decrypt(c4), [a + b for a, b in zip(t1, t2)], rtol=1e-2)
319
+ assert np.allclose(cs.decrypt(c5), [a * 3 for a in t1], rtol=1e-2)
320
+ assert np.allclose(cs.decrypt(c6), [a * b for a, b in zip(t1, t3)], rtol=1e-2)
321
+ ```
322
+
323
+ ### Vector Similarity Search with PHE
324
+
325
+ 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.
326
+
327
+ 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.
328
+
329
+ ```python
330
+ # build an additively homomorphic cryptosystem (e.g. Paillier)
331
+ cs = LightPHE("Paillier")
332
+
333
+ # define plain vectors - suppose those are l2 normalized already
334
+ alpha = [7.1, 5.2, 5.3, 2.4, 3.5, 4.6] # On-prem vector (user tower)
335
+ beta = [5.6, 3.7, 2.8, 4, 0, 5.9] # Cloud vector (item tower)
336
+
337
+ # encrypt embedding
338
+ encrypted_alpha = cs.encrypt(alpha)
339
+
340
+ # dot product of encrypted embedding and plain embedding
341
+ encrypted_cosine_similarity = encrypted_alpha @ beta
342
+
343
+ # decrypt similarity
344
+ cosine_similarity = cs.decrypt(encrypted_cosine_similarity)[0]
345
+
346
+ # proof of work
347
+ assert abs(cosine_similarity - sum(x * y for x, y in zip(alpha, beta))) < 1e-2
348
+ ```
349
+
276
350
  # Contributing
277
351
 
278
352
  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
353
 
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.
354
+ 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
355
 
282
356
  # Support
283
357
 
@@ -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.14"
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(