lightphe 0.0.23__tar.gz → 0.0.25__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.23 → lightphe-0.0.25}/PKG-INFO +14 -19
  2. {lightphe-0.0.23 → lightphe-0.0.25}/README.md +13 -18
  3. {lightphe-0.0.23 → lightphe-0.0.25}/lightphe/__init__.py +5 -2
  4. lightphe-0.0.25/lightphe/commons/key_validator.py +97 -0
  5. {lightphe-0.0.23 → lightphe-0.0.25}/lightphe/cryptosystems/Benaloh.py +5 -0
  6. {lightphe-0.0.23 → lightphe-0.0.25}/lightphe/cryptosystems/BonehGohNissim.py +21 -15
  7. {lightphe-0.0.23 → lightphe-0.0.25}/lightphe/cryptosystems/DamgardJurik.py +5 -0
  8. {lightphe-0.0.23 → lightphe-0.0.25}/lightphe/cryptosystems/ElGamal.py +5 -0
  9. {lightphe-0.0.23 → lightphe-0.0.25}/lightphe/cryptosystems/EllipticCurveElGamal.py +5 -0
  10. {lightphe-0.0.23 → lightphe-0.0.25}/lightphe/cryptosystems/GoldwasserMicali.py +5 -0
  11. {lightphe-0.0.23 → lightphe-0.0.25}/lightphe/cryptosystems/NaccacheStern.py +13 -6
  12. {lightphe-0.0.23 → lightphe-0.0.25}/lightphe/cryptosystems/OkamotoUchiyama.py +5 -0
  13. {lightphe-0.0.23 → lightphe-0.0.25}/lightphe/cryptosystems/Paillier.py +5 -0
  14. {lightphe-0.0.23 → lightphe-0.0.25}/lightphe/cryptosystems/RSA.py +5 -0
  15. {lightphe-0.0.23 → lightphe-0.0.25}/lightphe/cryptosystems/SanderYoungYung.py +5 -0
  16. {lightphe-0.0.23 → lightphe-0.0.25}/lightphe/models/Homomorphic.py +7 -0
  17. {lightphe-0.0.23 → lightphe-0.0.25}/lightphe.egg-info/PKG-INFO +14 -19
  18. {lightphe-0.0.23 → lightphe-0.0.25}/lightphe.egg-info/SOURCES.txt +2 -0
  19. {lightphe-0.0.23 → lightphe-0.0.25}/lightphe.egg-info/requires.txt +1 -1
  20. {lightphe-0.0.23 → lightphe-0.0.25}/requirements.txt +1 -1
  21. {lightphe-0.0.23 → lightphe-0.0.25}/setup.py +1 -1
  22. lightphe-0.0.25/tests/test_key_validator.py +100 -0
  23. {lightphe-0.0.23 → lightphe-0.0.25}/tests/test_naccache.py +4 -5
  24. {lightphe-0.0.23 → lightphe-0.0.25}/LICENSE +0 -0
  25. {lightphe-0.0.23 → lightphe-0.0.25}/lightphe/commons/__init__.py +0 -0
  26. {lightphe-0.0.23 → lightphe-0.0.25}/lightphe/commons/logger.py +0 -0
  27. {lightphe-0.0.23 → lightphe-0.0.25}/lightphe/commons/phe_utils.py +0 -0
  28. {lightphe-0.0.23 → lightphe-0.0.25}/lightphe/cryptosystems/__init__.py +0 -0
  29. {lightphe-0.0.23 → lightphe-0.0.25}/lightphe/models/Algorithm.py +0 -0
  30. {lightphe-0.0.23 → lightphe-0.0.25}/lightphe/models/Ciphertext.py +0 -0
  31. {lightphe-0.0.23 → lightphe-0.0.25}/lightphe/models/Tensor.py +0 -0
  32. {lightphe-0.0.23 → lightphe-0.0.25}/lightphe/models/__init__.py +0 -0
  33. {lightphe-0.0.23 → lightphe-0.0.25}/lightphe.egg-info/dependency_links.txt +0 -0
  34. {lightphe-0.0.23 → lightphe-0.0.25}/lightphe.egg-info/top_level.txt +0 -0
  35. {lightphe-0.0.23 → lightphe-0.0.25}/setup.cfg +0 -0
  36. {lightphe-0.0.23 → lightphe-0.0.25}/tests/__init__.py +0 -0
  37. {lightphe-0.0.23 → lightphe-0.0.25}/tests/test_benaloh.py +0 -0
  38. {lightphe-0.0.23 → lightphe-0.0.25}/tests/test_bonehgohnissim.py +0 -0
  39. {lightphe-0.0.23 → lightphe-0.0.25}/tests/test_ciphertexts.py +0 -0
  40. {lightphe-0.0.23 → lightphe-0.0.25}/tests/test_cloud.py +0 -0
  41. {lightphe-0.0.23 → lightphe-0.0.25}/tests/test_damgard.py +0 -0
  42. {lightphe-0.0.23 → lightphe-0.0.25}/tests/test_elgamal.py +0 -0
  43. {lightphe-0.0.23 → lightphe-0.0.25}/tests/test_ellipticcurveelgamal.py +0 -0
  44. {lightphe-0.0.23 → lightphe-0.0.25}/tests/test_exporting_keys.py +0 -0
  45. {lightphe-0.0.23 → lightphe-0.0.25}/tests/test_goldwasser.py +0 -0
  46. {lightphe-0.0.23 → lightphe-0.0.25}/tests/test_keys.py +0 -0
  47. {lightphe-0.0.23 → lightphe-0.0.25}/tests/test_large_keys.py +0 -0
  48. {lightphe-0.0.23 → lightphe-0.0.25}/tests/test_okamoto.py +0 -0
  49. {lightphe-0.0.23 → lightphe-0.0.25}/tests/test_paillier.py +0 -0
  50. {lightphe-0.0.23 → lightphe-0.0.25}/tests/test_rsa.py +0 -0
  51. {lightphe-0.0.23 → lightphe-0.0.25}/tests/test_salary.py +0 -0
  52. {lightphe-0.0.23 → lightphe-0.0.25}/tests/test_sanderyoungyung.py +0 -0
  53. {lightphe-0.0.23 → lightphe-0.0.25}/tests/test_tensors.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lightphe
