Skip to content

Commit

Permalink
january 26 improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
serengil committed Jan 26, 2024
1 parent ba53704 commit db2df15
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 24 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ lightphe.egg-info
**/.DS_Store
build/
dist/
*.pyc
*.pyc
tests/*.lphe
34 changes: 26 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<div align="center">

[![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)
Expand All @@ -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/)

Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down
13 changes: 11 additions & 2 deletions lightphe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand Down
17 changes: 10 additions & 7 deletions lightphe/cryptosystems/OkamotoUchiyama.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
11 changes: 6 additions & 5 deletions tests/test_cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")


Expand Down
26 changes: 26 additions & 0 deletions tests/test_exporting_keys.py
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit db2df15

Please sign in to comment.