def seal_obj(): # params obj params = EncryptionParameters() # set params params.set_poly_modulus("1x^4096 + 1") params.set_coeff_modulus(seal.coeff_modulus_128(4096)) params.set_plain_modulus(1 << 16) # get context context = SEALContext(params) # get evaluator evaluator = Evaluator(context) # gen keys keygen = KeyGenerator(context) public_key = keygen.public_key() private_key = keygen.secret_key() # evaluator keys ev_keys = EvaluationKeys() keygen.generate_evaluation_keys(30, ev_keys) # get encryptor and decryptor encryptor = Encryptor(context, public_key) decryptor = Decryptor(context, private_key) # float number encoder encoder = FractionalEncoder(context.plain_modulus(), context.poly_modulus(), 64, 32, 3) return evaluator, encoder.encode, encoder.decode, encryptor.encrypt, decryptor.decrypt, ev_keys
def initialize_fractional( poly_modulus_degree=4096, security_level_bits=128, plain_modulus_power_of_two=10, plain_modulus=None, encoder_integral_coefficients=1024, encoder_fractional_coefficients=3072, encoder_base=2 ): parameters = EncryptionParameters() poly_modulus = "1x^" + str(poly_modulus_degree) + " + 1" parameters.set_poly_modulus(poly_modulus) if security_level_bits == 128: parameters.set_coeff_modulus(seal.coeff_modulus_128(poly_modulus_degree)) elif security_level_bits == 192: parameters.set_coeff_modulus(seal.coeff_modulus_192(poly_modulus_degree)) else: parameters.set_coeff_modulus(seal.coeff_modulus_128(poly_modulus_degree)) print("Info: security_level_bits unknown - using default security_level_bits = 128") if plain_modulus is None: plain_modulus = 1 << plain_modulus_power_of_two parameters.set_plain_modulus(plain_modulus) context = SEALContext(parameters) print_parameters(context) global encoder encoder = FractionalEncoder( context.plain_modulus(), context.poly_modulus(), encoder_integral_coefficients, encoder_fractional_coefficients, encoder_base ) keygen = KeyGenerator(context) public_key = keygen.public_key() secret_key = keygen.secret_key() global encryptor encryptor = Encryptor(context, public_key) global evaluator evaluator = Evaluator(context) global decryptor decryptor = Decryptor(context, secret_key) global evaluation_keys evaluation_keys = EvaluationKeys() keygen.generate_evaluation_keys(16, evaluation_keys)
def _build_context(self, config): #set up encryption parameters and context parms = EncryptionParameters() parms.set_poly_modulus(config['poly_modulus']) parms.set_coeff_modulus(seal.coeff_modulus_128( config['coeff_modulus'])) parms.set_plain_modulus(1 << 18) context = SEALContext(parms) return context
def __init__(self): # set parameters for encryption parms = EncryptionParameters() parms.set_poly_modulus("1x^2048 + 1") parms.set_coeff_modulus(seal.coeff_modulus_128(2048)) parms.set_plain_modulus(1 << 8) self.context = SEALContext(parms) keygen = KeyGenerator(self.context) self.encoder = IntegerEncoder(self.context.plain_modulus()) public_key = keygen.public_key() self.encryptor = Encryptor(self.context, public_key) secret_key = keygen.secret_key() self.decryptor = Decryptor(self.context, secret_key)
def config(): # PySEAL wrapper for the SEAL library is used. # SEAL implements somewhat FHE algorithmic solutions => # each operation has a limit - 'invariant noise budget' in bits. Operations consume the noise budget # at a rate determined py the encryption parameters. Additions free of noise budget consumption, multiplications are not # Noise budget consumption is getting worse in sequential multiplications => multiplicative depth of the arithmetic circuit that needs to be evaluated. # Noise budget in a ciphertext -> 0 => ciphertext too corrupted to be decrypted => large enough parameters to be eble to restore the result ############# # noise_budget = log2(coeff_modulus/plain_modulus) bits (in a freshly encrypted ciphertext) ############# ############# # noise_budget_cnsumption = log2(plain_modulus) + (other terms) ############# params = EncryptionParameters() # set the polynomial modulus. (1x^(power of 2) +1) - power of 2 cyclotomic polynomial # affects the security of the scheme # larger more secure and larger ciphertext size, computation slower # from 1024 to 32768 ##params.set_poly_modulus("1x^1024 + 1") params.set_poly_modulus("1x^2048 + 1") # coefficient modulus determines the noise budget of the ciphertext # the bigger the more the budget and lower security -> increase the polynomial modulus # choosing parameters for polynomial modulus http://HomomorphicEncryption.org ###params.set_coeff_modulus(seal.coeff_modulus_128(8192)) params.set_coeff_modulus( seal.coeff_modulus_128(2048)) #plain_modulus = k*4096+1 #~ log2(coeff_modulus/plain_modulus) (bits) # plaintext modulus determines the size of the plaintext datatype, affects the noise budget in multiplication => keep the plaintext data type as small as possible #65537 params.set_plain_modulus(12289) #####For batching ###params.set_plain_modulus(65537) #params.set_plain_modulus(2049) #params.set_plain_modulus(8192) # check the validity of the parameters set, performs and stores several important pre-computations context = SEALContext(params) # print the chosen parameters #performance_test_st(context) return context, params
def initialize_encryption(): print_example_banner("Example: Basics I"); parms = EncryptionParameters() parms.set_poly_modulus("1x^2048 + 1") # factor: 0xfffffffff00001. parms.set_coeff_modulus(seal.coeff_modulus_128(2048)) parms.set_plain_modulus(1 << 8) context = SEALContext(parms) print_parameters(context); # Here we choose to create an IntegerEncoder with base b=2. encoder = IntegerEncoder(context.plain_modulus()) 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) return encryptor, evaluator, decryptor, encoder, context
def do_per_amount(amount, subtract_from=15): """ Called on every message in the stream """ print("Transaction amount ", amount) parms = EncryptionParameters() parms.set_poly_modulus("1x^2048 + 1") parms.set_coeff_modulus(seal.coeff_modulus_128(2048)) parms.set_plain_modulus(1 << 8) context = SEALContext(parms) # Encode encoder = FractionalEncoder(context.plain_modulus(), context.poly_modulus(), 64, 32, 3) # To create a fresh pair of keys one can call KeyGenerator::generate() at any time. keygen = KeyGenerator(context) public_key = keygen.public_key() secret_key = keygen.secret_key() encryptor = Encryptor(context, public_key) plain1 = encoder.encode(amount) encoded2 = encoder.encode(subtract_from) # Encrypt encrypted1 = Ciphertext(parms) encryptor.encrypt(plain1, encrypted1) # Evaluate evaluator = Evaluator(context) evaluated = evaluate_subtraction_from_plain(evaluator, encrypted1, encoded2) # Decrypt and decode decryptor = Decryptor(context, secret_key) plain_result = Plaintext() decryptor.decrypt(evaluated, plain_result) result = encoder.decode(plain_result) str_result = "Amount left = " + str(result) print(str_result) return str_result
def __init__(self, poly_modulus = 2048 ,bit_strength = 128 ,plain_modulus = 1<<8, integral_coeffs = 64, fractional_coeffs = 32, fractional_base = 3): parms = EncryptionParameters() parms.set_poly_modulus("1x^{} + 1".format(poly_modulus)) if (bit_strength == 128): parms.set_coeff_modulus(seal.coeff_modulus_128(poly_modulus)) else: parms.set_coeff_modulus(seal.coeff_modulus_192(poly_modulus)) parms.set_plain_modulus(plain_modulus) self.parms = parms context = SEALContext(parms) keygen = KeyGenerator(context) public_key = keygen.public_key() secret_key = keygen.secret_key() self.encryptor = Encryptor(context, public_key) self.evaluator = Evaluator(context) self.decryptor = Decryptor(context, secret_key) self.encoder = FractionalEncoder(context.plain_modulus(), context.poly_modulus(), integral_coeffs, fractional_coeffs, fractional_base)
class FracContext: primes = [ 0xffffffffffc0001, 0xfffffffff840001, 0xfffffffff240001, 0xffffffffe7c0001, 0xffffffffe740001, 0xffffffffe4c0001, 0xffffffffe440001, 0xffffffffe400001, 0xffffffffdbc0001, 0xffffffffd840001, 0xffffffffd680001, 0xffffffffd000001, 0xffffffffcf00001, 0xffffffffcdc0001, 0xffffffffcc40001, 0xffffffffc300001, 0xffffffffbf40001, 0xffffffffbdc0001, 0xffffffffb880001, 0xffffffffaec0001, 0xffffffffa380001, 0xffffffffa200001, 0xffffffffa0c0001, 0xffffffff9600001, 0xffffffff91c0001, 0xffffffff8f40001, 0xffffffff8680001, 0xffffffff7e40001, 0xffffffff7bc0001, 0xffffffff76c0001, 0xffffffff7680001, 0xffffffff6fc0001, 0xffffffff6880001, 0xffffffff6340001, 0xffffffff5d40001, 0xffffffff54c0001, 0xffffffff4d40001, 0xffffffff4380001, 0xffffffff3e80001, 0xffffffff37c0001, 0xffffffff36c0001, 0xffffffff2100001, 0xffffffff1d80001, 0xffffffff1cc0001, 0xffffffff1900001, 0xffffffff1740001, 0xffffffff15c0001, 0xffffffff0e80001, 0xfffffffeff80001, 0xfffffffeff40001, 0xfffffffeefc0001, 0xfffffffee8c0001, 0xfffffffede40001, 0xfffffffedcc0001, 0xfffffffed040001, 0xfffffffecf40001, 0xfffffffecec0001, 0xfffffffecb00001, 0xfffffffec380001, 0xfffffffebb40001 ] def __init__(self, poly_modulus="1x^1024 + 1", coef_modulus_n_primes=20, plain_modulus=1 << 32): """ Set up encryption context for encoder and decoder :param poly_modulus: :param coef_modulus_n_primes: :param plain_modulus: """ self.params = EncryptionParameters() self.params.set_poly_modulus(poly_modulus) self.params.set_coeff_modulus([ seal.SmallModulus(p) for p in FracContext.primes[:coef_modulus_n_primes] ]) self.params.set_plain_modulus(plain_modulus) self.context = SEALContext(self.params) self.print_parameters(self.context) self.keygen = KeyGenerator(self.context) self.public_key = self.keygen.public_key() self.secret_key = self.keygen.secret_key() self.evaluator = Evaluator(self.context) def print_parameters(self, context: SEALContext): """ Parameters description :param context: SEALContext object """ print("/ Encryption parameters:") print("| poly_modulus: " + context.poly_modulus().to_string()) # Print the size of the true (product) coefficient modulus print("| coeff_modulus_size: " + (str)(context.total_coeff_modulus().significant_bit_count()) + " bits") print("| plain_modulus: " + (str)(context.plain_modulus().value())) print("| noise_standard_deviation: " + (str)(context.noise_standard_deviation()))
class CipherMatrix: """ """ def __init__(self, matrix=None): """ :param matrix: numpy.ndarray to be encrypted. """ self.parms = EncryptionParameters() self.parms.set_poly_modulus("1x^2048 + 1") self.parms.set_coeff_modulus(seal.coeff_modulus_128(2048)) self.parms.set_plain_modulus(1 << 8) self.context = SEALContext(self.parms) # self.encoder = IntegerEncoder(self.context.plain_modulus()) self.encoder = FractionalEncoder(self.context.plain_modulus(), self.context.poly_modulus(), 64, 32, 3) self.keygen = KeyGenerator(self.context) self.public_key = self.keygen.public_key() self.secret_key = self.keygen.secret_key() self.encryptor = Encryptor(self.context, self.public_key) self.decryptor = Decryptor(self.context, self.secret_key) self.evaluator = Evaluator(self.context) self._saved = False self._encrypted = False self._id = '{0:04d}'.format(np.random.randint(1000)) if matrix is not None: assert len( matrix.shape) == 2, "Only 2D numpy matrices accepted currently" self.matrix = np.copy(matrix) self.encrypted_matrix = np.empty(self.matrix.shape, dtype=object) for i in range(self.matrix.shape[0]): for j in range(self.matrix.shape[1]): self.encrypted_matrix[i, j] = Ciphertext() else: self.matrix = None self.encrypted_matrix = None print(self._id, "Created") def __repr__(self): """ :return: """ print("Encrypted:", self._encrypted) if not self._encrypted: print(self.matrix) return "" else: return '[]' def __str__(self): """ :return: """ print("| Encryption parameters:") print("| poly_modulus: " + self.context.poly_modulus().to_string()) # Print the size of the true (product) coefficient modulus print("| coeff_modulus_size: " + ( str)(self.context.total_coeff_modulus().significant_bit_count()) + " bits") print("| plain_modulus: " + (str)(self.context.plain_modulus().value())) print("| noise_standard_deviation: " + (str)(self.context.noise_standard_deviation())) if self.matrix is not None: print(self.matrix.shape) return str(type(self)) def __add__(self, other): """ :param other: :return: """ assert isinstance( other, CipherMatrix), "Can only be added with a CipherMatrix" A_enc = self._encrypted B_enc = other._encrypted if A_enc: A = self.encrypted_matrix else: A = self.matrix if B_enc: B = other.encrypted_matrix else: B = other.matrix assert A.shape == B.shape, "Dimension mismatch, Matrices must be of same shape. Got {} and {}".format( A.shape, B.shape) shape = A.shape result = CipherMatrix(np.zeros(shape, dtype=np.int32)) result._update_cryptors(self.get_keygen()) if A_enc: if B_enc: res_mat = result.encrypted_matrix for i in range(shape[0]): for j in range(shape[1]): self.evaluator.add(A[i, j], B[i, j], res_mat[i, j]) result._encrypted = True else: res_mat = result.encrypted_matrix for i in range(shape[0]): for j in range(shape[1]): self.evaluator.add_plain(A[i, j], self.encoder.encode(B[i, j]), res_mat[i, j]) result._encrypted = True else: if B_enc: res_mat = result.encrypted_matrix for i in range(shape[0]): for j in range(shape[1]): self.evaluator.add_plain(B[i, j], self.encoder.encode(A[i, j]), res_mat[i, j]) result._encrypted = True else: result.matrix = A + B result._encrypted = False return result def __sub__(self, other): """ :param other: :return: """ assert isinstance(other, CipherMatrix) if other._encrypted: shape = other.encrypted_matrix.shape for i in range(shape[0]): for j in range(shape[1]): self.evaluator.negate(other.encrypted_matrix[i, j]) else: other.matrix = -1 * other.matrix return self + other def __mul__(self, other): """ :param other: :return: """ assert isinstance( other, CipherMatrix), "Can only be multiplied with a CipherMatrix" # print("LHS", self._id, "RHS", other._id) A_enc = self._encrypted B_enc = other._encrypted if A_enc: A = self.encrypted_matrix else: A = self.matrix if B_enc: B = other.encrypted_matrix else: B = other.matrix Ashape = A.shape Bshape = B.shape assert Ashape[1] == Bshape[0], "Dimensionality mismatch" result_shape = [Ashape[0], Bshape[1]] result = CipherMatrix(np.zeros(shape=result_shape)) if A_enc: if B_enc: for i in range(Ashape[0]): for j in range(Bshape[1]): result_array = [] for k in range(Ashape[1]): res = Ciphertext() self.evaluator.multiply(A[i, k], B[k, j], res) result_array.append(res) self.evaluator.add_many(result_array, result.encrypted_matrix[i, j]) result._encrypted = True else: for i in range(Ashape[0]): for j in range(Bshape[1]): result_array = [] for k in range(Ashape[1]): res = Ciphertext() self.evaluator.multiply_plain( A[i, k], self.encoder.encode(B[k, j]), res) result_array.append(res) self.evaluator.add_many(result_array, result.encrypted_matrix[i, j]) result._encrypted = True else: if B_enc: for i in range(Ashape[0]): for j in range(Bshape[1]): result_array = [] for k in range(Ashape[1]): res = Ciphertext() self.evaluator.multiply_plain( B[i, k], self.encoder.encode(A[k, j]), res) result_array.append(res) self.evaluator.add_many(result_array, result.encrypted_matrix[i, j]) result._encrypted = True else: result.matrix = np.matmul(A, B) result._encrypted = False return result def save(self, path): """ :param path: :return: """ save_dir = os.path.join(path, self._id) if self._saved: print("CipherMatrix already saved") else: assert not os.path.isdir(save_dir), "Directory already exists" os.mkdir(save_dir) if not self._encrypted: self.encrypt() shape = self.encrypted_matrix.shape for i in range(shape[0]): for j in range(shape[1]): element_name = str(i) + '-' + str(j) + '.ahem' self.encrypted_matrix[i, j].save( os.path.join(save_dir, element_name)) self.secret_key.save("/keys/" + "." + self._id + '.wheskey') self._saved = True return save_dir def load(self, path, load_secret_key=False): """ :param path: :param load_secret_key: :return: """ self._id = path.split('/')[-1] print("Loading Matrix:", self._id) file_list = os.listdir(path) index_list = [[file.split('.')[0].split('-'), file] for file in file_list] M = int(max([int(ind[0][0]) for ind in index_list])) + 1 N = int(max([int(ind[0][1]) for ind in index_list])) + 1 del self.encrypted_matrix self.encrypted_matrix = np.empty([M, N], dtype=object) for index in index_list: i = int(index[0][0]) j = int(index[0][1]) self.encrypted_matrix[i, j] = Ciphertext() self.encrypted_matrix[i, j].load(os.path.join(path, index[1])) if load_secret_key: self.secret_key.load("/keys/" + "." + self._id + '.wheskey') self.matrix = np.empty(self.encrypted_matrix.shape) self._encrypted = True def encrypt(self, matrix=None, keygen=None): """ :param matrix: :return: """ assert not self._encrypted, "Matrix already encrypted" if matrix is not None: assert self.matrix is None, "matrix already exists" self.matrix = np.copy(matrix) shape = self.matrix.shape self.encrypted_matrix = np.empty(shape, dtype=object) if keygen is not None: self._update_cryptors(keygen) for i in range(shape[0]): for j in range(shape[1]): val = self.encoder.encode(self.matrix[i, j]) self.encrypted_matrix[i, j] = Ciphertext() self.encryptor.encrypt(val, self.encrypted_matrix[i, j]) self._encrypted = True def decrypt(self, encrypted_matrix=None, keygen=None): """ :return: """ if encrypted_matrix is not None: self.encrypted_matrix = encrypted_matrix assert self._encrypted, "No encrypted matrix" del self.matrix shape = self.encrypted_matrix.shape self.matrix = np.empty(shape) if keygen is not None: self._update_cryptors(keygen) for i in range(shape[0]): for j in range(shape[1]): plain_text = Plaintext() self.decryptor.decrypt(self.encrypted_matrix[i, j], plain_text) self.matrix[i, j] = self.encoder.decode(plain_text) self._encrypted = False return np.copy(self.matrix) def get_keygen(self): """ :return: """ return self.keygen def _update_cryptors(self, keygen): """ :param keygen: :return: """ self.keygen = keygen self.public_key = keygen.public_key() self.secret_key = keygen.secret_key() self.encryptor = Encryptor(self.context, self.public_key) self.decryptor = Decryptor(self.context, self.secret_key) return
class encryption_handler(object): """ Methods: set_encoder: sets specified encoder encode_encrypt_2D: for encoding list of lists decrypt_1D: for decoding list Attributes: params: seal encrpytion parameters object batch: bool to batch operations context: seal context object secretkey: secret key kept on client side publickey: public key for encryption encoder: encoder for numerical inputs """ def __init__( self, security_level=128, #128 or 192 for now poly_modulus_pwr2=12, # 11 through 15 coeff_modulus=None, plain_modulus=2**8, batch=False, ): """ security level: 128 or 192 poly_modulus_pwr2: 11,12,13,14,or 15 poly=x^(2^thisvariable)+1 will define our polynomial ring by Z[x]/poly. Larger number means more security but longer computations. coeff_modulus: default None, If set then security level is ignored. This is important to set for batching as it needs to be prime. batch: default False, setting to true will design encryption scheme to allow parallel predictions """ self.params = EncryptionParameters() self.batch = batch power = 2**poly_modulus_pwr2 self.params.set_poly_modulus(f"1x^{power} + 1") if coeff_modulus != None: st.write("Security level is ignored since coeff_modulus was set.") self.params.set_coeff_modulus(coeff_modulus) else: if security_level == 128: self.params.set_coeff_modulus(seal.coeff_modulus_128(power)) if security_level == 192: self.params.set_coeff_modulus(seal.coeff_modulus_192(power)) try: self.params.set_plain_modulus(plain_modulus) except: raise ValueError("There was a problem setting the plain modulus.") try: self._cont = SEALContext(self.params) except Exception as e: raise ValueError("There was a problem with your parameters.") st.write(f"There was a problem with your parameters: {e}") _keygen = KeyGenerator(self._cont) self._secretkey = _keygen.secret_key() self._publickey = _keygen.public_key() @property def secretkey(self): return self._secretkey @property def publickey(self): return self._publickey @property def context(self): return self._cont
def pickle_ciphertext(): parms = EncryptionParameters() parms.set_poly_modulus("1x^2048 + 1") parms.set_coeff_modulus(seal.coeff_modulus_128(2048)) parms.set_plain_modulus(1 << 8) context = SEALContext(parms) # Print the parameters that we have chosen print_parameters(context); encoder = IntegerEncoder(context.plain_modulus()) keygen = KeyGenerator(context) public_key = keygen.public_key() secret_key = keygen.secret_key() # To be able to encrypt, we need to construct an instance of Encryptor. Note that # the Encryptor only requires the public key. encryptor = Encryptor(context, public_key) # Computations on the ciphertexts are performed with the Evaluator class. evaluator = Evaluator(context) # We will of course want to decrypt our results to verify that everything worked, # so we need to also construct an instance of Decryptor. Note that the Decryptor # requires the secret key. decryptor = Decryptor(context, secret_key) # We start by encoding two integers as plaintext polynomials. value1 = 5; plain1 = encoder.encode(value1); print("Encoded " + (str)(value1) + " as polynomial " + plain1.to_string() + " (plain1)") value2 = -7; plain2 = encoder.encode(value2); print("Encoded " + (str)(value2) + " as polynomial " + plain2.to_string() + " (plain2)") # Encrypting the values is easy. encrypted1 = Ciphertext() encrypted2 = Ciphertext() print("Encrypting plain1: ", encrypted1) encryptor.encrypt(plain1, encrypted1) print("Done (encrypted1)", encrypted1) print("Encrypting plain2: ") encryptor.encrypt(plain2, encrypted2) print("Done (encrypted2)") # output = open('ciphertest.pkl', 'wb') # dill.dumps(encrypted_save, output) # output.close() # encrypted1 = dill.load(open('ciphertest.pkl', 'rb')) output = open('session.pkl', 'wb') dill.dump_session('session.pkl') del encrypted1 sill.load_session('session.pkl') # To illustrate the concept of noise budget, we print the budgets in the fresh # encryptions. print("Noise budget in encrypted1: " + (str)(decryptor.invariant_noise_budget(encrypted1)) + " bits") print("Noise budget in encrypted2: " + (str)(decryptor.invariant_noise_budget(encrypted2)) + " bits") # As a simple example, we compute (-encrypted1 + encrypted2) * encrypted2. # Negation is a unary operation. evaluator.negate(encrypted1) # Negation does not consume any noise budget. print("Noise budget in -encrypted1: " + (str)(decryptor.invariant_noise_budget(encrypted1)) + " bits") # Addition can be done in-place (overwriting the first argument with the result, # or alternatively a three-argument overload with a separate destination variable # can be used. The in-place variants are always more efficient. Here we overwrite # encrypted1 with the sum. evaluator.add(encrypted1, encrypted2) # It is instructive to think that addition sets the noise budget to the minimum # of the input noise budgets. In this case both inputs had roughly the same # budget going on, and the output (in encrypted1) has just slightly lower budget. # Depending on probabilistic effects, the noise growth consumption may or may # not be visible when measured in whole bits. print("Noise budget in -encrypted1 + encrypted2: " + (str)(decryptor.invariant_noise_budget(encrypted1)) + " bits") # Finally multiply with encrypted2. Again, we use the in-place version of the # function, overwriting encrypted1 with the product. evaluator.multiply(encrypted1, encrypted2) # Multiplication consumes a lot of noise budget. This is clearly seen in the # print-out. The user can change the plain_modulus to see its effect on the # rate of noise budget consumption. print("Noise budget in (-encrypted1 + encrypted2) * encrypted2: " + (str)( decryptor.invariant_noise_budget(encrypted1)) + " bits") # Now we decrypt and decode our result. plain_result = Plaintext() print("Decrypting result: ") decryptor.decrypt(encrypted1, plain_result) print("Done") # Print the result plaintext polynomial. print("Plaintext polynomial: " + plain_result.to_string()) # Decode to obtain an integer result. print("Decoded integer: " + (str)(encoder.decode_int32(plain_result)))
for colIndex in range(rowIndex+1,n): if M[rowIndex][colIndex]==None: M[rowIndex][colIndex]=Ciphertext(M[colIndex][rowIndex]) print_plain(M) if __name__ == '__main__': multiprocessing.freeze_support() ########################## paramaters required ################################# parms = EncryptionParameters() parms.set_poly_modulus("1x^16384 + 1") parms.set_coeff_modulus(seal.coeff_modulus_128(8192)) parms.set_plain_modulus(1 << 25) context = SEALContext(parms) encoderF = FractionalEncoder(context.plain_modulus(), context.poly_modulus(), 34, 30, 3) 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) num_cores = multiprocessing.cpu_count() -1
def example_basics_ii(): print_example_banner("Example: Basics II") # In this example we explain what relinearization is, how to use it, and how # it affects noise budget consumption. # First we set the parameters, create a SEALContext, and generate the public # and secret keys. We use slightly larger parameters than be fore to be able # to do more homomorphic multiplications. parms = EncryptionParameters() parms.set_poly_modulus("1x^8192 + 1") # The default coefficient modulus consists of the following primes: # 0x7fffffffba0001, # 0x7fffffffaa0001, # 0x7fffffff7e0001, # 0x3fffffffd60001. # The total size is 219 bits. parms.set_coeff_modulus(seal.coeff_modulus_128(8192)) parms.set_plain_modulus(1 << 10) context = SEALContext(parms) print_parameters(context) keygen = KeyGenerator(context) public_key = keygen.public_key() secret_key = keygen.secret_key() # We also set up an Encryptor, Evaluator, and Decryptor here. We will # encrypt polynomials directly in this example, so there is no need for # an encoder. encryptor = Encryptor(context, public_key) evaluator = Evaluator(context) decryptor = Decryptor(context, secret_key) # There are actually two more types of keys in SEAL: `evaluation keys' and # `Galois keys'. Here we will discuss evaluation keys, and Galois keys will # be discussed later in example_batching(). # In SEAL, a valid ciphertext consists of two or more polynomials with # coefficients integers modulo the product of the primes in coeff_modulus. # The current size of a ciphertext can be found using Ciphertext::size(). # A freshly encrypted ciphertext always has size 2. #plain1 = Plaintext("1x^2 + 2x^1 + 3") plain1 = Plaintext("1x^2 + 2x^1 + 3") encrypted = Ciphertext() print("") print("Encrypting " + plain1.to_string() + ": ") encryptor.encrypt(plain1, encrypted) print("Done") print("Size of a fresh encryption: " + (str)(encrypted.size())) print("Noise budget in fresh encryption: " + (str)(decryptor.invariant_noise_budget(encrypted)) + " bits") # Homomorphic multiplication results in the output ciphertext growing in size. # More precisely, if the input ciphertexts have size M and N, then the output # ciphertext after homomorphic multiplication will have size M+N-1. In this # case we square encrypted twice to observe this growth (also observe noise # budget consumption). evaluator.square(encrypted) print("Size after squaring: " + (str)(encrypted.size())) print("Noise budget after squaring: " + (str)(decryptor.invariant_noise_budget(encrypted)) + " bits") plain2 = Plaintext() decryptor.decrypt(encrypted, plain2) print("Second power: " + plain2.to_string()) evaluator.square(encrypted) print("Size after squaring: " + (str)(encrypted.size())) print("Noise budget after squaring: " + (str)(decryptor.invariant_noise_budget(encrypted)) + " bits") # It does not matter that the size has grown -- decryption works as usual. # Observe from the print-out that the coefficients in the plaintext have # grown quite large. One more squaring would cause some of them to wrap # around plain_modulus (0x400), and as a result we would no longer obtain # the expected result as an integer-coefficient polynomial. We can fix this # problem to some extent by increasing plain_modulus. This would make sense, # since we still have plenty of noise budget left. plain2 = Plaintext() decryptor.decrypt(encrypted, plain2) print("Fourth power: " + plain2.to_string()) # The problem here is that homomorphic operations on large ciphertexts are # computationally much more costly than on small ciphertexts. Specifically, # homomorphic multiplication on input ciphertexts of size M and N will require # O(M*N) polynomial multiplications to be performed, and an addition will # require O(M+N) additions. Relinearization reduces the size of the ciphertexts # after multiplication back to the initial size (2). Thus, relinearizing one # or both inputs before the next multiplication, or e.g. before serializing the # ciphertexts, can have a huge positive impact on performance. # Another problem is that the noise budget consumption in multiplication is # bigger when the input ciphertexts sizes are bigger. In a complicated # computation the contribution of the sizes to the noise budget consumption # can actually become the dominant term. We will point this out again below # once we get to our example. # Relinearization itself has both a computational cost and a noise budget cost. # These both depend on a parameter called `decomposition bit count', which can # be any integer at least 1 [dbc_min()] and at most 60 [dbc_max()]. A large # decomposition bit count makes relinearization fast, but consumes more noise # budget. A small decomposition bit count can make relinearization slower, but # might not change the noise budget by any observable amount. # Relinearization requires a special type of key called `evaluation keys'. # These can be created by the KeyGenerator for any decomposition bit count. # To relinearize a ciphertext of size M >= 2 back to size 2, we actually need # M-2 evaluation keys. Attempting to relinearize a too large ciphertext with # too few evaluation keys will result in an exception being thrown. # We repeat our computation, but this time relinearize after both squarings. # Since our ciphertext never grows past size 3 (we relinearize after every # multiplication), it suffices to generate only one evaluation key. # First, we need to create evaluation keys. We use a decomposition bit count # of 16 here, which can be thought of as quite small. ev_keys16 = EvaluationKeys() # This function generates one single evaluation key. Another overload takes # the number of keys to be generated as an argument, but one is all we need # in this example (see above). keygen.generate_evaluation_keys(16, ev_keys16) print("") print("Encrypting " + plain1.to_string() + ": ") encryptor.encrypt(plain1, encrypted) print("Done") print("Size of a fresh encryption: " + (str)(encrypted.size())) print("Noise budget in fresh encryption: " + (str)(decryptor.invariant_noise_budget(encrypted)) + " bits") evaluator.square(encrypted) print("Size after squaring: " + (str)(encrypted.size())) print("Noise budget after squaring: " + (str)(decryptor.invariant_noise_budget(encrypted)) + " bits") evaluator.relinearize(encrypted, ev_keys16) print("Size after relinearization: " + (str)(encrypted.size())) print("Noise budget after relinearizing (dbs = " + (str)(ev_keys16.decomposition_bit_count()) + "): " + (str)(decryptor.invariant_noise_budget(encrypted)) + " bits") evaluator.square(encrypted) print("Size after second squaring: " + (str)(encrypted.size()) + " bits") print("Noise budget after second squaring: " + (str)(decryptor.invariant_noise_budget(encrypted)) + " bits") evaluator.relinearize(encrypted, ev_keys16) print("Size after relinearization: " + (str)(encrypted.size())) print("Noise budget after relinearizing (dbs = " + (str)(ev_keys16.decomposition_bit_count()) + "): " + (str)(decryptor.invariant_noise_budget(encrypted)) + " bits") decryptor.decrypt(encrypted, plain2) print("Fourth power: " + plain2.to_string()) # Of course the result is still the same, but this time we actually # used less of our noise budget. This is not surprising for two reasons: # - We used a very small decomposition bit count, which is why # relinearization itself did not consume the noise budget by any # observable amount; # - Since our ciphertext sizes remain small throughout the two # squarings, the noise budget consumption rate in multiplication # remains as small as possible. Recall from above that operations # on larger ciphertexts actually cause more noise growth. # To make matters even more clear, we repeat the computation a third time, # now using the largest possible decomposition bit count (60). We are not # measuring the time here, but relinearization with these evaluation keys # is significantly faster than with ev_keys16. ev_keys60 = EvaluationKeys() keygen.generate_evaluation_keys(seal.dbc_max(), ev_keys60) print("") print("Encrypting " + plain1.to_string() + ": ") encryptor.encrypt(plain1, encrypted) print("Done") print("Size of a fresh encryption: " + (str)(encrypted.size())) print("Noise budget in fresh encryption: " + (str)(decryptor.invariant_noise_budget(encrypted)) + " bits") evaluator.square(encrypted) print("Size after squaring: " + (str)(encrypted.size())) print("Noise budget after squaring: " + (str)(decryptor.invariant_noise_budget(encrypted)) + " bits") evaluator.relinearize(encrypted, ev_keys60) print("Size after relinearization: " + (str)(encrypted.size())) print("Noise budget after relinearizing (dbc = " + (str)(ev_keys60.decomposition_bit_count()) + "): " + (str)(decryptor.invariant_noise_budget(encrypted)) + " bits") evaluator.square(encrypted) print("Size after second squaring: " + (str)(encrypted.size())) print("Noise budget after second squaring: " + (str)(decryptor.invariant_noise_budget) + " bits") evaluator.relinearize(encrypted, ev_keys60) print("Size after relinearization: " + (str)(encrypted.size())) print("Noise budget after relinearizing (dbc = " + (str)(ev_keys60.decomposition_bit_count()) + "): " + (str)(decryptor.invariant_noise_budget(encrypted)) + " bits") decryptor.decrypt(encrypted, plain2) print("Fourth power: " + plain2.to_string()) # Observe from the print-out that we have now used significantly more of our # noise budget than in the two previous runs. This is again not surprising, # since the first relinearization chops off a huge part of the noise budget. # However, note that the second relinearization does not change the noise # budget by any observable amount. This is very important to understand when # optimal performance is desired: relinearization always drops the noise # budget from the maximum (freshly encrypted ciphertext) down to a fixed # amount depending on the encryption parameters and the decomposition bit # count. On the other hand, homomorphic multiplication always consumes the # noise budget from its current level. This is why the second relinearization # does not change the noise budget anymore: it is already consumed past the # fixed amount determinted by the decomposition bit count and the encryption # parameters. # We now perform a third squaring and observe an even further compounded # decrease in the noise budget. Again, relinearization does not consume the # noise budget at this point by any observable amount, even with the largest # possible decomposition bit count. evaluator.square(encrypted) print("Size after third squaring " + (str)(encrypted.size())) print("Noise budget after third squaring: " + (str)(decryptor.invariant_noise_budget(encrypted)) + " bits") evaluator.relinearize(encrypted, ev_keys60) print("Size after relinearization: " + (str)(encrypted.size())) print("Noise budget after relinearizing (dbc = " + (str)(ev_keys60.decomposition_bit_count()) + "): " + (str)(decryptor.invariant_noise_budget(encrypted)) + " bits") decryptor.decrypt(encrypted, plain2) print("Eighth power: " + plain2.to_string())
def example_basics_i(): print_example_banner("Example: Basics I") # In this example we demonstrate setting up encryption parameters and other # relevant objects for performing simple computations on encrypted integers. # SEAL uses the Fan-Vercauteren (FV) homomorphic encryption scheme. We refer to # https://eprint.iacr.org/2012/144 for full details on how the FV scheme works. # For better performance, SEAL implements the "FullRNS" optimization of FV, as # described in https://eprint.iacr.org/2016/510. # The first task is to set up an instance of the EncryptionParameters class. # It is critical to understand how these different parameters behave, how they # affect the encryption scheme, performance, and the security level. There are # three encryption parameters that are necessary to set: # - poly_modulus (polynomial modulus); # - coeff_modulus ([ciphertext] coefficient modulus); # - plain_modulus (plaintext modulus). # A fourth parameter -- noise_standard_deviation -- has a default value of 3.19 # and should not be necessary to modify unless the user has a specific reason # to and knows what they are doing. # The encryption scheme implemented in SEAL cannot perform arbitrary computations # on encrypted data. Instead, each ciphertext has a specific quantity called the # `invariant noise budget' -- or `noise budget' for short -- measured in bits. # The noise budget of a freshly encrypted ciphertext (initial noise budget) is # determined by the encryption parameters. Homomorphic operations consume the # noise budget at a rate also determined by the encryption parameters. In SEAL # the two basic homomorphic operations are additions and multiplications, of # which additions can generally be thought of as being nearly free in terms of # noise budget consumption compared to multiplications. Since noise budget # consumption is compounding in sequential multiplications, the most significant # factor in choosing appropriate encryption parameters is the multiplicative # depth of the arithmetic circuit that needs to be evaluated. Once the noise # budget in a ciphertext reaches zero, it becomes too corrupted to be decrypted. # Thus, it is essential to choose the parameters to be large enough to support # the desired computation; otherwise the result is impossible to make sense of # even with the secret key. parms = EncryptionParameters() # We first set the polynomial modulus. This must be a power-of-2 cyclotomic # polynomial, i.e. a polynomial of the form "1x^(power-of-2) + 1". The polynomial # modulus should be thought of mainly affecting the security level of the scheme; # larger polynomial modulus makes the scheme more secure. At the same time, it # makes ciphertext sizes larger, and consequently all operations slower. # Recommended degrees for poly_modulus are 1024, 2048, 4096, 8192, 16384, 32768, # but it is also possible to go beyond this. Since we perform only a very small # computation in this example, it suffices to use a very small polynomial modulus parms.set_poly_modulus("1x^2048 + 1") # Next we choose the [ciphertext] coefficient modulus (coeff_modulus). The size # of the coefficient modulus should be thought of as the most significant factor # in determining the noise budget in a freshly encrypted ciphertext: bigger means # more noise budget. Unfortunately, a larger coefficient modulus also lowers the # security level of the scheme. Thus, if a large noise budget is required for # complicated computations, a large coefficient modulus needs to be used, and the # reduction in the security level must be countered by simultaneously increasing # the polynomial modulus. # To make parameter selection easier for the user, we have constructed sets of # largest allowed coefficient moduli for 128-bit and 192-bit security levels # for different choices of the polynomial modulus. These recommended parameters # follow the Security white paper at http://HomomorphicEncryption.org. However, # due to the complexity of this topic, we highly recommend the user to directly # consult an expert in homomorphic encryption and RLWE-based encryption schemes # to determine the security of their parameter choices. # Our recommended values for the coefficient modulus can be easily accessed # through the functions # coeff_modulus_128bit(int) # coeff_modulus_192bit(int) # for 128-bit and 192-bit security levels. The integer parameter is the degree # of the polynomial modulus. # In SEAL the coefficient modulus is a positive composite number -- a product # of distinct primes of size up to 60 bits. When we talk about the size of the # coefficient modulus we mean the bit length of the product of the small primes. # The small primes are represented by instances of the SmallModulus class; for # example coeff_modulus_128bit(int) returns a vector of SmallModulus instances. # It is possible for the user to select their own small primes. Since SEAL uses # the Number Theoretic Transform (NTT) for polynomial multiplications modulo the # factors of the coefficient modulus, the factors need to be prime numbers # congruent to 1 modulo 2*degree(poly_modulus). We have generated a list of such # prime numbers of various sizes, that the user can easily access through the # functions # small_mods_60bit(int) # small_mods_50bit(int) # small_mods_40bit(int) # small_mods_30bit(int) # each of which gives access to an array of primes of the denoted size. These # primes are located in the source file util/globals.cpp. # Performance is mainly affected by the size of the polynomial modulus, and the # number of prime factors in the coefficient modulus. Thus, it is important to # use as few factors in the coefficient modulus as possible. # In this example we use the default coefficient modulus for a 128-bit security # level. Concretely, this coefficient modulus consists of only one 56-bit prime # factor: 0xfffffffff00001. parms.set_coeff_modulus(seal.coeff_modulus_128(2048)) # The plaintext modulus can be any positive integer, even though here we take # it to be a power of two. In fact, in many cases one might instead want it to # be a prime number; we will see this in example_batching(). The plaintext # modulus determines the size of the plaintext data type, but it also affects # the noise budget in a freshly encrypted ciphertext, and the consumption of # the noise budget in homomorphic multiplication. Thus, it is essential to try # to keep the plaintext data type as small as possible for good performance. # The noise budget in a freshly encrypted ciphertext is # ~ log2(coeff_modulus/plain_modulus) (bits) # and the noise budget consumption in a homomorphic multiplication is of the # form log2(plain_modulus) + (other terms). parms.set_plain_modulus(1 << 8) # Now that all parameters are set, we are ready to construct a SEALContext # object. This is a heavy class that checks the validity and properties of # the parameters we just set, and performs and stores several important # pre-computations. context = SEALContext(parms) # Print the parameters that we have chosen print_parameters(context) # Plaintexts in the FV scheme are polynomials with coefficients integers modulo # plain_modulus. To encrypt for example integers instead, one can use an # `encoding scheme' to represent the integers as such polynomials. SEAL comes # with a few basic encoders: # [IntegerEncoder] # Given an integer base b, encodes integers as plaintext polynomials as follows. # First, a base-b expansion of the integer is computed. This expansion uses # a `balanced' set of representatives of integers modulo b as the coefficients. # Namely, when b is odd the coefficients are integers between -(b-1)/2 and # (b-1)/2. When b is even, the integers are between -b/2 and (b-1)/2, except # when b is two and the usual binary expansion is used (coefficients 0 and 1). # Decoding amounts to evaluating the polynomial at x=b. For example, if b=2, # the integer # 26 = 2^4 + 2^3 + 2^1 # is encoded as the polynomial 1x^4 + 1x^3 + 1x^1. When b=3, # 26 = 3^3 - 3^0 # is encoded as the polynomial 1x^3 - 1. In memory polynomial coefficients are # always stored as unsigned integers by storing their smallest non-negative # representatives modulo plain_modulus. To create a base-b integer encoder, # use the constructor IntegerEncoder(plain_modulus, b). If no b is given, b=2 # is used. # [FractionalEncoder] # The FractionalEncoder encodes fixed-precision rational numbers as follows. # It expands the number in a given base b, possibly truncating an infinite # fractional part to finite precision, e.g. # 26.75 = 2^4 + 2^3 + 2^1 + 2^(-1) + 2^(-2) # when b=2. For the sake of the example, suppose poly_modulus is 1x^1024 + 1. # It then represents the integer part of the number in the same way as in # IntegerEncoder (with b=2 here), and moves the fractional part instead to the # highest degree part of the polynomial, but with signs of the coefficients # changed. In this example we would represent 26.75 as the polynomial # -1x^1023 - 1x^1022 + 1x^4 + 1x^3 + 1x^1. # In memory the negative coefficients of the polynomial will be represented as # their negatives modulo plain_modulus. # [PolyCRTBuilder] # If plain_modulus is a prime congruent to 1 modulo 2*degree(poly_modulus), the # plaintext elements can be viewed as 2-by-(degree(poly_modulus) / 2) matrices # with elements integers modulo plain_modulus. When a desired computation can be # vectorized, using PolyCRTBuilder can result in massive performance improvements # over naively encrypting and operating on each input number separately. Thus, # in more complicated computations this is likely to be by far the most important # and useful encoder. In example_batching() we show how to use and operate on # encrypted matrix plaintexts. # For performance reasons, in homomorphic encryption one typically wants to keep # the plaintext data types as small as possible, which can make it challenging to # prevent data type overflow in more complicated computations, especially when # operating on rational numbers that have been scaled to integers. When using # PolyCRTBuilder estimating whether an overflow occurs is a fairly standard task, # as the matrix slots are integers modulo plain_modulus, and each slot is operated # on independently of the others. When using IntegerEncoder or FractionalEncoder # it is substantially more difficult to estimate when an overflow occurs in the # plaintext, and choosing the plaintext modulus very carefully to be large enough # is critical to avoid unexpected results. Specifically, one needs to estimate how # large the largest coefficient in the polynomial view of all of the plaintext # elements becomes, and choose the plaintext modulus to be larger than this value. # SEAL comes with an automatic parameter selection tool that can help with this # task, as is demonstrated in example_parameter_selection(). # Here we choose to create an IntegerEncoder with base b=2. encoder = IntegerEncoder(context.plain_modulus()) # We are now ready to generate the secret and public keys. For this purpose we need # an instance of the KeyGenerator class. Constructing a KeyGenerator automatically # generates the public and secret key, which can then be read to local variables. # To create a fresh pair of keys one can call KeyGenerator::generate() at any time. keygen = KeyGenerator(context) public_key = keygen.public_key() secret_key = keygen.secret_key() # To be able to encrypt, we need to construct an instance of Encryptor. Note that # the Encryptor only requires the public key. encryptor = Encryptor(context, public_key) # Computations on the ciphertexts are performed with the Evaluator class. evaluator = Evaluator(context) # We will of course want to decrypt our results to verify that everything worked, # so we need to also construct an instance of Decryptor. Note that the Decryptor # requires the secret key. decryptor = Decryptor(context, secret_key) # We start by encoding two integers as plaintext polynomials. value1 = 5 plain1 = encoder.encode(value1) print("Encoded " + (str)(value1) + " as polynomial " + plain1.to_string() + " (plain1)") value2 = -7 plain2 = encoder.encode(value2) print("Encoded " + (str)(value2) + " as polynomial " + plain2.to_string() + " (plain2)") # Encrypting the values is easy. encrypted1 = Ciphertext() encrypted2 = Ciphertext() print("Encrypting plain1: ") encryptor.encrypt(plain1, encrypted1) print("Done (encrypted1)") print("Encrypting plain2: ") encryptor.encrypt(plain2, encrypted2) print("Done (encrypted2)") # To illustrate the concept of noise budget, we print the budgets in the fresh # encryptions. print("Noise budget in encrypted1: " + (str)(decryptor.invariant_noise_budget(encrypted1)) + " bits") print("Noise budget in encrypted2: " + (str)(decryptor.invariant_noise_budget(encrypted2)) + " bits") # As a simple example, we compute (-encrypted1 + encrypted2) * encrypted2. # Negation is a unary operation. evaluator.negate(encrypted1) # Negation does not consume any noise budget. print("Noise budget in -encrypted1: " + (str)(decryptor.invariant_noise_budget(encrypted1)) + " bits") # Addition can be done in-place (overwriting the first argument with the result, # or alternatively a three-argument overload with a separate destination variable # can be used. The in-place variants are always more efficient. Here we overwrite # encrypted1 with the sum. evaluator.add(encrypted1, encrypted2) # It is instructive to think that addition sets the noise budget to the minimum # of the input noise budgets. In this case both inputs had roughly the same # budget going on, and the output (in encrypted1) has just slightly lower budget. # Depending on probabilistic effects, the noise growth consumption may or may # not be visible when measured in whole bits. print("Noise budget in -encrypted1 + encrypted2: " + (str)(decryptor.invariant_noise_budget(encrypted1)) + " bits") # Finally multiply with encrypted2. Again, we use the in-place version of the # function, overwriting encrypted1 with the product. evaluator.multiply(encrypted1, encrypted2) # Multiplication consumes a lot of noise budget. This is clearly seen in the # print-out. The user can change the plain_modulus to see its effect on the # rate of noise budget consumption. print("Noise budget in (-encrypted1 + encrypted2) * encrypted2: " + (str)(decryptor.invariant_noise_budget(encrypted1)) + " bits") # Now we decrypt and decode our result. plain_result = Plaintext() print("Decrypting result: ") decryptor.decrypt(encrypted1, plain_result) print("Done") # Print the result plaintext polynomial. print("Plaintext polynomial: " + plain_result.to_string()) # Decode to obtain an integer result. print("Decoded integer: " + (str)(encoder.decode_int32(plain_result)))
Evaluator, \ IntegerEncoder, \ FractionalEncoder, \ KeyGenerator, \ MemoryPoolHandle, \ Plaintext, \ SEALContext, \ EvaluationKeys, \ GaloisKeys, \ PolyCRTBuilder, \ ChooserEncoder, \ ChooserEvaluator, \ ChooserPoly parms = EncryptionParameters() parms.set_poly_modulus("1x^8192 + 1") parms.set_coeff_modulus(seal.coeff_modulus_128(8192)) parms.set_plain_modulus(1 << 21) context = SEALContext(parms) encoder = IntegerEncoder(context.plain_modulus()) keygen = KeyGenerator(context) public_key = keygen.public_key() secret_key = keygen.secret_key() ev_keys40 = EvaluationKeys() ev_keys20 = EvaluationKeys() #keygen.generate_evaluation_keys(40,5,ev_keys40) keygen.generate_evaluation_keys(20, 3, ev_keys20) encryptor = Encryptor(context, public_key) evaluator = Evaluator(context) decryptor = Decryptor(context, secret_key)
X[rowIndex] += [None] * (n - len(X[rowIndex])) for rowIndex in range(n): for colIndex in range(rowIndex + 1, n): assert (X[rowIndex][colIndex] == None) X[rowIndex][colIndex] = X[colIndex][rowIndex] X_array = np.asarray(X) return (X_array) ########################## paramaters required ################################# #N= int(input("Enter dimension of matrix needed to reverse: ")) parms = EncryptionParameters() parms.set_poly_modulus("1x^32768 + 1") parms.set_coeff_modulus(seal.coeff_modulus_128(16384)) parms.set_plain_modulus(1 << 30) context = SEALContext(parms) encoderF = FractionalEncoder(context.plain_modulus(), context.poly_modulus(), 34, 30, 2) keygen = KeyGenerator(context) public_key = keygen.public_key() secret_key = keygen.secret_key() ev_keys = EvaluationKeys() keygen.generate_evaluation_keys(15, ev_keys) encryptor = Encryptor(context, public_key) evaluator = Evaluator(context)
Evaluator, \ IntegerEncoder, \ FractionalEncoder, \ KeyGenerator, \ MemoryPoolHandle, \ Plaintext, \ SEALContext, \ EvaluationKeys, \ GaloisKeys, \ PolyCRTBuilder, \ ChooserEncoder, \ ChooserEvaluator, \ ChooserPoly parms = EncryptionParameters() parms.set_poly_modulus("1x^4096 + 1") parms.set_coeff_modulus(seal.coeff_modulus_128(4096)) # Note that 40961 is a prime number and 2*4096 divides 40960. parms.set_plain_modulus(40961) context = SEALContext(parms) keygen = KeyGenerator(context) public_key = keygen.public_key() secret_key = keygen.secret_key() def inner_product(cypher1, cypher2): # We also set up an Encryptor, Evaluator, and Decryptor here. evaluator = Evaluator(context) decryptor = Decryptor(context, secret_key)
def example_batching(): print_example_banner("Example: Batching with PolyCRTBuilder"); parms = EncryptionParameters() parms.set_poly_modulus("1x^4096 + 1") parms.set_coeff_modulus(seal.coeff_modulus_128(4096)) parms.set_plain_modulus(40961) context = SEALContext(parms) print_parameters(context) qualifiers = context.qualifiers() keygen = KeyGenerator(context) public_key = keygen.public_key() secret_key = keygen.secret_key() gal_keys = GaloisKeys() keygen.generate_galois_keys(30, gal_keys) #ev_keys = EvaluationKeys() #keygen.generate_evaluation_keys(30, ev_keys) encryptor = Encryptor(context, public_key) evaluator = Evaluator(context) decryptor = Decryptor(context, secret_key) crtbuilder = PolyCRTBuilder(context) slot_count = (int)(crtbuilder.slot_count()) row_size = (int)(slot_count / 2) print("Plaintext matrix row size: " + (str)(row_size)) def print_matrix(matrix): print("") print_size = 5 current_line = " [" for i in range(print_size): current_line += ((str)(matrix[i]) + ", ") current_line += ("..., ") for i in range(row_size - print_size, row_size): current_line += ((str)(matrix[i])) if i != row_size-1: current_line += ", " else: current_line += "]" print(current_line) current_line = " [" for i in range(row_size, row_size + print_size): current_line += ((str)(matrix[i]) + ", ") current_line += ("..., ") for i in range(2*row_size - print_size, 2*row_size): current_line += ((str)(matrix[i])) if i != 2*row_size-1: current_line += ", " else: current_line += "]" print(current_line) print("") # [ 0, 1, 2, 3, 0, 0, ..., 0 ] # [ 4, 5, 6, 7, 0, 0, ..., 0 ] pod_matrix = [0]*slot_count pod_matrix[0] = 0 pod_matrix[1] = 1 pod_matrix[2] = 2 pod_matrix[3] = 3 pod_matrix[row_size] = 4 pod_matrix[row_size + 1] = 5 pod_matrix[row_size + 2] = 6 pod_matrix[row_size + 3] = 7 print("Input plaintext matrix:") print_matrix(pod_matrix) plain_matrix = Plaintext() crtbuilder.compose(pod_matrix, plain_matrix) encrypted_matrix = Ciphertext() print("Encrypting: ") encryptor.encrypt(plain_matrix, encrypted_matrix) print("Done") print("Noise budget in fresh encryption: " + (str)(decryptor.invariant_noise_budget(encrypted_matrix)) + " bits") pod_matrix2 = [] for i in range(slot_count): pod_matrix2.append((i % 2) + 1) plain_matrix2 = Plaintext() crtbuilder.compose(pod_matrix2, plain_matrix2) print("Second input plaintext matrix:") print_matrix(pod_matrix2) print("Adding and squaring: ") evaluator.add_plain(encrypted_matrix, plain_matrix2) evaluator.square(encrypted_matrix) evaluator.relinearize(encrypted_matrix, ev_keys) print("Done") print("Noise budget in result: " + (str)(decryptor.invariant_noise_budget(encrypted_matrix)) + " bits") plain_result = Plaintext() print("Decrypting result: ") decryptor.decrypt(encrypted_matrix, plain_result) print("Done") crtbuilder.decompose(plain_result) pod_result = [plain_result.coeff_at(i) for i in range(plain_result.coeff_count())] print("Result plaintext matrix:") print_matrix(pod_result) encryptor.encrypt(plain_matrix, encrypted_matrix) print("Unrotated matrix: ") print_matrix(pod_matrix) print("Noise budget in fresh encryption: " + (str)(decryptor.invariant_noise_budget(encrypted_matrix)) + " bits") # Now rotate the rows to the left 3 steps, decrypt, decompose, and print. evaluator.rotate_rows(encrypted_matrix, 3, gal_keys) print("Rotated rows 3 steps left: ") decryptor.decrypt(encrypted_matrix, plain_result) crtbuilder.decompose(plain_result) pod_result = [plain_result.coeff_at(i) for i in range(plain_result.coeff_count())] print_matrix(pod_result) print("Noise budget after rotation" + (str)(decryptor.invariant_noise_budget(encrypted_matrix)) + " bits") # Rotate columns (swap rows), decrypt, decompose, and print. evaluator.rotate_columns(encrypted_matrix, gal_keys) print("Rotated columns: ") decryptor.decrypt(encrypted_matrix, plain_result) crtbuilder.decompose(plain_result) pod_result = [plain_result.coeff_at(i) for i in range(plain_result.coeff_count())] print_matrix(pod_result) print("Noise budget after rotation: " + (str)(decryptor.invariant_noise_budget(encrypted_matrix)) + " bits") # Rotate rows to the right 4 steps, decrypt, decompose, and print. evaluator.rotate_rows(encrypted_matrix, -4, gal_keys) print("Rotated rows 4 steps right: ") decryptor.decrypt(encrypted_matrix, plain_result) crtbuilder.decompose(plain_result) pod_result = [plain_result.coeff_at(i) for i in range(plain_result.coeff_count())] print_matrix(pod_result) print("Noise budget after rotation: " + (str)(decryptor.invariant_noise_budget(encrypted_matrix)) + " bits")
def dot_product(): print("Example: Weighted Average") # In this example we demonstrate the FractionalEncoder, and use it to compute # a weighted average of 10 encrypted rational numbers. In this computation we # perform homomorphic multiplications of ciphertexts by plaintexts, which is # much faster than regular multiplications of ciphertexts by ciphertexts. # Moreover, such `plain multiplications' never increase the ciphertext size, # which is why we have no need for evaluation keys in this example. # We start by creating encryption parameters, setting up the SEALContext, keys, # and other relevant objects. Since our computation has multiplicative depth of # only two, it suffices to use a small poly_modulus. parms = EncryptionParameters() parms.set_poly_modulus("1x^2048 + 1") parms.set_coeff_modulus(seal.coeff_modulus_128(2048)) parms.set_plain_modulus(1 << 8) context = SEALContext(parms) print_parameters(context) keygen = KeyGenerator(context) keygen2 = KeyGenerator(context) public_key = keygen.public_key() secret_key = keygen.secret_key() secret_key2 = keygen.secret_key() # We also set up an Encryptor, Evaluator, and Decryptor here. encryptor = Encryptor(context, public_key) evaluator = Evaluator(context) decryptor = Decryptor(context, secret_key2) # Create a vector of 10 rational numbers (as doubles). # rational_numbers = [3.1, 4.159, 2.65, 3.5897, 9.3, 2.3, 8.46, 2.64, 3.383, 2.795] rational_numbers = np.random.rand(10) # Create a vector of weights. # coefficients = [0.1, 0.05, 0.05, 0.2, 0.05, 0.3, 0.1, 0.025, 0.075, 0.05] coefficients = np.random.rand(10) my_result = np.dot(rational_numbers, coefficients) # We need a FractionalEncoder to encode the rational numbers into plaintext # polynomials. In this case we decide to reserve 64 coefficients of the # polynomial for the integral part (low-degree terms) and expand the fractional # part to 32 digits of precision (in base 3) (high-degree terms). These numbers # can be changed according to the precision that is needed; note that these # choices leave a lot of unused space in the 2048-coefficient polynomials. encoder = FractionalEncoder(context.plain_modulus(), context.poly_modulus(), 64, 32, 3) # We create a vector of ciphertexts for encrypting the rational numbers. encrypted_rationals = [] rational_numbers_string = "Encoding and encrypting: " for i in range(10): # We create our Ciphertext objects into the vector by passing the # encryption parameters as an argument to the constructor. This ensures # that enough memory is allocated for a size 2 ciphertext. In this example # our ciphertexts never grow in size (plain multiplication does not cause # ciphertext growth), so we can expect the ciphertexts to remain in the same # location in memory throughout the computation. In more complicated examples # one might want to call a constructor that reserves enough memory for the # ciphertext to grow to a specified size to avoid costly memory moves when # multiplications and relinearizations are performed. encrypted_rationals.append(Ciphertext(parms)) encryptor.encrypt(encoder.encode(rational_numbers[i]), encrypted_rationals[i]) rational_numbers_string += (str)(rational_numbers[i])[:6] if i < 9: rational_numbers_string += ", " print(rational_numbers_string) # Next we encode the coefficients. There is no reason to encrypt these since they # are not private data. encoded_coefficients = [] encoded_coefficients_string = "Encoding plaintext coefficients: " encrypted_coefficients =[] for i in range(10): encoded_coefficients.append(encoder.encode(coefficients[i])) encrypted_coefficients.append(Ciphertext(parms)) encryptor.encrypt(encoded_coefficients[i], encrypted_coefficients[i]) encoded_coefficients_string += (str)(coefficients[i])[:6] if i < 9: encoded_coefficients_string += ", " print(encoded_coefficients_string) # We also need to encode 0.1. Multiplication by this plaintext will have the # effect of dividing by 10. Note that in SEAL it is impossible to divide # ciphertext by another ciphertext, but in this way division by a plaintext is # possible. div_by_ten = encoder.encode(0.1) # Now compute each multiplication. prod_result = [Ciphertext() for i in range(10)] prod_result2 = [Ciphertext() for i in range(10)] print("Computing products: ") for i in range(10): # Note how we use plain multiplication instead of usual multiplication. The # result overwrites the first argument in the function call. evaluator.multiply_plain(encrypted_rationals[i], encoded_coefficients[i], prod_result[i]) evaluator.multiply(encrypted_rationals[i], encrypted_coefficients[i], prod_result2[i]) print("Done") # To obtain the linear sum we need to still compute the sum of the ciphertexts # in encrypted_rationals. There is an easy way to add together a vector of # Ciphertexts. encrypted_result = Ciphertext() encrypted_result2 = Ciphertext() print("Adding up all 10 ciphertexts: ") evaluator.add_many(prod_result, encrypted_result) evaluator.add_many(prod_result2, encrypted_result2) print("Done") # Perform division by 10 by plain multiplication with div_by_ten. # print("Dividing by 10: ") # evaluator.multiply_plain(encrypted_result, div_by_ten) # print("Done") # How much noise budget do we have left? print("Noise budget in result: " + (str)(decryptor.invariant_noise_budget(encrypted_result)) + " bits") # Decrypt, decode, and print result. plain_result = Plaintext() plain_result2 = Plaintext() print("Decrypting result: ") decryptor.decrypt(encrypted_result, plain_result) decryptor.decrypt(encrypted_result2, plain_result2) print("Done") result = encoder.decode(plain_result) print("Weighted average: " + (str)(result)[:8]) result2 = encoder.decode(plain_result2) print("Weighted average: " + (str)(result2)[:8]) print('\n\n', my_result)
def unit_test(): parms = EncryptionParameters() parms.set_poly_modulus("1x^8192 + 1") parms.set_coeff_modulus(seal.coeff_modulus_128(8192)) parms.set_plain_modulus(1 << 10) context = SEALContext(parms) # Print the parameters that we have chosen print_parameters(context) encoder = FractionalEncoder(context.plain_modulus(), context.poly_modulus(), 64, 32, 3) keygen = KeyGenerator(context) public_key = keygen.public_key() secret_key = keygen.secret_key() encryptor = Encryptor(context, public_key) # Computations on the ciphertexts are performed with the Evaluator class. evaluator = Evaluator(context) # We will of course want to decrypt our results to verify that everything worked, # so we need to also construct an instance of Decryptor. Note that the Decryptor # requires the secret key. decryptor = Decryptor(context, secret_key) learningRate = 0.1 learningRate_data = encoder.encode(learningRate) learningRate_e = Ciphertext() encryptor.encrypt(learningRate_data, learningRate_e) updatedVals = [] updatedVals.append(50) updatedVals.append(50) updatedVals_unenc = [] updatedVals_unenc.append(updatedVals[0]) updatedVals_unenc.append(updatedVals[1]) for i in range(15): x = 1 y = 1 updatedVals = learn(x, y, evaluator, updatedVals[0], updatedVals[1], encoder, encryptor, learningRate_e, decryptor) ypred = updatedVals_unenc[0] * x + updatedVals_unenc[1] error = ypred - y updatedVals_unenc[0] = updatedVals_unenc[0] - x * error * learningRate updatedVals_unenc[1] = updatedVals_unenc[1] - error * learningRate print((str)(updatedVals[1]) + ":" + (str)(updatedVals[0]) + ":" + (str)(updatedVals_unenc[1]) + ":" + (str)(updatedVals_unenc[0])) x = 2 y = 3 updatedVals = learn(x, y, evaluator, updatedVals[0], updatedVals[1], encoder, encryptor, learningRate_e, decryptor) ypred = updatedVals_unenc[0] * x + updatedVals_unenc[1] error = ypred - y updatedVals_unenc[0] = updatedVals_unenc[0] - x * error * learningRate updatedVals_unenc[1] = updatedVals_unenc[1] - error * learningRate print((str)(updatedVals[1]) + ":" + (str)(updatedVals[0]) + ":" + (str)(updatedVals_unenc[1]) + ":" + (str)(updatedVals_unenc[0])) x = 4 y = 3 updatedVals = learn(x, y, evaluator, updatedVals[0], updatedVals[1], encoder, encryptor, learningRate_e, decryptor) ypred = updatedVals_unenc[0] * x + updatedVals_unenc[1] error = ypred - y updatedVals_unenc[0] = updatedVals_unenc[0] - x * error * learningRate updatedVals_unenc[1] = updatedVals_unenc[1] - error * learningRate print((str)(updatedVals[1]) + ":" + (str)(updatedVals[0]) + ":" + (str)(updatedVals_unenc[1]) + ":" + (str)(updatedVals_unenc[0])) x = 3 y = 2 updatedVals = learn(x, y, evaluator, updatedVals[0], updatedVals[1], encoder, encryptor, learningRate_e, decryptor) ypred = updatedVals_unenc[0] * x + updatedVals_unenc[1] error = ypred - y updatedVals_unenc[0] = updatedVals_unenc[0] - x * error * learningRate updatedVals_unenc[1] = updatedVals_unenc[1] - error * learningRate print((str)(updatedVals[1]) + ":" + (str)(updatedVals[0]) + ":" + (str)(updatedVals_unenc[1]) + ":" + (str)(updatedVals_unenc[0])) x = 5 y = 5 updatedVals = learn(x, y, evaluator, updatedVals[0], updatedVals[1], encoder, encryptor, learningRate_e, decryptor) ypred = updatedVals_unenc[0] * x + updatedVals_unenc[1] error = ypred - y updatedVals_unenc[0] = updatedVals_unenc[0] - x * error * learningRate updatedVals_unenc[1] = updatedVals_unenc[1] - error * learningRate print((str)(updatedVals[1]) + ":" + (str)(updatedVals[0]) + ":" + (str)(updatedVals_unenc[1]) + ":" + (str)(updatedVals_unenc[0]))
GaloisKeys, \ PolyCRTBuilder, \ ChooserEncoder, \ ChooserEvaluator, \ ChooserPoly # Set up an instance of the EncryptionParameters class # three encryption parameters that are necessary to set: # - poly_modulus (polynomial modulus); # - coeff_modulus ([ciphertext] coefficient modulus); # - plain_modulus (plaintext modulus). parms = EncryptionParameters() #polynomial modulus must be a power-of-2 cyclotomic polynomial # Recommended degrees for poly_modulus are 1024, 2048, 4096, 8192, 16384, 32768, # but it is also possible to go beyond this. parms.set_poly_modulus("1x^2048 + 1") # To make parameter selection easier for the user, we have constructed sets of # largest allowed coefficient moduli for 128-bit and 192-bit security levels # for different choices of the polynomial modulus. These recommended parameters # follow the Security white paper at http://HomomorphicEncryption.org. However, # due to the complexity of this topic, we highly recommend the user to directly # consult an expert in homomorphic encryption and RLWE-based encryption schemes # to determine the security of their parameter choices. # Our recommended values for the coefficient modulus can be easily accessed # through the functions # coeff_modulus_128bit(int) # coeff_modulus_192bit(int) parms.set_coeff_modulus(seal.coeff_modulus_128(2048)) # The plaintext modulus can be any positive integer, even though here we take
class FHECryptoEngine(CryptoEngine): def __init__(self): CryptoEngine.__init__(self, defs.ENC_MODE_FHE) self.log_id = 'FHECryptoEngine' self.encrypt_params = None self.context = None self.encryptor = None self.evaluator = None self.decryptor = None return def load_keys(self): self.private_key = SecretKey() self.private_key.load(defs.FN_FHE_PRIVATE_KEY) self.public_key = PublicKey() self.public_key.load(defs.FN_FHE_PUBLIC_KEY) return True def generate_keys(self): if self.encrypt_params == None or \ self.context == None: self.init_encrypt_params() keygen = KeyGenerator(self.context) # Generate the private key self.private_key = keygen.secret_key() self.private_key.save(defs.FN_FHE_PRIVATE_KEY) # Generate the public key self.public_key = keygen.public_key() self.public_key.save(defs.FN_FHE_PUBLIC_KEY) return True def init_encrypt_params(self): self.encrypt_params = EncryptionParameters() self.encrypt_params.set_poly_modulus("1x^2048 + 1") self.encrypt_params.set_coeff_modulus(seal.coeff_modulus_128(2048)) self.encrypt_params.set_plain_modulus(1 << 8) self.context = SEALContext(self.encrypt_params) return def initialize(self, use_old_keys=False): # Initialize encryption params self.init_encrypt_params() # Check if the public & private key files exist if os.path.isfile(defs.FN_FHE_PUBLIC_KEY) and \ os.path.isfile(defs.FN_FHE_PRIVATE_KEY) and \ use_old_keys == True: self.log("Keys already exist. Reusing them instead.") if not self.load_keys(): self.log("Failed to load keys") return False else: # If not, then attempt to generate new ones if not self.generate_keys(): self.log("Failed to generate keys") return False # Setup the rest of the crypto engine self.encryptor = Encryptor(self.context, self.public_key) self.evaluator = Evaluator(self.context) self.decryptor = Decryptor(self.context, self.private_key) # Set the initialized flag self.initialized = True return True def encrypt(self, data): if not self.initialized: self.log("Not initialized") return False # Setup the encoder encoder = FractionalEncoder(self.context.plain_modulus(), self.context.poly_modulus(), 64, 32, 3) # Create the array of encrypted data objects encrypted_data = [] for raw_data in data: encrypted_data.append(Ciphertext(self.encrypt_params)) self.encryptor.encrypt( encoder.encode(raw_data), encrypted_data[-1] ) # Pickle each Ciphertext, base64 encode it, and store it in the array for i in range(0, len(encrypted_data)): encrypted_data[i].save("fhe_enc.bin") encrypted_data[i] = base64.b64encode( pickle.dumps(encrypted_data[i]) ) return encrypted_data def evaluate(self, encrypted_data, lower_idx=0, higher_idx=-1): result = Ciphertext() # Setup the encoder encoder = FractionalEncoder(self.context.plain_modulus(), self.context.poly_modulus(), 64, 32, 3) # Unpack the data first unpacked_data = [] for d in encrypted_data[lower_idx:higher_idx]: unpacked_data.append(pickle.loads(base64.b64decode(d))) # Perform operations self.evaluator.add_many(unpacked_data, result) div = encoder.encode(1/len(unpacked_data)) self.evaluator.multiply_plain(result, div) # Pack the result result = base64.b64encode( pickle.dumps(result) ) return result def decrypt(self, raw_data): if not self.initialized: self.log("Not initialized") return False # Setup the encoder encoder = FractionalEncoder(self.context.plain_modulus(), self.context.poly_modulus(), 64, 32, 3) # Unpickle, base64 decode, and decrypt each ciphertext result decrypted_data = [] for d in raw_data: encrypted_data = pickle.loads( base64.b64decode(d) ) plain_data = Plaintext() self.decryptor.decrypt(encrypted_data, plain_data) decrypted_data.append( str(encoder.decode(plain_data)) ) return decrypted_data