def example_integer_encoder(): print("Example: Encoders / Integer Encoder") #[IntegerEncoder] (For BFV scheme only) # #The IntegerEncoder encodes integers to BFV plaintext polynomials as follows. #First, a binary expansion of the integer is computed. Next, a polynomial is #created with the bits as coefficients. For example, the integer # # 26 = 2^4 + 2^3 + 2^1 # #is encoded as the polynomial 1x^4 + 1x^3 + 1x^1. Conversely, plaintext #polynomials are decoded by evaluating them at x=2. For negative numbers the #IntegerEncoder simply stores all coefficients as either 0 or -1, where -1 is #represented by the unsigned integer plain_modulus - 1 in memory. # #Since encrypted computations operate on the polynomials rather than on the #encoded integers themselves, the polynomial coefficients will grow in the #course of such computations. For example, computing the sum of the encrypted #encoded integer 26 with itself will result in an encrypted polynomial with #larger coefficients: 2x^4 + 2x^3 + 2x^1. Squaring the encrypted encoded #integer 26 results also in increased coefficients due to cross-terms, namely, # # (2x^4 + 2x^3 + 2x^1)^2 = 1x^8 + 2x^7 + 1x^6 + 2x^5 + 2x^4 + 1x^2; # #further computations will quickly increase the coefficients much more. #Decoding will still work correctly in this case (evaluating the polynomial #at x=2), but since the coefficients of plaintext polynomials are really #integers modulo plain_modulus, implicit reduction modulo plain_modulus may #yield unexpected results. For example, adding 1x^4 + 1x^3 + 1x^1 to itself #plain_modulus many times will result in the constant polynomial 0, which is #clearly not equal to 26 * plain_modulus. It can be difficult to predict when #such overflow will take place especially when computing several sequential #multiplications. # #The IntegerEncoder is easy to understand and use for simple computations, #and can be a good tool to experiment with for users new to Microsoft SEAL. #However, advanced users will probably prefer more efficient approaches, #such as the BatchEncoder or the CKKSEncoder. parms = EncryptionParameters(scheme_type.BFV) poly_modulus_degree = 4096 parms.set_poly_modulus_degree(poly_modulus_degree) parms.set_coeff_modulus(CoeffModulus.BFVDefault(poly_modulus_degree)) #There is no hidden logic behind our choice of the plain_modulus. The only #thing that matters is that the plaintext polynomial coefficients will not #exceed this value at any point during our computation; otherwise the result #will be incorrect. parms.set_plain_modulus(512) context = SEALContext.Create(parms) print_parameters(context) keygen = KeyGenerator(context) public_key = keygen.public_key() secret_key = keygen.secret_key() encryptor = Encryptor(context, public_key) evaluator = Evaluator(context) decryptor = Decryptor(context, secret_key) #We create an IntegerEncoder. encoder = IntegerEncoder(context) #First, we encode two integers as plaintext polynomials. Note that encoding #is not encryption: at this point nothing is encrypted. value1 = 5 plain1 = encoder.encode(value1) print("Encode {} as polynomial {} (plain1), ".format( value1, plain1.to_string())) value2 = -7 plain2 = encoder.encode(value2) print(" encode {} as polynomial {} (plain2)".format( value2, plain2.to_string())) #Now we can encrypt the plaintext polynomials. encrypted1 = Ciphertext() encrypted2 = Ciphertext() print("Encrypt plain1 to encrypted1 and plain2 to encrypted2.") encryptor.encrypt(plain1, encrypted1) encryptor.encrypt(plain2, encrypted2) print(" + Noise budget in encrypted1: {} bits".format( decryptor.invariant_noise_budget(encrypted1))) print(" + Noise budget in encrypted2: {} bits".format( decryptor.invariant_noise_budget(encrypted2))) #As a simple example, we compute (-encrypted1 + encrypted2) * encrypted2. encryptor.encrypt(plain2, encrypted2) encrypted_result = Ciphertext() print( "Compute encrypted_result = (-encrypted1 + encrypted2) * encrypted2.") evaluator.negate(encrypted1, encrypted_result) evaluator.add_inplace(encrypted_result, encrypted2) evaluator.multiply_inplace(encrypted_result, encrypted2) print(" + Noise budget in encrypted_result: {} bits".format( decryptor.invariant_noise_budget(encrypted_result))) plain_result = Plaintext() print("Decrypt encrypted_result to plain_result.") decryptor.decrypt(encrypted_result, plain_result) #Print the result plaintext polynomial. The coefficients are not even close #to exceeding our plain_modulus, 512. print(" + Plaintext polynomial: {}".format(plain_result.to_string())) #Decode to obtain an integer result. print("Decode plain_result.") print(" + Decoded integer: {} ...... Correct.".format( encoder.decode_int32(plain_result)))
class PySeal: evaluator: Evaluator parms: EncryptionParameters def __init__(self, seal_server_params, cipher_save_path): self.parms = EncryptionParameters(scheme_type.CKKS) self.cipher_save_path = cipher_save_path # Set Coeff modulus coeff_modulus = [ SmallModulus(i) for i in seal_server_params["coeff_modulus"] ] self.parms.set_coeff_modulus(coeff_modulus) # Set Plain modulus self.parms.set_plain_modulus(seal_server_params["plain_modulus"]) # Set Poly modulus degree self.parms.set_poly_modulus_degree( seal_server_params["poly_modulus_degree"]) self.context = SEALContext.Create(self.parms) self.evaluator = Evaluator(self.context) # Store the encrypted here self._encrypts = [] def get_param_info(self): res = { "scheme": self.get_scheme_name(self.parms.scheme()), "poly_modulus_degree": self.parms.poly_modulus_degree(), "coeff_modulus": [i.value() for i in self.parms.coeff_modulus()], "coeff_modulus_size": [i.bit_count() for i in self.parms.coeff_modulus()], "plain_modulus": self.parms.plain_modulus().value(), } return res @staticmethod def get_scheme_name(scheme): if scheme == scheme_type.BFV: scheme_name = "BFV" elif scheme == scheme_type.CKKS: scheme_name = "CKKS" else: scheme_name = "unsupported scheme" return scheme_name @classmethod def print_parameters(cls, context): context_data = context.key_context_data() scheme_name = cls.get_scheme_name(context_data.parms().scheme()) print("/") print("| Encryption parameters:") print("| scheme: " + scheme_name) print("| poly_modulus_degree: " + str(context_data.parms().poly_modulus_degree())) print("| coeff_modulus size: ", end="") coeff_modulus = context_data.parms().coeff_modulus() coeff_modulus_sum = 0 for j in coeff_modulus: coeff_modulus_sum += j.bit_count() print(str(coeff_modulus_sum) + "(", end="") for i in range(len(coeff_modulus) - 1): print(str(coeff_modulus[i].bit_count()) + " + ", end="") print(str(coeff_modulus[-1].bit_count()) + ") bits") if context_data.parms().scheme() == scheme_type.BFV: print("| plain_modulus: " + str(context_data.parms().plain_modulus().value())) print("\\") def save_cipher_and_encode64(self, encrypted_object: Ciphertext): encrypted_object.save(self.cipher_save_path) with open(self.cipher_save_path, 'rb') as f: encrypted_text = f.read() # print(encrypted_text) res = base64.b64encode(encrypted_text) os.remove(self.cipher_save_path) # logging.debug("Resulting call type: {}".format(type(res))) # logging.debug(res[-10:]) return res.decode('utf-8') def decode64_and_get_cipher_object(self, ins: str): decoded = base64.b64decode(ins.encode('utf-8')) with open(self.cipher_save_path, 'wb') as f: f.write(decoded) cipher_object = Ciphertext() cipher_object.load(self.context, self.cipher_save_path) os.remove(self.cipher_save_path) return cipher_object def save_encrypted_weight(self, weight): self._encrypts.append(weight) def aggregate_encrypted_weights(self): if len(self._encrypts) == 0: return [], 0 weight = self._encrypts[0] num_party = len(self._encrypts) if num_party > 1: # Iterate through layers for layer_idx in range(len(weight)): # Iterate through layer partitions for layer_partition_idx in range(len(weight[layer_idx])): agg_layer_partition_weights = [] # Iterate through results from workers for worker_result in range(num_party): cipher_object = self.decode64_and_get_cipher_object( self._encrypts[worker_result][layer_idx] [layer_partition_idx]["encrypted_content"]) agg_layer_partition_weights.append(cipher_object) # print(agg_layer_weights) res = self.add_encrypted_matrix( *agg_layer_partition_weights) weight[layer_idx][layer_partition_idx][ "encrypted_content"] = self.save_cipher_and_encode64( res) # print("weight agg:", weight[layer_idx]) del self._encrypts[:num_party] return weight, num_party def add_encrypted_matrix(self, *argv): inputs = [] for arg in argv: inputs.append(arg) if len(inputs) <= 1: # Case 1: If there is only one input matrix return inputs[0] # Case 2: If there are at least 2 inputs encrypted_result = Ciphertext() self.evaluator.add(inputs[0], inputs[1], encrypted_result) for i in range(2, len(inputs)): self.evaluator.add_inplace(encrypted_result, inputs[i]) res = encrypted_result return res
class SealOps: @classmethod def with_env(cls): parms = EncryptionParameters(scheme_type.CKKS) parms.set_poly_modulus_degree(POLY_MODULUS_DEGREE) parms.set_coeff_modulus( CoeffModulus.Create(POLY_MODULUS_DEGREE, PRIME_SIZE_LIST)) context = SEALContext.Create(parms) keygen = KeyGenerator(context) public_key = keygen.public_key() secret_key = keygen.secret_key() relin_keys = keygen.relin_keys() galois_keys = keygen.galois_keys() return cls(context=context, public_key=public_key, secret_key=secret_key, relin_keys=relin_keys, galois_keys=galois_keys, poly_modulus_degree=POLY_MODULUS_DEGREE, scale=SCALE) def __init__(self, context: SEALContext, scale: float, poly_modulus_degree: int, public_key: PublicKey = None, secret_key: SecretKey = None, relin_keys: RelinKeys = None, galois_keys: GaloisKeys = None): self.scale = scale self.context = context self.encoder = CKKSEncoder(context) self.evaluator = Evaluator(context) self.encryptor = Encryptor(context, public_key) self.decryptor = Decryptor(context, secret_key) self.relin_keys = relin_keys self.galois_keys = galois_keys self.poly_modulus_degree_log = np.log2(poly_modulus_degree) def encrypt(self, matrix: np.array): matrix = Matrix.from_numpy_array(array=matrix) cipher_matrix = CipherMatrix(rows=matrix.rows, cols=matrix.cols) for i in range(matrix.rows): encoded_row = Plaintext() self.encoder.encode(matrix[i], self.scale, encoded_row) self.encryptor.encrypt(encoded_row, cipher_matrix[i]) return cipher_matrix def decrypt(self, cipher_matrix: CipherMatrix) -> Matrix: matrix = Matrix(rows=cipher_matrix.rows, cols=cipher_matrix.cols) for i in range(matrix.rows): row = Vector() encoded_row = Plaintext() self.decryptor.decrypt(cipher_matrix[i], encoded_row) self.encoder.decode(encoded_row, row) matrix[i] = row return matrix def add(self, matrix_a: CipherMatrix, matrix_b: CipherMatrix) -> CipherMatrix: self.validate_same_dimension(matrix_a, matrix_b) result_matrix = CipherMatrix(rows=matrix_a.rows, cols=matrix_a.cols) for i in range(matrix_a.rows): a_tag, b_tag = self.get_matched_scale_vectors( matrix_a[i], matrix_b[i]) self.evaluator.add(a_tag, b_tag, result_matrix[i]) return result_matrix def add_plain(self, matrix_a: CipherMatrix, matrix_b: np.array) -> CipherMatrix: matrix_b = Matrix.from_numpy_array(matrix_b) self.validate_same_dimension(matrix_a, matrix_b) result_matrix = CipherMatrix(rows=matrix_a.rows, cols=matrix_a.cols) for i in range(matrix_a.rows): row = matrix_b[i] encoded_row = Plaintext() self.encoder.encode(row, self.scale, encoded_row) self.evaluator.mod_switch_to_inplace(encoded_row, matrix_a[i].parms_id()) self.evaluator.add_plain(matrix_a[i], encoded_row, result_matrix[i]) return result_matrix def multiply_plain(self, matrix_a: CipherMatrix, matrix_b: np.array) -> CipherMatrix: matrix_b = Matrix.from_numpy_array(matrix_b) self.validate_same_dimension(matrix_a, matrix_b) result_matrix = CipherMatrix(rows=matrix_a.rows, cols=matrix_a.cols) for i in range(matrix_a.rows): row = matrix_b[i] encoded_row = Plaintext() self.encoder.encode(row, self.scale, encoded_row) self.evaluator.mod_switch_to_inplace(encoded_row, matrix_a[i].parms_id()) self.evaluator.multiply_plain(matrix_a[i], encoded_row, result_matrix[i]) return result_matrix def dot_vector(self, a: Ciphertext, b: Ciphertext) -> Ciphertext: result = Ciphertext() self.evaluator.multiply(a, b, result) self.evaluator.relinearize_inplace(result, self.relin_keys) self.vector_sum_inplace(result) self.get_vector_first_element(result) self.evaluator.rescale_to_next_inplace(result) return result def dot_vector_with_plain(self, a: Ciphertext, b: DoubleVector) -> Ciphertext: result = Ciphertext() b_plain = Plaintext() self.encoder.encode(b, self.scale, b_plain) self.evaluator.multiply_plain(a, b_plain, result) self.vector_sum_inplace(result) self.get_vector_first_element(result) self.evaluator.rescale_to_next_inplace(result) return result def get_vector_range(self, vector_a: Ciphertext, i: int, j: int) -> Ciphertext: cipher_range = Ciphertext() one_and_zeros = DoubleVector([0.0 if x < i else 1.0 for x in range(j)]) plain = Plaintext() self.encoder.encode(one_and_zeros, self.scale, plain) self.evaluator.mod_switch_to_inplace(plain, vector_a.parms_id()) self.evaluator.multiply_plain(vector_a, plain, cipher_range) return cipher_range def dot_matrix_with_matrix_transpose(self, matrix_a: CipherMatrix, matrix_b: CipherMatrix): result_matrix = CipherMatrix(rows=matrix_a.rows, cols=matrix_a.cols) rows_a = matrix_a.rows cols_b = matrix_b.rows for i in range(rows_a): vector_dot_products = [] zeros = Plaintext() for j in range(cols_b): vector_dot_products += [ self.dot_vector(matrix_a[i], matrix_b[j]) ] if j == 0: zero = DoubleVector() self.encoder.encode(zero, vector_dot_products[j].scale(), zeros) self.evaluator.mod_switch_to_inplace( zeros, vector_dot_products[j].parms_id()) self.evaluator.add_plain(vector_dot_products[j], zeros, result_matrix[i]) else: self.evaluator.rotate_vector_inplace( vector_dot_products[j], -j, self.galois_keys) self.evaluator.add_inplace(result_matrix[i], vector_dot_products[j]) for vec in result_matrix: self.evaluator.relinearize_inplace(vec, self.relin_keys) self.evaluator.rescale_to_next_inplace(vec) return result_matrix def dot_matrix_with_plain_matrix_transpose(self, matrix_a: CipherMatrix, matrix_b: np.array): matrix_b = Matrix.from_numpy_array(matrix_b) result_matrix = CipherMatrix(rows=matrix_a.rows, cols=matrix_a.cols) rows_a = matrix_a.rows cols_b = matrix_b.rows for i in range(rows_a): vector_dot_products = [] zeros = Plaintext() for j in range(cols_b): vector_dot_products += [ self.dot_vector_with_plain(matrix_a[i], matrix_b[j]) ] if j == 0: zero = DoubleVector() self.encoder.encode(zero, vector_dot_products[j].scale(), zeros) self.evaluator.mod_switch_to_inplace( zeros, vector_dot_products[j].parms_id()) self.evaluator.add_plain(vector_dot_products[j], zeros, result_matrix[i]) else: self.evaluator.rotate_vector_inplace( vector_dot_products[j], -j, self.galois_keys) self.evaluator.add_inplace(result_matrix[i], vector_dot_products[j]) for vec in result_matrix: self.evaluator.relinearize_inplace(vec, self.relin_keys) self.evaluator.rescale_to_next_inplace(vec) return result_matrix @staticmethod def validate_same_dimension(matrix_a, matrix_b): if matrix_a.rows != matrix_b.rows or matrix_a.cols != matrix_b.cols: raise ArithmeticError("Matrices aren't of the same dimension") def vector_sum_inplace(self, cipher: Ciphertext): rotated = Ciphertext() for i in range(int(self.poly_modulus_degree_log - 1)): self.evaluator.rotate_vector(cipher, pow(2, i), self.galois_keys, rotated) self.evaluator.add_inplace(cipher, rotated) def get_vector_first_element(self, cipher: Ciphertext): one_and_zeros = DoubleVector([1.0]) plain = Plaintext() self.encoder.encode(one_and_zeros, self.scale, plain) self.evaluator.multiply_plain_inplace(cipher, plain) def get_matched_scale_vectors(self, a: Ciphertext, b: Ciphertext) -> (Ciphertext, Ciphertext): a_tag = Ciphertext(a) b_tag = Ciphertext(b) a_index = self.context.get_context_data(a.parms_id()).chain_index() b_index = self.context.get_context_data(b.parms_id()).chain_index() # Changing the mod if required, else just setting the scale if a_index < b_index: self.evaluator.mod_switch_to_inplace(b_tag, a.parms_id()) elif a_index > b_index: self.evaluator.mod_switch_to_inplace(a_tag, b.parms_id()) a_tag.set_scale(self.scale) b_tag.set_scale(self.scale) return a_tag, b_tag