def multiply_sub_plain_with_delta(ct, pt, context_data): """Subtract plaintext from ciphertext. Args: ct (Ciphertext): ct is pre-computed carrier polynomial where we can add message data. pt (Plaintext): A plaintext representation of integer data to be encrypted. context (Context): Context for extracting encryption parameters. Returns: A Ciphertext object with the encrypted result of encryption process. """ ct_param_id = ct.param_id coeff_modulus = context_data.param.coeff_modulus pt = pt.data plain_coeff_count = len(pt) delta = context_data.coeff_div_plain_modulus ct0, ct1 = ct.data # here ct = pk * u * e # Coefficients of plain m multiplied by coeff_modulus q, divided by plain_modulus t, # and rounded to the nearest integer (rounded up in case of a tie). Equivalent to for i in range(plain_coeff_count): for j in range(len(coeff_modulus)): temp = round(delta[j] * pt[i]) % coeff_modulus[j] ct0[j][i] = (ct0[j][i] - temp) % coeff_modulus[j] return CipherText([ct0, ct1], ct_param_id) # ct0 = pk0 * u * e - delta * pt
def multiply_add_plain_with_delta(phase, message, context): """Add message (PlainText) into phase. Args: phase: phase is pre-computed carrier polynomial where we can add message data. message (Plaintext): A plaintext representation of integer data to be encrypted. context (Context): Context for extracting encryption parameters. Returns: A Ciphertext object with the encrypted result of encryption process. """ coeff_modulus = context.param.coeff_modulus message = message.data plain_coeff_count = len(message) delta = context.coeff_div_plain_modulus phase0, phase1 = phase.data # here phase = pk * u * e # Coefficients of plain m multiplied by coeff_modulus q, divided by plain_modulus t, # and rounded to the nearest integer (rounded up in case of a tie). Equivalent to for i in range(plain_coeff_count): for j in range(len(coeff_modulus)): temp = round(delta[j] * message[i]) % coeff_modulus[j] phase0[j][i] = (phase0[j][i] + temp) % coeff_modulus[j] return CipherText([phase0, phase1]) # phase0 = pk0 * u * e + delta * m
def _sub_cipher_cipher(self, ct1, ct2): """Subtract two ciphertexts. Args: ct1 (Ciphertext): First polynomial argument (Minuend). ct2 (Ciphertext): Second polynomial argument (Subtrahend). Returns: A Ciphertext object with value equivalent to result of subtraction of two provided arguments. """ ct1, ct2 = copy.deepcopy(ct1.data), copy.deepcopy(ct2.data) result = ct2 if len(ct2) > len(ct1) else ct1 min_size, max_size = min(len(ct1), len(ct2)), max(len(ct1), len(ct2)) for i in range(min_size): for j in range(len(self.coeff_modulus)): result[i][j] = poly_sub_mod(ct1[i][j], ct2[i][j], self.coeff_modulus[j], self.poly_modulus) for i in range(min_size + 1, max_size): for j in range(len(self.coeff_modulus)): result[i][j] = poly_negate_mod(result[i][j], self.coeff_modulus[j]) return CipherText(result)
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 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 negate(self, ct): """Negate a cipher i.e -(ct_value) Args: ct (Ciphertext): Ciphertext to be negated. Returns: A Ciphertext object with value equivalent to result of -(ct_value). """ result = copy.deepcopy(ct.data) for i in range(len(result)): for j in range(len(result[i])): for k in range(len(result[i][j])): result[i][j][k] = negate_mod(ct.data[i][j][k], self.coeff_modulus[j]) return CipherText(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 _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 _encrypt(self, message, param_id, is_asymmetric): """Encrypts the message according to the key provided. Args: message (Plaintext): An Plaintext object which has to be encrypted. param_id: Parameter id for accessing the correct parameters from the context chain. is_asymmetric (bool): Based on the key provided for encryption select the mode of encryption. Returns: A Ciphertext object contating the encrypted result. """ result = None if is_asymmetric: prev_context_id = self.context.context_data_map[ param_id].prev_context_id if prev_context_id is not None: # Requires modulus switching prev_context_data = self.context.context_data_map[ prev_context_id] rns_tool = prev_context_data.rns_tool temp = encrypt_asymmetric(self.context, prev_context_id, self.key.data).data for j in range(2): temp[j] = rns_tool.divide_and_round_q_last_inplace(temp[j]) result = CipherText(temp, param_id) else: result = encrypt_asymmetric( self.context, param_id, self.key.data) # Public key used for encryption else: result = encrypt_symmetric( self.context, param_id, self.key.data) # Secret key used for encryption return multiply_add_plain_with_delta( result, message, self.context.context_data_map[self.context.first_param_id])
def negate(self, ct): """Negate a cipher i.e -(ct_value) Args: ct (Ciphertext): Ciphertext to be negated. Returns: A Ciphertext object with value equivalent to result of -(ct_value). """ context_data = self.context.context_data_map[ct.param_id] coeff_modulus = context_data.param.coeff_modulus result = copy.deepcopy(ct.data) for i in range(len(result)): for j in range(len(coeff_modulus)): for k in range(len(result[i][j])): result[i][j][k] = negate_mod(result[i][j][k], coeff_modulus[j]) return CipherText(result, ct.param_id)
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: # TODO: perform relinearization operation. raise RuntimeError( "Cannot multiply ciphertext of size >2, Perform relinearization operation." ) if len(ct2) > 2: # TODO: perform relinearization operation. raise RuntimeError( "Cannot multiply ciphertext of size >2, Perform relinearization operation." ) # Now the size of ciphertexts is 2. # Multiplication operation of ciphertext: # result = [r1, r2, r3] where # r1 = ct1[0] * ct2[0] # r2 = ct1[0] * ct2[1] + ct1[1] * ct2[0] # r3 = ct1[1] * ct2[1] # # where ct1[i], ct2[i] are polynomials. ct10, ct11 = ct1 ct20, ct21 = ct2 result = [ [0] * len(self.coeff_modulus), [0] * len(self.coeff_modulus), [0] * len(self.coeff_modulus), ] for i in range(len(self.coeff_modulus)): result[0][i] = poly_mul(ct10[i], ct20[i], self.poly_modulus) result[1][i] = poly_add( poly_mul(ct11[i], ct20[i], self.poly_modulus), poly_mul(ct10[i], ct21[i], self.poly_modulus), self.poly_modulus, ) result[2][i] = poly_mul(ct11[i], ct21[i], self.poly_modulus) for i in range(len(result)): for j in range(len(self.coeff_modulus)): result[i][j] = [ round(((x * self.plain_modulus) / self.coeff_modulus[j])) % self.coeff_modulus[j] for x in result[i][j] ] return CipherText(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)