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.
- {lightphe-0.0.22 → lightphe-0.0.24}/PKG-INFO +3 -3
- {lightphe-0.0.22 → lightphe-0.0.24}/README.md +2 -2
- {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/__init__.py +5 -1
- lightphe-0.0.24/lightphe/commons/key_validator.py +97 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/cryptosystems/Benaloh.py +5 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/cryptosystems/BonehGohNissim.py +54 -23
- {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/cryptosystems/DamgardJurik.py +5 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/cryptosystems/ElGamal.py +5 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/cryptosystems/EllipticCurveElGamal.py +5 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/cryptosystems/GoldwasserMicali.py +5 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/cryptosystems/NaccacheStern.py +5 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/cryptosystems/OkamotoUchiyama.py +5 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/cryptosystems/Paillier.py +5 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/cryptosystems/RSA.py +5 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/cryptosystems/SanderYoungYung.py +5 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/models/Homomorphic.py +7 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/lightphe.egg-info/PKG-INFO +3 -3
- {lightphe-0.0.22 → lightphe-0.0.24}/lightphe.egg-info/SOURCES.txt +2 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/lightphe.egg-info/requires.txt +1 -1
- {lightphe-0.0.22 → lightphe-0.0.24}/requirements.txt +1 -1
- {lightphe-0.0.22 → lightphe-0.0.24}/setup.py +1 -1
- {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_bonehgohnissim.py +71 -0
- lightphe-0.0.24/tests/test_key_validator.py +100 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/LICENSE +0 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/commons/__init__.py +0 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/commons/logger.py +0 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/commons/phe_utils.py +0 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/cryptosystems/__init__.py +0 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/models/Algorithm.py +0 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/models/Ciphertext.py +0 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/models/Tensor.py +0 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/lightphe/models/__init__.py +0 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/lightphe.egg-info/dependency_links.txt +0 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/lightphe.egg-info/top_level.txt +0 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/setup.cfg +0 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/tests/__init__.py +0 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_benaloh.py +0 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_ciphertexts.py +0 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_cloud.py +0 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_damgard.py +0 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_elgamal.py +0 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_ellipticcurveelgamal.py +0 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_exporting_keys.py +0 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_goldwasser.py +0 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_keys.py +0 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_large_keys.py +0 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_naccache.py +0 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_okamoto.py +0 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_paillier.py +0 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_rsa.py +0 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_salary.py +0 -0
- {lightphe-0.0.22 → lightphe-0.0.24}/tests/test_sanderyoungyung.py +0 -0
- {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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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(
|
|
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
|
-
|
|
170
|
-
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
@@ -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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|