lightphe 0.0.26__tar.gz → 0.0.27__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.26 → lightphe-0.0.27}/PKG-INFO +250 -20
- {lightphe-0.0.26 → lightphe-0.0.27}/README.md +249 -19
- lightphe-0.0.27/lightphe/__init__.py +1029 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe/commons/key_validator.py +27 -1
- lightphe-0.0.27/lightphe/commons/safe_primes.py +30 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe/cryptosystems/Benaloh.py +12 -4
- lightphe-0.0.27/lightphe/cryptosystems/BrakerskiVaikuntanathan.py +398 -0
- lightphe-0.0.27/lightphe/cryptosystems/BressonCatalanoPointcheval.py +295 -0
- lightphe-0.0.27/lightphe/cryptosystems/CamenischShoup.py +602 -0
- lightphe-0.0.27/lightphe/cryptosystems/DamgardGeislerKroigaard.py +801 -0
- lightphe-0.0.27/lightphe/cryptosystems/EllipticCurvePaillier.py +324 -0
- lightphe-0.0.27/lightphe/cryptosystems/FreemanBonehGohNissim.py +451 -0
- lightphe-0.0.27/lightphe/cryptosystems/GentryHaleviVaikuntanathan.py +391 -0
- lightphe-0.0.27/lightphe/cryptosystems/Regev.py +354 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe/models/Algorithm.py +8 -0
- lightphe-0.0.27/lightphe/models/CatalanoFiore.py +293 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe/models/Ciphertext.py +41 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe/models/Homomorphic.py +88 -1
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe.egg-info/PKG-INFO +250 -20
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe.egg-info/SOURCES.txt +19 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/setup.py +1 -1
- lightphe-0.0.27/tests/test_brakerskivaikuntanathan.py +234 -0
- lightphe-0.0.27/tests/test_bressoncatalanopointcheval.py +255 -0
- lightphe-0.0.27/tests/test_camenischshoup.py +317 -0
- lightphe-0.0.27/tests/test_catalanofiore.py +222 -0
- lightphe-0.0.27/tests/test_damgardgeislerkroigaard.py +568 -0
- lightphe-0.0.27/tests/test_ellipticcurvepaillier.py +169 -0
- lightphe-0.0.27/tests/test_freemanbonehgohnissim.py +187 -0
- lightphe-0.0.27/tests/test_gentryhalevivaikuntanathan.py +189 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/tests/test_ishaipaskin.py +61 -3
- {lightphe-0.0.26 → lightphe-0.0.27}/tests/test_key_validator.py +26 -1
- lightphe-0.0.27/tests/test_regev.py +204 -0
- lightphe-0.0.26/lightphe/__init__.py +0 -565
- {lightphe-0.0.26 → lightphe-0.0.27}/LICENSE +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe/commons/__init__.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe/commons/logger.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe/commons/phe_utils.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe/cryptosystems/BonehGohNissim.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe/cryptosystems/CastagnosLaguillaumie.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe/cryptosystems/DamgardJurik.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe/cryptosystems/ElGamal.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe/cryptosystems/EllipticCurveElGamal.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe/cryptosystems/GoldwasserMicali.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe/cryptosystems/IshaiPaskin.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe/cryptosystems/JoyeLibert.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe/cryptosystems/NaccacheStern.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe/cryptosystems/OkamotoUchiyama.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe/cryptosystems/Paillier.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe/cryptosystems/RSA.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe/cryptosystems/SanderYoungYung.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe/cryptosystems/__init__.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe/models/Tensor.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe/models/__init__.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe.egg-info/dependency_links.txt +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe.egg-info/requires.txt +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/lightphe.egg-info/top_level.txt +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/requirements.txt +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/setup.cfg +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/tests/__init__.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/tests/test_benaloh.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/tests/test_bonehgohnissim.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/tests/test_castagnoslaguillaumie.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/tests/test_ciphertexts.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/tests/test_cloud.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/tests/test_damgard.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/tests/test_elgamal.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/tests/test_ellipticcurveelgamal.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/tests/test_exporting_keys.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/tests/test_goldwasser.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/tests/test_joyelibert.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/tests/test_keys.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/tests/test_large_keys.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/tests/test_naccache.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/tests/test_okamoto.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/tests/test_paillier.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/tests/test_rsa.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/tests/test_salary.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/tests/test_sanderyoungyung.py +0 -0
- {lightphe-0.0.26 → lightphe-0.0.27}/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.27
|
|
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
|
|
@@ -27,7 +27,7 @@ License-File: LICENSE
|
|
|
27
27
|
|
|
28
28
|
<p align="center"><img src="https://raw.githubusercontent.com/serengil/LightPHE/master/icons/phi.png" width="200" height="240"></p>
|
|
29
29
|
|
|
30
|
-
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/), `Joye-Libert`, `Castagnos-Laguillaumie`, `Ishai-Paskin`.
|
|
30
|
+
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/), `Joye-Libert`, `Castagnos-Laguillaumie`, `Ishai-Paskin`, `Damgard-Geisler-Kroigaard`, `Bresson-Catalano-Pointcheval`, `Camenisch-Shoup`, `Elliptic Curve Paillier`, `Regev`, `Brakerski-Vaikuntanathan`, `Freeman-Boneh-Goh-Nissim`, `Gentry-Halevi-Vaikuntanathan`.
|
|
31
31
|
|
|
32
32
|
# Partially vs Fully Homomorphic Encryption
|
|
33
33
|
|
|
@@ -58,23 +58,33 @@ from lightphe import LightPHE
|
|
|
58
58
|
|
|
59
59
|
In summary, LightPHE is covering following algorithms and these are partially homomorphic and somewhat homomorphic with respect to the operations mentioned in the following table.
|
|
60
60
|
|
|
61
|
-
| Algorithm | Multiplicatively<br>Homomorphic | Additively<br>Homomorphic | Scalar Multiplication | Bitwise-XOR Homomorphic | Bitwise-AND Homomorphic | Regeneration<br>of Ciphertext | Decision Tree<br>Evaluation |
|
|
62
|
-
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
63
|
-
| RSA | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
64
|
-
| ElGamal | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
|
|
65
|
-
| Exponential ElGamal | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ |
|
|
66
|
-
| Elliptic Curve ElGamal | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ |
|
|
67
|
-
| Paillier | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ |
|
|
68
|
-
|
|
|
69
|
-
|
|
|
70
|
-
|
|
|
71
|
-
|
|
|
72
|
-
|
|
|
73
|
-
|
|
|
74
|
-
|
|
|
75
|
-
|
|
|
76
|
-
|
|
|
77
|
-
|
|
|
61
|
+
| Algorithm | Multiplicatively<br>Homomorphic | Additively<br>Homomorphic | Scalar Multiplication | Bitwise-XOR Homomorphic | Bitwise-AND Homomorphic | Regeneration<br>of Ciphertext | Decision Tree<br>Evaluation | Secure<br>Comparison | Encrypted<br>Argmax |
|
|
62
|
+
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
|
63
|
+
| RSA | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
64
|
+
| ElGamal | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
65
|
+
| Exponential ElGamal | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
66
|
+
| Elliptic Curve ElGamal | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
67
|
+
| Paillier | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
68
|
+
| Elliptic Curve Paillier | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
69
|
+
| Damgard-Jurik | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
70
|
+
| Benaloh | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
71
|
+
| Naccache-Stern | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
72
|
+
| Okamoto-Uchiyama | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
73
|
+
| Joye-Libert | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
74
|
+
| Castagnos-Laguillaumie | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
75
|
+
| Damgard-Geisler-Kroigaard | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ |
|
|
76
|
+
| Bresson-Catalano-Pointcheval | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
77
|
+
| Camenisch-Shoup | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
78
|
+
| Ishai-Paskin | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ |
|
|
79
|
+
| Regev | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
80
|
+
| Goldwasser-Micali | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
81
|
+
| Sander-Young-Yung | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
|
82
|
+
| Boneh-Goh-Nissim | 1️⃣ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
83
|
+
| Freeman-Boneh-Goh-Nissim | 1️⃣ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
84
|
+
| Brakerski-Vaikuntanathan | 1️⃣ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
85
|
+
| Gentry-Halevi-Vaikuntanathan | 1️⃣ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
86
|
+
|
|
87
|
+
1️⃣ Boneh-Goh-Nissim, Freeman-Boneh-Goh-Nissim, Brakerski-Vaikuntanathan and Gentry-Halevi-Vaikuntanathan support one multiplication natively. Most additively homomorphic algorithms can support one multiplication with [Catalano-Fiore transform](#adding-multiplication-support-to-additive-algorithms) as well.
|
|
78
88
|
|
|
79
89
|
# Building cryptosystem
|
|
80
90
|
|
|
@@ -97,6 +107,14 @@ algorithms = [
|
|
|
97
107
|
"Joye-Libert",
|
|
98
108
|
"Castagnos-Laguillaumie",
|
|
99
109
|
"Ishai-Paskin",
|
|
110
|
+
"Damgard-Geisler-Kroigaard",
|
|
111
|
+
"Bresson-Catalano-Pointcheval",
|
|
112
|
+
"Camenisch-Shoup",
|
|
113
|
+
"EllipticCurve-Paillier",
|
|
114
|
+
"Regev",
|
|
115
|
+
"Brakerski-Vaikuntanathan",
|
|
116
|
+
"Freeman-Boneh-Goh-Nissim",
|
|
117
|
+
"Gentry-Halevi-Vaikuntanathan",
|
|
100
118
|
]
|
|
101
119
|
|
|
102
120
|
cs = LightPHE(algorithm_name = algorithms[0])
|
|
@@ -185,7 +203,11 @@ See [`curves`](https://github.com/serengil/LightECC#supported-curves) page for a
|
|
|
185
203
|
|
|
186
204
|
### Decision Trees
|
|
187
205
|
|
|
188
|
-
|
|
206
|
+
LightPHE can evaluate decision trees privately. Suppose a bank has a secret decision tree that decides a customer's credit limit, and a customer has private data. The bank does not want to reveal its tree, and the customer does not want to reveal their data. The customer sends encrypted data, the bank runs its tree on it without seeing it, and only the customer can decrypt the result. The customer learns the credit limit but not the rest of the tree. Two cryptosystems support this: Ishai-Paskin for trees on yes / no answers, and Damgard-Geisler-Kroigaard for trees on numeric features.
|
|
207
|
+
|
|
208
|
+
#### Decision Trees with Nominal Decisions
|
|
209
|
+
|
|
210
|
+
Ishai-Paskin evaluates trees whose questions have yes / no answers, in a single round: the customer sends encrypted answers, and the bank sends back the encrypted result.
|
|
189
211
|
|
|
190
212
|
Let the bank's tree be the following, where questions are numbered 0, 1 and 2 and leaves are credit limits in thousand dollars.
|
|
191
213
|
|
|
@@ -235,6 +257,214 @@ assert customer_cs.decrypt(encrypted_limit) == 5
|
|
|
235
257
|
|
|
236
258
|
The size of the result grows with the depth of the tree, not with the number of its nodes. Still, each extra level makes the computation noticeably slower, so this is practical for shallow trees.
|
|
237
259
|
|
|
260
|
+
#### Decision Trees with Numeric Decisions
|
|
261
|
+
|
|
262
|
+
Damgard-Geisler-Kroigaard evaluates decision trees with numeric thresholds instead of yes / no answers, using the secure comparison protocol below at each node. It requires a few rounds of interaction between the customer and the bank, but its cost grows linearly with the number of nodes.
|
|
263
|
+
|
|
264
|
+
Let the bank's tree be the following, where features are numbered 0 (age), 1 (income in thousand dollars) and 2 (number of open loans), and leaves are credit limits in thousand dollars.
|
|
265
|
+
|
|
266
|
+
```
|
|
267
|
+
F1: income <= 40?
|
|
268
|
+
/yes \no
|
|
269
|
+
F0: age <= 25? F2: open loans <= 2?
|
|
270
|
+
/yes \no /yes \no
|
|
271
|
+
0 5 20 10
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Each decision node is written as a tuple of `(feature index, threshold, what to do if feature <= threshold, what to do otherwise)`, and leaves are plain integers. So, the tree above becomes:
|
|
275
|
+
|
|
276
|
+
```python
|
|
277
|
+
credit_limit_tree = (1, 40, (0, 25, 0, 5), (2, 2, 20, 10))
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Then, the customer and the bank run the following steps.
|
|
281
|
+
|
|
282
|
+
```python
|
|
283
|
+
# customer: age = 30, income = 75 and open loans = 1
|
|
284
|
+
# so, the expected path is income > 40 -> open loans <= 2 -> credit limit of 20
|
|
285
|
+
customer_cs = LightPHE(algorithm_name = "Damgard-Geisler-Kroigaard")
|
|
286
|
+
encrypted_features = customer_cs.encrypt_decision_tree_input(
|
|
287
|
+
features = [30, 75, 1], bit_length = 8
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
# bank, having the customer's public key only
|
|
291
|
+
bank_cs = LightPHE(algorithm_name = "Damgard-Geisler-Kroigaard", key_file = "public.txt")
|
|
292
|
+
encrypted_comparisons, bank_state = bank_cs.evaluate_decision_tree_comparisons(
|
|
293
|
+
tree = credit_limit_tree, encrypted_features = encrypted_features
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
# customer answers randomly flipped comparisons, so cannot follow the path in the tree
|
|
297
|
+
encrypted_answers = customer_cs.answer_decision_tree_comparisons(encrypted_comparisons)
|
|
298
|
+
|
|
299
|
+
# bank keeps bank_state secret and finds the reached leaf in encrypted form
|
|
300
|
+
encrypted_leaves = bank_cs.evaluate_decision_tree_leaves(
|
|
301
|
+
server_state = bank_state, encrypted_answers = encrypted_answers
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
# customer learns only the credit limit
|
|
305
|
+
assert customer_cs.decrypt_decision_tree_output(encrypted_leaves) == 20
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Secure Comparison
|
|
309
|
+
|
|
310
|
+
Damgard-Geisler-Kroigaard lets a client and a server find out whether the client's private value is less than or equal to the server's private value, without revealing the values to each other. For instance, a bidder can check whether their bid beats the reserve price of an auction.
|
|
311
|
+
|
|
312
|
+
```python
|
|
313
|
+
# client encrypts its value bit by bit
|
|
314
|
+
client_cs = LightPHE(algorithm_name = "Damgard-Geisler-Kroigaard")
|
|
315
|
+
encrypted_bid = client_cs.encrypt_comparison_input(value = 120, bit_length = 16)
|
|
316
|
+
|
|
317
|
+
# server compares it with its own value having the client's public key only
|
|
318
|
+
server_cs = LightPHE(algorithm_name = "Damgard-Geisler-Kroigaard", key_file = "public.txt")
|
|
319
|
+
encrypted_result = server_cs.evaluate_comparison(encrypted_input = encrypted_bid, value = 150)
|
|
320
|
+
|
|
321
|
+
# client learns only whether bid <= reserve price
|
|
322
|
+
assert client_cs.decrypt_comparison(encrypted_result) is True
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Encrypted Argmax
|
|
326
|
+
|
|
327
|
+
Damgard-Geisler-Kroigaard can also find the index of the maximum among encrypted values. For instance, a server can compute encrypted class scores of a linear classifier on a client's encrypted features, and then find the predicted class. The client learns only the index, neither the scores nor the intermediate comparisons, and the server learns nothing. The protocol needs a few round trips per value, so the server talks to the client through a callable. Locally, it can be the client's `answer_argmax` directly. Otherwise, wrap your network call with it.
|
|
328
|
+
|
|
329
|
+
```python
|
|
330
|
+
# client encrypts its features
|
|
331
|
+
client_cs = LightPHE(algorithm_name = "Damgard-Geisler-Kroigaard")
|
|
332
|
+
encrypted_features = [client_cs.encrypt(feature) for feature in [3, 1, 4]]
|
|
333
|
+
|
|
334
|
+
# server computes class scores with its private weights homomorphically
|
|
335
|
+
server_cs = LightPHE(algorithm_name = "Damgard-Geisler-Kroigaard", key_file = "public.txt")
|
|
336
|
+
weights = [[1, 2, 0], [0, 1, 3], [2, 0, 1]]
|
|
337
|
+
encrypted_scores = []
|
|
338
|
+
for class_weights in weights:
|
|
339
|
+
score = encrypted_features[0] * class_weights[0]
|
|
340
|
+
for encrypted_feature, weight in zip(encrypted_features[1:], class_weights[1:]):
|
|
341
|
+
score = score + encrypted_feature * weight
|
|
342
|
+
encrypted_scores.append(score)
|
|
343
|
+
|
|
344
|
+
# server finds the argmax with the client's help - scores must fit into bit_length bits
|
|
345
|
+
encrypted_class = server_cs.evaluate_argmax(
|
|
346
|
+
encrypted_values = encrypted_scores, bit_length = 8, client = client_cs.answer_argmax
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
# client learns only the predicted class
|
|
350
|
+
assert client_cs.decrypt(encrypted_class) == 1
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Master Key Decryption
|
|
354
|
+
|
|
355
|
+
Bresson-Catalano-Pointcheval has two ways to decrypt a ciphertext. Each user decrypts with its own private key, while an authority holding the master key decrypts ciphertexts of all users. For instance, a company can let every employee encrypt with their own key, and still recover the data if an employee leaves. Keep in mind that the master key holder can read everything by design.
|
|
356
|
+
|
|
357
|
+
```python
|
|
358
|
+
# authority generates the master key and publishes its public key
|
|
359
|
+
authority_cs = LightPHE(algorithm_name = "Bresson-Catalano-Pointcheval")
|
|
360
|
+
authority_cs.export_keys(target_file = "public.txt", public = True)
|
|
361
|
+
|
|
362
|
+
# each user generates its own key pair with the public key of the authority
|
|
363
|
+
public_cs = LightPHE(algorithm_name = "Bresson-Catalano-Pointcheval", key_file = "public.txt")
|
|
364
|
+
alice_cs = LightPHE(algorithm_name = "Bresson-Catalano-Pointcheval", keys = public_cs.generate_user_keys())
|
|
365
|
+
bob_cs = LightPHE(algorithm_name = "Bresson-Catalano-Pointcheval", keys = public_cs.generate_user_keys())
|
|
366
|
+
|
|
367
|
+
alice_ciphertext = alice_cs.encrypt(100) + alice_cs.encrypt(50)
|
|
368
|
+
bob_ciphertext = bob_cs.encrypt(200)
|
|
369
|
+
|
|
370
|
+
# users decrypt their own ciphertexts only
|
|
371
|
+
assert alice_cs.decrypt(alice_ciphertext) == 150
|
|
372
|
+
assert alice_cs.decrypt(bob_ciphertext) != 200
|
|
373
|
+
|
|
374
|
+
# authority decrypts ciphertexts of all users
|
|
375
|
+
assert authority_cs.decrypt_with_master_key(alice_ciphertext) == 150
|
|
376
|
+
assert authority_cs.decrypt_with_master_key(bob_ciphertext) == 200
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Verifiable Encryption
|
|
380
|
+
|
|
381
|
+
Camenisch-Shoup lets you encrypt a secret for someone else and prove that the ciphertext really contains it, without revealing the secret. For instance, Alice can escrow the private key of her crypto wallet with a trusted third party. A bank, which knows only her public key, verifies that the escrowed ciphertext contains its private key, and the trusted third party can recover it only under the conditions written in the label.
|
|
382
|
+
|
|
383
|
+
```python
|
|
384
|
+
from lightecc import LightECC
|
|
385
|
+
|
|
386
|
+
# trusted third party builds the cryptosystem and publishes its public key
|
|
387
|
+
ttp_cs = LightPHE(algorithm_name = "Camenisch-Shoup")
|
|
388
|
+
ttp_cs.export_keys(target_file = "public.txt", public = True)
|
|
389
|
+
|
|
390
|
+
# alice's wallet key pair on secp256k1
|
|
391
|
+
ecc = LightECC(curve_name = "secp256k1")
|
|
392
|
+
private_key = 123456789
|
|
393
|
+
public_key = ecc.G * private_key
|
|
394
|
+
|
|
395
|
+
# alice encrypts her private key for the trusted third party with a proof
|
|
396
|
+
label = "release only if alice loses access to her wallet"
|
|
397
|
+
alice_cs = LightPHE(algorithm_name = "Camenisch-Shoup", key_file = "public.txt")
|
|
398
|
+
escrow = alice_cs.encrypt_verifiably(secret = private_key, label = label)
|
|
399
|
+
|
|
400
|
+
# bank verifies it with public keys only, without learning alice's private key
|
|
401
|
+
bank_cs = LightPHE(algorithm_name = "Camenisch-Shoup", key_file = "public.txt")
|
|
402
|
+
assert bank_cs.verify_encryption(escrow, public_point = public_key, label = label) is True
|
|
403
|
+
|
|
404
|
+
# trusted third party recovers the private key under the same label only
|
|
405
|
+
assert ttp_cs.decrypt_verifiable_ciphertext(escrow, label = label) == private_key
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Private keys of other curves can be escrowed by passing `form` and `curve` arguments while building the cryptosystem. Verifiable ciphertexts reject any modification, so homomorphic operations are applied to regular ciphertexts created with `encrypt`.
|
|
409
|
+
|
|
410
|
+
### Adding Multiplication Support to Additive Algorithms
|
|
411
|
+
|
|
412
|
+
Additively homomorphic algorithms cannot multiply two ciphertexts. Boosting them with Catalano-Fiore transform adds one multiplication on top of them, without changing the algorithms themselves. This lets you evaluate degree-2 functions on encrypted data, such as dot products of two encrypted vectors, variances or squared distances. It supports Paillier, Damgard-Jurik, Bresson-Catalano-Pointcheval, Camenisch-Shoup, Elliptic Curve Paillier, Joye-Libert, Castagnos-Laguillaumie, Naccache-Stern, Benaloh and Damgard-Geisler-Kroigaard.
|
|
413
|
+
|
|
414
|
+
```python
|
|
415
|
+
# client builds a boosted cryptosystem, and encrypts its private vectors
|
|
416
|
+
client_cs = LightPHE(algorithm_name = "Paillier", boost = "Catalano-Fiore")
|
|
417
|
+
|
|
418
|
+
x = [3, 1, 4]
|
|
419
|
+
y = [1, 5, 9]
|
|
420
|
+
encrypted_x = [client_cs.encrypt(value) for value in x]
|
|
421
|
+
encrypted_y = [client_cs.encrypt(value) for value in y]
|
|
422
|
+
|
|
423
|
+
# server computes dot product of encrypted vectors - ciphertexts carry the public key only
|
|
424
|
+
encrypted_dot = sum(ex * ey for ex, ey in zip(encrypted_x, encrypted_y))
|
|
425
|
+
|
|
426
|
+
# squared euclidean distance is also a degree-2 function
|
|
427
|
+
encrypted_distance = sum((ex - ey) * (ex - ey) for ex, ey in zip(encrypted_x, encrypted_y))
|
|
428
|
+
|
|
429
|
+
# client decrypts the results
|
|
430
|
+
assert client_cs.decrypt(encrypted_dot) == 44
|
|
431
|
+
assert client_cs.decrypt(encrypted_distance) == 45
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
Multiplied ciphertexts can be added and multiplied with constants, but not multiplied with ciphertexts again. They keep a pair of ciphertexts for every product, so a dot product of length d grows d times. Only integers are supported.
|
|
435
|
+
|
|
436
|
+
### Post-Quantum Security
|
|
437
|
+
|
|
438
|
+
Most algorithms in LightPHE rely on factoring or discrete logarithm problems, which a large enough quantum computer could solve with Shor's algorithm. Regev and Brakerski-Vaikuntanathan rely on learning with errors problems instead, which are believed to be hard for quantum computers too. Their default key sizes give about 128-bit security against quantum attacks.
|
|
439
|
+
|
|
440
|
+
Regev is additively homomorphic. Its key size is the lattice dimension, and its default is 2048.
|
|
441
|
+
|
|
442
|
+
```python
|
|
443
|
+
cs = LightPHE(algorithm_name = "Regev")
|
|
444
|
+
|
|
445
|
+
c1 = cs.encrypt(1500)
|
|
446
|
+
c2 = cs.encrypt(2500)
|
|
447
|
+
|
|
448
|
+
assert cs.decrypt(c1 + c2) == 4000
|
|
449
|
+
assert cs.decrypt(c1 * 3) == 4500
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
Brakerski-Vaikuntanathan additionally supports one multiplication of ciphertexts like Boneh-Goh-Nissim, but without pairings. Its key size is the degree of its polynomial ring, and its default is 4096.
|
|
453
|
+
|
|
454
|
+
```python
|
|
455
|
+
cs = LightPHE(algorithm_name = "Brakerski-Vaikuntanathan")
|
|
456
|
+
|
|
457
|
+
c1 = cs.encrypt(15)
|
|
458
|
+
c2 = cs.encrypt(25)
|
|
459
|
+
c3 = cs.encrypt(100)
|
|
460
|
+
|
|
461
|
+
# one multiplication, then additions and multiplications with constants
|
|
462
|
+
assert cs.decrypt(c1 * c2 + c3) == 475
|
|
463
|
+
assert cs.decrypt((c1 * c2) * 2 + c1 * c3) == 2250
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
Every ciphertext of these algorithms carries a small noise, which grows with each homomorphic operation, and decryption silently fails once it grows too much. With default settings, plaintexts must be less than 2^24. Regev has room for thousands of additions or multiplications with constants up to a few thousands, and a product of Brakerski-Vaikuntanathan has room for millions of times more noise. You can increase the plaintext space with `plaintext_limit` argument in exchange of less room for noise. Floats and tensors are not supported.
|
|
467
|
+
|
|
238
468
|
### Vector Embeddings
|
|
239
469
|
|
|
240
470
|
LightPHE supports homomorphic encryption on vector embeddings. This is useful in privacy-preserving machine learning, secure aggregation, and confidential data processing.
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
<p align="center"><img src="https://raw.githubusercontent.com/serengil/LightPHE/master/icons/phi.png" width="200" height="240"></p>
|
|
13
13
|
|
|
14
|
-
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/), `Joye-Libert`, `Castagnos-Laguillaumie`, `Ishai-Paskin`.
|
|
14
|
+
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/), `Joye-Libert`, `Castagnos-Laguillaumie`, `Ishai-Paskin`, `Damgard-Geisler-Kroigaard`, `Bresson-Catalano-Pointcheval`, `Camenisch-Shoup`, `Elliptic Curve Paillier`, `Regev`, `Brakerski-Vaikuntanathan`, `Freeman-Boneh-Goh-Nissim`, `Gentry-Halevi-Vaikuntanathan`.
|
|
15
15
|
|
|
16
16
|
# Partially vs Fully Homomorphic Encryption
|
|
17
17
|
|
|
@@ -42,23 +42,33 @@ from lightphe import LightPHE
|
|
|
42
42
|
|
|
43
43
|
In summary, LightPHE is covering following algorithms and these are partially homomorphic and somewhat homomorphic with respect to the operations mentioned in the following table.
|
|
44
44
|
|
|
45
|
-
| Algorithm | Multiplicatively<br>Homomorphic | Additively<br>Homomorphic | Scalar Multiplication | Bitwise-XOR Homomorphic | Bitwise-AND Homomorphic | Regeneration<br>of Ciphertext | Decision Tree<br>Evaluation |
|
|
46
|
-
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
47
|
-
| RSA | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
48
|
-
| ElGamal | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
|
|
49
|
-
| Exponential ElGamal | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ |
|
|
50
|
-
| Elliptic Curve ElGamal | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ |
|
|
51
|
-
| Paillier | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ |
|
|
52
|
-
|
|
|
53
|
-
|
|
|
54
|
-
|
|
|
55
|
-
|
|
|
56
|
-
|
|
|
57
|
-
|
|
|
58
|
-
|
|
|
59
|
-
|
|
|
60
|
-
|
|
|
61
|
-
|
|
|
45
|
+
| Algorithm | Multiplicatively<br>Homomorphic | Additively<br>Homomorphic | Scalar Multiplication | Bitwise-XOR Homomorphic | Bitwise-AND Homomorphic | Regeneration<br>of Ciphertext | Decision Tree<br>Evaluation | Secure<br>Comparison | Encrypted<br>Argmax |
|
|
46
|
+
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
|
47
|
+
| RSA | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
48
|
+
| ElGamal | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
49
|
+
| Exponential ElGamal | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
50
|
+
| Elliptic Curve ElGamal | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
51
|
+
| Paillier | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
52
|
+
| Elliptic Curve Paillier | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
53
|
+
| Damgard-Jurik | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
54
|
+
| Benaloh | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
55
|
+
| Naccache-Stern | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
56
|
+
| Okamoto-Uchiyama | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
57
|
+
| Joye-Libert | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
58
|
+
| Castagnos-Laguillaumie | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
59
|
+
| Damgard-Geisler-Kroigaard | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ |
|
|
60
|
+
| Bresson-Catalano-Pointcheval | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
61
|
+
| Camenisch-Shoup | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
62
|
+
| Ishai-Paskin | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ |
|
|
63
|
+
| Regev | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
64
|
+
| Goldwasser-Micali | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
65
|
+
| Sander-Young-Yung | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
|
66
|
+
| Boneh-Goh-Nissim | 1️⃣ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
67
|
+
| Freeman-Boneh-Goh-Nissim | 1️⃣ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
68
|
+
| Brakerski-Vaikuntanathan | 1️⃣ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
69
|
+
| Gentry-Halevi-Vaikuntanathan | 1️⃣ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
70
|
+
|
|
71
|
+
1️⃣ Boneh-Goh-Nissim, Freeman-Boneh-Goh-Nissim, Brakerski-Vaikuntanathan and Gentry-Halevi-Vaikuntanathan support one multiplication natively. Most additively homomorphic algorithms can support one multiplication with [Catalano-Fiore transform](#adding-multiplication-support-to-additive-algorithms) as well.
|
|
62
72
|
|
|
63
73
|
# Building cryptosystem
|
|
64
74
|
|
|
@@ -81,6 +91,14 @@ algorithms = [
|
|
|
81
91
|
"Joye-Libert",
|
|
82
92
|
"Castagnos-Laguillaumie",
|
|
83
93
|
"Ishai-Paskin",
|
|
94
|
+
"Damgard-Geisler-Kroigaard",
|
|
95
|
+
"Bresson-Catalano-Pointcheval",
|
|
96
|
+
"Camenisch-Shoup",
|
|
97
|
+
"EllipticCurve-Paillier",
|
|
98
|
+
"Regev",
|
|
99
|
+
"Brakerski-Vaikuntanathan",
|
|
100
|
+
"Freeman-Boneh-Goh-Nissim",
|
|
101
|
+
"Gentry-Halevi-Vaikuntanathan",
|
|
84
102
|
]
|
|
85
103
|
|
|
86
104
|
cs = LightPHE(algorithm_name = algorithms[0])
|
|
@@ -169,7 +187,11 @@ See [`curves`](https://github.com/serengil/LightECC#supported-curves) page for a
|
|
|
169
187
|
|
|
170
188
|
### Decision Trees
|
|
171
189
|
|
|
172
|
-
|
|
190
|
+
LightPHE can evaluate decision trees privately. Suppose a bank has a secret decision tree that decides a customer's credit limit, and a customer has private data. The bank does not want to reveal its tree, and the customer does not want to reveal their data. The customer sends encrypted data, the bank runs its tree on it without seeing it, and only the customer can decrypt the result. The customer learns the credit limit but not the rest of the tree. Two cryptosystems support this: Ishai-Paskin for trees on yes / no answers, and Damgard-Geisler-Kroigaard for trees on numeric features.
|
|
191
|
+
|
|
192
|
+
#### Decision Trees with Nominal Decisions
|
|
193
|
+
|
|
194
|
+
Ishai-Paskin evaluates trees whose questions have yes / no answers, in a single round: the customer sends encrypted answers, and the bank sends back the encrypted result.
|
|
173
195
|
|
|
174
196
|
Let the bank's tree be the following, where questions are numbered 0, 1 and 2 and leaves are credit limits in thousand dollars.
|
|
175
197
|
|
|
@@ -219,6 +241,214 @@ assert customer_cs.decrypt(encrypted_limit) == 5
|
|
|
219
241
|
|
|
220
242
|
The size of the result grows with the depth of the tree, not with the number of its nodes. Still, each extra level makes the computation noticeably slower, so this is practical for shallow trees.
|
|
221
243
|
|
|
244
|
+
#### Decision Trees with Numeric Decisions
|
|
245
|
+
|
|
246
|
+
Damgard-Geisler-Kroigaard evaluates decision trees with numeric thresholds instead of yes / no answers, using the secure comparison protocol below at each node. It requires a few rounds of interaction between the customer and the bank, but its cost grows linearly with the number of nodes.
|
|
247
|
+
|
|
248
|
+
Let the bank's tree be the following, where features are numbered 0 (age), 1 (income in thousand dollars) and 2 (number of open loans), and leaves are credit limits in thousand dollars.
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
F1: income <= 40?
|
|
252
|
+
/yes \no
|
|
253
|
+
F0: age <= 25? F2: open loans <= 2?
|
|
254
|
+
/yes \no /yes \no
|
|
255
|
+
0 5 20 10
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Each decision node is written as a tuple of `(feature index, threshold, what to do if feature <= threshold, what to do otherwise)`, and leaves are plain integers. So, the tree above becomes:
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
credit_limit_tree = (1, 40, (0, 25, 0, 5), (2, 2, 20, 10))
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Then, the customer and the bank run the following steps.
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
# customer: age = 30, income = 75 and open loans = 1
|
|
268
|
+
# so, the expected path is income > 40 -> open loans <= 2 -> credit limit of 20
|
|
269
|
+
customer_cs = LightPHE(algorithm_name = "Damgard-Geisler-Kroigaard")
|
|
270
|
+
encrypted_features = customer_cs.encrypt_decision_tree_input(
|
|
271
|
+
features = [30, 75, 1], bit_length = 8
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# bank, having the customer's public key only
|
|
275
|
+
bank_cs = LightPHE(algorithm_name = "Damgard-Geisler-Kroigaard", key_file = "public.txt")
|
|
276
|
+
encrypted_comparisons, bank_state = bank_cs.evaluate_decision_tree_comparisons(
|
|
277
|
+
tree = credit_limit_tree, encrypted_features = encrypted_features
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
# customer answers randomly flipped comparisons, so cannot follow the path in the tree
|
|
281
|
+
encrypted_answers = customer_cs.answer_decision_tree_comparisons(encrypted_comparisons)
|
|
282
|
+
|
|
283
|
+
# bank keeps bank_state secret and finds the reached leaf in encrypted form
|
|
284
|
+
encrypted_leaves = bank_cs.evaluate_decision_tree_leaves(
|
|
285
|
+
server_state = bank_state, encrypted_answers = encrypted_answers
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
# customer learns only the credit limit
|
|
289
|
+
assert customer_cs.decrypt_decision_tree_output(encrypted_leaves) == 20
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Secure Comparison
|
|
293
|
+
|
|
294
|
+
Damgard-Geisler-Kroigaard lets a client and a server find out whether the client's private value is less than or equal to the server's private value, without revealing the values to each other. For instance, a bidder can check whether their bid beats the reserve price of an auction.
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
# client encrypts its value bit by bit
|
|
298
|
+
client_cs = LightPHE(algorithm_name = "Damgard-Geisler-Kroigaard")
|
|
299
|
+
encrypted_bid = client_cs.encrypt_comparison_input(value = 120, bit_length = 16)
|
|
300
|
+
|
|
301
|
+
# server compares it with its own value having the client's public key only
|
|
302
|
+
server_cs = LightPHE(algorithm_name = "Damgard-Geisler-Kroigaard", key_file = "public.txt")
|
|
303
|
+
encrypted_result = server_cs.evaluate_comparison(encrypted_input = encrypted_bid, value = 150)
|
|
304
|
+
|
|
305
|
+
# client learns only whether bid <= reserve price
|
|
306
|
+
assert client_cs.decrypt_comparison(encrypted_result) is True
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Encrypted Argmax
|
|
310
|
+
|
|
311
|
+
Damgard-Geisler-Kroigaard can also find the index of the maximum among encrypted values. For instance, a server can compute encrypted class scores of a linear classifier on a client's encrypted features, and then find the predicted class. The client learns only the index, neither the scores nor the intermediate comparisons, and the server learns nothing. The protocol needs a few round trips per value, so the server talks to the client through a callable. Locally, it can be the client's `answer_argmax` directly. Otherwise, wrap your network call with it.
|
|
312
|
+
|
|
313
|
+
```python
|
|
314
|
+
# client encrypts its features
|
|
315
|
+
client_cs = LightPHE(algorithm_name = "Damgard-Geisler-Kroigaard")
|
|
316
|
+
encrypted_features = [client_cs.encrypt(feature) for feature in [3, 1, 4]]
|
|
317
|
+
|
|
318
|
+
# server computes class scores with its private weights homomorphically
|
|
319
|
+
server_cs = LightPHE(algorithm_name = "Damgard-Geisler-Kroigaard", key_file = "public.txt")
|
|
320
|
+
weights = [[1, 2, 0], [0, 1, 3], [2, 0, 1]]
|
|
321
|
+
encrypted_scores = []
|
|
322
|
+
for class_weights in weights:
|
|
323
|
+
score = encrypted_features[0] * class_weights[0]
|
|
324
|
+
for encrypted_feature, weight in zip(encrypted_features[1:], class_weights[1:]):
|
|
325
|
+
score = score + encrypted_feature * weight
|
|
326
|
+
encrypted_scores.append(score)
|
|
327
|
+
|
|
328
|
+
# server finds the argmax with the client's help - scores must fit into bit_length bits
|
|
329
|
+
encrypted_class = server_cs.evaluate_argmax(
|
|
330
|
+
encrypted_values = encrypted_scores, bit_length = 8, client = client_cs.answer_argmax
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
# client learns only the predicted class
|
|
334
|
+
assert client_cs.decrypt(encrypted_class) == 1
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Master Key Decryption
|
|
338
|
+
|
|
339
|
+
Bresson-Catalano-Pointcheval has two ways to decrypt a ciphertext. Each user decrypts with its own private key, while an authority holding the master key decrypts ciphertexts of all users. For instance, a company can let every employee encrypt with their own key, and still recover the data if an employee leaves. Keep in mind that the master key holder can read everything by design.
|
|
340
|
+
|
|
341
|
+
```python
|
|
342
|
+
# authority generates the master key and publishes its public key
|
|
343
|
+
authority_cs = LightPHE(algorithm_name = "Bresson-Catalano-Pointcheval")
|
|
344
|
+
authority_cs.export_keys(target_file = "public.txt", public = True)
|
|
345
|
+
|
|
346
|
+
# each user generates its own key pair with the public key of the authority
|
|
347
|
+
public_cs = LightPHE(algorithm_name = "Bresson-Catalano-Pointcheval", key_file = "public.txt")
|
|
348
|
+
alice_cs = LightPHE(algorithm_name = "Bresson-Catalano-Pointcheval", keys = public_cs.generate_user_keys())
|
|
349
|
+
bob_cs = LightPHE(algorithm_name = "Bresson-Catalano-Pointcheval", keys = public_cs.generate_user_keys())
|
|
350
|
+
|
|
351
|
+
alice_ciphertext = alice_cs.encrypt(100) + alice_cs.encrypt(50)
|
|
352
|
+
bob_ciphertext = bob_cs.encrypt(200)
|
|
353
|
+
|
|
354
|
+
# users decrypt their own ciphertexts only
|
|
355
|
+
assert alice_cs.decrypt(alice_ciphertext) == 150
|
|
356
|
+
assert alice_cs.decrypt(bob_ciphertext) != 200
|
|
357
|
+
|
|
358
|
+
# authority decrypts ciphertexts of all users
|
|
359
|
+
assert authority_cs.decrypt_with_master_key(alice_ciphertext) == 150
|
|
360
|
+
assert authority_cs.decrypt_with_master_key(bob_ciphertext) == 200
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Verifiable Encryption
|
|
364
|
+
|
|
365
|
+
Camenisch-Shoup lets you encrypt a secret for someone else and prove that the ciphertext really contains it, without revealing the secret. For instance, Alice can escrow the private key of her crypto wallet with a trusted third party. A bank, which knows only her public key, verifies that the escrowed ciphertext contains its private key, and the trusted third party can recover it only under the conditions written in the label.
|
|
366
|
+
|
|
367
|
+
```python
|
|
368
|
+
from lightecc import LightECC
|
|
369
|
+
|
|
370
|
+
# trusted third party builds the cryptosystem and publishes its public key
|
|
371
|
+
ttp_cs = LightPHE(algorithm_name = "Camenisch-Shoup")
|
|
372
|
+
ttp_cs.export_keys(target_file = "public.txt", public = True)
|
|
373
|
+
|
|
374
|
+
# alice's wallet key pair on secp256k1
|
|
375
|
+
ecc = LightECC(curve_name = "secp256k1")
|
|
376
|
+
private_key = 123456789
|
|
377
|
+
public_key = ecc.G * private_key
|
|
378
|
+
|
|
379
|
+
# alice encrypts her private key for the trusted third party with a proof
|
|
380
|
+
label = "release only if alice loses access to her wallet"
|
|
381
|
+
alice_cs = LightPHE(algorithm_name = "Camenisch-Shoup", key_file = "public.txt")
|
|
382
|
+
escrow = alice_cs.encrypt_verifiably(secret = private_key, label = label)
|
|
383
|
+
|
|
384
|
+
# bank verifies it with public keys only, without learning alice's private key
|
|
385
|
+
bank_cs = LightPHE(algorithm_name = "Camenisch-Shoup", key_file = "public.txt")
|
|
386
|
+
assert bank_cs.verify_encryption(escrow, public_point = public_key, label = label) is True
|
|
387
|
+
|
|
388
|
+
# trusted third party recovers the private key under the same label only
|
|
389
|
+
assert ttp_cs.decrypt_verifiable_ciphertext(escrow, label = label) == private_key
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
Private keys of other curves can be escrowed by passing `form` and `curve` arguments while building the cryptosystem. Verifiable ciphertexts reject any modification, so homomorphic operations are applied to regular ciphertexts created with `encrypt`.
|
|
393
|
+
|
|
394
|
+
### Adding Multiplication Support to Additive Algorithms
|
|
395
|
+
|
|
396
|
+
Additively homomorphic algorithms cannot multiply two ciphertexts. Boosting them with Catalano-Fiore transform adds one multiplication on top of them, without changing the algorithms themselves. This lets you evaluate degree-2 functions on encrypted data, such as dot products of two encrypted vectors, variances or squared distances. It supports Paillier, Damgard-Jurik, Bresson-Catalano-Pointcheval, Camenisch-Shoup, Elliptic Curve Paillier, Joye-Libert, Castagnos-Laguillaumie, Naccache-Stern, Benaloh and Damgard-Geisler-Kroigaard.
|
|
397
|
+
|
|
398
|
+
```python
|
|
399
|
+
# client builds a boosted cryptosystem, and encrypts its private vectors
|
|
400
|
+
client_cs = LightPHE(algorithm_name = "Paillier", boost = "Catalano-Fiore")
|
|
401
|
+
|
|
402
|
+
x = [3, 1, 4]
|
|
403
|
+
y = [1, 5, 9]
|
|
404
|
+
encrypted_x = [client_cs.encrypt(value) for value in x]
|
|
405
|
+
encrypted_y = [client_cs.encrypt(value) for value in y]
|
|
406
|
+
|
|
407
|
+
# server computes dot product of encrypted vectors - ciphertexts carry the public key only
|
|
408
|
+
encrypted_dot = sum(ex * ey for ex, ey in zip(encrypted_x, encrypted_y))
|
|
409
|
+
|
|
410
|
+
# squared euclidean distance is also a degree-2 function
|
|
411
|
+
encrypted_distance = sum((ex - ey) * (ex - ey) for ex, ey in zip(encrypted_x, encrypted_y))
|
|
412
|
+
|
|
413
|
+
# client decrypts the results
|
|
414
|
+
assert client_cs.decrypt(encrypted_dot) == 44
|
|
415
|
+
assert client_cs.decrypt(encrypted_distance) == 45
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
Multiplied ciphertexts can be added and multiplied with constants, but not multiplied with ciphertexts again. They keep a pair of ciphertexts for every product, so a dot product of length d grows d times. Only integers are supported.
|
|
419
|
+
|
|
420
|
+
### Post-Quantum Security
|
|
421
|
+
|
|
422
|
+
Most algorithms in LightPHE rely on factoring or discrete logarithm problems, which a large enough quantum computer could solve with Shor's algorithm. Regev and Brakerski-Vaikuntanathan rely on learning with errors problems instead, which are believed to be hard for quantum computers too. Their default key sizes give about 128-bit security against quantum attacks.
|
|
423
|
+
|
|
424
|
+
Regev is additively homomorphic. Its key size is the lattice dimension, and its default is 2048.
|
|
425
|
+
|
|
426
|
+
```python
|
|
427
|
+
cs = LightPHE(algorithm_name = "Regev")
|
|
428
|
+
|
|
429
|
+
c1 = cs.encrypt(1500)
|
|
430
|
+
c2 = cs.encrypt(2500)
|
|
431
|
+
|
|
432
|
+
assert cs.decrypt(c1 + c2) == 4000
|
|
433
|
+
assert cs.decrypt(c1 * 3) == 4500
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
Brakerski-Vaikuntanathan additionally supports one multiplication of ciphertexts like Boneh-Goh-Nissim, but without pairings. Its key size is the degree of its polynomial ring, and its default is 4096.
|
|
437
|
+
|
|
438
|
+
```python
|
|
439
|
+
cs = LightPHE(algorithm_name = "Brakerski-Vaikuntanathan")
|
|
440
|
+
|
|
441
|
+
c1 = cs.encrypt(15)
|
|
442
|
+
c2 = cs.encrypt(25)
|
|
443
|
+
c3 = cs.encrypt(100)
|
|
444
|
+
|
|
445
|
+
# one multiplication, then additions and multiplications with constants
|
|
446
|
+
assert cs.decrypt(c1 * c2 + c3) == 475
|
|
447
|
+
assert cs.decrypt((c1 * c2) * 2 + c1 * c3) == 2250
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
Every ciphertext of these algorithms carries a small noise, which grows with each homomorphic operation, and decryption silently fails once it grows too much. With default settings, plaintexts must be less than 2^24. Regev has room for thousands of additions or multiplications with constants up to a few thousands, and a product of Brakerski-Vaikuntanathan has room for millions of times more noise. You can increase the plaintext space with `plaintext_limit` argument in exchange of less room for noise. Floats and tensors are not supported.
|
|
451
|
+
|
|
222
452
|
### Vector Embeddings
|
|
223
453
|
|
|
224
454
|
LightPHE supports homomorphic encryption on vector embeddings. This is useful in privacy-preserving machine learning, secure aggregation, and confidential data processing.
|