lightphe 0.0.22__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.22 → lightphe-0.0.24}/PKG-INFO +3 -3
  2. {lightphe-0.0.22 → lightphe-0.0.24}/README.md +2 -2
  3. {lightphe-0.0.22 → 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.22 → lightphe-0.0.24}/lightphe/cryptosystems/Benaloh.py +5 -0
  6. {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/cryptosystems/BonehGohNissim.py +54 -23
  7. {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/cryptosystems/DamgardJurik.py +5 -0
  8. {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/cryptosystems/ElGamal.py +5 -0
  9. {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/cryptosystems/EllipticCurveElGamal.py +5 -0
  10. {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/cryptosystems/GoldwasserMicali.py +5 -0
  11. {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/cryptosystems/NaccacheStern.py +5 -0
  12. {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/cryptosystems/OkamotoUchiyama.py +5 -0
  13. {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/cryptosystems/Paillier.py +5 -0
  14. {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/cryptosystems/RSA.py +5 -0
  15. {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/cryptosystems/SanderYoungYung.py +5 -0
  16. {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/models/Homomorphic.py +7 -0
  17. {lightphe-0.0.22 → lightphe-0.0.24}/lightphe.egg-info/PKG-INFO +3 -3
  18. {lightphe-0.0.22 → lightphe-0.0.24}/lightphe.egg-info/SOURCES.txt +2 -0
  19. {lightphe-0.0.22 → lightphe-0.0.24}/lightphe.egg-info/requires.txt +1 -1
  20. {lightphe-0.0.22 → lightphe-0.0.24}/requirements.txt +1 -1
  21. {lightphe-0.0.22 → lightphe-0.0.24}/setup.py +1 -1
  22. {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_bonehgohnissim.py +71 -0
  23. lightphe-0.0.24/tests/test_key_validator.py +100 -0
  24. {lightphe-0.0.22 → lightphe-0.0.24}/LICENSE +0 -0
  25. {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/commons/__init__.py +0 -0
  26. {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/commons/logger.py +0 -0
  27. {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/commons/phe_utils.py +0 -0
  28. {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/cryptosystems/__init__.py +0 -0
  29. {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/models/Algorithm.py +0 -0
  30. {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/models/Ciphertext.py +0 -0
  31. {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/models/Tensor.py +0 -0
  32. {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/models/__init__.py +0 -0
  33. {lightphe-0.0.22 → lightphe-0.0.24}/lightphe.egg-info/dependency_links.txt +0 -0
  34. {lightphe-0.0.22 → lightphe-0.0.24}/lightphe.egg-info/top_level.txt +0 -0
  35. {lightphe-0.0.22 → lightphe-0.0.24}/setup.cfg +0 -0
  36. {lightphe-0.0.22 → lightphe-0.0.24}/tests/__init__.py +0 -0
  37. {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_benaloh.py +0 -0
  38. {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_ciphertexts.py +0 -0
  39. {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_cloud.py +0 -0
  40. {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_damgard.py +0 -0
  41. {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_elgamal.py +0 -0
  42. {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_ellipticcurveelgamal.py +0 -0
  43. {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_exporting_keys.py +0 -0
  44. {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_goldwasser.py +0 -0
  45. {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_keys.py +0 -0
  46. {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_large_keys.py +0 -0
  47. {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_naccache.py +0 -0
  48. {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_okamoto.py +0 -0
  49. {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_paillier.py +0 -0
  50. {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_rsa.py +0 -0
  51. {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_salary.py +0 -0
  52. {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_sanderyoungyung.py +0 -0
  53. {lightphe-0.0.22 → 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.22
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
@@ -36,7 +36,7 @@ License-File: LICENSE
36
36
 
37
37
  <p align="center"><img src="https://raw.githubusercontent.com/serengil/LightPHE/master/icons/phi.png" width="200" height="240"></p>
38
38
 
39
- LightPHE is a lightweight partially homomorphic encryption library for python. It is a hybrid homomoprhic encryption library wrapping many schemes such as [`RSA`](https://sefiks.com/2023/03/06/a-step-by-step-partially-homomorphic-encryption-example-with-rsa-in-python/), [`ElGamal`](https://sefiks.com/2023/03/27/a-step-by-step-partially-homomorphic-encryption-example-with-elgamal-in-python/), [`Exponential ElGamal`](https://sefiks.com/2023/03/27/a-step-by-step-partially-homomorphic-encryption-example-with-elgamal-in-python/), [`Elliptic Curve ElGamal`](https://sefiks.com/2018/08/21/elliptic-curve-elgamal-encryption/) ([`Weierstrass`](https://sefiks.com/2016/03/13/the-math-behind-elliptic-curve-cryptography/), [`Koblitz`](sefiks.com/2016/03/13/the-math-behind-elliptic-curves-over-binary-field/) and [`Edwards`](https://sefiks.com/2018/12/19/a-gentle-introduction-to-edwards-curves/) forms), [`Paillier`](https://sefiks.com/2023/04/03/a-step-by-step-partially-homomorphic-encryption-example-with-paillier-in-python/), [`Damgard-Jurik`](https://sefiks.com/2023/10/20/a-step-by-step-partially-homomorphic-encryption-example-with-damgard-jurik-in-python/), [`Okamoto–Uchiyama`](https://sefiks.com/2023/10/20/a-step-by-step-partially-homomorphic-encryption-example-with-okamoto-uchiyama-in-python/), [`Benaloh`](https://sefiks.com/2023/10/06/a-step-by-step-partially-homomorphic-encryption-example-with-benaloh-in-python-from-scratch/), [`Naccache–Stern`](https://sefiks.com/2023/10/26/a-step-by-step-partially-homomorphic-encryption-example-with-naccache-stern-in-python/), [`Goldwasser–Micali`](https://sefiks.com/2023/10/27/a-step-by-step-partially-homomorphic-encryption-example-with-goldwasser-micali-in-python/), [`Sander-Young-Yung`](https://sefiks.com/2026/04/02/a-step-by-step-partially-homomorphic-sander-young-yung-example-in-python/), [`Boneh-Goh-Nissim`](https://sefiks.com/2026/04/02/a-step-by-step-somewhat-homomorphic-encryption-example-with-boneh-goh-nissim-in-python/).
39
+ LightPHE is a lightweight homomorphic encryption library for python supporting various partially and somewhat homomorphic encryptions schemes such as [`RSA`](https://sefiks.com/2023/03/06/a-step-by-step-partially-homomorphic-encryption-example-with-rsa-in-python/), [`ElGamal`](https://sefiks.com/2023/03/27/a-step-by-step-partially-homomorphic-encryption-example-with-elgamal-in-python/), [`Exponential ElGamal`](https://sefiks.com/2023/03/27/a-step-by-step-partially-homomorphic-encryption-example-with-elgamal-in-python/), [`Elliptic Curve ElGamal`](https://sefiks.com/2018/08/21/elliptic-curve-elgamal-encryption/) ([`Weierstrass`](https://sefiks.com/2016/03/13/the-math-behind-elliptic-curve-cryptography/), [`Koblitz`](sefiks.com/2016/03/13/the-math-behind-elliptic-curves-over-binary-field/) and [`Edwards`](https://sefiks.com/2018/12/19/a-gentle-introduction-to-edwards-curves/) forms), [`Paillier`](https://sefiks.com/2023/04/03/a-step-by-step-partially-homomorphic-encryption-example-with-paillier-in-python/), [`Damgard-Jurik`](https://sefiks.com/2023/10/20/a-step-by-step-partially-homomorphic-encryption-example-with-damgard-jurik-in-python/), [`Okamoto–Uchiyama`](https://sefiks.com/2023/10/20/a-step-by-step-partially-homomorphic-encryption-example-with-okamoto-uchiyama-in-python/), [`Benaloh`](https://sefiks.com/2023/10/06/a-step-by-step-partially-homomorphic-encryption-example-with-benaloh-in-python-from-scratch/), [`Naccache–Stern`](https://sefiks.com/2023/10/26/a-step-by-step-partially-homomorphic-encryption-example-with-naccache-stern-in-python/), [`Goldwasser–Micali`](https://sefiks.com/2023/10/27/a-step-by-step-partially-homomorphic-encryption-example-with-goldwasser-micali-in-python/), [`Sander-Young-Yung`](https://sefiks.com/2026/04/02/a-step-by-step-partially-homomorphic-sander-young-yung-example-in-python/), [`Boneh-Goh-Nissim`](https://sefiks.com/2026/04/02/a-step-by-step-somewhat-homomorphic-encryption-example-with-boneh-goh-nissim-in-python/).
40
40
 
41
41
  # Partially vs Fully Homomorphic Encryption
42
42
 
@@ -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)
@@ -20,7 +20,7 @@
20
20
 
21
21
  <p align="center"><img src="https://raw.githubusercontent.com/serengil/LightPHE/master/icons/phi.png" width="200" height="240"></p>
22
22
 
23
- LightPHE is a lightweight partially homomorphic encryption library for python. It is a hybrid homomoprhic encryption library wrapping many schemes such as [`RSA`](https://sefiks.com/2023/03/06/a-step-by-step-partially-homomorphic-encryption-example-with-rsa-in-python/), [`ElGamal`](https://sefiks.com/2023/03/27/a-step-by-step-partially-homomorphic-encryption-example-with-elgamal-in-python/), [`Exponential ElGamal`](https://sefiks.com/2023/03/27/a-step-by-step-partially-homomorphic-encryption-example-with-elgamal-in-python/), [`Elliptic Curve ElGamal`](https://sefiks.com/2018/08/21/elliptic-curve-elgamal-encryption/) ([`Weierstrass`](https://sefiks.com/2016/03/13/the-math-behind-elliptic-curve-cryptography/), [`Koblitz`](sefiks.com/2016/03/13/the-math-behind-elliptic-curves-over-binary-field/) and [`Edwards`](https://sefiks.com/2018/12/19/a-gentle-introduction-to-edwards-curves/) forms), [`Paillier`](https://sefiks.com/2023/04/03/a-step-by-step-partially-homomorphic-encryption-example-with-paillier-in-python/), [`Damgard-Jurik`](https://sefiks.com/2023/10/20/a-step-by-step-partially-homomorphic-encryption-example-with-damgard-jurik-in-python/), [`Okamoto–Uchiyama`](https://sefiks.com/2023/10/20/a-step-by-step-partially-homomorphic-encryption-example-with-okamoto-uchiyama-in-python/), [`Benaloh`](https://sefiks.com/2023/10/06/a-step-by-step-partially-homomorphic-encryption-example-with-benaloh-in-python-from-scratch/), [`Naccache–Stern`](https://sefiks.com/2023/10/26/a-step-by-step-partially-homomorphic-encryption-example-with-naccache-stern-in-python/), [`Goldwasser–Micali`](https://sefiks.com/2023/10/27/a-step-by-step-partially-homomorphic-encryption-example-with-goldwasser-micali-in-python/), [`Sander-Young-Yung`](https://sefiks.com/2026/04/02/a-step-by-step-partially-homomorphic-sander-young-yung-example-in-python/), [`Boneh-Goh-Nissim`](https://sefiks.com/2026/04/02/a-step-by-step-somewhat-homomorphic-encryption-example-with-boneh-goh-nissim-in-python/).
23
+ LightPHE is a lightweight homomorphic encryption library for python supporting various partially and somewhat homomorphic encryptions schemes such as [`RSA`](https://sefiks.com/2023/03/06/a-step-by-step-partially-homomorphic-encryption-example-with-rsa-in-python/), [`ElGamal`](https://sefiks.com/2023/03/27/a-step-by-step-partially-homomorphic-encryption-example-with-elgamal-in-python/), [`Exponential ElGamal`](https://sefiks.com/2023/03/27/a-step-by-step-partially-homomorphic-encryption-example-with-elgamal-in-python/), [`Elliptic Curve ElGamal`](https://sefiks.com/2018/08/21/elliptic-curve-elgamal-encryption/) ([`Weierstrass`](https://sefiks.com/2016/03/13/the-math-behind-elliptic-curve-cryptography/), [`Koblitz`](sefiks.com/2016/03/13/the-math-behind-elliptic-curves-over-binary-field/) and [`Edwards`](https://sefiks.com/2018/12/19/a-gentle-introduction-to-edwards-curves/) forms), [`Paillier`](https://sefiks.com/2023/04/03/a-step-by-step-partially-homomorphic-encryption-example-with-paillier-in-python/), [`Damgard-Jurik`](https://sefiks.com/2023/10/20/a-step-by-step-partially-homomorphic-encryption-example-with-damgard-jurik-in-python/), [`Okamoto–Uchiyama`](https://sefiks.com/2023/10/20/a-step-by-step-partially-homomorphic-encryption-example-with-okamoto-uchiyama-in-python/), [`Benaloh`](https://sefiks.com/2023/10/06/a-step-by-step-partially-homomorphic-encryption-example-with-benaloh-in-python-from-scratch/), [`Naccache–Stern`](https://sefiks.com/2023/10/26/a-step-by-step-partially-homomorphic-encryption-example-with-naccache-stern-in-python/), [`Goldwasser–Micali`](https://sefiks.com/2023/10/27/a-step-by-step-partially-homomorphic-encryption-example-with-goldwasser-micali-in-python/), [`Sander-Young-Yung`](https://sefiks.com/2026/04/02/a-step-by-step-partially-homomorphic-sander-young-yung-example-in-python/), [`Boneh-Goh-Nissim`](https://sefiks.com/2026/04/02/a-step-by-step-somewhat-homomorphic-encryption-example-with-boneh-goh-nissim-in-python/).
24
24
 
25
25
  # Partially vs Fully Homomorphic Encryption
26
26
 
@@ -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.22"
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,
@@ -54,7 +61,10 @@ class BonehGohNissim(Homomorphic):
54
61
  },
55
62
  )
56
63
 
57
- self.plaintext_modulo = self.ec.modulo
64
+ if "private_key" in self.keys and "q2" in self.keys["private_key"]:
65
+ self.plaintext_modulo = self.keys["private_key"]["q2"]
66
+ else:
67
+ self.plaintext_modulo = self.keys["public_key"]["curve"]["n"]
58
68
  self.ciphertext_modulo = self.ec.modulo
59
69
 
60
70
  def generate_keys(
@@ -103,22 +113,21 @@ class BonehGohNissim(Homomorphic):
103
113
 
104
114
  G = self.__find_generator(p, max_tries=max_tries)
105
115
 
106
- # order of the curve may not be exactly n, but it should be
107
- self.ec = LightECC(
108
- form_name="weierstrass",
109
- curve_name="custom",
110
- config={"a": a, "b": b, "p": p, "G": G, "n": None},
111
- )
112
-
113
- nG = n * self.ec.G
114
- if (
115
- nG == self.ec.O
116
- ): # check if nG is the point at infinity / identity element / neutral element
117
- logger.debug("nG is the point at infinity, as expected.")
118
- logger.debug(f"q1 = {q1}, q2 = {q2}, n = {n}, p = {p}, l = {l}")
119
- 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
120
127
 
121
- 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
122
131
  else:
123
132
  raise Exception(f"Failed to generate keys after {max_tries} attempts")
124
133
 
@@ -160,14 +169,28 @@ class BonehGohNissim(Homomorphic):
160
169
 
161
170
  raise Exception(f"Failed to find a generator after {max_tries} attempts")
162
171
 
172
+ def _has_private_key(self) -> bool:
173
+ return "private_key" in self.keys and "q2" in self.keys["private_key"]
174
+
163
175
  def generate_random_key(self) -> int:
164
176
  """
165
177
  Boneh-Goh-Nissim requires to generate one-time random key per encryption
166
178
  Returns:
167
179
  random key (int): one time random key for encryption
168
180
  """
169
- q2 = self.keys["private_key"]["q2"]
170
- return random.randint(1, q2 - 1)
181
+ if self._has_private_key():
182
+ q2 = self.keys["private_key"]["q2"]
183
+ return random.randint(1, q2 - 1)
184
+
185
+ n = self.keys["public_key"]["curve"]["n"]
186
+ logger.warn(
187
+ "Private key is not available. Random key will be bounded by n"
188
+ " instead of q2. Encryption will work for small plaintexts,"
189
+ " but negative number encoding via plaintext_modulo will not"
190
+ " round-trip correctly because plaintext_modulo is set to n"
191
+ " (public) instead of q2 (private)."
192
+ )
193
+ return random.randint(1, n - 1)
171
194
 
172
195
  def encrypt(
173
196
  self, plaintext: int, random_key: Optional[int] = None
@@ -200,23 +223,28 @@ class BonehGohNissim(Homomorphic):
200
223
  plaintext (int): restored message
201
224
  """
202
225
  q1 = self.keys["private_key"]["q1"]
226
+ q2 = self.keys["private_key"]["q2"]
203
227
 
204
228
  if isinstance(ciphertext, tuple):
205
- return self._decrypt_gt(ciphertext, q1)
229
+ return self._decrypt_gt(ciphertext, q1, q2)
206
230
 
207
231
  G = self.ec.G
208
232
 
209
233
  mP = ciphertext * q1
210
234
  P = G * q1
211
235
 
212
- for i in range(1, q1 + 1):
236
+ # P has order q2, so DLP result is in [0, q2)
237
+ if mP == self.ec.O:
238
+ return 0
239
+
240
+ for i in range(1, q2):
213
241
  iP = P * i
214
242
  if iP == mP:
215
243
  return i
216
244
 
217
245
  raise Exception("Decryption failed")
218
246
 
219
- def _decrypt_gt(self, ciphertext: tuple, q1: int) -> int:
247
+ def _decrypt_gt(self, ciphertext: tuple, q1: int, q2: int) -> int:
220
248
  """
221
249
  Decrypt a G_T ciphertext (result of homomorphic multiplication).
222
250
 
@@ -241,9 +269,12 @@ class BonehGohNissim(Homomorphic):
241
269
  base = _fp2_pow(e_gg, q1, p)
242
270
  target = _fp2_pow(ciphertext, q1, p)
243
271
 
244
- # brute-force DLP in G_T
272
+ # brute-force DLP in G_T — result is in [0, q2)
273
+ if target == (1, 0):
274
+ return 0
275
+
245
276
  acc = (1, 0)
246
- for i in range(1, q1 + 1):
277
+ for i in range(1, q2):
247
278
  acc = _fp2_mul(acc, base, p)
248
279
  if acc == target:
249
280
  return i
@@ -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.22
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
@@ -36,7 +36,7 @@ License-File: LICENSE
36
36
 
37
37
  <p align="center"><img src="https://raw.githubusercontent.com/serengil/LightPHE/master/icons/phi.png" width="200" height="240"></p>
38
38
 
39
- LightPHE is a lightweight partially homomorphic encryption library for python. It is a hybrid homomoprhic encryption library wrapping many schemes such as [`RSA`](https://sefiks.com/2023/03/06/a-step-by-step-partially-homomorphic-encryption-example-with-rsa-in-python/), [`ElGamal`](https://sefiks.com/2023/03/27/a-step-by-step-partially-homomorphic-encryption-example-with-elgamal-in-python/), [`Exponential ElGamal`](https://sefiks.com/2023/03/27/a-step-by-step-partially-homomorphic-encryption-example-with-elgamal-in-python/), [`Elliptic Curve ElGamal`](https://sefiks.com/2018/08/21/elliptic-curve-elgamal-encryption/) ([`Weierstrass`](https://sefiks.com/2016/03/13/the-math-behind-elliptic-curve-cryptography/), [`Koblitz`](sefiks.com/2016/03/13/the-math-behind-elliptic-curves-over-binary-field/) and [`Edwards`](https://sefiks.com/2018/12/19/a-gentle-introduction-to-edwards-curves/) forms), [`Paillier`](https://sefiks.com/2023/04/03/a-step-by-step-partially-homomorphic-encryption-example-with-paillier-in-python/), [`Damgard-Jurik`](https://sefiks.com/2023/10/20/a-step-by-step-partially-homomorphic-encryption-example-with-damgard-jurik-in-python/), [`Okamoto–Uchiyama`](https://sefiks.com/2023/10/20/a-step-by-step-partially-homomorphic-encryption-example-with-okamoto-uchiyama-in-python/), [`Benaloh`](https://sefiks.com/2023/10/06/a-step-by-step-partially-homomorphic-encryption-example-with-benaloh-in-python-from-scratch/), [`Naccache–Stern`](https://sefiks.com/2023/10/26/a-step-by-step-partially-homomorphic-encryption-example-with-naccache-stern-in-python/), [`Goldwasser–Micali`](https://sefiks.com/2023/10/27/a-step-by-step-partially-homomorphic-encryption-example-with-goldwasser-micali-in-python/), [`Sander-Young-Yung`](https://sefiks.com/2026/04/02/a-step-by-step-partially-homomorphic-sander-young-yung-example-in-python/), [`Boneh-Goh-Nissim`](https://sefiks.com/2026/04/02/a-step-by-step-somewhat-homomorphic-encryption-example-with-boneh-goh-nissim-in-python/).
39
+ LightPHE is a lightweight homomorphic encryption library for python supporting various partially and somewhat homomorphic encryptions schemes such as [`RSA`](https://sefiks.com/2023/03/06/a-step-by-step-partially-homomorphic-encryption-example-with-rsa-in-python/), [`ElGamal`](https://sefiks.com/2023/03/27/a-step-by-step-partially-homomorphic-encryption-example-with-elgamal-in-python/), [`Exponential ElGamal`](https://sefiks.com/2023/03/27/a-step-by-step-partially-homomorphic-encryption-example-with-elgamal-in-python/), [`Elliptic Curve ElGamal`](https://sefiks.com/2018/08/21/elliptic-curve-elgamal-encryption/) ([`Weierstrass`](https://sefiks.com/2016/03/13/the-math-behind-elliptic-curve-cryptography/), [`Koblitz`](sefiks.com/2016/03/13/the-math-behind-elliptic-curves-over-binary-field/) and [`Edwards`](https://sefiks.com/2018/12/19/a-gentle-introduction-to-edwards-curves/) forms), [`Paillier`](https://sefiks.com/2023/04/03/a-step-by-step-partially-homomorphic-encryption-example-with-paillier-in-python/), [`Damgard-Jurik`](https://sefiks.com/2023/10/20/a-step-by-step-partially-homomorphic-encryption-example-with-damgard-jurik-in-python/), [`Okamoto–Uchiyama`](https://sefiks.com/2023/10/20/a-step-by-step-partially-homomorphic-encryption-example-with-okamoto-uchiyama-in-python/), [`Benaloh`](https://sefiks.com/2023/10/06/a-step-by-step-partially-homomorphic-encryption-example-with-benaloh-in-python-from-scratch/), [`Naccache–Stern`](https://sefiks.com/2023/10/26/a-step-by-step-partially-homomorphic-encryption-example-with-naccache-stern-in-python/), [`Goldwasser–Micali`](https://sefiks.com/2023/10/27/a-step-by-step-partially-homomorphic-encryption-example-with-goldwasser-micali-in-python/), [`Sander-Young-Yung`](https://sefiks.com/2026/04/02/a-step-by-step-partially-homomorphic-sander-young-yung-example-in-python/), [`Boneh-Goh-Nissim`](https://sefiks.com/2026/04/02/a-step-by-step-somewhat-homomorphic-encryption-example-with-boneh-goh-nissim-in-python/).
40
40
 
41
41
  # Partially vs Fully Homomorphic Encryption
42
42
 
@@ -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.22",
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",
@@ -108,6 +108,77 @@ def test_linear_regression():
108
108
  )
109
109
 
110
110
 
111
+ def test_cosine_similarity():
112
+ tic = time.time()
113
+ i1 = 5
114
+ i2 = 2
115
+ i3 = 8
116
+
117
+ # v2 = [1, 1, 2]
118
+ j1 = 1
119
+ j2 = 1
120
+ j3 = 2
121
+
122
+ i1_enc = cs.encrypt(i1)
123
+ i2_enc = cs.encrypt(i2)
124
+ i3_enc = cs.encrypt(i3)
125
+
126
+ j1_enc = cs.encrypt(j1)
127
+ j2_enc = cs.encrypt(j2)
128
+ j3_enc = cs.encrypt(j3)
129
+
130
+ # homomorphic operation
131
+ cosine_sim_enc = i1_enc * j1_enc + i2_enc * j2_enc + i3_enc * j3_enc
132
+
133
+ # proof of work
134
+ assert cs.decrypt(cosine_sim_enc) == i1 * j1 + i2 * j2 + i3 * j3
135
+
136
+ logger.info(
137
+ f"✅ Boneh-God-Nissim cosine similarity test succeeded "
138
+ f"({security_level} bit ECC security level)"
139
+ f" in {time.time() - tic:.2f} seconds"
140
+ )
141
+
142
+
143
+ def test_euclidean_distance():
144
+ tic = time.time()
145
+
146
+ # v1 = [5, 2, 3]
147
+ i1 = 5
148
+ i2 = 2
149
+ i3 = 8
150
+
151
+ # v2 = [1, 1, 2]
152
+ j1 = 1
153
+ j2 = 1
154
+ j3 = 2
155
+
156
+ i1_enc = cs.encrypt(i1)
157
+ i2_enc = cs.encrypt(i2)
158
+ i3_enc = cs.encrypt(i3)
159
+
160
+ j1_enc = cs.encrypt(-j1)
161
+ j2_enc = cs.encrypt(-j2)
162
+ j3_enc = cs.encrypt(-j3)
163
+
164
+ euclidean_sqrt_enc = (
165
+ (i1_enc + j1_enc) * (i1_enc + j1_enc)
166
+ + (i2_enc + j2_enc) * (i2_enc + j2_enc)
167
+ + (i3_enc + j3_enc) * (i3_enc + j3_enc)
168
+ )
169
+
170
+ assert (
171
+ cs.decrypt(euclidean_sqrt_enc)
172
+ == (i1 - j1) ** 2 + (i2 - j2) ** 2 + (i3 - j3) ** 2
173
+ )
174
+
175
+ logger.info(
176
+ f"✅ Boneh-God-Nissim euclidean distance test succeeded "
177
+ f"({security_level} bit ECC security level)"
178
+ f" in {time.time() - tic:.2f} seconds"
179
+ )
180
+
181
+
111
182
  def test_api_with_predefined_keys():
112
183
  keys = {
113
184
  "private_key": {"q1": 1260048562698661, "q2": 1495813357973021},
@@ -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