def add(self, ciph1, ciph2): """Adds two ciphertexts. Adds two ciphertexts within the context. Args: ciph1 (Ciphertext): First ciphertext. ciph2 (Ciphertext): Second ciphertext. Returns: A Ciphertext which is the sum of the two ciphertexts. """ assert isinstance(ciph1, Ciphertext) assert isinstance(ciph2, Ciphertext) assert ciph1.scaling_factor == ciph2.scaling_factor, "Scaling factors are not equal. " \ + "Ciphertext 1 scaling factor: %d bits, Ciphertext 2 scaling factor: %d bits" \ % (math.log(ciph1.scaling_factor, 2), math.log(ciph2.scaling_factor, 2)) assert ciph1.modulus == ciph2.modulus, "Moduli are not equal. " \ + "Ciphertext 1 modulus: %d bits, Ciphertext 2 modulus: %d bits" \ % (math.log(ciph1.modulus, 2), math.log(ciph2.modulus, 2)) modulus = ciph1.modulus c0 = ciph1.c0.add(ciph2.c0, modulus) c0 = c0.mod_small(modulus) c1 = ciph1.c1.add(ciph2.c1, modulus) c1 = c1.mod_small(modulus) return Ciphertext(c0, c1, ciph1.scaling_factor, modulus)
def encrypt(self, plain): """Encrypts a message. Encrypts the message and returns the corresponding ciphertext. Args: plain (Plaintext): Plaintext to be encrypted. Returns: A ciphertext consisting of a pair of polynomials in the ciphertext space. """ p0 = self.public_key.p0 p1 = self.public_key.p1 random_vec = Polynomial(self.poly_degree, sample_triangle(self.poly_degree)) error1 = Polynomial(self.poly_degree, sample_triangle(self.poly_degree)) error2 = Polynomial(self.poly_degree, sample_triangle(self.poly_degree)) c0 = p0.multiply(random_vec, self.coeff_modulus, crt=self.crt_context) c0 = error1.add(c0, self.coeff_modulus) c0 = c0.add(plain.poly, self.coeff_modulus) c0 = c0.mod_small(self.coeff_modulus) c1 = p1.multiply(random_vec, self.coeff_modulus, crt=self.crt_context) c1 = error2.add(c1, self.coeff_modulus) c1 = c1.mod_small(self.coeff_modulus) return Ciphertext(c0, c1, plain.scaling_factor, self.coeff_modulus)
def subtract(self, ciph1, ciph2): """Subtracts second ciphertext from first ciphertext. Computes ciph1 - ciph2. Args: ciph1 (Ciphertext): First ciphertext. ciph2 (Ciphertext): Second ciphertext. Returns: A Ciphertext which is the difference between the two ciphertexts. """ assert isinstance(ciph1, Ciphertext) assert isinstance(ciph2, Ciphertext) assert ciph1.scaling_factor == ciph2.scaling_factor, "Scaling factors are not equal. " \ + "Ciphertext 1 scaling factor: %d bits, Ciphertext 2 scaling factor: %d bits" \ % (math.log(ciph1.scaling_factor, 2), math.log(ciph2.scaling_factor, 2)) assert ciph1.modulus == ciph2.modulus, "Moduli are not equal. " \ + "Ciphertext 1 modulus: %d bits, Ciphertext 2 modulus: %d bits" \ % (math.log(ciph1.modulus, 2), math.log(ciph2.modulus, 2)) modulus = ciph1.modulus c0 = ciph1.c0.subtract(ciph2.c0, modulus) c0 = c0.mod_small(modulus) c1 = ciph1.c1.subtract(ciph2.c1, modulus) c1 = c1.mod_small(modulus) return Ciphertext(c0, c1, ciph1.scaling_factor, modulus)
def switch_key(self, ciph, key): """Outputs ciphertext with switching key. Performs KS procedure as described in CKKS paper. Args: ciph (Ciphertext): Ciphertext to change. switching_key (PublicKey): Switching key. Returns: A Ciphertext which encrypts the same message under a different key. """ c0 = key.p0.multiply(ciph.c1, ciph.modulus * self.big_modulus, crt=self.crt_context) c0 = c0.mod_small(ciph.modulus * self.big_modulus) c0 = c0.scalar_integer_divide(self.big_modulus) c0 = c0.add(ciph.c0, ciph.modulus) c0 = c0.mod_small(ciph.modulus) c1 = key.p1.multiply(ciph.c1, ciph.modulus * self.big_modulus, crt=self.crt_context) c1 = c1.mod_small(ciph.modulus * self.big_modulus) c1 = c1.scalar_integer_divide(self.big_modulus) c1 = c1.mod_small(ciph.modulus) return Ciphertext(c0, c1, ciph.scaling_factor, ciph.modulus)
def encrypt(self, message): """Encrypts a message. Encrypts the message and returns the corresponding ciphertext. Args: message (Plaintext): Plaintext to be encrypted. Returns: A ciphertext consisting of a pair of polynomials in the ciphertext space. """ p0 = self.public_key.p0 p1 = self.public_key.p1 scaled_message = message.poly.scalar_multiply(self.scaling_factor, self.coeff_modulus) random_vec = Polynomial(self.poly_degree, sample_triangle(self.poly_degree)) error1 = Polynomial(self.poly_degree, sample_triangle(self.poly_degree)) error1 = Polynomial(self.poly_degree, [0] * self.poly_degree) error2 = Polynomial(self.poly_degree, sample_triangle(self.poly_degree)) error2 = Polynomial(self.poly_degree, [0] * self.poly_degree) c0 = error1.add(p0.multiply(random_vec, self.coeff_modulus), self.coeff_modulus).add(scaled_message, self.coeff_modulus) c1 = error2.add(p1.multiply(random_vec, self.coeff_modulus), self.coeff_modulus) return Ciphertext(c0, c1)
def relinearize(self, relin_key, c0, c1, c2): """Relinearizes a 3-dimensional ciphertext. Reduces 3-dimensional ciphertext back down to 2 dimensions. Args: relin_key (RelinKey): Relinearization keys. c0 (Polynomial): First component of ciphertext. c1 (Polynomial): Second component of ciphertext. c2 (Polynomial): Third component of ciphertext. Returns: A Ciphertext which has only two components. """ keys = relin_key.keys base = relin_key.base num_levels = len(keys) c2_decomposed = c2.base_decompose(base, num_levels) new_c0 = c0 new_c1 = c1 for i in range(num_levels): new_c0 = new_c0.add( keys[i][0].multiply(c2_decomposed[i], self.coeff_modulus), self.coeff_modulus) new_c1 = new_c1.add( keys[i][1].multiply(c2_decomposed[i], self.coeff_modulus), self.coeff_modulus) return Ciphertext(new_c0, new_c1)
def encrypt_with_secret_key(self, plain): """Encrypts a message with secret key encryption. Encrypts the message for secret key encryption and returns the corresponding ciphertext. Args: plain (Plaintext): Plaintext to be encrypted. Returns: A ciphertext consisting of a pair of polynomials in the ciphertext space. """ assert self.secret_key != None, 'Secret key does not exist' sk = self.secret_key.s random_vec = Polynomial(self.poly_degree, sample_triangle(self.poly_degree)) error = Polynomial(self.poly_degree, sample_triangle(self.poly_degree)) c0 = sk.multiply(random_vec, self.coeff_modulus, crt=self.crt_context) c0 = error.add(c0, self.coeff_modulus) c0 = c0.add(plain.poly, self.coeff_modulus) c0 = c0.mod_small(self.coeff_modulus) c1 = random_vec.scalar_multiply(-1, self.coeff_modulus) c1 = c1.mod_small(self.coeff_modulus) return Ciphertext(c0, c1, plain.scaling_factor, self.coeff_modulus)
def relinearize(self, relin_key, c0, c1, c2, new_scaling_factor, modulus): """Relinearizes a 3-dimensional ciphertext. Reduces 3-dimensional ciphertext back down to 2 dimensions. Args: relin_key (PublicKey): Relinearization keys. c0 (Polynomial): First component of ciphertext. c1 (Polynomial): Second component of ciphertext. c2 (Polynomial): Third component of ciphertext. new_scaling_factor (float): New scaling factor for ciphertext. modulus (int): Ciphertext modulus. Returns: A Ciphertext which has only two components. """ new_c0 = relin_key.p0.multiply(c2, modulus * self.big_modulus, crt=self.crt_context) new_c0 = new_c0.mod_small(modulus * self.big_modulus) new_c0 = new_c0.scalar_integer_divide(self.big_modulus) new_c0 = new_c0.add(c0, modulus) new_c0 = new_c0.mod_small(modulus) new_c1 = relin_key.p1.multiply(c2, modulus * self.big_modulus, crt=self.crt_context) new_c1 = new_c1.mod_small(modulus * self.big_modulus) new_c1 = new_c1.scalar_integer_divide(self.big_modulus) new_c1 = new_c1.add(c1, modulus) new_c1 = new_c1.mod_small(modulus) return Ciphertext(new_c0, new_c1, new_scaling_factor, modulus)
def add_plain(self, cipher, plain): assert isinstance(cipher, Ciphertext) assert isinstance(plain, Plaintext) assert cipher.scaling_factor == plain.scaling_factor, "Scaling factors are not equal. " \ + "Ciphertext scaling factor: %d bits, Plaintext scaling factor: %d bits" \ % (math.log(cipher.scaling_factor, 2), math.log(plain.scaling_factor, 2)) c0 = cipher.c0.add(plain.poly, cipher.modulus) c0 = c0.mod_small(cipher.modulus) return Ciphertext(c0, cipher.c1, cipher.scaling_factor, cipher.modulus)
def add(self, cipher1, cipher2): assert isinstance(cipher1, Ciphertext) assert isinstance(cipher2, Ciphertext) assert cipher1.scaling_factor == cipher2.scaling_factor, "Scaling factors are not equal. " \ + "Ciphertext 1 scaling factor: %d bits, Ciphertext 2 scaling factor: %d bits" \ % (math.log(cipher1.scaling_factor, 2), math.log(cipher2.scaling_factor, 2)) assert cipher1.modulus == cipher2.modulus, "Moduli are not equal. " \ + "Ciphertext 1 modulus: %d bits, Ciphertext 2 modulus: %d bits" \ % (math.log(cipher1.modulus, 2), math.log(cipher2.modulus, 2)) modulus = cipher1.modulus c0 = cipher1.c0.add(cipher2.c0, modulus) c1 = cipher1.c1.add(cipher2.c1, modulus) return Ciphertext(c0, c1, cipher1.scaling_factor, cipher1.modulus)
def multiply_plain(self, cipher, plain): assert isinstance(cipher, Ciphertext) assert isinstance(plain, Plaintext) c0 = cipher.c0.multiply(plain.poly, cipher.modulus, crt=self.crt_context) c0 = c0.mod_small(cipher.modulus) c1 = cipher.c1.multiply(plain.poly, cipher.modulus, crt=self.crt_context) c1 = c1.mod_small(cipher.modulus) return Ciphertext(c0, c1, cipher.scaling_factor * plain.scaling_factor, cipher.modulus)
def encrypt(self, plain): p0 = self.public_key[0] p1 = self.public_key[1] random_vec = Poly(self.poly_degree, sample_triangle(self.poly_degree)) error1 = Poly(self.poly_degree, sample_triangle(self.poly_degree)) error2 = Poly(self.poly_degree, sample_triangle(self.poly_degree)) c0 = p0.multiply(random_vec, self.coeff_modulus, crt=self.crt_context) c0 = error1.add(c0, self.coeff_modulus) c0 = c0.add(plain.poly, self.coeff_modulus) c0 = c0.mod_small(self.coeff_modulus) c1 = p1.multiply(random_vec, self.coeff_modulus, crt=self.crt_context) c1 = error2.add(c1, self.coeff_modulus) c1 = c1.mod_small(self.coeff_modulus) return Ciphertext(c0, c1, plain.scaling_factor, self.coeff_modulus)
def rescale(self, ciph, division_factor): """Rescales a ciphertext to a new scaling factor. Divides ciphertext by division factor, and updates scaling factor and ciphertext. modulus. Args: ciph (Ciphertext): Ciphertext to modify. division_factor (float): Factor to divide by. Returns: Rescaled ciphertext. """ c0 = ciph.c0.scalar_integer_divide(division_factor) c1 = ciph.c1.scalar_integer_divide(division_factor) return Ciphertext(c0, c1, ciph.scaling_factor // division_factor, ciph.modulus // division_factor)
def lower_modulus(self, ciph, division_factor): """Rescales a ciphertext to a new scaling factor. Divides ciphertext by division factor, and updates scaling factor and ciphertext modulus. Args: ciph (Ciphertext): Ciphertext to modify. division_factor (float): Factor to divide by. Returns: Rescaled ciphertext. """ new_modulus = ciph.modulus // division_factor c0 = ciph.c0.mod_small(new_modulus) c1 = ciph.c1.mod_small(new_modulus) return Ciphertext(c0, c1, ciph.scaling_factor, new_modulus)
def add(self, ciph1, ciph2): """Adds two ciphertexts. Adds two ciphertexts within the context. Args: ciph1 (Ciphertext): First ciphertext. ciph2 (Ciphertext): Second ciphertext. Returns: A Ciphertext which is the sum of the two ciphertexts. """ assert isinstance(ciph1, Ciphertext) assert isinstance(ciph2, Ciphertext) new_ciph_c0 = ciph1.c0.add(ciph2.c0, self.coeff_modulus) new_ciph_c1 = ciph1.c1.add(ciph2.c1, self.coeff_modulus) return Ciphertext(new_ciph_c0, new_ciph_c1)
def conjugate(self, ciph, conj_key): """Conjugates the ciphertext. Returns a ciphertext for a plaintext which is conjugated. Args: ciph (Ciphertext): Ciphertext to conjugate. conj_key (PublicKey): Conjugation key. Returns: A Ciphertext which is the encryption of the conjugation of the original plaintext. """ conj_ciph0 = ciph.c0.conjugate().mod_small(ciph.modulus) conj_ciph1 = ciph.c1.conjugate().mod_small(ciph.modulus) conj_ciph = Ciphertext(conj_ciph0, conj_ciph1, ciph.scaling_factor, ciph.modulus) return self.switch_key(conj_ciph, conj_key)
def rotate(self, ciph, rotation, rot_key): """Rotates a ciphertext by the amount specified in rotation. Returns a ciphertext for a plaintext which is rotated by the amount in rotation. Args: ciph (Ciphertext): Ciphertext to rotate. rotation (int): Amount to rotate by. rot_key (RotationKey): Rotation key corresponding to the rotation. Returns: A Ciphertext which is the encryption of the rotation of the original plaintext. """ rot_ciph0 = ciph.c0.rotate(rotation) rot_ciph1 = ciph.c1.rotate(rotation) rot_ciph = Ciphertext(rot_ciph0, rot_ciph1, ciph.scaling_factor, ciph.modulus) return self.switch_key(rot_ciph, rot_key.key)
def run_test_simple_rotate(self, message, rot): poly = Polynomial(self.degree // 2, message) plain = self.encoder.encode(message, self.scaling_factor) rot_message = [0] * poly.ring_degree for i in range(poly.ring_degree): rot_message[i] = poly.coeffs[(i + rot) % poly.ring_degree] ciph = self.encryptor.encrypt(plain) ciph_rot0 = ciph.c0.rotate(rot).mod_small(self.ciph_modulus) ciph_rot1 = ciph.c1.rotate(rot).mod_small(self.ciph_modulus) ciph_rot = Ciphertext(ciph_rot0, ciph_rot1, ciph.scaling_factor, self.ciph_modulus) decryptor = CKKSDecryptor(self.params, SecretKey(self.secret_key.s.rotate(rot))) decrypted_rot = decryptor.decrypt(ciph_rot) decoded_rot = self.encoder.decode(decrypted_rot) check_complex_vector_approx_eq(rot_message, decoded_rot, error=0.005)
def add_plain(self, ciph, plain): """Adds a ciphertext with a plaintext. Adds a ciphertext with a plaintext polynomial within the context. Args: ciph (Ciphertext): A ciphertext to add. plain (Plaintext): A plaintext to add. Returns: A Ciphertext which is the sum of the ciphertext and plaintext. """ assert isinstance(ciph, Ciphertext) assert isinstance(plain, Plaintext) assert ciph.scaling_factor == plain.scaling_factor, "Scaling factors are not equal. " \ + "Ciphertext scaling factor: %d bits, Plaintext scaling factor: %d bits" \ % (math.log(ciph.scaling_factor, 2), math.log(plain.scaling_factor, 2)) c0 = ciph.c0.add(plain.poly, ciph.modulus) c0 = c0.mod_small(ciph.modulus) return Ciphertext(c0, ciph.c1, ciph.scaling_factor, ciph.modulus)
def multiply_plain(self, ciph, plain): """Multiplies a ciphertext with a plaintext. Multiplies a ciphertext with a plaintext polynomial within the context. Args: ciph (Ciphertext): A ciphertext to multiply. plain (Plaintext): A plaintext to multiply. Returns: A Ciphertext which is the product of the ciphertext and plaintext. """ assert isinstance(ciph, Ciphertext) assert isinstance(plain, Plaintext) c0 = ciph.c0.multiply(plain.poly, ciph.modulus, crt=self.crt_context) c0 = c0.mod_small(ciph.modulus) c1 = ciph.c1.multiply(plain.poly, ciph.modulus, crt=self.crt_context) c1 = c1.mod_small(ciph.modulus) return Ciphertext(c0, c1, ciph.scaling_factor * plain.scaling_factor, ciph.modulus)