3
- Version: 0.0.23
3
+ Version: 0.0.25
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
@@ -65,7 +65,7 @@ from lightphe import LightPHE
65
65
 
66
66
  # Summary of Homomorphic Features of Different Cryptosystems in LightPHE
67
67
 
68
- In summary, LightPHE is covering following algorithms and these are partially homomorphic with respect to the operations mentioned in the following table.
68
+ In summary, LightPHE is covering following algorithms and these are partially homomorphic and somewhat homomorphic with respect to the operations mentioned in the following table.
69
69
 
70
70
  | Algorithm | Multiplicatively<br>Homomorphic | Additively<br>Homomorphic | Scalar Multiplication | Bitwise-XOR Homomorphic | Bitwise-AND Homomorphic | Regeneration<br>of Ciphertext |
71
71
  | --- | --- | --- | --- | --- | --- | --- |
@@ -156,7 +156,7 @@ assert cs.decrypt(c3) == m1 * m2
156
156
 
157
157
  ### Ciphertext Regeneration
158
158
 
159
- The most of additively homomorphic algorithms allow you to regenerate ciphertext while you are not breaking its plaintext restoration. You may consider to do this re-generation many times to have stronger ciphertexts.
159
+ The most of homomorphic algorithms allow you to regenerate ciphertext while you are not breaking its plaintext restoration. You may consider to do this re-generation many times to have stronger ciphertexts.
160
160
 
161
161
  ```python
162
162
  c1_prime = cs.regenerate_ciphertext(c1)
@@ -248,22 +248,17 @@ Also, your company's logo will be shown on README on GitHub if you become sponso
248
248
  Please cite LightPHE in your publications if it helps your research. Here is its BibTex entry:
249
249
 
250
250
  ```BibTeX
