Skip to content

Commit

Permalink
vector operations
Browse files Browse the repository at this point in the history
  • Loading branch information
serengil committed Dec 18, 2023
1 parent a4e8d70 commit 2893414
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 59 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Even though fully homomorphic encryption (FHE) has become available in recent ti
- 🧠 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
- 🗝️ 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,7 +161,7 @@ However, if you tried to multiply ciphertexts with RSA, or xor ciphertexts with

# Working with vectors

You can encrypt the output tensors 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.

```python
# build an additively homomorphic cryptosystem
Expand Down
24 changes: 3 additions & 21 deletions lightphe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,40 +157,28 @@ def __encrypt_tensors(self, tensor: list) -> EncryptedTensor:
"""
encrypted_tensor: List[Fraction] = []
for m in tensor:
sign = 1 if m >= 0 else -1
if isinstance(m, int):
dividend_encrypted = self.cs.encrypt(
plaintext=(m % self.cs.plaintext_modulo) * pow(10, self.precision)
)
abs_dividend_encrypted = self.cs.encrypt(
plaintext=(abs(m) % self.cs.plaintext_modulo) * pow(10, self.precision)
)
divisor_encrypted = self.cs.encrypt(plaintext=pow(10, self.precision))
c = Fraction(
dividend=dividend_encrypted,
abs_dividend=abs_dividend_encrypted,
divisor=divisor_encrypted,
sign=sign,
)
elif isinstance(m, float):
dividend, divisor = phe_utils.fractionize(
value=(m % self.cs.plaintext_modulo),
modulo=self.cs.plaintext_modulo,
precision=self.precision,
)
abs_dividend, _ = phe_utils.fractionize(
value=(abs(m) % self.cs.plaintext_modulo),
modulo=self.cs.plaintext_modulo,
precision=self.precision,
)
# print(f"{m} mod n = {(m % self.cs.plaintext_modulo)} = {dividend} / {divisor}")
# print(f" = {dividend / divisor}")
dividend_encrypted = self.cs.encrypt(plaintext=dividend)
abs_dividend_encrypted = self.cs.encrypt(plaintext=abs_dividend)
divisor_encrypted = self.cs.encrypt(plaintext=divisor)
c = Fraction(
dividend=dividend_encrypted,
abs_dividend=abs_dividend_encrypted,
divisor=divisor_encrypted,
sign=sign,
)
else:
raise ValueError(f"unimplemented type - {type(m)}")
Expand All @@ -211,14 +199,8 @@ def __decrypt_tensors(self, encrypted_tensor: EncryptedTensor) -> Union[List[int
raise ValueError("Ciphertext items must be type of Fraction")

dividend = self.cs.decrypt(ciphertext=c.dividend)
abs_dividend = self.cs.decrypt(ciphertext=c.abs_dividend)
divisor = self.cs.decrypt(ciphertext=c.divisor)
sign = c.sign

if sign == 1:
m = dividend / divisor
else:
m = -1 * (abs_dividend / divisor)
m = dividend / divisor

plain_tensor.append(m)
return plain_tensor
Expand Down
10 changes: 1 addition & 9 deletions lightphe/models/Ciphertext.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,7 @@ def __rmul__(self, constant: Union[int, float]) -> "Ciphertext":
Returns
scalar multiplication of ciphertext
"""
if self.cs.keys.get("public_key") is None:
raise ValueError("You must have public key to perform scalar multiplication")

if isinstance(constant, float):
constant = phe_utils.parse_int(value=constant, modulo=self.cs.plaintext_modulo)

# Handle multiplication with a constant on the right
result = self.cs.multiply_by_contant(ciphertext=self.value, constant=constant)
return Ciphertext(algorithm_name=self.algorithm_name, keys=self.keys, value=result)
return self.__mul__(other=constant)

def __xor__(self, other: "Ciphertext") -> "Ciphertext":
"""
Expand Down
21 changes: 4 additions & 17 deletions lightphe/models/Tensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,16 @@ class Fraction:
def __init__(
self,
dividend: Union[int, tuple, list],
abs_dividend: Union[int, tuple, list],
divisor: Union[int, tuple, list],
sign: int,
):
self.dividend = dividend
self.abs_dividend = abs_dividend
self.divisor = divisor
self.sign = sign

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

def __repr__(self):
"""
Expand Down Expand Up @@ -82,7 +77,7 @@ def __mul__(self, other: Union["EncryptedTensor", int, float]) -> "EncryptedTens
beta_tensor = other.fractions[i]

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

current_divisor = self.cs.multiply(
Expand All @@ -91,9 +86,7 @@ def __mul__(self, other: Union["EncryptedTensor", int, float]) -> "EncryptedTens

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

fractions.append(fraction)
Expand All @@ -108,14 +101,10 @@ def __mul__(self, other: Union["EncryptedTensor", int, float]) -> "EncryptedTens
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
)
# notice that divisor is alpha tensor's divisor instead of addition
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)
Expand Down Expand Up @@ -152,12 +141,10 @@ def __add__(self, other: "EncryptedTensor") -> "EncryptedTensor":
current_dividend = self.cs.add(
ciphertext1=alpha_tensor.dividend, ciphertext2=beta_tensor.dividend
)

# notice that divisor is alpha tensor's divisor instead of addition
current_tensor = Fraction(
dividend=current_dividend,
abs_dividend=current_dividend,
divisor=alpha_tensor.divisor,
sign=1,
)

current_tensors.append(current_tensor)
Expand Down
37 changes: 27 additions & 10 deletions tests/test_tensors.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,42 @@
THRESHOLD = 0.5


def test_tensor_operations():
def convert_negative_float_to_int(value: float, modulo: int) -> float:
x, y = phe_utils.fractionize(
value=value % modulo,
modulo=modulo,
precision=5,
)
return int(x / y)


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

tensor = [1.005, 2.05, 3.005, 4.005, -5.05, 6, 7.003005]
tensor = [1.005, 2.05, 3.005, 4.005, -5.05, 6, 7.003005, -3.5 * 7.002]

encrypted_tensors = cs.encrypt(tensor)

decrypted_tensors = cs.decrypt(encrypted_tensors)

for i, decrypted_tensor in enumerate(decrypted_tensors):
assert abs(tensor[i] - decrypted_tensor) <= THRESHOLD
expected_tensor = tensor[i]
if expected_tensor >= 0:
assert abs(expected_tensor - decrypted_tensor) <= THRESHOLD
else:
expected_tensor_int = convert_negative_float_to_int(
expected_tensor, cs.cs.plaintext_modulo
)
assert abs(expected_tensor_int - decrypted_tensor) <= THRESHOLD

logger.info("✅ Tensor tests succeeded")


def test_homomorphic_multiplication():
cs = LightPHE(algorithm_name="RSA")

t1 = [1.005, 2.05, -3.5, 3.5, 4]
t2 = [5, 6.2, 7.002, 7.002, 8.02]
t1 = [1.005, 2.05, 3.5, 3.1, 4]
t2 = [5, 6.2, 7.002, 7.1, 8.02]

c1: EncryptedTensor = cs.encrypt(t1)
c2: EncryptedTensor = cs.encrypt(t2)
Expand All @@ -40,7 +56,8 @@ def test_homomorphic_multiplication():
restored_tensors = cs.decrypt(c3)

for i, restored_tensor in enumerate(restored_tensors):
assert abs((t1[i] * t2[i]) - restored_tensor) < THRESHOLD
if (t1[i] > 0 and t2[i] > 0) or (t1[i] < 0 and t2[i] < 0):
assert abs((t1[i] * t2[i]) - restored_tensor) < THRESHOLD

with pytest.raises(ValueError):
_ = c1 + c2
Expand All @@ -54,7 +71,7 @@ def test_homomorphic_multiplication():
def test_homomorphic_multiply_by_a_constant():
cs = LightPHE(algorithm_name="Paillier")

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

Expand All @@ -71,7 +88,7 @@ def test_homomorphic_multiply_by_a_constant():
def test_homomorphic_multiply_with_int_constant():
cs = LightPHE(algorithm_name="Paillier")

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

Expand Down Expand Up @@ -105,8 +122,8 @@ def test_homomorphic_multiply_with_float_constant():
def test_homomorphic_addition():
cs = LightPHE(algorithm_name="Paillier", key_size=30)

t1 = [1.005, 2.05, -3.5, 4]
t2 = [5, 6.2, 7.002, 8.02]
t1 = [1.005, 2.05, 3.5, 4, 3.5]
t2 = [5, 6.2, 7.002, 8.02, 4.5]

c1: EncryptedTensor = cs.encrypt(t1)
c2: EncryptedTensor = cs.encrypt(t2)
Expand Down

0 comments on commit 2893414

Please sign in to comment.