diff --git a/.gitignore b/.gitignore
index 74b3e5e..122b811 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,5 @@ lightphe.egg-info
**/.DS_Store
build/
dist/
-*.pyc
\ No newline at end of file
+*.pyc
+tests/*.lphe
\ No newline at end of file
diff --git a/README.md b/README.md
index 3d1fbd8..301105e 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
[![PyPI Downloads](https://static.pepy.tech/personalized-badge/lightphe?period=total&units=international_system&left_color=grey&right_color=blue&left_text=pypi%20downloads)](https://pepy.tech/project/lightphe)
-[![Stars](https://img.shields.io/github/stars/serengil/LightPHE?color=yellow&style=flat)](https://github.com/serengil/LightPHE/stargazers)
+[![Stars](https://img.shields.io/github/stars/serengil/LightPHE?color=yellow&style=flat&label=%E2%AD%90%20stars)](https://github.com/serengil/LightPHE/stargazers)
[![Tests](https://github.com/serengil/LightPHE/actions/workflows/tests.yml/badge.svg)](https://github.com/serengil/LightPHE/actions/workflows/tests.yml)
[![License](http://img.shields.io/:license-MIT-green.svg?style=flat)](https://github.com/serengil/LightPHE/blob/master/LICENSE)
[![Support me on Patreon](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.vercel.app%2Fapi%3Fusername%3Dserengil%26type%3Dpatrons&style=flat)](https://www.patreon.com/serengil?repo=lightphe)
@@ -25,11 +25,10 @@ Even though fully homomorphic encryption (FHE) has become available in recent ti
- 🏎️ Notably faster
- 💻 Demands fewer computational resources
-- 📏 Generating smaller ciphertexts
+- 📏 Generating much smaller ciphertexts
+- 🔑 Distributing much smaller keys
- 🧠 Well-suited for memory-constrained environments
- ⚖️ Strikes a favorable balance for practical use cases
-- 🔑 Supporting encryption and decryption of vectors
-- 🗝️ Performing homomorphic addition, homomorphic element-wise multiplication and scalar multiplication on encrypted vectors
# Installation [![PyPI](https://img.shields.io/pypi/v/lightphe.svg)](https://pypi.org/project/lightphe/)
@@ -161,14 +160,14 @@ However, if you tried to multiply ciphertexts with RSA, or xor ciphertexts with
# Working with vectors
-You can encrypt the output vectors of machine learning models with LightPHE. These encrypted tensors come with homomorphic operation support.
+You can encrypt the output vectors of machine learning models with LightPHE. These encrypted tensors come with homomorphic operation support including homomorphic addition, element-wise multiplication and scalar multiplication.
```python
# build an additively homomorphic cryptosystem
cs = LightPHE(algorithm_name="Paillier")
# define plain tensors
-t1 = [1.005, 2.05, -3.5, 4]
+t1 = [1.005, 2.05, 3.5, 4]
t2 = [5, 6.2, 7.002, 8.02]
# encrypt tensors
@@ -178,13 +177,32 @@ c2 = cs.encrypt(t2)
# perform homomorphic addition
c3 = c1 + c2
+# perform homomorphic element-wise multiplication
+c4 = c1 * c2
+
+# perform homomorphic scalar multiplication
+k = 5
+c5 = k * c1
+
# decrypt the addition tensor
t3 = cs.decrypt(c3)
-for i, tensor in enumerate(t3):
- assert abs((t1[i] + t2[i]) - restored_tensor) < 0.5
+# decrypt the element-wise multiplied tensor
+t4 = cs.decrypt(c4)
+
+# decrypt the scalar multiplied tensor
+t5 = cs.decrypt(c5)
+
+# data validations
+threshold = 0.5
+for i in range(0, len(t1)):
+ assert abs((t1[i] + t2[i]) - t3[i]) < threshold
+ assert abs((t1[i] * t2[i]) - t4[i]) < threshold
+ assert abs((t1[i] * k) - t5[i]) < threshold
```
+Unfortunately, vector multiplication (dot product) requires both homomorphic addition and homomorphic multiplication and this cannot be done with partially homomorphic encryption algorithms.
+
# Contributing
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.
diff --git a/lightphe/__init__.py b/lightphe/__init__.py
index 0a46fad..c54d51e 100644
--- a/lightphe/__init__.py
+++ b/lightphe/__init__.py
@@ -116,8 +116,8 @@ def encrypt(self, plaintext: Union[int, float, list]) -> Union[Ciphertext, Encry
Returns
ciphertext (from lightphe.models.Ciphertext import Ciphertext): encrypted message
"""
- if self.cs.keys.get("private_key") is None:
- raise ValueError("You must have private key to perform encryption")
+ if self.cs.keys.get("public_key") is None:
+ raise ValueError("You must have public key to perform encryption")
if isinstance(plaintext, list):
# then encrypt tensors
@@ -141,6 +141,9 @@ def decrypt(
if self.cs.keys.get("private_key") is None:
raise ValueError("You must have private key to perform decryption")
+ if self.cs.keys.get("public_key") is None:
+ raise ValueError("You must have public key to perform decryption")
+
if isinstance(ciphertext, EncryptedTensor):
# then this is encrypted tensor
return self.__decrypt_tensors(encrypted_tensor=ciphertext)
@@ -243,7 +246,9 @@ def export_keys(self, target_file: str, public: bool = False) -> None:
to publicly.
"""
keys = self.cs.keys
+ private_key = None
if public is True and keys.get("private_key") is not None:
+ private_key = keys["private_key"]
del keys["private_key"]
if public is False:
@@ -255,6 +260,10 @@ def export_keys(self, target_file: str, public: bool = False) -> None:
with open(target_file, "w", encoding="UTF-8") as file:
file.write(json.dumps(keys))
+ # restore private key if you dropped
+ if private_key is not None:
+ self.cs.keys["private_key"] = private_key
+
def restore_keys(self, target_file: str) -> dict:
"""
Restore keys from a file
diff --git a/lightphe/cryptosystems/OkamotoUchiyama.py b/lightphe/cryptosystems/OkamotoUchiyama.py
index 671d19d..d6af543 100644
--- a/lightphe/cryptosystems/OkamotoUchiyama.py
+++ b/lightphe/cryptosystems/OkamotoUchiyama.py
@@ -81,18 +81,21 @@ def encrypt(self, plaintext: int, random_key: Optional[int] = None) -> int:
Returns:
ciphertext (int): encrypted message
"""
- p = self.keys["private_key"]["p"]
+
g = self.keys["public_key"]["g"]
n = self.keys["public_key"]["n"]
h = self.keys["public_key"]["h"]
r = random_key or self.generate_random_key()
- if plaintext > p:
- plaintext = plaintext % p
- logger.debug(
- f"plaintext must be in scale [0, {p=}] but this is exceeded."
- "New plaintext is {plaintext}"
- )
+ # having private key is not a must to encrypt but still if you have
+ if self.keys.get("private_key") is not None:
+ p = self.keys["private_key"]["p"]
+ if plaintext > p:
+ plaintext = plaintext % p
+ logger.debug(
+ f"plaintext must be in scale [0, {p=}] but this is exceeded."
+ "New plaintext is {plaintext}"
+ )
return (pow(g, plaintext, n) * pow(h, r, n)) % n
def decrypt(self, ciphertext: int):
diff --git a/setup.py b/setup.py
index 33cfe90..261f0fd 100644
--- a/setup.py
+++ b/setup.py
@@ -8,10 +8,11 @@
setuptools.setup(
name="lightphe",
- version="0.0.5",
+ version="0.0.6",
author="Sefik Ilkin Serengil",
author_email="serengil@gmail.com",
description="A Lightweight Partially Homomorphic Encryption Library for Python",
+ data_files=[("", ["README.md", "requirements.txt"])],
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/serengil/LightPHE",
diff --git a/tests/test_cloud.py b/tests/test_cloud.py
index 821394b..6fc546e 100644
--- a/tests/test_cloud.py
+++ b/tests/test_cloud.py
@@ -10,7 +10,10 @@
def test_encryption():
- cs = LightPHE(algorithm_name="RSA", keys=PRIVATE)
+ # i actually have both private and public key
+ # but one can encrypt messages with public key only
+ cs = LightPHE(algorithm_name="RSA", keys=PUBLIC)
+ secret_cs = LightPHE(algorithm_name="RSA", keys=PRIVATE)
# plaintexts
m1 = 10000
@@ -19,15 +22,13 @@ def test_encryption():
m2 = 1.05
c2 = cs.encrypt(m2)
- assert cs.decrypt(c1) == m1
-
+ assert secret_cs.decrypt(c1) == m1
logger.info("✅ Cloud encryption tests done")
c3_val = homomorphic_operations(c1=c1.value, c2=c2.value)
c3 = cs.create_ciphertext_obj(c3_val)
- assert cs.decrypt(c3) == m1 * m2
-
+ assert secret_cs.decrypt(c3) == m1 * m2
logger.info("✅ Cloud decryption tests done")
diff --git a/tests/test_exporting_keys.py b/tests/test_exporting_keys.py
new file mode 100644
index 0000000..0558fea
--- /dev/null
+++ b/tests/test_exporting_keys.py
@@ -0,0 +1,26 @@
+import os
+from lightphe import LightPHE
+from lightphe.commons.logger import Logger
+
+logger = Logger(module="tests/test_goldwasser.py")
+
+
+# pylint: disable=eval-used
+def test_private_available_after_export():
+ target_file = "my_public_key.lphe"
+ cs = LightPHE(algorithm_name="RSA")
+ # we are dropping private key while exporting public key
+ cs.export_keys(public=True, target_file=target_file)
+ assert cs.cs.keys.get("private_key") is not None
+ logger.info("✅ private key is not available in public key file as expected")
+
+ with open(target_file, "r", encoding="UTF-8") as file:
+ key_str = file.read()
+ keys = eval(key_str)
+ assert keys.get("private_key") is None
+ logger.info(
+ "✅ private key is available in cryptosystem's keys after"
+ "its public key exported as expected"
+ )
+
+ os.remove(target_file)