#!/usr/bin/env python3
import random
# Alice's Private/Public key pair, hard coded for simplicity
class PrivateKey():
	lambdA=73842165240981452554905699590449542088961757004289873177979834078905122488912
	mu=14623866067924162049758185900912462985982902037214491087468255488155427133263

class PublicKey():
	n=73842165240981452554905699590449542089513117936657660262085094366199671389241
	n2=5452665367476409421070096932669081750981552257584472365472731868555542659457163641469759175897028833132670904633495629764635203858875472896093430930556081
	g=73842165240981452554905699590449542089513117936657660262085094366199671389242

# Alice's Implementation of a fully homomorphic Paillier cipher
class FullyHomoCipher():
	def __init__(self, a1, b1):
		self.a = a1
		self.b = b1

	def expCalc(self, base,exponent,modulus):
		result = 1
		while exponent > 0:
			if exponent & 1 == 1:
				result = (result * base) % modulus
			exponent = exponent >> 1
			base = (base * base) % modulus
		return result

	def encrypt(self, pub, plain):
		while True: 
			r = random.getrandbits(128)
			if r > 0 and r < pub.n:
				break
		x = self.expCalc(r, pub.n, pub.n2)
		cipher = (self.expCalc(pub.g, plain, pub.n2) * x) % pub.n2
		return cipher
		
	def decrypt(self, priv, pub, cipher):
		x = self.expCalc(cipher, priv.lambdA, pub.n2) - 1
		plain = ((x // pub.n) * priv.mu) % pub.n
		return plain    

	def encrypt_message(self, pub, m):
		r = random.randrange(256, pub.n)
		b = self.encrypt(pub, r)
		a = (m-r) % pub.n
		self.a = a
		self.b = b
       
	def decrypt_message(self, priv, pub):
		val = (self.a + self.decrypt(priv, pub, self.b)) % pub.n
		return val

# Bob's encrypted calculation service
class BobsCalculationService():
	# Add two encrypted numbers
	def encrypted_add(self, pub, a, b):
		return a * b % pub.n2

	def sum (self, c1, c2, pub):
		a = (c1.a + c2.a) % pub.n
		b = self.encrypted_add(pub, c1.b, c2.b)
		c = FullyHomoCipher(a, b)
		return c

	# Multiply two encrypted numbers with a constant (Bob)
	def encrypted_mult(self, pub, a, n):
		return FullyHomoCipher(-1,-1).expCalc(a, n, pub.n2)

	def product(self, const, c1, pub):
		a = (c1.a * const) % pub.n
		b = self.encrypted_mult(pub, c1.b, const)
		c = FullyHomoCipher(a,b)
		return c
		
# THE SAAS EXAMPLE BEGINS HERE
if __name__ == '__main__':
	# Alice's Key Pair
	pub=PublicKey
	priv=PrivateKey
	
	# The top secret numbers Alice wants to use
	secretNumber1=5
	secretNumber2=10
	secretNumber3=6
	const=3
	
	# The Cipher objects Alice uses for encryption
	alice1 = FullyHomoCipher(-1,-1)
	alice2 = FullyHomoCipher(-1,-1)
	alice3 = FullyHomoCipher(-1,-1)
	
	# Alice performs encryption
	print ("SaaS Example:")
	print ("Alice wants to use Bob's calculation service to calculate ",secretNumber1,"+",secretNumber2)
	print ("She encrypts ", secretNumber1)
	alice1.encrypt_message(pub, secretNumber1)
	print ("...and the encrypted value is ",alice1.a,alice1.b)
	print ("She then encrypts ",secretNumber2)
	alice2.encrypt_message(pub, secretNumber2)
	print ("...and the encypted value is ",alice2.a,alice2.b)
	print ("")
	print ("Alice also wants to to multiply ",secretNumber3," with the constant ",const)
	print ("She encrypts ", secretNumber3)
	alice3.encrypt_message(pub, secretNumber3)
	print ("...and the encrypted value is ",alice3.a,alice3.b)
	print ("")
	print ("Then Alice sends the encrypted values to Bob along with her public key")
	print ("")
	
	# These are the encrypted values Alice sends to Bob
	encr1_a=alice1.a
	encr1_b=alice1.b
	encr2_a=alice2.a
	encr2_b=alice2.b
	encr3_a=alice3.a
	encr3_b=alice3.b

	# Bob's Cipher objects, initialized with Alice's encrypted numbers
	# Since Bob doesn't have the private key he can't decrypt the numbers
	bob1 = FullyHomoCipher(encr1_a,encr1_b)
	bob2 = FullyHomoCipher(encr2_a,encr2_b)
	bob3 = FullyHomoCipher(encr3_a,encr3_b)
	
	# Addition 
	print ("Bob adds the two encrypted values without knowing what they are")
	result1=BobsCalculationService().sum(bob1, bob2, pub)
	print ("the encrypted result is ",result1.a,result1.b)
	# Multiplication with a constant
	print ("Bob multiplies the third encrypted value with the constant")
	result2=BobsCalculationService().product(const, bob3, pub)
	print ("the encrypted result is ",result2.a,result2.b)
	print ("")

	print ("Bob sends the encrypted results back to Alice")
	print ("Alice uses her private key to view the plain text results:")
	print ("Addition: ",result1.decrypt_message(priv, pub))
	print ("Multiplication: ",result2.decrypt_message(priv, pub))



