lightphe 0.0.11__tar.gz → 0.0.13__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.11 → lightphe-0.0.13}/PKG-INFO +100 -23
- {lightphe-0.0.11 → lightphe-0.0.13}/README.md +99 -22
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/__init__.py +154 -44
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/cryptosystems/EllipticCurveElGamal.py +11 -18
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/cryptosystems/GoldwasserMicali.py +38 -11
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/cryptosystems/OkamotoUchiyama.py +10 -4
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/models/EllipticCurve.py +36 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/models/Tensor.py +193 -7
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe.egg-info/PKG-INFO +100 -23
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe.egg-info/SOURCES.txt +2 -0
- lightphe-0.0.13/lightphe.egg-info/requires.txt +3 -0
- lightphe-0.0.11/lightphe.egg-info/requires.txt → lightphe-0.0.13/requirements.txt +1 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/setup.py +1 -1
- lightphe-0.0.13/tests/test_ciphertexts.py +33 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/tests/test_goldwasser.py +19 -16
- lightphe-0.0.13/tests/test_keys.py +60 -0
- lightphe-0.0.13/tests/test_tensors.py +337 -0
- lightphe-0.0.11/requirements.txt +0 -2
- lightphe-0.0.11/tests/test_tensors.py +0 -174
- {lightphe-0.0.11 → lightphe-0.0.13}/LICENSE +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/commons/__init__.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/commons/binary_operations.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/commons/logger.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/commons/phe_utils.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/cryptosystems/Benaloh.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/cryptosystems/DamgardJurik.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/cryptosystems/ElGamal.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/cryptosystems/NaccacheStern.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/cryptosystems/Paillier.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/cryptosystems/RSA.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/cryptosystems/__init__.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/elliptic_curve_forms/__init__.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/elliptic_curve_forms/edwards.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/elliptic_curve_forms/koblitz.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/elliptic_curve_forms/weierstrass.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/models/Algorithm.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/models/Ciphertext.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/models/Curve.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/models/Homomorphic.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe/models/__init__.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe.egg-info/dependency_links.txt +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/lightphe.egg-info/top_level.txt +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/setup.cfg +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/tests/__init__.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/tests/test_benaloh.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/tests/test_cloud.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/tests/test_damgard.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/tests/test_elgamal.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/tests/test_ellipticcurve.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/tests/test_ellipticcurveelgamal.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/tests/test_exporting_keys.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/tests/test_naccache.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/tests/test_okamoto.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/tests/test_paillier.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/tests/test_rsa.py +0 -0
- {lightphe-0.0.11 → lightphe-0.0.13}/tests/test_salary.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.13
|
|
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
|
|
@@ -23,6 +23,7 @@ License-File: LICENSE
|
|
|
23
23
|
[](https://github.com/serengil/LightPHE/actions/workflows/tests.yml)
|
|
24
24
|
[](https://github.com/serengil/LightPHE/blob/master/LICENSE)
|
|
25
25
|
[](https://arxiv.org/abs/2408.05219)
|
|
26
|
+
[](https://news.ycombinator.com/item?id=43024283)
|
|
26
27
|
<!-- [](https://arxiv.org/abs/2408.05219) -->
|
|
27
28
|
|
|
28
29
|
[](https://sefiks.com)
|
|
@@ -250,27 +251,6 @@ However, if you tried to multiply ciphertexts with RSA, or xor ciphertexts with
|
|
|
250
251
|
|
|
251
252
|
ECC is a powerful public-key cryptosystem based on the algebraic structure of elliptic curves over finite fields. The library supports 3 elliptic curve forms (weierstrass, edwards and koblitz) and 100+ standard elliptic curve configurations.
|
|
252
253
|
|
|
253
|
-
Building an elliptic curve with LightPHE is very straightforward. Once the curve is initialized, you can perform operations such as point addition and scalar multiplication.
|
|
254
|
-
|
|
255
|
-
```python
|
|
256
|
-
from lightphe import ECC
|
|
257
|
-
|
|
258
|
-
forms = ["weierstrass", "edwards", "koblitz"]
|
|
259
|
-
|
|
260
|
-
# construct an elliptic curve
|
|
261
|
-
ed = ECC(form_name="edwards")
|
|
262
|
-
|
|
263
|
-
# Base Point
|
|
264
|
-
G = ed.G
|
|
265
|
-
|
|
266
|
-
_2G = G + G
|
|
267
|
-
_3G = _2G + G
|
|
268
|
-
_5G = _3G + _2G
|
|
269
|
-
|
|
270
|
-
_20G = 4 * G
|
|
271
|
-
_19G = _20G - G
|
|
272
|
-
```
|
|
273
|
-
|
|
274
254
|
In LightPHE, the [Elliptic Curve ElGamal](https://sefiks.com/2018/08/21/elliptic-curve-elgamal-encryption/) scheme is implemented, offering a secure and efficient homomorphic encryption option.
|
|
275
255
|
|
|
276
256
|
```python
|
|
@@ -288,11 +268,108 @@ Each curve in LightPHE has a specific order, which is carefully chosen to balanc
|
|
|
288
268
|
|
|
289
269
|
See [`curves`](https://github.com/serengil/LightPHE/tree/master/lightphe/elliptic_curve_forms) page for a list of all supported forms, curves and their details.
|
|
290
270
|
|
|
271
|
+
### Vector Embeddings and Tensors
|
|
272
|
+
|
|
273
|
+
LightPHE supports homomorphic encryption on vector embeddings and tensors. This is useful in privacy-preserving machine learning, secure aggregation, and confidential data processing.
|
|
274
|
+
|
|
275
|
+
```python
|
|
276
|
+
# build a cryptosystem
|
|
277
|
+
cs = LightPHE(algorithm_name="Paillier")
|
|
278
|
+
|
|
279
|
+
# define plain embedding
|
|
280
|
+
tensor = [1.005, 2.05, 3.005, 4.005, -5.05, 6, 7.003, 7.002]
|
|
281
|
+
|
|
282
|
+
# encrypt vector embedding
|
|
283
|
+
encrypted_tensors = cs.encrypt(tensor)
|
|
284
|
+
|
|
285
|
+
# restore embedding
|
|
286
|
+
decrypted_tensors = cs.decrypt(encrypted_tensors)
|
|
287
|
+
|
|
288
|
+
# proof of work
|
|
289
|
+
assert all(abs(original - decrypted) < 1e-2 for original, decrypted in zip(tensor, decrypted_tensors))
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Encrypted embeddings retain their homomorphic properties, enabling secure computations on encrypted data without decryption. For example, two embeddings encrypted using a multiplicatively homomorphic algorithm can be multiplied element-wise.
|
|
293
|
+
|
|
294
|
+
```python
|
|
295
|
+
# build a multiplicatively homomorphic cryptosystem (e.g. RSA)
|
|
296
|
+
cs = LightPHE("RSA")
|
|
297
|
+
|
|
298
|
+
# define plain embeddings
|
|
299
|
+
t1 = [1.005, 2.05, -3.5, 3.1, -4]
|
|
300
|
+
t2 = [5, 6.2, -7.002, -7.1, 8.02]
|
|
301
|
+
|
|
302
|
+
# encrypt embeddings
|
|
303
|
+
c1, c2 = cs.encrypt(t1), cs.encrypt(t2)
|
|
304
|
+
|
|
305
|
+
# perform element-wise homomorphic multiplication
|
|
306
|
+
c3 = c1 * c2
|
|
307
|
+
|
|
308
|
+
# proof of work
|
|
309
|
+
assert np.allclose(cs.decrypt(c3), [a * b for a, b in zip(t1, t2)], rtol=1e-2)
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Similarly, two embeddings encrypted with an additively homomorphic algorithm can be added. Additionally, an encrypted embedding can be multiplied by a constant or a plain embedding.
|
|
313
|
+
|
|
314
|
+
```python
|
|
315
|
+
# build an additively homomorphic cryptosystem (e.g. Paillier)
|
|
316
|
+
cs = LightPHE("Paillier")
|
|
317
|
+
|
|
318
|
+
# define plain embeddings
|
|
319
|
+
t1 = [1.005, 2.05, 3.6, 4, 4.02, 3.5]
|
|
320
|
+
t2 = [5, 6.2, 7.5, 8.02, 8.02, 4.5]
|
|
321
|
+
t3 = [1.03, 2.04, 3.05, 7.02, 2.01, 1.06]
|
|
322
|
+
|
|
323
|
+
# encrypt embeddings
|
|
324
|
+
c1, c2 = cs.encrypt(t1), cs.encrypt(t2)
|
|
325
|
+
|
|
326
|
+
# perform addition of two encrypted embeddings
|
|
327
|
+
c4 = c1 + c2
|
|
328
|
+
|
|
329
|
+
# perform scalar multiplication on an embedding
|
|
330
|
+
c5 = 3 * c1
|
|
331
|
+
|
|
332
|
+
# perform element-wise multiplication between an encrypted embedding and plain embedding
|
|
333
|
+
c6 = c1 * t3 # Encrypted-plaintext multiplication
|
|
334
|
+
|
|
335
|
+
# proof of work
|
|
336
|
+
assert np.allclose(cs.decrypt(c4), [a + b for a, b in zip(t1, t2)], rtol=1e-2)
|
|
337
|
+
assert np.allclose(cs.decrypt(c5), [a * 3 for a in t1], rtol=1e-2)
|
|
338
|
+
assert np.allclose(cs.decrypt(c6), [a * b for a, b in zip(t1, t3)], rtol=1e-2)
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Vector Similarity Search with PHE
|
|
342
|
+
|
|
343
|
+
Many machine learning models rely on a two-tower architecture, including facial recognition, reverse image search, recommendation engines, large language models, and more. In this setup, user and item inputs are separately mapped to vector embeddings.
|
|
344
|
+
|
|
345
|
+
For example, suppose all facial embeddings in your database are encrypted on-prem in advance. When verifying an identity, the attempted facial embedding is generated on an edge device or in the cloud. You can compute the encrypted similarity by performing a dot product between the encrypted vector and the plain vector, ensuring secure comparison without decrypting sensitive data. Only additively homomorphic cryptosystems offer encrypted similarity calculation.
|
|
346
|
+
|
|
347
|
+
```python
|
|
348
|
+
# build an additively homomorphic cryptosystem (e.g. Paillier)
|
|
349
|
+
cs = LightPHE("Paillier")
|
|
350
|
+
|
|
351
|
+
# define plain vectors - suppose those are l2 normalized already
|
|
352
|
+
alpha = [7.1, 5.2, 5.3, 2.4, 3.5, 4.6] # On-prem vector (user tower)
|
|
353
|
+
beta = [5.6, 3.7, 2.8, 4, 0, 5.9] # Cloud vector (item tower)
|
|
354
|
+
|
|
355
|
+
# encrypt embedding
|
|
356
|
+
encrypted_alpha = cs.encrypt(alpha)
|
|
357
|
+
|
|
358
|
+
# dot product of encrypted embedding and plain embedding
|
|
359
|
+
encrypted_cosine_similarity = encrypted_alpha @ beta
|
|
360
|
+
|
|
361
|
+
# decrypt similarity
|
|
362
|
+
cosine_similarity = cs.decrypt(encrypted_cosine_similarity)[0]
|
|
363
|
+
|
|
364
|
+
# proof of work
|
|
365
|
+
assert abs(cosine_similarity - sum(x * y for x, y in zip(alpha, beta))) < 1e-2
|
|
366
|
+
```
|
|
367
|
+
|
|
291
368
|
# Contributing
|
|
292
369
|
|
|
293
370
|
All PRs are more than welcome! If you are planning to contribute a large patch, please create an issue first to get any upfront questions or design decisions out of the way first.
|
|
294
371
|
|
|
295
|
-
You should be able run `make test` and `make lint` commands successfully before committing. Once a PR is created, GitHub test workflow will be run automatically and unit test results will be available in [GitHub actions](https://github.com/serengil/LightPHE/actions/workflows/tests.yml) before approval.
|
|
372
|
+
You should be able run `make test` and `make lint` commands successfully before committing. Once a PR is created, GitHub test workflow will be run automatically and unit test results will be available in [GitHub actions](https://github.com/serengil/LightPHE/actions/workflows/tests.yml) before approval.
|
|
296
373
|
|
|
297
374
|
# Support
|
|
298
375
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
[](https://github.com/serengil/LightPHE/actions/workflows/tests.yml)
|
|
8
8
|
[](https://github.com/serengil/LightPHE/blob/master/LICENSE)
|
|
9
9
|
[](https://arxiv.org/abs/2408.05219)
|
|
10
|
+
[](https://news.ycombinator.com/item?id=43024283)
|
|
10
11
|
<!-- [](https://arxiv.org/abs/2408.05219) -->
|
|
11
12
|
|
|
12
13
|
[](https://sefiks.com)
|
|
@@ -234,27 +235,6 @@ However, if you tried to multiply ciphertexts with RSA, or xor ciphertexts with
|
|
|
234
235
|
|
|
235
236
|
ECC is a powerful public-key cryptosystem based on the algebraic structure of elliptic curves over finite fields. The library supports 3 elliptic curve forms (weierstrass, edwards and koblitz) and 100+ standard elliptic curve configurations.
|
|
236
237
|
|
|
237
|
-
Building an elliptic curve with LightPHE is very straightforward. Once the curve is initialized, you can perform operations such as point addition and scalar multiplication.
|
|
238
|
-
|
|
239
|
-
```python
|
|
240
|
-
from lightphe import ECC
|
|
241
|
-
|
|
242
|
-
forms = ["weierstrass", "edwards", "koblitz"]
|
|
243
|
-
|
|
244
|
-
# construct an elliptic curve
|
|
245
|
-
ed = ECC(form_name="edwards")
|
|
246
|
-
|
|
247
|
-
# Base Point
|
|
248
|
-
G = ed.G
|
|
249
|
-
|
|
250
|
-
_2G = G + G
|
|
251
|
-
_3G = _2G + G
|
|
252
|
-
_5G = _3G + _2G
|
|
253
|
-
|
|
254
|
-
_20G = 4 * G
|
|
255
|
-
_19G = _20G - G
|
|
256
|
-
```
|
|
257
|
-
|
|
258
238
|
In LightPHE, the [Elliptic Curve ElGamal](https://sefiks.com/2018/08/21/elliptic-curve-elgamal-encryption/) scheme is implemented, offering a secure and efficient homomorphic encryption option.
|
|
259
239
|
|
|
260
240
|
```python
|
|
@@ -272,11 +252,108 @@ Each curve in LightPHE has a specific order, which is carefully chosen to balanc
|
|
|
272
252
|
|
|
273
253
|
See [`curves`](https://github.com/serengil/LightPHE/tree/master/lightphe/elliptic_curve_forms) page for a list of all supported forms, curves and their details.
|
|
274
254
|
|
|
255
|
+
### Vector Embeddings and Tensors
|
|
256
|
+
|
|
257
|
+
LightPHE supports homomorphic encryption on vector embeddings and tensors. This is useful in privacy-preserving machine learning, secure aggregation, and confidential data processing.
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
# build a cryptosystem
|
|
261
|
+
cs = LightPHE(algorithm_name="Paillier")
|
|
262
|
+
|
|
263
|
+
# define plain embedding
|
|
264
|
+
tensor = [1.005, 2.05, 3.005, 4.005, -5.05, 6, 7.003, 7.002]
|
|
265
|
+
|
|
266
|
+
# encrypt vector embedding
|
|
267
|
+
encrypted_tensors = cs.encrypt(tensor)
|
|
268
|
+
|
|
269
|
+
# restore embedding
|
|
270
|
+
decrypted_tensors = cs.decrypt(encrypted_tensors)
|
|
271
|
+
|
|
272
|
+
# proof of work
|
|
273
|
+
assert all(abs(original - decrypted) < 1e-2 for original, decrypted in zip(tensor, decrypted_tensors))
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Encrypted embeddings retain their homomorphic properties, enabling secure computations on encrypted data without decryption. For example, two embeddings encrypted using a multiplicatively homomorphic algorithm can be multiplied element-wise.
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
# build a multiplicatively homomorphic cryptosystem (e.g. RSA)
|
|
280
|
+
cs = LightPHE("RSA")
|
|
281
|
+
|
|
282
|
+
# define plain embeddings
|
|
283
|
+
t1 = [1.005, 2.05, -3.5, 3.1, -4]
|
|
284
|
+
t2 = [5, 6.2, -7.002, -7.1, 8.02]
|
|
285
|
+
|
|
286
|
+
# encrypt embeddings
|
|
287
|
+
c1, c2 = cs.encrypt(t1), cs.encrypt(t2)
|
|
288
|
+
|
|
289
|
+
# perform element-wise homomorphic multiplication
|
|
290
|
+
c3 = c1 * c2
|
|
291
|
+
|
|
292
|
+
# proof of work
|
|
293
|
+
assert np.allclose(cs.decrypt(c3), [a * b for a, b in zip(t1, t2)], rtol=1e-2)
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Similarly, two embeddings encrypted with an additively homomorphic algorithm can be added. Additionally, an encrypted embedding can be multiplied by a constant or a plain embedding.
|
|
297
|
+
|
|
298
|
+
```python
|
|
299
|
+
# build an additively homomorphic cryptosystem (e.g. Paillier)
|
|
300
|
+
cs = LightPHE("Paillier")
|
|
301
|
+
|
|
302
|
+
# define plain embeddings
|
|
303
|
+
t1 = [1.005, 2.05, 3.6, 4, 4.02, 3.5]
|
|
304
|
+
t2 = [5, 6.2, 7.5, 8.02, 8.02, 4.5]
|
|
305
|
+
t3 = [1.03, 2.04, 3.05, 7.02, 2.01, 1.06]
|
|
306
|
+
|
|
307
|
+
# encrypt embeddings
|
|
308
|
+
c1, c2 = cs.encrypt(t1), cs.encrypt(t2)
|
|
309
|
+
|
|
310
|
+
# perform addition of two encrypted embeddings
|
|
311
|
+
c4 = c1 + c2
|
|
312
|
+
|
|
313
|
+
# perform scalar multiplication on an embedding
|
|
314
|
+
c5 = 3 * c1
|
|
315
|
+
|
|
316
|
+
# perform element-wise multiplication between an encrypted embedding and plain embedding
|
|
317
|
+
c6 = c1 * t3 # Encrypted-plaintext multiplication
|
|
318
|
+
|
|
319
|
+
# proof of work
|
|
320
|
+
assert np.allclose(cs.decrypt(c4), [a + b for a, b in zip(t1, t2)], rtol=1e-2)
|
|
321
|
+
assert np.allclose(cs.decrypt(c5), [a * 3 for a in t1], rtol=1e-2)
|
|
322
|
+
assert np.allclose(cs.decrypt(c6), [a * b for a, b in zip(t1, t3)], rtol=1e-2)
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Vector Similarity Search with PHE
|
|
326
|
+
|
|
327
|
+
Many machine learning models rely on a two-tower architecture, including facial recognition, reverse image search, recommendation engines, large language models, and more. In this setup, user and item inputs are separately mapped to vector embeddings.
|
|
328
|
+
|
|
329
|
+
For example, suppose all facial embeddings in your database are encrypted on-prem in advance. When verifying an identity, the attempted facial embedding is generated on an edge device or in the cloud. You can compute the encrypted similarity by performing a dot product between the encrypted vector and the plain vector, ensuring secure comparison without decrypting sensitive data. Only additively homomorphic cryptosystems offer encrypted similarity calculation.
|
|
330
|
+
|
|
331
|
+
```python
|
|
332
|
+
# build an additively homomorphic cryptosystem (e.g. Paillier)
|
|
333
|
+
cs = LightPHE("Paillier")
|
|
334
|
+
|
|
335
|
+
# define plain vectors - suppose those are l2 normalized already
|
|
336
|
+
alpha = [7.1, 5.2, 5.3, 2.4, 3.5, 4.6] # On-prem vector (user tower)
|
|
337
|
+
beta = [5.6, 3.7, 2.8, 4, 0, 5.9] # Cloud vector (item tower)
|
|
338
|
+
|
|
339
|
+
# encrypt embedding
|
|
340
|
+
encrypted_alpha = cs.encrypt(alpha)
|
|
341
|
+
|
|
342
|
+
# dot product of encrypted embedding and plain embedding
|
|
343
|
+
encrypted_cosine_similarity = encrypted_alpha @ beta
|
|
344
|
+
|
|
345
|
+
# decrypt similarity
|
|
346
|
+
cosine_similarity = cs.decrypt(encrypted_cosine_similarity)[0]
|
|
347
|
+
|
|
348
|
+
# proof of work
|
|
349
|
+
assert abs(cosine_similarity - sum(x * y for x, y in zip(alpha, beta))) < 1e-2
|
|
350
|
+
```
|
|
351
|
+
|
|
275
352
|
# Contributing
|
|
276
353
|
|
|
277
354
|
All PRs are more than welcome! If you are planning to contribute a large patch, please create an issue first to get any upfront questions or design decisions out of the way first.
|
|
278
355
|
|
|
279
|
-
You should be able run `make test` and `make lint` commands successfully before committing. Once a PR is created, GitHub test workflow will be run automatically and unit test results will be available in [GitHub actions](https://github.com/serengil/LightPHE/actions/workflows/tests.yml) before approval.
|
|
356
|
+
You should be able run `make test` and `make lint` commands successfully before committing. Once a PR is created, GitHub test workflow will be run automatically and unit test results will be available in [GitHub actions](https://github.com/serengil/LightPHE/actions/workflows/tests.yml) before approval.
|
|
280
357
|
|
|
281
358
|
# Support
|
|
282
359
|
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
# built-in dependencies
|
|
2
|
+
import time
|
|
2
3
|
import json
|
|
3
4
|
from typing import Optional, Union, List
|
|
5
|
+
import multiprocessing
|
|
6
|
+
from contextlib import closing
|
|
7
|
+
import traceback
|
|
8
|
+
import copy
|
|
9
|
+
|
|
10
|
+
# 3rd party dependencies
|
|
11
|
+
from tqdm import tqdm
|
|
4
12
|
|
|
5
13
|
# project dependencies
|
|
6
14
|
from lightphe.models.Homomorphic import Homomorphic
|
|
@@ -30,7 +38,7 @@ from lightphe.elliptic_curve_forms.koblitz import Koblitz
|
|
|
30
38
|
|
|
31
39
|
logger = Logger(module="lightphe/__init__.py")
|
|
32
40
|
|
|
33
|
-
VERSION = "0.0.
|
|
41
|
+
VERSION = "0.0.13"
|
|
34
42
|
|
|
35
43
|
|
|
36
44
|
class LightPHE:
|
|
@@ -149,12 +157,13 @@ class LightPHE:
|
|
|
149
157
|
return cs
|
|
150
158
|
|
|
151
159
|
def encrypt(
|
|
152
|
-
self, plaintext: Union[int, float, list]
|
|
160
|
+
self, plaintext: Union[int, float, list], silent: bool = False
|
|
153
161
|
) -> Union[Ciphertext, EncryptedTensor]:
|
|
154
162
|
"""
|
|
155
163
|
Encrypt a plaintext with a built cryptosystem
|
|
156
164
|
Args:
|
|
157
165
|
plaintext (int, float or tensor): message
|
|
166
|
+
silent (bool): set this to True if you do not want to see progress bar
|
|
158
167
|
Returns
|
|
159
168
|
ciphertext (from lightphe.models.Ciphertext import Ciphertext): encrypted message
|
|
160
169
|
"""
|
|
@@ -163,16 +172,21 @@ class LightPHE:
|
|
|
163
172
|
|
|
164
173
|
if isinstance(plaintext, list):
|
|
165
174
|
# then encrypt tensors
|
|
166
|
-
return self.__encrypt_tensors(tensor=plaintext)
|
|
175
|
+
return self.__encrypt_tensors(tensor=plaintext, silent=silent)
|
|
167
176
|
|
|
168
177
|
ciphertext = self.cs.encrypt(
|
|
169
178
|
plaintext=phe_utils.normalize_input(
|
|
170
179
|
value=plaintext, modulo=self.cs.plaintext_modulo
|
|
171
180
|
)
|
|
172
181
|
)
|
|
182
|
+
|
|
183
|
+
public_keys = self.cs.keys.copy()
|
|
184
|
+
if public_keys.get("private_key") is not None:
|
|
185
|
+
del public_keys["private_key"]
|
|
186
|
+
|
|
173
187
|
return Ciphertext(
|
|
174
188
|
algorithm_name=self.algorithm_name,
|
|
175
|
-
keys=
|
|
189
|
+
keys=public_keys,
|
|
176
190
|
value=ciphertext,
|
|
177
191
|
form=self.form,
|
|
178
192
|
curve=self.curve,
|
|
@@ -200,55 +214,61 @@ class LightPHE:
|
|
|
200
214
|
|
|
201
215
|
return self.cs.decrypt(ciphertext=ciphertext.value)
|
|
202
216
|
|
|
203
|
-
def __encrypt_tensors(self, tensor: list) -> EncryptedTensor:
|
|
217
|
+
def __encrypt_tensors(self, tensor: list, silent: bool = False) -> EncryptedTensor:
|
|
204
218
|
"""
|
|
205
219
|
Encrypt a given tensor
|
|
206
220
|
Args:
|
|
207
221
|
tensor (list of int or float)
|
|
222
|
+
silent (bool): set this to True if you do not want to see progress bar
|
|
208
223
|
Returns
|
|
209
224
|
encrypted tensor (list of encrypted tensor object)
|
|
210
225
|
"""
|
|
211
226
|
encrypted_tensor: List[Fraction] = []
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
precision=self.precision,
|
|
233
|
-
)
|
|
234
|
-
abs_dividend, _ = phe_utils.fractionize(
|
|
235
|
-
value=(abs(m) % self.cs.plaintext_modulo),
|
|
236
|
-
modulo=self.cs.plaintext_modulo,
|
|
237
|
-
precision=self.precision,
|
|
238
|
-
)
|
|
239
|
-
dividend_encrypted = self.cs.encrypt(plaintext=dividend)
|
|
240
|
-
abs_dividend_encrypted = self.cs.encrypt(plaintext=abs_dividend)
|
|
241
|
-
divisor_encrypted = self.cs.encrypt(plaintext=divisor)
|
|
242
|
-
c = Fraction(
|
|
243
|
-
dividend=dividend_encrypted,
|
|
244
|
-
divisor=divisor_encrypted,
|
|
245
|
-
abs_dividend=abs_dividend_encrypted,
|
|
246
|
-
sign=1 if m >= 0 else -1,
|
|
227
|
+
|
|
228
|
+
encrypted_zero = self.cs.encrypt(plaintext=0)
|
|
229
|
+
divisor_encrypted = self.cs.encrypt(plaintext=10**self.precision)
|
|
230
|
+
|
|
231
|
+
num_workers = min(len(tensor), 2 * multiprocessing.cpu_count())
|
|
232
|
+
logger.debug(f"encrypting tensors in {num_workers} parallel")
|
|
233
|
+
|
|
234
|
+
with closing(multiprocessing.Pool(num_workers)) as pool:
|
|
235
|
+
funclist = []
|
|
236
|
+
|
|
237
|
+
for m in tensor:
|
|
238
|
+
f = pool.apply_async(
|
|
239
|
+
encrypt_float,
|
|
240
|
+
(
|
|
241
|
+
m,
|
|
242
|
+
divisor_encrypted,
|
|
243
|
+
self.cs,
|
|
244
|
+
self.precision,
|
|
245
|
+
encrypted_zero,
|
|
246
|
+
),
|
|
247
247
|
)
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
248
|
+
funclist.append(f)
|
|
249
|
+
|
|
250
|
+
tic = time.time()
|
|
251
|
+
encrypted_tensor = []
|
|
252
|
+
for f in tqdm(
|
|
253
|
+
funclist,
|
|
254
|
+
desc="Encrypting tensors",
|
|
255
|
+
disable=silent,
|
|
256
|
+
):
|
|
257
|
+
result = f.get(timeout=10)
|
|
258
|
+
encrypted_tensor.append(result)
|
|
259
|
+
|
|
260
|
+
toc = time.time()
|
|
261
|
+
logger.debug(f"encryption took {toc - tic} seconds")
|
|
262
|
+
|
|
263
|
+
public_cs = copy.deepcopy(self.cs)
|
|
264
|
+
if public_cs.keys.get("private_key") is not None:
|
|
265
|
+
del public_cs.keys["private_key"]
|
|
266
|
+
|
|
267
|
+
return EncryptedTensor(
|
|
268
|
+
fractions=encrypted_tensor,
|
|
269
|
+
cs=public_cs,
|
|
270
|
+
precision=self.precision,
|
|
271
|
+
)
|
|
252
272
|
|
|
253
273
|
def __decrypt_tensors(
|
|
254
274
|
self, encrypted_tensor: EncryptedTensor
|
|
@@ -268,7 +288,10 @@ class LightPHE:
|
|
|
268
288
|
sign = c.sign
|
|
269
289
|
abs_dividend = self.cs.decrypt(ciphertext=c.abs_dividend)
|
|
270
290
|
# dividend = self.cs.decrypt(ciphertext=c.dividend)
|
|
291
|
+
|
|
292
|
+
# TODO: do I really need encrypted divisor? cannot I store current_precision
|
|
271
293
|
divisor = self.cs.decrypt(ciphertext=c.divisor)
|
|
294
|
+
|
|
272
295
|
m = sign * abs_dividend / divisor
|
|
273
296
|
|
|
274
297
|
plain_tensor.append(m)
|
|
@@ -356,6 +379,93 @@ class LightPHE:
|
|
|
356
379
|
)
|
|
357
380
|
|
|
358
381
|
|
|
382
|
+
def encrypt_float(
|
|
383
|
+
m: Union[int, float],
|
|
384
|
+
divisor_encrypted: int,
|
|
385
|
+
cs: Homomorphic,
|
|
386
|
+
precision: int,
|
|
387
|
+
encrypted_zero: int,
|
|
388
|
+
) -> Fraction:
|
|
389
|
+
"""
|
|
390
|
+
Encrypt a float value
|
|
391
|
+
Args:
|
|
392
|
+
m (int or float): message to encrypt
|
|
393
|
+
divisor_encrypted (int): pre-calculated encrypted divisor
|
|
394
|
+
cs (Homomorphic): cryptosystem itself
|
|
395
|
+
precision (int): define how many digits after dot
|
|
396
|
+
encrypted_zero (int): pre-calculated encrypted value of 0
|
|
397
|
+
Returns:
|
|
398
|
+
result (Fraction): encrypted float value
|
|
399
|
+
"""
|
|
400
|
+
try:
|
|
401
|
+
if m == 0:
|
|
402
|
+
# this is very common in VGG-Face embeddings
|
|
403
|
+
c = Fraction(
|
|
404
|
+
dividend=encrypted_zero,
|
|
405
|
+
divisor=divisor_encrypted,
|
|
406
|
+
abs_dividend=encrypted_zero,
|
|
407
|
+
sign=1,
|
|
408
|
+
)
|
|
409
|
+
elif isinstance(m, int):
|
|
410
|
+
dividend_encrypted = cs.encrypt(
|
|
411
|
+
plaintext=(m % cs.plaintext_modulo) * pow(10, precision)
|
|
412
|
+
)
|
|
413
|
+
abs_dividend_encrypted = (
|
|
414
|
+
dividend_encrypted
|
|
415
|
+
if m > 0
|
|
416
|
+
else cs.encrypt(
|
|
417
|
+
plaintext=(abs(m) % cs.plaintext_modulo) * pow(10, precision)
|
|
418
|
+
)
|
|
419
|
+
)
|
|
420
|
+
# divisor_encrypted = self.cs.encrypt(plaintext=pow(10, self.precision))
|
|
421
|
+
c = Fraction(
|
|
422
|
+
dividend=dividend_encrypted,
|
|
423
|
+
divisor=divisor_encrypted,
|
|
424
|
+
abs_dividend=abs_dividend_encrypted,
|
|
425
|
+
sign=1 if m >= 0 else -1,
|
|
426
|
+
)
|
|
427
|
+
elif isinstance(m, float):
|
|
428
|
+
# got `int too large to convert float` while m mod plaintext modulo
|
|
429
|
+
# when security level is set to 128
|
|
430
|
+
dividend, _ = phe_utils.fractionize(
|
|
431
|
+
value=(m % cs.plaintext_modulo if m > cs.plaintext_modulo else m),
|
|
432
|
+
modulo=cs.plaintext_modulo,
|
|
433
|
+
precision=precision,
|
|
434
|
+
)
|
|
435
|
+
abs_dividend = (
|
|
436
|
+
dividend
|
|
437
|
+
if m > 0
|
|
438
|
+
else phe_utils.fractionize(
|
|
439
|
+
value=(
|
|
440
|
+
abs(m) % cs.plaintext_modulo
|
|
441
|
+
if abs(m) > cs.plaintext_modulo
|
|
442
|
+
else abs(m)
|
|
443
|
+
),
|
|
444
|
+
modulo=cs.plaintext_modulo,
|
|
445
|
+
precision=precision,
|
|
446
|
+
)[0]
|
|
447
|
+
)
|
|
448
|
+
dividend_encrypted = cs.encrypt(plaintext=dividend)
|
|
449
|
+
abs_dividend_encrypted = (
|
|
450
|
+
dividend_encrypted if m > 0 else cs.encrypt(plaintext=abs_dividend)
|
|
451
|
+
)
|
|
452
|
+
# divisor_encrypted = self.cs.encrypt(plaintext=_divisor)
|
|
453
|
+
c = Fraction(
|
|
454
|
+
dividend=dividend_encrypted,
|
|
455
|
+
divisor=divisor_encrypted,
|
|
456
|
+
abs_dividend=abs_dividend_encrypted,
|
|
457
|
+
sign=1 if m >= 0 else -1,
|
|
458
|
+
)
|
|
459
|
+
else:
|
|
460
|
+
raise ValueError(f"unimplemented type - {type(m)}")
|
|
461
|
+
|
|
462
|
+
return c
|
|
463
|
+
except Exception as err:
|
|
464
|
+
logger.error(f"Exception while running encrypt_float: {str(err)}")
|
|
465
|
+
logger.error(traceback.format_exc())
|
|
466
|
+
raise err
|
|
467
|
+
|
|
468
|
+
|
|
359
469
|
class ECC:
|
|
360
470
|
__version__ = VERSION
|
|
361
471
|
|
|
@@ -46,8 +46,13 @@ class EllipticCurveElGamal(Homomorphic):
|
|
|
46
46
|
self.ecc = ECC(form_name=form, curve_name=curve)
|
|
47
47
|
|
|
48
48
|
self.keys = keys or self.generate_keys(key_size or self.ecc.n.bit_length())
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
|
|
50
|
+
if curve is not None:
|
|
51
|
+
self.keys["curve"] = curve
|
|
52
|
+
|
|
53
|
+
if form is not None:
|
|
54
|
+
self.keys["form"] = form
|
|
55
|
+
|
|
51
56
|
self.plaintext_modulo = self.ecc.modulo
|
|
52
57
|
self.ciphertext_modulo = self.ecc.modulo
|
|
53
58
|
|
|
@@ -74,7 +79,7 @@ class EllipticCurveElGamal(Homomorphic):
|
|
|
74
79
|
# public key
|
|
75
80
|
Qa = self.ecc.G * ka
|
|
76
81
|
|
|
77
|
-
keys["public_key"]["Qa"] = Qa
|
|
82
|
+
keys["public_key"]["Qa"] = Qa.get_point()
|
|
78
83
|
keys["private_key"]["ka"] = ka
|
|
79
84
|
|
|
80
85
|
return keys
|
|
@@ -98,7 +103,8 @@ class EllipticCurveElGamal(Homomorphic):
|
|
|
98
103
|
ciphertext (tuple): c1 and c2
|
|
99
104
|
"""
|
|
100
105
|
# public key
|
|
101
|
-
|
|
106
|
+
x, y = self.keys["public_key"]["Qa"]
|
|
107
|
+
Qa = EllipticCurvePoint(x=x, y=y, curve=self.ecc.curve)
|
|
102
108
|
|
|
103
109
|
# random key
|
|
104
110
|
r = random_key or self.generate_random_key()
|
|
@@ -134,20 +140,7 @@ class EllipticCurveElGamal(Homomorphic):
|
|
|
134
140
|
# we need to find k from known s_prime and G
|
|
135
141
|
# this requires to solve ECDLP
|
|
136
142
|
|
|
137
|
-
|
|
138
|
-
G = self.ecc.G
|
|
139
|
-
k = 2
|
|
140
|
-
while True:
|
|
141
|
-
G = G + self.ecc.G
|
|
142
|
-
|
|
143
|
-
if G == s_prime:
|
|
144
|
-
return k
|
|
145
|
-
|
|
146
|
-
k = k + 1
|
|
147
|
-
if k > self.ecc.n:
|
|
148
|
-
raise ValueError(
|
|
149
|
-
f"Cannot restore scalar from {s_prime} = k x {self.ecc.G}"
|
|
150
|
-
)
|
|
143
|
+
return s_prime / self.ecc.G
|
|
151
144
|
|
|
152
145
|
def multiply(self, ciphertext1: tuple, ciphertext2: tuple) -> tuple:
|
|
153
146
|
raise ValueError(
|