251
- @article{serengil2024lightphe,
252
- title={LightPHE: Integrating Partially Homomorphic Encryption into Python with Extensive Cloud Environment Evaluations},
253
- author={Serengil, Sefik Ilkin and Ozpinar, Alper},
254
- journal={arXiv preprint arXiv:2408.05219},
255
- note={doi: 10.48550/arXiv.2408.05219. [Online]. Available: \url{https://arxiv.org/abs/2408.05219}},
256
- year={2025}
257
- }
258
- ```
259
-
260
- ```BibTeX
261
- @article{serengil2025vectorsimilarity,
262
- title={Encrypted Vector Similarity Computations Using Partially Homomorphic Encryption: Applications and Performance Analysis},
263
- author={Serengil, Sefik and Ozpinar, Alper},
264
- journal={arXiv preprint arXiv:2503.05850},
265
- note={doi: 10.48550/arXiv.2503.05850. [Online]. Available: \url{https://arxiv.org/abs/2503.05850}},
266
- year={2025}
251
+ @article{sym18050832,
252
+ title = {Sustainable Cryptography: Carbon Asymmetry in Partially Homomorphic Encryption in the Cloud},
253
+ author = {Ozpinar, Alper and Serengil, Sefik Ilkin},
254
+ journal = {Symmetry},
255
+ volume = {18},
256
+ number = {5},
257
+ pages = {832},
258
+ url = {https://www.mdpi.com/2073-8994/18/5/832},
259
+ doi = {10.3390/sym18050832},
260
+ note = {Special Issue: Symmetry in Cryptography and Cybersecurity},
261
+ year = {2026}
267
262
  }
268
263
  ```
269
264
 
@@ -49,7 +49,7 @@ from lightphe import LightPHE
49
49
 
50
50
  # Summary of Homomorphic Features of Different Cryptosystems in LightPHE
51
51
 
52
- In summary, LightPHE is covering following algorithms and these are partially homomorphic with respect to the operations mentioned in the following table.
52
+ In summary, LightPHE is covering following algorithms and these are partially homomorphic and somewhat homomorphic with respect to the operations mentioned in the following table.
53
53
 
54
54
  | Algorithm | Multiplicatively<br>Homomorphic | Additively<br>Homomorphic | Scalar Multiplication | Bitwise-XOR Homomorphic | Bitwise-AND Homomorphic | Regeneration<br>of Ciphertext |
55
55
  | --- | --- | --- | --- | --- | --- | --- |
@@ -140,7 +140,7 @@ assert cs.decrypt(c3) == m1 * m2
140
140
 
141
141
  ### Ciphertext Regeneration
142
142
 
143
- The most of additively homomorphic algorithms allow you to regenerate ciphertext while you are not breaking its plaintext restoration. You may consider to do this re-generation many times to have stronger ciphertexts.
143
+ The most of homomorphic algorithms allow you to regenerate ciphertext while you are not breaking its plaintext restoration. You may consider to do this re-generation many times to have stronger ciphertexts.
144
144
 
145
145
  ```python
146
146
  c1_prime = cs.regenerate_ciphertext(c1)
@@ -232,22 +232,17 @@ Also, your company's logo will be shown on README on GitHub if you become sponso
232
232
  Please cite LightPHE in your publications if it helps your research. Here is its BibTex entry:
233
233
 
234
234
  ```BibTeX
235
- @article{serengil2024lightphe,
236
- title={LightPHE: Integrating Partially Homomorphic Encryption into Python with Extensive Cloud Environment Evaluations},
237
- author={Serengil, Sefik Ilkin and Ozpinar, Alper},
238
- journal={arXiv preprint arXiv:2408.05219},
239
- note={doi: 10.48550/arXiv.2408.05219. [Online]. Available: \url{https://arxiv.org/abs/2408.05219}},
240
- year={2025}
241
- }
242
- ```
243
-
244
- ```BibTeX
245
- @article{serengil2025vectorsimilarity,
246
- title={Encrypted Vector Similarity Computations Using Partially Homomorphic Encryption: Applications and Performance Analysis},
247
- author={Serengil, Sefik and Ozpinar, Alper},
248
- journal={arXiv preprint arXiv:2503.05850},
249
- note={doi: 10.48550/arXiv.2503.05850. [Online]. Available: \url{https://arxiv.org/abs/2503.05850}},
250
- year={2025}
235
+ @article{sym18050832,
236
+ title = {Sustainable Cryptography: Carbon Asymmetry in Partially Homomorphic Encryption in the Cloud},
237
+ author = {Ozpinar, Alper and Serengil, Sefik Ilkin},
238
+ journal = {Symmetry},
239
+ volume = {18},
240
+ number = {5},
241
+ pages = {832},
242
+ url = {https://www.mdpi.com/2073-8994/18/5/832},
243
+ doi = {10.3390/sym18050832},
244
+ note = {Special Issue: Symmetry in Cryptography and Cybersecurity},
245
+ year = {2026}
251
246
  }
252
247
  ```
253
248
 
@@ -16,6 +16,7 @@ from lightphe.models.Ciphertext import Ciphertext
16
16
  from lightphe.models.Algorithm import Algorithm
17
17
  from lightphe.models.Tensor import Fraction, EncryptedTensor
18
18
  from lightphe.commons import phe_utils
19
+ from lightphe.commons.key_validator import validate_keys
19
20
  from lightphe.commons.logger import Logger
20
21
 
21
22
  # cryptosystems
@@ -31,12 +32,11 @@ from lightphe.cryptosystems.EllipticCurveElGamal import EllipticCurveElGamal
31
32
  from lightphe.cryptosystems.SanderYoungYung import SanderYoungYung
32
33
  from lightphe.cryptosystems.BonehGohNissim import BonehGohNissim
33
34
 
34
-
35
35
  # pylint: disable=eval-used, simplifiable-if-expression, too-few-public-methods
36
36
 
37
37
  logger = Logger(module="lightphe/__init__.py")
38
38
 
39
- VERSION = "0.0.23"
39
+ VERSION = "0.0.25"
40
40
 
41
41
 
42
42
  class LightPHE:
@@ -90,6 +90,9 @@ class LightPHE:
90
90
  if key_file is not None:
91
91
  keys = self.restore_keys(target_file=key_file)
92
92
 
93
+ if keys is not None:
94
+ validate_keys(algorithm_name=algorithm_name, keys=keys)
95
+
93
96
  self.cs: Homomorphic = self.__build_cryptosystem(
94
97
  algorithm_name=algorithm_name,
95
98
  keys=keys,
@@ -0,0 +1,97 @@
1
+ from typing import Dict, Type
2
+
3
+ from lightphe.models.Algorithm import Algorithm
4
+ from lightphe.models.Homomorphic import Homomorphic
5
+ from lightphe.cryptosystems.RSA import RSA
6
+ from lightphe.cryptosystems.ElGamal import ElGamal
7
+ from lightphe.cryptosystems.EllipticCurveElGamal import EllipticCurveElGamal
8
+ from lightphe.cryptosystems.Paillier import Paillier
9
+ from lightphe.cryptosystems.DamgardJurik import DamgardJurik
10
+ from lightphe.cryptosystems.OkamotoUchiyama import OkamotoUchiyama
11
+ from lightphe.cryptosystems.Benaloh import Benaloh
12
+ from lightphe.cryptosystems.NaccacheStern import NaccacheStern
13
+ from lightphe.cryptosystems.GoldwasserMicali import GoldwasserMicali
14
+ from lightphe.cryptosystems.SanderYoungYung import SanderYoungYung
15
+ from lightphe.cryptosystems.BonehGohNissim import BonehGohNissim
16
+
17
+
18
+ # Map user-facing algorithm names to the class that owns the REQUIRED_KEYS spec.
19
+ # The spec itself lives as a class attribute on each cryptosystem.
20
+ # Exponential-ElGamal reuses ElGamal's key structure.
21
+ _ALGORITHM_TO_CLASS: Dict[str, Type[Homomorphic]] = {
22
+ Algorithm.RSA: RSA,
23
+ Algorithm.ElGamal: ElGamal,
24
+ Algorithm.ExponentialElGamal: ElGamal,
25
+ Algorithm.EllipticCurveElGamal: EllipticCurveElGamal,
26
+ Algorithm.Paillier: Paillier,
27
+ Algorithm.DamgardJurik: DamgardJurik,
28
+ Algorithm.OkamotoUchiyama: OkamotoUchiyama,
29
+ Algorithm.Benaloh: Benaloh,
30
+ Algorithm.NaccacheStern: NaccacheStern,
31
+ Algorithm.GoldwasserMicali: GoldwasserMicali,
32
+ Algorithm.SanderYoungYung: SanderYoungYung,
33
+ Algorithm.BonehGohNissim: BonehGohNissim,
34
+ }
35
+
36
+
37
+ def validate_keys(algorithm_name: str, keys: dict) -> None:
38
+ """
39
+ Validate that user-supplied keys carry the required sub-fields for the
40
+ given cryptosystem. `public_key` is always required (every cryptosystem
41
+ needs it for encryption); `private_key` is optional (absent in
42
+ encryption-only setups). Whichever is present must contain all its
43
+ required sub-fields.
44
+
45
+ The required-field spec is read from the cryptosystem class's
46
+ `REQUIRED_KEYS` attribute (a dict with "public_key", "private_key", and
47
+ an optional "nested" entry for dict-valued public_key sub-fields).
48
+
49
+ Args:
50
+ algorithm_name (str): user-facing algorithm name (e.g. "RSA")
51
+ keys (dict): user-supplied keys dict
52
+ Raises:
53
+ ValueError: when required fields are missing or structure is wrong
54
+ """
55
+ cls = _ALGORITHM_TO_CLASS.get(algorithm_name)
56
+ if cls is None:
57
+ return
58
+ spec = cls.REQUIRED_KEYS
59
+
60
+ if not isinstance(keys, dict):
61
+ raise ValueError(
62
+ f"{algorithm_name} keys must be a dict, got {type(keys).__name__}"
63
+ )
64
+
65
+ if "public_key" not in keys:
66
+ raise ValueError(f"{algorithm_name} keys must contain 'public_key'")
67
+
68
+ for kind in ("public_key", "private_key"):
69
+ if kind not in keys:
70
+ continue
71
+ if not isinstance(keys[kind], dict):
72
+ raise ValueError(
73
+ f"{algorithm_name} '{kind}' must be a dict, "
74
+ f"got {type(keys[kind]).__name__}"
75
+ )
76
+ missing = [f for f in spec[kind] if f not in keys[kind]]
77
+ if missing:
78
+ raise ValueError(
79
+ f"{algorithm_name} '{kind}' is missing required fields: {missing}"
80
+ )
81
+
82
+ nested = spec.get("nested") or {}
83
+ for parent, required in nested.items():
84
+ if "public_key" not in keys or parent not in keys["public_key"]:
85
+ continue
86
+ sub = keys["public_key"][parent]
87
+ if not isinstance(sub, dict):
88
+ raise ValueError(
89
+ f"{algorithm_name} 'public_key.{parent}' must be a dict, "
90
+ f"got {type(sub).__name__}"
91
+ )
92
+ missing = [f for f in required if f not in sub]
93
+ if missing:
94
+ raise ValueError(
95
+ f"{algorithm_name} 'public_key.{parent}' is missing required "
96
+ f"fields: {missing}"
97
+ )
@@ -15,6 +15,11 @@ logger = Logger(module="lightphe/cryptosystems/Benaloh.py")
15
15
 
16
16
 
17
17
  class Benaloh(Homomorphic):
18
+ REQUIRED_KEYS = {
19
+ "public_key": ["y", "r", "n"],
20
+ "private_key": ["p", "q", "phi", "x"],
21
+ }
22
+
18
23
  def __init__(
19
24
  self,
20
25
  keys: Optional[dict] = None,
@@ -5,6 +5,7 @@ from typing import Optional, Tuple, Union
5
5
 
6
6
  # third-party dependencies
7
7
  from lightecc import LightECC
8
+ from lightecc.commons.errors import InvalidCurveOrder, PointNotOnCurve
8
9
  from lightecc.interfaces.elliptic_curve import EllipticCurvePoint
9
10
  from lightecc.commons.pairing import _fp2_pow, _fp2_mul
10
11
  import sympy
@@ -25,6 +26,12 @@ class BonehGohNissim(Homomorphic):
25
26
  Ref: https://sefiks.com/2026/04/02/a-step-by-step-somewhat-homomorphic-encryption-example-with-boneh-goh-nissim-in-python/
26
27
  """
27
28
 
29
+ REQUIRED_KEYS = {
30
+ "public_key": ["curve", "G", "u", "h", "l"],
31
+ "private_key": ["q1", "q2"],
32
+ "nested": {"curve": ["a", "b", "p", "G", "n"]},
33
+ }
34
+
28
35
  def __init__(
29
36
  self,
30
37
  keys: Optional[dict] = None,
@@ -106,22 +113,21 @@ class BonehGohNissim(Homomorphic):
106
113
 
107
114
  G = self.__find_generator(p, max_tries=max_tries)
108
115
 
109
- # order of the curve may not be exactly n, but it should be
110
- self.ec = LightECC(
111
- form_name="weierstrass",
112
- curve_name="custom",
113
- config={"a": a, "b": b, "p": p, "G": G, "n": None},
114
- )
115
-
116
- nG = n * self.ec.G
117
- if (
118
- nG == self.ec.O
119
- ): # check if nG is the point at infinity / identity element / neutral element
120
- logger.debug("nG is the point at infinity, as expected.")
121
- logger.debug(f"q1 = {q1}, q2 = {q2}, n = {n}, p = {p}, l = {l}")
122
- break
116
+ try:
117
+ self.ec = LightECC(
118
+ form_name="weierstrass",
119
+ curve_name="custom",
120
+ config={"a": a, "b": b, "p": p, "G": G, "n": n},
121
+ )
122
+ except (InvalidCurveOrder, PointNotOnCurve):
123
+ logger.debug(
124
+ f"Curve order validation failed for attempt {key_attempt + 1}, retrying"
125
+ )
126
+ continue
123
127
 
124
- logger.debug(f"nG not infinity, retrying (attempt {key_attempt + 1})")
128
+ logger.debug("Curve order validated by LightECC.")
129
+ logger.debug(f"q1 = {q1}, q2 = {q2}, n = {n}, p = {p}, l = {l}")
130
+ break
125
131
  else:
126
132
  raise Exception(f"Failed to generate keys after {max_tries} attempts")
127
133
 
@@ -15,6 +15,11 @@ class DamgardJurik(Homomorphic):
15
15
  Ref: https://sefiks.com/2023/10/20/a-step-by-step-partially-homomorphic-encryption-example-with-damgard-jurik-in-python/
16
16
  """
17
17
 
18
+ REQUIRED_KEYS = {
19
+ "public_key": ["g", "n", "s"],
20
+ "private_key": ["phi"],
21
+ }
22
+
18
23
  def __init__(self, s: int = 2, keys: Optional[dict] = None, key_size: Optional[int] = None):
19
24
  """
20
25
  Args:
@@ -19,6 +19,11 @@ class ElGamal(Homomorphic):
19
19
  Ref: https://sefiks.com/2023/03/27/a-step-by-step-partially-homomorphic-encryption-example-with-elgamal-in-python/
20
20
  """
21
21
 
22
+ REQUIRED_KEYS = {
23
+ "public_key": ["p", "g", "y"],
24
+ "private_key": ["x"],
25
+ }
26
+
22
27
  def __init__(self, keys: Optional[dict] = None, exponential=False, key_size: Optional[int] = None):
23
28
  """
24
29
  Args:
@@ -22,6 +22,11 @@ class EllipticCurveElGamal(Homomorphic):
22
22
  Ref: https://sefiks.com/2018/08/21/elliptic-curve-elgamal-encryption/
23
23
  """
24
24
 
25
+ REQUIRED_KEYS = {
26
+ "public_key": ["Qa"],
27
+ "private_key": ["ka"],
28
+ }
29
+
25
30
  def __init__(
26
31
  self,
27
32
  keys: Optional[dict] = None,
@@ -23,6 +23,11 @@ class GoldwasserMicali(Homomorphic):
23
23
  Ref: sefiks.com/2023/10/27/a-step-by-step-partially-homomorphic-encryption-example-with-goldwasser-micali-in-python/
24
24
  """
25
25
 
26
+ REQUIRED_KEYS = {
27
+ "public_key": ["n", "x"],
28
+ "private_key": ["p", "q"],
29
+ }
30
+
26
31
  def __init__(
27
32
  self,
28
33
  keys: Optional[dict] = None,
@@ -20,6 +20,11 @@ class NaccacheStern(Homomorphic):
20
20
  Original paper: https://dl.acm.org/doi/pdf/10.1145/288090.288106
21
21
  """
22
22
 
23
+ REQUIRED_KEYS = {
24
+ "public_key": ["n", "g", "sigma"],
25
+ "private_key": ["a", "b", "p", "q", "phi"],
26
+ }
27
+
23
28
  def __init__(
24
29
  self,
25
30
  keys: Optional[dict] = None,
@@ -54,10 +59,11 @@ class NaccacheStern(Homomorphic):
54
59
  Returns:
55
60
  keys (dict): containing 'private_key' and 'public_key'
56
61
  """
57
- # Small prime set (the largest one is 10-bits)
58
- # Using a smaller prime_set to make key generation feasible for large key sizes
59
- # prime_set = [3, 5, 7, 11, 13, 17]
60
- prime_set = [3, 5, 7, 11]
62
+ # Small primes are required so DLP remains solvable during decryption.
63
+ # Picking 4 out of these 8 keeps sigma small (<= ~23^4) and splits
64
+ # evenly into u and v.
65
+ small_primes = [3, 5, 7, 11, 13, 17, 19, 23]
66
+ prime_set = sorted(random.sample(small_primes, 4))
61
67
  k = len(prime_set)
62
68
 
63
69
  if all(sympy.isprime(prime) is True for prime in prime_set) is False:
@@ -167,7 +173,6 @@ class NaccacheStern(Homomorphic):
167
173
  "p": p,
168
174
  "q": q,
169
175
  "phi": phi,
170
- "prime_set": prime_set,
171
176
  },
172
177
  }
173
178
  logger.debug(
@@ -227,7 +232,9 @@ class NaccacheStern(Homomorphic):
227
232
  phi = self.keys["private_key"]["phi"]
228
233
  n = self.keys["public_key"]["n"]
229
234
  g = self.keys["public_key"]["g"]
230
- prime_set = self.keys["private_key"]["prime_set"]
235
+ sigma = self.keys["public_key"]["sigma"]
236
+ # sigma is the product of the small primes; recover them by factoring.
237
+ prime_set = sorted(sympy.factorint(sigma).keys())
231
238
 
232
239
  remainders = []
233
240
  for i, prime in enumerate(prime_set):
@@ -14,6 +14,11 @@ class OkamotoUchiyama(Homomorphic):
14
14
  Ref: https://sefiks.com/2023/10/20/a-step-by-step-partially-homomorphic-encryption-example-with-okamoto-uchiyama-in-python/
15
15
  """
16
16
 
17
+ REQUIRED_KEYS = {
18
+ "public_key": ["n", "g", "h"],
19
+ "private_key": ["p", "q"],
20
+ }
21
+
17
22
  def __init__(self, keys: Optional[dict] = None, key_size: Optional[int] = None):
18
23
  """
19
24
  Args:
@@ -15,6 +15,11 @@ class Paillier(Homomorphic):
15
15
  Ref: https://sefiks.com/2023/04/03/a-step-by-step-partially-homomorphic-encryption-example-with-paillier-in-python/
16
16
  """
17
17
 
18
+ REQUIRED_KEYS = {
19
+ "public_key": ["g", "n"],
20
+ "private_key": ["phi"],
21
+ }
22
+
18
23
  def __init__(self, keys: Optional[dict] = None, key_size: Optional[int] = None):
19
24
  """
20
25
  Args:
@@ -14,6 +14,11 @@ class RSA(Homomorphic):
14
14
  Ref: https://sefiks.com/2023/03/06/a-step-by-step-partially-homomorphic-encryption-example-with-rsa-in-python/
15
15
  """
16
16
 
17
+ REQUIRED_KEYS = {
18
+ "public_key": ["n", "e"],
19
+ "private_key": ["d"],
20
+ }
21
+
17
22
  def __init__(
18
23
  self,
19
24
  keys: Optional[dict] = None,
@@ -21,6 +21,11 @@ class SanderYoungYung(Homomorphic):
21
21
  Ref: https://sefiks.com/2026/04/02/a-step-by-step-partially-homomorphic-sander-young-yung-example-in-python/
22
22
  """
23
23
 
24
+ REQUIRED_KEYS = {
25
+ "public_key": ["n", "x", "l"],
26
+ "private_key": ["p", "q"],
27
+ }
28
+
24
29
  def __init__(
25
30
  self,
26
31
  keys: Optional[dict] = None,
@@ -17,6 +17,13 @@ class Homomorphic(ABC):
17
17
  plaintext_modulo: int
18
18
  ciphertext_modulo: int
19
19
 
20
+ # Cryptosystems must define REQUIRED_KEYS as a dict with:
21
+ # - "public_key": list of required sub-fields in keys["public_key"]
22
+ # - "private_key": list of required sub-fields in keys["private_key"]
23
+ # - "nested" (optional): dict mapping a public_key sub-field that itself
24
+ # holds a dict to its required sub-fields.
25
+ REQUIRED_KEYS: dict
26
+
20
27
  @abstractmethod
21
28
  def generate_keys(
22
29
  self,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lightphe
3
- Version: 0.0.23
3
+ Version: 0.0.25
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
@@ -65,7 +65,7 @@ from lightphe import LightPHE
65
65
 
66
66
  # Summary of Homomorphic Features of Different Cryptosystems in LightPHE
67
67
 
68
- In summary, LightPHE is covering following algorithms and these are partially homomorphic with respect to the operations mentioned in the following table.
68
+ In summary, LightPHE is covering following algorithms and these are partially homomorphic and somewhat homomorphic with respect to the operations mentioned in the following table.
69
69
 
70
70
  | Algorithm | Multiplicatively<br>Homomorphic | Additively<br>Homomorphic | Scalar Multiplication | Bitwise-XOR Homomorphic | Bitwise-AND Homomorphic | Regeneration<br>of Ciphertext |
71
71
  | --- | --- | --- | --- | --- | --- | --- |
@@ -156,7 +156,7 @@ assert cs.decrypt(c3) == m1 * m2
156
156
 
157
157
  ### Ciphertext Regeneration
158
158
 
159
- The most of additively homomorphic algorithms allow you to regenerate ciphertext while you are not breaking its plaintext restoration. You may consider to do this re-generation many times to have stronger ciphertexts.
159
+ The most of homomorphic algorithms allow you to regenerate ciphertext while you are not breaking its plaintext restoration. You may consider to do this re-generation many times to have stronger ciphertexts.
160
160
 
161
161
  ```python
162
162
  c1_prime = cs.regenerate_ciphertext(c1)
@@ -248,22 +248,17 @@ Also, your company's logo will be shown on README on GitHub if you become sponso
248
248
  Please cite LightPHE in your publications if it helps your research. Here is its BibTex entry:
249
249
 
250
250
  ```BibTeX
251
- @article{serengil2024lightphe,
252
- title={LightPHE: Integrating Partially Homomorphic Encryption into Python with Extensive Cloud Environment Evaluations},
253
- author={Serengil, Sefik Ilkin and Ozpinar, Alper},
254
- journal={arXiv preprint arXiv:2408.05219},
255
- note={doi: 10.48550/arXiv.2408.05219. [Online]. Available: \url{https://arxiv.org/abs/2408.05219}},
256
- year={2025}
257
- }
258
- ```
259
-
260
- ```BibTeX
261
- @article{serengil2025vectorsimilarity,
262
- title={Encrypted Vector Similarity Computations Using Partially Homomorphic Encryption: Applications and Performance Analysis},
263
- author={Serengil, Sefik and Ozpinar, Alper},
264
- journal={arXiv preprint arXiv:2503.05850},
265
- note={doi: 10.48550/arXiv.2503.05850. [Online]. Available: \url{https://arxiv.org/abs/2503.05850}},
266
- year={2025}
251
+ @article{sym18050832,
252
+ title = {Sustainable Cryptography: Carbon Asymmetry in Partially Homomorphic Encryption in the Cloud},
253
+ author = {Ozpinar, Alper and Serengil, Sefik Ilkin},
254
+ journal = {Symmetry},
255
+ volume = {18},
256
+ number = {5},
257
+ pages = {832},
258
+ url = {https://www.mdpi.com/2073-8994/18/5/832},
259
+ doi = {10.3390/sym18050832},
260
+ note = {Special Issue: Symmetry in Cryptography and Cybersecurity},
261
+ year = {2026}
267
262
  }
268
263
  ```
269
264
 
@@ -9,6 +9,7 @@ lightphe.egg-info/dependency_links.txt
9
9
  lightphe.egg-info/requires.txt
10
10
  lightphe.egg-info/top_level.txt
11
11
  lightphe/commons/__init__.py
12
+ lightphe/commons/key_validator.py
12
13
  lightphe/commons/logger.py
13
14
  lightphe/commons/phe_utils.py
14
15
  lightphe/cryptosystems/Benaloh.py
@@ -38,6 +39,7 @@ tests/test_elgamal.py
38
39
  tests/test_ellipticcurveelgamal.py
39
40
  tests/test_exporting_keys.py
40
41
  tests/test_goldwasser.py
42
+ tests/test_key_validator.py
41
43
  tests/test_keys.py
42
44
  tests/test_large_keys.py
43
45
  tests/test_naccache.py
@@ -1,3 +1,3 @@
1
1
  sympy>=1.12
2
2
  tqdm
3
- lightecc>=0.0.5
3
+ lightecc>=0.0.6
@@ -1,3 +1,3 @@
1
1
  sympy>=1.12
2
2
  tqdm
3
- lightecc>=0.0.5
3
+ lightecc>=0.0.6
@@ -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.23",
11
+ version="0.0.25",
12
12
  author="Sefik Ilkin Serengil",
13
13
  author_email="serengil@gmail.com",
14
14
  description="A Lightweight Partially Homomorphic Encryption Library for Python",
@@ -0,0 +1,100 @@
1
+ import copy
2
+
3
+ import pytest
4
+
5
+ from lightphe import LightPHE
6
+ from lightphe.commons.logger import Logger
7
+
8
+ logger = Logger(module="tests/test_key_validator.py")
9
+
10
+
11
+ ALGORITHMS = [
12
+ ("RSA", 50),
13
+ ("ElGamal", 50),
14
+ ("Exponential-ElGamal", 50),
15
+ ("EllipticCurve-ElGamal", 50),
16
+ ("Paillier", 50),
17
+ ("Damgard-Jurik", 50),
18
+ ("Okamoto-Uchiyama", 50),
19
+ ("Benaloh", 50),
20
+ ("Naccache-Stern", 50),
21
+ ("Goldwasser-Micali", 50),
22
+ ("Sander-Young-Yung", 50),
23
+ ("Boneh-Goh-Nissim", 50),
24
+ ]
25
+
26
+
27
+ @pytest.mark.parametrize("algorithm_name,key_size", ALGORITHMS)
28
+ def test_missing_public_key_field_is_rejected(algorithm_name, key_size):
29
+ """Dropping a required public_key sub-field must raise ValueError."""
30
+ cs = LightPHE(algorithm_name=algorithm_name, key_size=key_size, max_tries=10000)
31
+ field = cs.cs.REQUIRED_KEYS["public_key"][0]
32
+
33
+ broken = copy.deepcopy(cs.cs.keys)
34
+ del broken["public_key"][field]
35
+
36
+ with pytest.raises(ValueError, match=f"missing required fields.*{field}"):
37
+ LightPHE(algorithm_name=algorithm_name, keys=broken)
38
+
39
+ logger.info(f"✅ {algorithm_name}: missing public_key.{field} rejected")
40
+
41
+
42
+ @pytest.mark.parametrize("algorithm_name,key_size", ALGORITHMS)
43
+ def test_missing_private_key_field_is_rejected(algorithm_name, key_size):
44
+ """Dropping a required private_key sub-field must raise ValueError."""
45
+ cs = LightPHE(algorithm_name=algorithm_name, key_size=key_size, max_tries=10000)
46
+ field = cs.cs.REQUIRED_KEYS["private_key"][0]
47
+
48
+ broken = copy.deepcopy(cs.cs.keys)
49
+ del broken["private_key"][field]
50
+
51
+ with pytest.raises(ValueError, match=f"missing required fields.*{field}"):
52
+ LightPHE(algorithm_name=algorithm_name, keys=broken)
53
+
54
+ logger.info(f"✅ {algorithm_name}: missing private_key.{field} rejected")
55
+
56
+
57
+ def test_missing_public_key_dict_is_rejected():
58
+ """A keys dict without public_key must be rejected."""
59
+ with pytest.raises(ValueError, match="must contain 'public_key'"):
60
+ LightPHE(algorithm_name="RSA", keys={})
61
+
62
+
63
+ def test_private_key_only_is_rejected():
64
+ """Supplying private_key without public_key must be rejected."""
65
+ cs = LightPHE(algorithm_name="RSA", key_size=50)
66
+ private_only = {"private_key": copy.deepcopy(cs.cs.keys["private_key"])}
67
+ with pytest.raises(ValueError, match="must contain 'public_key'"):
68
+ LightPHE(algorithm_name="RSA", keys=private_only)
69
+
70
+
71
+ def test_non_dict_keys_is_rejected():
72
+ """A non-dict `keys` argument must be rejected."""
73
+ with pytest.raises(ValueError, match="keys must be a dict"):
74
+ LightPHE(algorithm_name="RSA", keys="not-a-dict")
75
+
76
+
77
+ def test_public_key_only_is_accepted():
78
+ """Supplying only a full public_key must be accepted (encryption-only use)."""
79
+ cs = LightPHE(algorithm_name="RSA", key_size=50)
80
+ public_only = {"public_key": copy.deepcopy(cs.cs.keys["public_key"])}
81
+ # should not raise
82
+ LightPHE(algorithm_name="RSA", keys=public_only)
83
+
84
+
85
+ def test_bgn_nested_curve_field_is_rejected():
86
+ """Dropping a required BGN curve sub-field must be rejected."""
87
+ cs = LightPHE(algorithm_name="Boneh-Goh-Nissim", key_size=50, max_tries=10000)
88
+ nested = cs.cs.REQUIRED_KEYS.get("nested", {}).get("curve", [])
89
+ assert nested, "BGN must declare nested curve required fields"
90
+ field = nested[0]
91
+
92
+ broken = copy.deepcopy(cs.cs.keys)
93
+ del broken["public_key"]["curve"][field]
94
+
95
+ with pytest.raises(
96
+ ValueError, match=rf"public_key\.curve.*missing required fields.*{field}"
97
+ ):
98
+ LightPHE(algorithm_name="Boneh-Goh-Nissim", keys=broken)
99
+
100
+ logger.info(f"✅ BGN: missing public_key.curve.{field} rejected")
@@ -23,7 +23,6 @@ def test_predefined_keys():
23
23
  "p": 2435378721695886057509882054966317293278393717736720131443144187793510457493213363945300094351007614623267282257550830261688773235268185768920658432760349591,
24
24
  "q": 30909025587173888031151320439694649143770740813345918276468695473548278783448460260839705132326515527669756851362284434022860932531472563869828240743607816519,
25
25
  "phi": 75275183223356977395233015128636801841182684353869611205877871399639096259927846711964449336873721703958210179653876541687516247046310691985977881277598653806134265954388527744361895141375770783671899073915178636812972605279037227887538978553609742276161772286758627935090233305041395360684118422229078186256527620,
26
- "prime_set": [3, 5, 7, 11, 13, 17],
27
26
  },
28
27
  }
29
28
 
@@ -33,7 +32,7 @@ def test_predefined_keys():
33
32
 
34
33
  security_level = cs.cs.keys["public_key"]["n"].bit_length()
35
34
 
36
- logger.info(
35
+ logger.debug(
37
36
  f"Naccache-Stern cs built with {security_level} bits key. Recommended >= 768."
38
37
  )
39
38
 
@@ -44,13 +43,13 @@ def test_predefined_keys():
44
43
  c1 = cs.encrypt(plaintext=m1)
45
44
  c2 = cs.encrypt(plaintext=m2)
46
45
  toc = time.time()
47
- logger.info(f"Naccache-Stern encryption time: {(toc - tic)/2:.4f} seconds")
46
+ logger.debug(f"Naccache-Stern encryption time: {(toc - tic)/2:.4f} seconds")
48
47
 
49
48
  # homomorphic addition
50
49
  tic = time.time()
51
50
  assert cs.decrypt(c1 + c2) == m1 + m2
52
51
  toc = time.time()
53
- logger.info(f"Naccache-Stern homomorphic addition time: {(toc - tic):.4f} seconds")
52
+ logger.debug(f"Naccache-Stern homomorphic addition time: {(toc - tic):.4f} seconds")
54
53
 
55
54
  # homomorphic scalar multiplication
56
55
  tic = time.time()
@@ -59,7 +58,7 @@ def test_predefined_keys():
59
58
  m2_times_c1 = m2 * c1
60
59
  m1_times_c2 = m1 * c2
61
60
  toc = time.time()
62
- logger.info(
61
+ logger.debug(
63
62
  f"Naccache-Stern homomorphic scalar multiplication time: {(toc - tic)/4:.4f} seconds"
64
63
  )
65
64
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes