Skip to content

Commit

Permalink
linting comments
Browse files Browse the repository at this point in the history
  • Loading branch information
serengil committed Dec 17, 2023
1 parent f47b01e commit a4e8d70
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 41 deletions.
28 changes: 18 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ Even though fully homomorphic encryption (FHE) has become available in recent ti
- 📏 Generating smaller ciphertexts
- 🧠 Well-suited for memory-constrained environments
- ⚖️ Strikes a favorable balance for practical use cases
- 🔑 Supporting encryption and decryption of vectors
- 🗝️ Performing homomorphic addition and multiplication on encrypted vectors

# Installation [![PyPI](https://img.shields.io/pypi/v/lightphe.svg)](https://pypi.org/project/lightphe/)

Expand Down Expand Up @@ -157,24 +159,30 @@ with pytest.raises(ValueError, match="Paillier is not homomorphic with respect t

However, if you tried to multiply ciphertexts with RSA, or xor ciphertexts with Goldwasser-Micali, these will be succeeded because those cryptosystems support those homomorphic operations.

# Encrypt & Decrypt Tensors
# Working with vectors

You can encrypt the output tensors of machine learning models with LightPHE.
You can encrypt the output tensors of machine learning models with LightPHE. These encrypted tensors come with homomorphic operation support.

```python
# build an additively homomorphic cryptosystem
cs = LightPHE(algorithm_name="Paillier")

# define plain tensor
tensor = [1.005, 2.005, 3.005, -4.005, 5.005]
# define plain tensors
t1 = [1.005, 2.05, -3.5, 4]
t2 = [5, 6.2, 7.002, 8.02]

# encrypt tensor
encrypted_tensors = cs.encrypt(tensor)
# encrypt tensors
c1 = cs.encrypt(t1)
c2 = cs.encrypt(t2)

# decrypt tensor
decrypted_tensors = cs.decrypt(encrypted_tensors)
# perform homomorphic addition
c3 = c1 + c2

# decrypt the addition tensor
t3 = cs.decrypt(c3)

for i, decrypted_tensor in enumerate(decrypted_tensors):
assert tensor[i] == decrypted_tensor
for i, tensor in enumerate(t3):
assert abs((t1[i] + t2[i]) - restored_tensor) < 0.5
```

# Contributing
Expand Down
2 changes: 1 addition & 1 deletion lightphe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ def __encrypt_tensors(self, tensor: list) -> EncryptedTensor:
modulo=self.cs.plaintext_modulo,
precision=self.precision,
)
abs_dividend, abs_divisor = phe_utils.fractionize(
abs_dividend, _ = phe_utils.fractionize(
value=(abs(m) % self.cs.plaintext_modulo),
modulo=self.cs.plaintext_modulo,
precision=self.precision,
Expand Down
132 changes: 102 additions & 30 deletions lightphe/models/Tensor.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from typing import Union, List
from lightphe.models.Homomorphic import Homomorphic
from lightphe.commons import phe_utils


# TODO: add docstrings, validate private key is available in keys


# pylint: disable=too-few-public-methods
# pylint: disable=too-few-public-methods, no-else-return
class Fraction:
"""
Class to store fractional values
"""

def __init__(
self,
dividend: Union[int, tuple, list],
Expand All @@ -20,56 +22,126 @@ def __init__(
self.sign = sign

def __str__(self):
return f"EncryptedTensor({self.sign} * {self.dividend} / {self.divisor})"
"""
Print Fraction Class Object
"""
sign_char = "-" if self.sign == -1 else ""
return f"Fraction({sign_char} * {self.abs_dividend} / {self.divisor})"

def __repr__(self):
"""
Print Fraction Class Object
"""
return self.__str__()


class EncryptedTensor:
"""
Class to store encrypted tensor objects
"""

def __init__(self, fractions: List[Fraction], cs: Homomorphic):
"""
Initialization method
Args:
fractions (list): list of fractions storing individual encrypted tensor items
cs: cryptosystem
"""
self.fractions = fractions
self.cs = cs

def __str__(self):
"""
Print encrypted tensor object
"""
results = []
for i in self.fractions:
results.append(f"{i}")
return ", ".join(results)

def __repr__(self):
"""
Print encrypted tensor object
"""
return self.__str__()

def __mul__(self, other: Union["EncryptedTensor", int, float]) -> "EncryptedTensor":
if isinstance(other, EncryptedTensor) and len(self.fractions) != len(other.fractions):
raise ValueError("Tensor sizes must be equal in homomorphic multiplication")

# TODO: cover scalar multiplication here

fractions = []
for i, alpha_tensor in enumerate(self.fractions):
beta_tensor = other.fractions[i]

current_dividend = self.cs.multiply(
ciphertext1=alpha_tensor.abs_dividend, ciphertext2=beta_tensor.dividend
)

current_divisor = self.cs.multiply(
ciphertext1=alpha_tensor.divisor, ciphertext2=beta_tensor.divisor
)

fraction = Fraction(
dividend=current_dividend,
abs_dividend=current_dividend,
divisor=current_divisor,
sign=alpha_tensor.sign * beta_tensor.sign,
"""
Perform homomorphic multipliction on tensors or multiplication of an encrypted tensor with a constant
Args:
other: encrypted tensor or constant
Returns:
encrypted tensor
"""
if isinstance(other, EncryptedTensor):
if isinstance(other, EncryptedTensor) and len(self.fractions) != len(other.fractions):
raise ValueError("Tensor sizes must be equal in homomorphic multiplication")

fractions = []
for i, alpha_tensor in enumerate(self.fractions):
beta_tensor = other.fractions[i]

current_dividend = self.cs.multiply(
ciphertext1=alpha_tensor.abs_dividend, ciphertext2=beta_tensor.dividend
)

current_divisor = self.cs.multiply(
ciphertext1=alpha_tensor.divisor, ciphertext2=beta_tensor.divisor
)

fraction = Fraction(
dividend=current_dividend,
abs_dividend=current_dividend,
divisor=current_divisor,
sign=alpha_tensor.sign * beta_tensor.sign,
)

fractions.append(fraction)

return EncryptedTensor(fractions=fractions, cs=self.cs)
elif isinstance(other, (int, float)):
if isinstance(other, float):
other = phe_utils.parse_int(value=other, modulo=self.cs.plaintext_modulo)

fractions = []
for alpha_tensor in self.fractions:
dividend = self.cs.multiply_by_contant(
ciphertext=alpha_tensor.dividend, constant=other
)
abs_dividend = self.cs.multiply_by_contant(
ciphertext=alpha_tensor.abs_dividend, constant=other
)
fraction = Fraction(
dividend=dividend,
abs_dividend=abs_dividend,
divisor=alpha_tensor.divisor,
sign=alpha_tensor.sign,
)
fractions.append(fraction)
return EncryptedTensor(fractions=fractions, cs=self.cs)
else:
raise ValueError(
"Encrypted tensor can be multiplied by an encrypted tensor or constant"
)

fractions.append(fraction)

return EncryptedTensor(fractions=fractions, cs=self.cs)
def __rmul__(self, constant: Union[int, float]) -> "EncryptedTensor":
"""
Perform multiplication of encrypted tensor with a constant
Args:
constant: scalar value
Returns:
encrypted tensor
"""
return self.__mul__(other=constant)

def __add__(self, other: "EncryptedTensor") -> "EncryptedTensor":
"""
Perform homomorphic addition
Args:
other: encrypted tensor
Returns:
encrypted tensor
"""
if len(self.fractions) != len(other.fractions):
raise ValueError("Fraction sizes must be equal")

Expand Down
54 changes: 54 additions & 0 deletions tests/test_tensors.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,63 @@ def test_homomorphic_multiplication():
with pytest.raises(ValueError):
_ = c1 + c2

with pytest.raises(ValueError):
_ = c2 * 2

logger.info("✅ Homomorphic multiplication tests succeeded")


def test_homomorphic_multiply_by_a_constant():
cs = LightPHE(algorithm_name="Paillier")

t1 = [5, 6.2, -7.002, 7.002, 8.02]
constant = 2
c1: EncryptedTensor = cs.encrypt(t1)

c2 = c1 * constant

t2 = cs.decrypt(c2)

for i, restored_tensor in enumerate(t2):
assert abs((t1[i] * constant) - restored_tensor) < THRESHOLD

logger.info("✅ Homomorphic multiplication by a constant tests succeeded")


def test_homomorphic_multiply_with_int_constant():
cs = LightPHE(algorithm_name="Paillier")

t1 = [5, 6.2, 7.002, -7.002, 8.02]
constant = 2
c1: EncryptedTensor = cs.encrypt(t1)

c2 = constant * c1

t2 = cs.decrypt(c2)

for i, restored_tensor in enumerate(t2):
assert abs((t1[i] * constant) - restored_tensor) < THRESHOLD

logger.info("✅ Homomorphic multiplication with an integer constant tests succeeded")


def test_homomorphic_multiply_with_float_constant():
cs = LightPHE(algorithm_name="Paillier")

t1 = [10000.0, 15000, 20000]
constant = 1.05
c1: EncryptedTensor = cs.encrypt(t1)

c2 = constant * c1

t2 = cs.decrypt(c2)

for i, restored_tensor in enumerate(t2):
assert abs((t1[i] * constant) - restored_tensor) < THRESHOLD

logger.info("✅ Homomorphic multiplication with a float constant tests succeeded")


def test_homomorphic_addition():
cs = LightPHE(algorithm_name="Paillier", key_size=30)

Expand Down

0 comments on commit a4e8d70

Please sign in to comment.