def _add_cipher_cipher(self, ct1, ct2): """Adds two ciphertexts. Args: ct1 (Ciphertext): First polynomial argument (Augend). ct2 (Ciphertext): Second polynomial argument (Addend). Returns: A Ciphertext object with value equivalent to result of addition of two provided arguments. """ param_id = ct1.param_id context_data = self.context.context_data_map[param_id] coeff_modulus = context_data.param.coeff_modulus ct1, ct2 = copy.deepcopy(ct1.data), copy.deepcopy(ct2.data) result = ct2 if len(ct2) > len(ct1) else ct1 for i in range(min(len(ct1), len(ct2))): for j in range(len(coeff_modulus)): result[i][j] = poly_add_mod(ct1[i][j], ct2[i][j], coeff_modulus[j], self.poly_modulus) return CipherText(result, param_id)
def _mul_ct_sk(self, encrypted): """Calculate [c0 + c1 * sk + c2 * sk^2 ...]_q where [c0, c1, ...] represents ciphertext element and sk^n represents secret key raised to the power n. Args: encrypted: A ciphertext object of encrypted data. Returns: A 2-dim list containing result of [c0 + c1 * sk + c2 * sk^2 ...]_q. """ phase = encrypted[0] secret_key_array = self._get_sufficient_sk_power(len(encrypted) - 1) for j in range(1, len(encrypted)): for i in range(len(self._coeff_modulus)): phase[i] = poly_add_mod( poly_mul_mod( encrypted[j][i], secret_key_array[j - 1][i], self._coeff_modulus[i], self._coeff_count, ), phase[i], self._coeff_modulus[i], self._coeff_count, ) return phase
def encrypt_symmetric(context, secret_key): """Create encryption of zero values with a secret key which can be used in subsequent processes to add a message into it. Args: context (Context): A valid context required for extracting the encryption parameters. secret_key (SecretKey): A secret key generated with same encryption parameters. Returns: A ciphertext object containing encryption of zeroes by symmetric encryption procedure. """ coeff_modulus = context.param.coeff_modulus coeff_mod_size = len(coeff_modulus) # Sample uniformly at random c1 = sample_poly_uniform(context.param) # Sample e <-- chi e = sample_poly_normal(context.param) # calculate -(a*s + e) (mod q) and store in c0 c0 = [0] * coeff_mod_size for i in range(coeff_mod_size): c0[i] = poly_negate_mod( poly_add_mod( poly_mul_mod(c1[i], secret_key[i], coeff_modulus[i]), e[i], coeff_modulus[i] ), coeff_modulus[i], ) return CipherText([c0, c1])
def encrypt_asymmetric(context, public_key): """Create encryption of zero values with a public key which can be used in subsequent processes to add a message into it. Args: context (Context): A valid context required for extracting the encryption parameters. public_key (PublicKey): A public key generated with same encryption parameters. Returns: A ciphertext object containing encryption of zeroes by asymmetric encryption procedure. """ param = context.param coeff_modulus = param.coeff_modulus coeff_mod_size = len(coeff_modulus) encrypted_size = len(public_key) # Generate u <-- R_3 u = sample_poly_ternary(param) c_0 = [0] * coeff_mod_size c_1 = [0] * coeff_mod_size result = [c_0, c_1] # c[i] = u * public_key[i] # Generate e_j <-- chi # c[i] = public_key[i] * u + e[i] for j in range(encrypted_size): e = sample_poly_normal(param) for i in range(coeff_mod_size): result[j][i] = poly_add_mod( poly_mul_mod(public_key[j][i], u[i], coeff_modulus[i]), e[i], coeff_modulus[i] ) return CipherText(result)
def _add_plain_plain(self, pt1, pt2): """Adds two plaintexts object. Args: pt1 (Plaintext): First argument. pt2 (Plaintext): Second argument. Returns: A Plaintext object with value equivalent to result of addition of two provided arguments. """ pt1, pt2 = copy.deepcopy(pt1), copy.deepcopy(pt2) return PlainText(poly_add_mod(pt1.data, pt2.data, self.plain_modulus))
def _generate_one_kswitch_key(self, sk_power_2): decomp_mod_count = len(self._coeff_modulus) - 1 result = [0] * (decomp_mod_count) for i in range(decomp_mod_count): result[i] = encrypt_symmetric(self._context, self._context.key_param_id, self._secret_key).data factor = self._coeff_modulus[-1] % self._coeff_modulus[i] temp = [(x * factor) for x in sk_power_2[i]] result[i][0][i] = poly_add_mod(result[i][0][i], temp, self._coeff_modulus[i], self._poly_modulus) return result
def _add_cipher_cipher(self, ct1, ct2): """Adds two ciphertexts. Args: ct1 (Ciphertext): First argument. ct2 (Ciphertext): Second argument. Returns: A Ciphertext object with value equivalent to result of addition of two provided arguments. """ ct1, ct2 = copy.deepcopy(ct1.data), copy.deepcopy(ct2.data) result = ct2 if len(ct2) > len(ct1) else ct1 for i in range(min(len(ct1), len(ct2))): for j in range(len(self.coeff_modulus)): result[i][j] = poly_add_mod(ct1[i][j], ct2[i][j], self.coeff_modulus[j]) return CipherText(result)
def test_poly_add_mod(op1, op2, mod, result): assert poly_add_mod(op1, op2, mod) == result
def _mul_cipher_cipher(self, ct1, ct2): """Multiply two operands using FV scheme. Args: op1 (Ciphertext): First polynomial argument (Multiplicand). op2 (Ciphertext): Second polynomial argument (Multiplier). Returns: A Ciphertext object with a value equivalent to the result of the product of two operands. """ ct1, ct2 = ct1.data, ct2.data if len(ct1) > 2 or len(ct2) > 2: # TODO: perform relinearization operation. raise Warning("Multiplying ciphertext of size >2 should be avoided.") rns_tool = self.context.rns_tool bsk_base_mod = rns_tool.base_Bsk.base bsk_base_mod_count = len(bsk_base_mod) dest_count = len(ct2) + len(ct1) - 1 # Step 0: fast base convert from q to Bsk U {m_tilde} # Step 1: reduce q-overflows in Bsk # Iterate over all the ciphertexts inside ct1 tmp_encrypted1_bsk = [] for i in range(len(ct1)): tmp_encrypted1_bsk.append(rns_tool.sm_mrq(rns_tool.fastbconv_m_tilde(ct1[i]))) # Similarly, itterate over all the ciphertexts inside ct2 tmp_encrypted2_bsk = [] for i in range(len(ct2)): tmp_encrypted2_bsk.append(rns_tool.sm_mrq(rns_tool.fastbconv_m_tilde(ct2[i]))) tmp_des_coeff_base = [ [[0] for y in range(len(self.coeff_modulus))] for x in range(dest_count) ] tmp_des_bsk_base = [[[0] for y in range(bsk_base_mod_count)] for x in range(dest_count)] for m in range(dest_count): # Loop over encrypted1 components [i], seeing if a match exists with an encrypted2 # component [j] such that [i+j]=[m] # Only need to check encrypted1 components up to and including [m], # and strictly less than [encrypted_array.size()] current_encrypted1_limit = min(len(ct1), m + 1) for encrypted1_index in range(current_encrypted1_limit): # check if a corresponding component in encrypted2 exists if len(ct2) > m - encrypted1_index: encrypted2_index = m - encrypted1_index for i in range(len(self.coeff_modulus)): tmp_des_coeff_base[m][i] = poly_add_mod( poly_mul_mod( ct1[encrypted1_index][i], ct2[encrypted2_index][i], self.coeff_modulus[i], self.poly_modulus, ), tmp_des_coeff_base[m][i], self.coeff_modulus[i], self.poly_modulus, ) for i in range(bsk_base_mod_count): tmp_des_bsk_base[m][i] = poly_add_mod( poly_mul_mod( tmp_encrypted1_bsk[encrypted1_index][i], tmp_encrypted2_bsk[encrypted2_index][i], bsk_base_mod[i], self.poly_modulus, ), tmp_des_bsk_base[m][i], bsk_base_mod[i], self.poly_modulus, ) # Now we multiply plain modulus to both results in base q and Bsk and # allocate them together in one container as # (te0)q(te'0)Bsk | ... |te count)q (te' count)Bsk to make it ready for # fast_floor result = [] for i in range(dest_count): temp = [] for j in range(len(self.coeff_modulus)): temp.append( [ (x * self.plain_modulus) % self.coeff_modulus[j] for x in tmp_des_coeff_base[i][j] ] ) for j in range(bsk_base_mod_count): temp.append( [(x * self.plain_modulus) % bsk_base_mod[j] for x in tmp_des_bsk_base[i][j]] ) result.append(rns_tool.fastbconv_sk(rns_tool.fast_floor(temp))) return CipherText(result)
def _switch_key_inplace(self, ct, key): if not isinstance(key, RelinKey): raise RuntimeError("Relinearization key is invalid") param_id = ct.param_id ct = ct.data key_vector = key.data context_data = self.context.context_data_map[param_id] key_context = self.context.context_data_map[self.context.key_param_id] coeff_modulus = context_data.param.coeff_modulus decomp_mod_count = len(coeff_modulus) key_mod = key_context.param.coeff_modulus key_mod_count = len(key_mod) rns_mod_count = decomp_mod_count + 1 target = ct[-1] # Last component of ciphertext modswitch_factors = key_context.rns_tool.inv_q_last_mod_q for i in range(decomp_mod_count): local_small_poly_0 = copy.deepcopy(target[i]) temp_poly = [[[0] for x in range(rns_mod_count)], [[0] for x in range(rns_mod_count)]] for j in range(rns_mod_count): index = key_mod_count - 1 if j == decomp_mod_count else j if key_mod[i] <= key_mod[index]: local_small_poly_1 = copy.deepcopy(local_small_poly_0) else: local_small_poly_1 = [ x % key_mod[index] for x in local_small_poly_0 ] for k in range(2): local_small_poly_2 = poly_mul_mod( local_small_poly_1, key_vector[i][k][index], key_mod[index], self.poly_modulus, ) temp_poly[k][j] = poly_add_mod(local_small_poly_2, temp_poly[k][j], key_mod[index], self.poly_modulus) # Results are now stored in temp_poly[k] # Modulus switching should be performed for k in range(2): temp_poly_ptr = temp_poly[k][decomp_mod_count] temp_last_poly_ptr = temp_poly[k][decomp_mod_count] temp_poly_ptr = [x % key_mod[-1] for x in temp_poly_ptr] # Add (p-1)/2 to change from flooring to rounding. half = key_mod[-1] >> 1 temp_last_poly_ptr = [(x + half) % key_mod[-1] for x in temp_last_poly_ptr] encrypted_ptr = ct[k] for j in range(decomp_mod_count): temp_poly_ptr = temp_poly[k][j] temp_poly_ptr = [x % key_mod[j] for x in temp_poly_ptr] local_small_poly = [x % key_mod[j] for x in temp_last_poly_ptr] half_mod = half % key_mod[j] local_small_poly = [(x - half_mod) % key_mod[j] for x in local_small_poly] # ((ct mod qi) - (ct mod qk)) mod qi temp_poly_ptr = poly_sub_mod(temp_poly_ptr, local_small_poly, key_mod[j], self.poly_modulus) # qk^(-1) * ((ct mod qi) - (ct mod qk)) mod qi temp_poly_ptr = [(x * modswitch_factors[j]) % key_mod[j] for x in temp_poly_ptr] encrypted_ptr[j] = poly_add_mod(temp_poly_ptr, encrypted_ptr[j], key_mod[j], self.poly_modulus) return CipherText(ct[0:2], param_id)