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 _get_sufficient_sk_power(self, max_power): """Generate an list of secret key polynomial raised to 1...max_power. Args: max_power: heighest power up to which we want to raise secretkey. Returns: A 2-dim list having secretkey powers. """ if max_power == len(self._secret_key_array): return self._secret_key_array while len(self._secret_key_array) < max_power: sk_extra_power = [0] * len(self._coeff_modulus) for i in range(len(self._coeff_modulus)): sk_extra_power[i] = poly_mul_mod( self._secret_key_array[-1][i], self._secret_key_array[0][i], self._coeff_modulus[i], self._coeff_count, ) self._secret_key_array.append(sk_extra_power) return self._secret_key_array
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 _get_sk_power_2(self, sk): sk_power_2 = [] for i in range(len(self._coeff_modulus)): sk_power_2.append( poly_mul_mod(sk[i], sk[i], self._coeff_modulus[i], self._poly_modulus)) return sk_power_2
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 _get_sufficient_sk_power(self, max_power): """Generate an list of secret key polynomial raised to 1...max_power. Args: max_power: heighest power up to which we want to raise secretkey. Returns: A 2-dim list having secretkey powers. """ param = self.context.context_data_map[self.context.key_param_id].param coeff_modulus = param.coeff_modulus coeff_count = param.poly_modulus if max_power == len(self.secret_key_list): return self.secret_key_list while len(self.secret_key_list) < max_power: sk_extra_power = [0] * len(coeff_modulus) for i in range(len(coeff_modulus)): sk_extra_power[i] = poly_mul_mod( self.secret_key_list[-1][i], self.secret_key_list[0][i], coeff_modulus[i], coeff_count, ) self.secret_key_list.append(sk_extra_power) return self.secret_key_list
def _mul_plain_plain(self, pt1, pt2): """Multiply two operands using FV scheme. Args: op1 (Plaintext): First polynomial argument (Multiplicand). op2 (Plaintext): Second polynomial argument (Multiplier). Returns: A Ciphertext object with a value equivalent to the result of the product of two operands. """ pt1, pt2 = pt1.data, pt2.data return PlainText(poly_mul_mod(pt1, pt2, self.plain_modulus, self.poly_modulus))
def _mul_cipher_plain(self, ct, pt): """Multiply two operands using FV scheme. Args: op1 (Ciphertext): First polynomial argument (Multiplicand). op2 (Plaintext): Second polynomial argument (Multiplier). Returns: A Ciphertext object with a value equivalent to the result of the product of two operands. """ ct, pt = ct.data, pt.data result = copy.deepcopy(ct) for i in range(len(result)): for j in range(len(self.coeff_modulus)): result[i][j] = poly_mul_mod(ct[i][0], pt, self.coeff_modulus[j], self.poly_modulus) return CipherText(result)
def test_poly_mul_mod(op1, op2, mod, result): print("test poly_mul_mod : ", poly_mul_mod(op1, op2, mod)) assert poly_mul_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 test_poly_mul_mod(op1, op2, coeff_mod, poly_mod, result): assert poly_mul_mod(op1, op2, coeff_mod, poly_mod) == 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)