lightphe 0.0.23__tar.gz → 0.0.24__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.24}/PKG-INFO +2 -2
  2. {lightphe-0.0.23 → lightphe-0.0.24}/README.md +1 -1
  3. {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/__init__.py +5 -1
  4. lightphe-0.0.24/lightphe/commons/key_validator.py +97 -0
  5. {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/cryptosystems/Benaloh.py +5 -0
  6. {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/cryptosystems/BonehGohNissim.py +21 -15
  7. {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/cryptosystems/DamgardJurik.py +5 -0
  8. {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/cryptosystems/ElGamal.py +5 -0
  9. {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/cryptosystems/EllipticCurveElGamal.py +5 -0
  10. {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/cryptosystems/GoldwasserMicali.py +5 -0
  11. {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/cryptosystems/NaccacheStern.py +5 -0
  12. {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/cryptosystems/OkamotoUchiyama.py +5 -0
  13. {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/cryptosystems/Paillier.py +5 -0
  14. {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/cryptosystems/RSA.py +5 -0
  15. {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/cryptosystems/SanderYoungYung.py +5 -0
  16. {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/models/Homomorphic.py +7 -0
  17. {lightphe-0.0.23 → lightphe-0.0.24}/lightphe.egg-info/PKG-INFO +2 -2
  18. {lightphe-0.0.23 → lightphe-0.0.24}/lightphe.egg-info/SOURCES.txt +2 -0
  19. {lightphe-0.0.23 → lightphe-0.0.24}/lightphe.egg-info/requires.txt +1 -1
  20. {lightphe-0.0.23 → lightphe-0.0.24}/requirements.txt +1 -1
  21. {lightphe-0.0.23 → lightphe-0.0.24}/setup.py +1 -1
  22. lightphe-0.0.24/tests/test_key_validator.py +100 -0
  23. {lightphe-0.0.23 → lightphe-0.0.24}/LICENSE +0 -0
  24. {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/commons/__init__.py +0 -0
  25. {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/commons/logger.py +0 -0
  26. {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/commons/phe_utils.py +0 -0
  27. {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/cryptosystems/__init__.py +0 -0
  28. {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/models/Algorithm.py +0 -0
  29. {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/models/Ciphertext.py +0 -0
  30. {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/models/Tensor.py +0 -0
  31. {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/models/__init__.py +0 -0
  32. {lightphe-0.0.23 → lightphe-0.0.24}/lightphe.egg-info/dependency_links.txt +0 -0
  33. {lightphe-0.0.23 → lightphe-0.0.24}/lightphe.egg-info/top_level.txt +0 -0
  34. {lightphe-0.0.23 → lightphe-0.0.24}/setup.cfg +0 -0
  35. {lightphe-0.0.23 → lightphe-0.0.24}/tests/__init__.py +0 -0
  36. {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_benaloh.py +0 -0
  37. {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_bonehgohnissim.py +0 -0
  38. {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_ciphertexts.py +0 -0
  39. {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_cloud.py +0 -0
  40. {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_damgard.py +0 -0
  41. {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_elgamal.py +0 -0
  42. {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_ellipticcurveelgamal.py +0 -0
  43. {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_exporting_keys.py +0 -0
  44. {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_goldwasser.py +0 -0
  45. {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_keys.py +0 -0
  46. {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_large_keys.py +0 -0
  47. {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_naccache.py +0 -0
  48. {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_okamoto.py +0 -0
  49. {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_paillier.py +0 -0
  50. {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_rsa.py +0 -0
  51. {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_salary.py +0 -0
  52. {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_sanderyoungyung.py +0 -0
  53. {lightphe-0.0.23 → lightphe-0.0.24}/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.24
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
@@ -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)
@@ -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)
@@ -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
@@ -36,7 +37,7 @@ from lightphe.cryptosystems.BonehGohNissim import BonehGohNissim
36
37
 
37
38
  logger = Logger(module="lightphe/__init__.py")
38
39
 
39
- VERSION = "0.0.23"
40
+ VERSION = "0.0.24"
40
41
 
41
42
 
42
43
  class LightPHE:
@@ -90,6 +91,9 @@ class LightPHE:
90
91
  if key_file is not None:
91
92
  keys = self.restore_keys(target_file=key_file)
92
93
 
94
+ if keys is not None:
95
+ validate_keys(algorithm_name=algorithm_name, keys=keys)
96
+
93
97
  self.cs: Homomorphic = self.__build_cryptosystem(
94
98
  algorithm_name=algorithm_name,
95
99
  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", "prime_set"],
26
+ }
27
+
23
28
  def __init__(
24
29
  self,
25
30
  keys: Optional[dict] = None,
@@ -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.24
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
@@ -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)
@@ -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.24",
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")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes