def test_Pyfhel_4c_operations_batch_array(self): pyfhel = Pyfhel() pyfhel.contextGen(p=1964769281, m=8192, base=2, sec=192, flagBatching=True) pyfhel.keyGen() pyfhel.rotateKeyGen(60) ctxti = pyfhel.encryptBatch([1, 2, 3, 4, 5, 6]) ctxti2 = pyfhel.encryptArray(np.array([-6, -5, -4, -3, -2, -1], dtype=np.int64)) ptxti = pyfhel.encodeArray(np.array([12, 15, 18, 21, 24, 27], dtype=np.int64)) ctxt_add = pyfhel.add(ctxti, ctxti2, in_new_ctxt=True) ctxt_add2 = pyfhel.add_plain(ctxti, ptxti, in_new_ctxt=True) ctxt_sub = pyfhel.sub(ctxti, ctxti2, in_new_ctxt=True) ctxt_sub2 = pyfhel.sub_plain(ctxti, ptxti, in_new_ctxt=True) ctxt_mult = pyfhel.multiply(ctxti, ctxti2, in_new_ctxt=True) ctxt_mult2 = pyfhel.multiply_plain(ctxti, ptxti, in_new_ctxt=True) ctxt_rotate = pyfhel.rotate(ctxti, -2, in_new_ctxt=True) ctxt_rotate2 = pyfhel.rotate(ctxti, 2, in_new_ctxt=True) self.assertEqual(pyfhel.decryptBatch(ctxt_add)[:6], [-5, -3, -1, 1, 3, 5]) self.assertEqual(pyfhel.decryptBatch(ctxt_add2)[:6], [13, 17, 21, 25, 29, 33]) self.assertEqual(pyfhel.decryptBatch(ctxt_sub)[:6], [7, 7, 7, 7, 7, 7]) self.assertEqual( pyfhel.decryptBatch(ctxt_sub2)[:6], [-11, -13, -15, -17, -19, -21] ) self.assertEqual( pyfhel.decryptBatch(ctxt_mult)[:6], [-6, -10, -12, -12, -10, -6] ) self.assertEqual( pyfhel.decryptBatch(ctxt_mult2)[:6], [12, 30, 54, 84, 120, 162] ) self.assertEqual(pyfhel.decryptBatch(ctxt_rotate)[:6], [0, 0, 1, 2, 3, 4]) self.assertEqual(pyfhel.decryptBatch(ctxt_rotate2)[:6], [3, 4, 5, 6, 0, 0])
def test_Pyfhel_4a_operations_integer(self): pyfhel = Pyfhel() pyfhel.contextGen(p=1964769281, m=8192, base=3, sec=192) pyfhel.keyGen() # self.pyfhel.rotateKeyGen(60) # self.pyfhel.relinKeyGen(60) ctxti = pyfhel.encryptInt(127) ctxti2 = pyfhel.encryptInt(-2) ptxti = pyfhel.encodeInt(3) ctxt_add = pyfhel.add(ctxti, ctxti2, in_new_ctxt=True) ctxt_add2 = pyfhel.add_plain(ctxti, ptxti, in_new_ctxt=True) ctxt_sub = pyfhel.sub(ctxti, ctxti2, in_new_ctxt=True) ctxt_sub2 = pyfhel.sub_plain(ctxti, ptxti, in_new_ctxt=True) ctxt_mult = pyfhel.multiply(ctxti, ctxti2, in_new_ctxt=True) ctxt_mult2 = pyfhel.multiply_plain(ctxti, ptxti, in_new_ctxt=True) # self.ctxt_rotate = self.pyfhel.rotate(self.ctxti, 2) # self.ctxt_expon = self.pyfhel.power(self.ctxti, 3) # self.ctxt_expon2 = self.pyfhel.power(self.ctxti2, 3) # self.ctxt_polyEval = self.pyfhel.polyEval(self.ctxti, [1, 2, 1], in_new_ctxt=True) self.assertEqual(pyfhel.decryptInt(ctxt_add), 125) self.assertEqual(pyfhel.decryptInt(ctxt_add2), 130) self.assertEqual(pyfhel.decryptInt(ctxt_sub), 129) self.assertEqual(pyfhel.decryptInt(ctxt_sub2), 124) self.assertEqual(pyfhel.decryptInt(ctxt_mult), -254) self.assertEqual(pyfhel.decryptInt(ctxt_mult2), 381)
def test_Pyfhel_4b_operations_frac(self): pyfhel = Pyfhel() pyfhel.contextGen(p=1964769281, m=8192, base=3, sec=192) pyfhel.keyGen() ctxti = pyfhel.encryptFrac(19.37) ctxti2 = pyfhel.encryptFrac(-2.25) ptxti = pyfhel.encodeFrac(3.12) ctxt_add = pyfhel.add(ctxti, ctxti2, in_new_ctxt=True) ctxt_add2 = pyfhel.add_plain(ctxti, ptxti, in_new_ctxt=True) ctxt_sub = pyfhel.sub(ctxti, ctxti2, in_new_ctxt=True) ctxt_sub2 = pyfhel.sub_plain(ctxti, ptxti, in_new_ctxt=True) ctxt_mult = pyfhel.multiply(ctxti, ctxti2, in_new_ctxt=True) ctxt_mult2 = pyfhel.multiply_plain(ctxti, ptxti, in_new_ctxt=True) self.assertEqual(round(pyfhel.decryptFrac(ctxt_add), 2), 17.12) self.assertEqual(round(pyfhel.decryptFrac(ctxt_add2), 2), 22.49) self.assertEqual(round(pyfhel.decryptFrac(ctxt_sub), 2), 21.62) self.assertEqual(round(pyfhel.decryptFrac(ctxt_sub2), 2), 16.25) self.assertEqual(round(pyfhel.decryptFrac(ctxt_mult), 2), -43.58) self.assertEqual(round(pyfhel.decryptFrac(ctxt_mult2), 2), 60.43)
def test_Pyfhel_4a_operations_integer(self): pyfhel = Pyfhel() pyfhel.contextGen(p=1964769281, m=8192, base=3, sec=192) pyfhel.keyGen() ctxti = pyfhel.encryptInt(127) ctxti2 = pyfhel.encryptInt(-2) ptxti = pyfhel.encodeInt(3) ctxt_add = pyfhel.add(ctxti, ctxti2, in_new_ctxt=True) ctxt_add2 = pyfhel.add_plain(ctxti, ptxti, in_new_ctxt=True) ctxt_sub = pyfhel.sub(ctxti, ctxti2, in_new_ctxt=True) ctxt_sub2 = pyfhel.sub_plain(ctxti, ptxti, in_new_ctxt=True) ctxt_mult = pyfhel.multiply(ctxti, ctxti2, in_new_ctxt=True) ctxt_mult2 = pyfhel.multiply_plain(ctxti, ptxti, in_new_ctxt=True) self.assertEqual(pyfhel.decryptInt(ctxt_add), 125) self.assertEqual(pyfhel.decryptInt(ctxt_add2), 130) self.assertEqual(pyfhel.decryptInt(ctxt_sub), 129) self.assertEqual(pyfhel.decryptInt(ctxt_sub2), 124) self.assertEqual(pyfhel.decryptInt(ctxt_mult), -254) self.assertEqual(pyfhel.decryptInt(ctxt_mult2), 381)
class EncryptedNet: def __init__(self, test, test_labels, model, n, t, precision, verbosity=False, base_dir="storage/contexts/", preprocess_dir="storage/layers/preprocessed/"): self.test = test self.test_labels = test_labels self.model = model self.n = n self.t = t self.precision = precision self.verbosity = verbosity self.prec = pow(10, precision) self.evaluate_start = None self.evaluate_end = None self.base_dir = base_dir self.preprocess_dir = preprocess_dir self.ctxt_dir = base_dir + "ctx_" + str(t) + "_" + str(n) self.enclayers_dir = self.ctxt_dir + "/layers/precision_" + str( precision) self.keys_dir = self.ctxt_dir + "/keys" self.generate_context() self.input_folder = self.enclayers_dir + "/input" self.conv_folder = self.enclayers_dir + "/conv" self.dense1_folder = self.enclayers_dir + "/dense1" self.dense2_folder = self.enclayers_dir + "/dense2" self.dense3_folder = self.enclayers_dir + "/dense3" self.out_folder = self.enclayers_dir + "/output" createDir(self.enclayers_dir) createDir(self.input_folder) createDir(self.conv_folder) createDir(self.dense1_folder) createDir(self.dense2_folder) createDir(self.dense3_folder) createDir(self.out_folder) def evaluate(self, get_acc=False): self.evaluate_start = timeit.default_timer() if self.verbosity: print( "==============================================================" ) print("CLIENT") print( "==============================================================" ) self.preprocess_input() if self.verbosity: print( "==============================================================" ) print("SERVICE PROVIDER") print( "==============================================================" ) self.convolution(15, 3, 2, (225, )) self.dense1((7, 7, 5), (49, 5)) self.dense2((100, )) self.fully_connected((10, )) accuracy = self.predict(self.test_labels, (10, ), get_acc) if get_acc: m = Model() acc = m.getAccuracy(self.model, self.test, self.test_labels, self.n) print("Original Accuracy: " + str(acc) + "%") print("") if (acc == accuracy): print( "EncryptedNet and Keras models give the same accuracy (about " + str(round(accuracy)) + "%)") else: print( "[ERR] EncryptedNet evaluation fails. The difference with " + "Keras model is about " + str(round(acc - accuracy)) + "%") # ========================================================================= # UTILITY FUNCTIONS # ========================================================================= def generate_context(self): context = self.ctxt_dir + "/context.ctxt" self.py = Pyfhel() if path.exists(context): if self.verbosity: print("Restoring the crypto context...") self.py.restoreContext(context) else: if self.verbosity: print("Creating the crypto context...") self.ctx = self.py.contextGen(self.t, self.n, True, 2, 128, 32, 32) self.py.saveContext(context) if path.exists(self.keys_dir): if self.verbosity: print("Restoring keys from local storage...") self.py.restorepublicKey(self.keys_dir + "/public.key") self.py.restoresecretKey(self.keys_dir + "/secret.key") self.py.restorerelinKey(self.keys_dir + "/relin.keys") else: if self.verbosity: print("Creating keys for this contest...") createDir(self.keys_dir) self.py.keyGen() self.py.relinKeyGen(16, 4) self.py.saverelinKey(self.keys_dir + "/relin.keys") self.py.savepublicKey(self.keys_dir + "/public.key") self.py.savesecretKey(self.keys_dir + "/secret.key") def store(self, arr, folder): arr = arr.flatten() if self.is_stored_before(folder): createDir(folder, True) for i in range(arr.__len__()): fname = folder + "/enc_" + str(i) arr[i].save(fname) def retrieve(self, folder, shape): if not self.is_stored_before(folder): raise Exception("Required files not foud in folder " + folder) to_populate = np.empty(shape=shape) to_populate = to_populate.flatten() l = [] for i in range(to_populate.__len__()): p = PyCtxt() fname = folder + "/enc_" + str(i) if not path.exists(fname): raise Exception("File ", fname, "not exists") p.load(fname, 'batch') l.append(p) return np.reshape(l, shape) def get_results(self): out_file = self.out_folder + "/results.npy" if not path.exists(out_file): raise Exception( "Impossibile to retrieve the result. Evaluation needed.") res = np.load(out_file) return res def is_stored_before(self, folder): fname = folder + "/enc_0" return path.exists(fname) # ========================================================================= # ENCODING FOR HE # ========================================================================= def truncate_to(self, val, precision): tr = "{0:." + str(precision) + "f}" return float(tr.format(val)) def truncate_tensor(self, tensor, precision): ish = tensor.shape tensor = tensor.flatten() res = [] for el in tensor: res.append(self.truncate_to(el, precision)) return tensor.reshape(ish) def check_encode(self, val): t = round(self.t / 2) t += 1 if val >= t or val <= -t: return False return True def check_encode_tensor(self, tensor): tensor = tensor.flatten() t2 = round(self.t / 2) for el in tensor: if not self.check_encode(el): raise Exception("Value (" + str(el) + ") must be in [-" + str(t2) + ", +" + str(t2) + "] interval") def encode(self, val): val = round(val * self.prec) if self.check_encode(val): return int(val) else: t2 = round(self.t / 2) raise Exception("Value (" + str(val) + ") must be in [-" + str(t2) + ", +" + str(t2) + "] interval") def encode_array(self, arr): ret = [] for el in arr: ret.append(self.encode(el)) return ret def encode_tensor(self, tensor): input_shape = tensor.shape tensor = tensor.flatten() tensor = np.array(self.encode_array(tensor)) tensor = tensor.reshape(input_shape) return self.truncate_tensor(tensor, self.precision) def get_map(self, el): tns = [el] * self.n return self.py.encodeBatch(tns) # ========================================================================= # GETTING NN W. AND B. # ========================================================================= def get_conv(self): layer = self.model.layers[0] w = layer.get_weights()[0] ew = np.empty(shape=(3, 3, 5)) for i in range(3): for x in range(3): for y in range(5): ew[i, x, y] = w[i, x, 0, y] b = layer.get_weights()[1] return ew, b def get_dense1(self): layer = self.model.layers[2] w = layer.get_weights()[0] b = layer.get_weights()[1] return w, b def get_dense2(self): layer = self.model.layers[3] w = layer.get_weights()[0] b = layer.get_weights()[1] return w, b def get_fully(self): layer = self.model.layers[4] w = layer.get_weights()[0] b = layer.get_weights()[1] return w, b # ========================================================================= # INPUT # ========================================================================= def preprocess_input(self): input_dim, dim, dim1, indx = self.test.shape pre_flat = self.test.flatten() pixel_arr_dim = dim * dim1 if self.is_stored_before(self.input_folder): print("Input layer preprocessed before. You can found it in " + self.input_folder + " folder") return None print("Processing input...") print("Input shape: ", self.test.shape) arr = [] for x in range(pre_flat.__len__()): if x < pixel_arr_dim: arr.append([pre_flat[x]]) else: pi = (x % pixel_arr_dim) arr[pi].append(pre_flat[x]) arr = np.array(arr) print("Output shape:", arr.shape) print("") print("Encoding input...") arr = self.encode_tensor(arr) print("Encrypting input...") out = [] for el in arr: encoded = self.py.encodeBatch(el) encrypted = self.py.encryptPtxt(encoded) out.append(encrypted) out = np.array(out) self.store(out, self.input_folder) out = None # ========================================================================= # CONVOLUTION # ========================================================================= def _get_conv_window_(self, size, kernel): res = [] x = 0 for i in range(kernel): x = size * i for j in range(kernel): res.append(x + j) return res def _get_conv_indexes_(self, pixel, size, kernel, stride, padding=0): res = [] output_size = int(((size - kernel + (2 * padding)) / stride)) + 1 x = pixel for i in range(output_size): x = pixel + ((size * stride) * i) for j in range(output_size): res.append(x) x += stride return res def get_conv_map(self, size, kernel, stride): window = self._get_conv_window_(size, kernel) conv_map = [] for i in range(window.__len__()): conv_map.append( self._get_conv_indexes_(window[i], size, kernel, stride)) return np.array(conv_map) def get_conv_windows(self, size, kernel, stride): cm = self.get_conv_map(size, kernel, stride) windows = [] for i in range(cm.shape[1]): w = [] for j in range(cm.shape[0]): w.append(cm[j, i]) windows.append(w) return np.array(windows) def convolution(self, size, kernel, stride, shape): if self.is_stored_before(self.conv_folder): print("Convolution layer processed before. You can found it in " + self.conv_folder + " folder") return None w, b = self.get_conv() w = self.encode_tensor(w) b = self.encode_tensor(b) cw = self.get_conv_windows(size, kernel, stride) w = w.reshape((w.shape[0] * w.shape[1], w.shape[2])) fw = np.empty(shape=(w.shape[1], w.shape[0])) out_shape = (cw.shape[0], fw.shape[0]) inpt = self.retrieve(self.input_folder, shape) print("Computing convolution...") print("Input shape:", inpt.shape) print("Output shape: ", out_shape) print("") for i in range(w.shape[0]): for j in range(w.shape[1]): fw[j, i] = w[i, j] res = [] i = 0 for f in fw: encb = self.get_map(b[i]) for y in range(cw.shape[0]): local_sum = None for k in range(cw.shape[1]): encw = self.get_map(f[k]) # el = np.multiply(inpt[cw[y,k]], encw) el = self.py.multiply_plain(inpt[cw[y, k]], encw, True) if (local_sum == None): local_sum = el else: local_sum = self.py.add(local_sum, el) local_sum = self.py.add_plain(local_sum, encb) res.append(local_sum) i += 1 res = np.array(res) lr = [] for i in range(res.shape[0]): if (lr.__len__() < cw.shape[0]): lr.append([res[i]]) else: inx = i % cw.shape[0] lr[inx].append(res[i]) out = np.array(lr) self.store(out, self.conv_folder) out = None # ========================================================================= # DENSE 1 # ========================================================================= def dense1(self, input_shape, shape): if self.is_stored_before(self.dense1_folder): print("First Dense layer processed before. You can found it in " + self.dense1_folder + " folder") return None w, b = self.get_dense1() w = self.encode_tensor(w) b = self.encode_tensor(b) per = input_shape[0] * input_shape[1] filters = input_shape[2] flat = per * filters if flat != w.shape[0]: raise Exception("Input shape " + str(input_shape) + " is not compatible with preprocessed input " + str(w.shape)) if w.shape[1] != b.shape[0]: raise Exception("Preprocessed weights " + str(w.shape) + " and biases " + str(b.shape) + " are incopatible.") out = [] out_conv = self.retrieve(self.conv_folder, shape) print("Computing first dense...") print("Input shape: ", out_conv.shape) print("Output shape:", w.shape[1]) print("") for x in range(w.shape[1]): local_sum = None for i in range(per): for j in range(filters): # fname = out_conv + "/" + str(i) + "_filter" + str(j) row = ((i * filters) + j) encw = self.get_map(w[row][x]) el = self.py.multiply_plain(out_conv[i, j], encw, True) if (local_sum == None): local_sum = el else: local_sum = self.py.add(local_sum, el) enc_b = self.get_map(b[x]) ts = self.py.add_plain(local_sum, enc_b) ts = self.py.square(ts) out.append(ts) # self.check_encode_tensor(out) out = np.array(out) self.store(out, self.dense1_folder) out = None # ========================================================================= # DENSE 2 # ========================================================================= def dense2(self, shape): if self.is_stored_before(self.dense2_folder): print("Second Dense layer processed before. You can found it in " + self.dense2_folder + " folder") return None w, b = self.get_dense2() w = self.encode_tensor(w) b = self.encode_tensor(b) if w.shape[1] != b.shape[0]: raise Exception("Preprocessed weights " + str(w.shape) + " and biases " + str(b.shape) + "are incopatible.") # out = [] d1 = self.retrieve(self.dense1_folder, shape) print("Computing second dense...") print("Input shape: ", d1.shape) print("Output shape:", w.shape[1]) print("") ind = 0 for x in range(w.shape[1]): local_sum = None for i in range(w.shape[0]): encw = self.get_map(w[i][x]) el = self.py.multiply_plain(d1[i], encw) if (local_sum == None): local_sum = el else: local_sum = self.py.add(local_sum, el) enc_b = self.get_map(b[x]) ts = self.py.add_plain(local_sum, enc_b, True) ts = self.py.square(ts) # out.append(ts) fname = self.dense2_folder + "/enc_" + str(ind) ts.save(fname) local_sum = None ts = None ind += 1 # self.check_encode_tensor(out) # SAVED BEFORE (memory issue) # out = np.array(out) # self.store(out, self.dense2_folder) # out = None # ========================================================================= # FULLY CONNECTED # ========================================================================= def fully_connected(self, shape): if self.is_stored_before(self.dense3_folder): print("Third Dense layer processed before. You can found it in " + self.dense3_folder + " folder") return None w, b = self.get_fully() w = self.encode_tensor(w) b = self.encode_tensor(b) if w.shape[1] != b.shape[0]: raise Exception("Preprocessed weights " + str(w.shape) + " and biases " + str(b.shape) + "are incopatible.") # out = [] d2 = self.retrieve(self.dense2_folder, shape) print("Computing fully connected...") print("Input shape: ", d2.shape) print("Output shape:", w.shape[1]) print("") ind = 0 for x in range(w.shape[1]): local_sum = None for i in range(w.shape[0]): # fname = input_folder + "/square_" + str(i) encw = self.get_map(w[i][x]) el = self.py.multiply_plain(d2[i], encw) if (local_sum == None): local_sum = el else: local_sum = self.py.add(local_sum, el) enc_b = self.get_map(b[x]) ts = self.py.add_plain(local_sum, enc_b) # out.append(ts) fname = self.dense3_folder + "/enc_" + str(ind) ts.save(fname) local_sum = None ts = None ind += 1 # self.check_encode_tensor(out) # out = np.append(out) # self.store(out, self.dense3_folder) # out = None # ========================================================================= # PREDICT / EVALUATE # ========================================================================= def predict(self, test_labels, shape, get_evaluation=False): if path.exists(self.out_folder + "/results.npy"): print("Output processed before. You can found it in " + self.out_folder + "/results folder") return None else: print("Elaborating output...") fc = self.retrieve(self.dense3_folder, shape) out = [] for el in fc: ptxt = self.py.decrypt(el) ptxt = self.py.decodeBatch(ptxt) out.append(ptxt) fc = np.array(out) self.evaluate_end = timeit.default_timer() evt = round(self.evaluate_end - self.evaluate_start) print( str(fc.shape[1]) + "/" + str(fc.shape[1]) + " [==============================] - " + str(evt) + "s") el = [] for i in range(test_labels.shape[1]): # file = out_folder + "/fc_"+str(i) if (el.__len__() <= i): el.append([]) for j in range(fc[i].__len__()): if (el.__len__() <= j): el.append([fc[i][j]]) else: el[j].append(fc[i][j]) el = np.array(el) print("El shape:", el.shape) np.save("./" + self.out_folder + "/results", el) if get_evaluation: print("================================") print(el[0]) pos = 0 for i in range(el.shape[0]): mp = np.argmax(el[i]) ml = np.argmax(test_labels[i]) if (mp == ml): pos += 1 acc = (pos / self.n) * 100 print("Model Accuracy: " + str(acc) + "% (" + str(pos) + "/" + str(test_labels.shape[0]) + ")") print("") return acc # ========================================================================= # PREDICTED OUTPUT PER LAYER (FOR DEBUG PRUPOSE) # ========================================================================= def get_conv_out(self): lm = self.model lm.outputs = [lm.layers[0].output] ye = lm.predict(self.test) return ye def get_dense1_out(self): lm = self.model lm.outputs = [lm.layers[2].output] ye = lm.predict(self.test) return ye def get_dense2_out(self): lm = self.model lm.outputs = [lm.layers[3].output] ye = lm.predict(self.test) return ye def get_fully_out(self): lm = self.model lm.outputs = [lm.layers[4].output] ye = lm.predict(self.test) return ye
class Encryption: def __init__( self, verbosity=False, p_modulus=1964769281, # Plaintext modulus. All operations are modulo p. (t) coeff_modulus=8192, # Coefficient modulus (n) batching=True, # Set to true to enable batching poly_base=2, # Polynomial base (x) security_level=128, # Security level equivalent in AES. 128 or 192. (10 || 12 rounds) intDigits=64, # Truncated positions for integer part. fracDigits=32, # Truncated positions for fractional part., relin_keys_count=2, # The number of relinKeys will be generated/restored relin_bitcount=16, # [1,60] bigger is faster but noiser relin_size=4, # |cxtx| = K+1 ==> size at least K-1 base_dir="storage/contexts/", preprocess_dir="storage/layers/preprocessed/", precision=4): self.verbosity = verbosity self.precision = precision self.t = p_modulus self.n = coeff_modulus self.batching = batching self.pbase = poly_base self.security = security_level self.idig = intDigits self.fdig = fracDigits self.relin_bits = relin_bitcount self.relin_size = relin_size self.py = Pyfhel() #Required directories self.preprocess_dir = preprocess_dir self.base_dir = base_dir self.ctxt_dir = base_dir + "ctx_" + str(p_modulus) + "_" + str( coeff_modulus) self.enclayers_dir = self.ctxt_dir + "/layers/precision_" + str( precision) self.keys_dir = self.ctxt_dir + "/keys" createDir(self.enclayers_dir) context = self.ctxt_dir + "/context.ctxt" if path.exists(context): if self.verbosity: print("Restoring the crypto context...") self.py.restoreContext(context) else: if self.verbosity: print("Creating the crypto context...") self.py.contextGen(p_modulus, coeff_modulus, batching, poly_base, security_level, intDigits, fracDigits) self.py.saveContext(context) if path.exists(self.keys_dir): if self.verbosity: print("Restoring keys from local storage...") self.py.restorepublicKey(self.keys_dir + "/public.key") self.py.restoresecretKey(self.keys_dir + "/secret.key") self.py.restorerelinKey(self.keys_dir + "/relin.keys") else: if self.verbosity: print("Creating keys for this contest...") createDir(self.keys_dir) self.py.keyGen() if self.verbosity: print("Generating " + str(relin_keys_count) + " relinearization key(s)") for i in range(relin_keys_count): self.py.relinKeyGen(relin_bitcount, relin_size) self.py.saverelinKey(self.keys_dir + "/relin.keys") self.py.savepublicKey(self.keys_dir + "/public.key") self.py.savesecretKey(self.keys_dir + "/secret.key") if self.verbosity: print("Created with success with the following parameters:") self.context_info() def context_info(self): """ Print the local context information """ print("") print("Context parameters") print("============================") print("Batch encoding: " + str(self.py.getflagBatch())) print("Polynomial base: " + str(self.py.getbase())) print("Frac digits: " + str(self.py.getfracDigits())) print("Int digits: " + str(self.py.getintDigits())) print("Plaintext coeff (m): " + str(self.py.getm())) print("Slots fitting in a ctxt: " + str(self.py.getnSlots())) print("Plaintext modulus (p): " + str(self.py.getp())) print("Security level (AES): " + str(self.py.getsec())) print("") print("") # ========================================================================= # CONVOLUTION LAYER # ------------------------------------------------------------------------- # It is computed given the preprocessed input and the preprocessed # weights and biases from the keras model. # ========================================================================= def convolution(self, size, kernel, stride): if self.verbosity: print("Computing Convolution") print("==================================") conv_folder = self.enclayers_dir + "/conv" pre_conv = conv_folder + "/pre" out_conv = conv_folder + "/output" if not path.exists(conv_folder): createDir(conv_folder) conv_w = self.preprocess_dir + "precision_" + str( self.precision) + "/pre_0_conv2d_3.npy" conv_b = self.preprocess_dir + "precision_" + str( self.precision) + "/pre_bias_0_conv2d_3.npy" if path.exists(pre_conv): print("(Pre)processed before. You can found it in " + pre_conv + " folder.") elif not path.exists(conv_w): print( "Convolution weights need to be preprocessed before (with precision " + str(self.precision) + ").") print("") else: createDir(pre_conv) filters = np.load(conv_w) start = timeit.default_timer() fshape = filters.shape f = filters.reshape((fshape[0] * fshape[1], fshape[2])) conv_map = self.get_conv_map(size, kernel, stride) if (conv_map.shape[0] != f.shape[0]): raise Exception( "Convolution map and filter shapes must match.") if self.verbosity: print("Convolution: output preprocessing...") print("0%") for x in range(f.shape[0]): for y in range(f.shape[1]): w_filter = self.get_map(f[x, y]) for k in range(conv_map.shape[1]): enc_pixel = self.getEncryptedPixel(conv_map[x, k]) # computing |self.n| dot products at time res = self.py.multiply_plain(enc_pixel, w_filter, True) f_name = pre_conv + "/pixel" + str( conv_map[x, k]) + "_filter" + str(y) res.save(f_name) if self.verbosity: perc = int(((x + 1) / f.shape[0]) * 100) print( str(perc) + "% (" + str(x + 1) + "/" + str(f.shape[0]) + ")") stop = timeit.default_timer() if self.verbosity: print("Convolution: output preprocessed in " + str(stop - start) + " s.") if path.exists(out_conv): print("Processed before. You can found it in " + out_conv + " folder.") print("") elif not path.exists(conv_b): print( "Convolution biases need to be preprocessed before (with precision " + str(self.precision) + ").") print("") else: createDir(out_conv) biases = np.load(conv_b) start = timeit.default_timer() bshape = biases.shape windows = self.get_conv_windows(size, kernel, stride) wshape = windows.shape if self.verbosity: print("Convolution: output processing...") print("0%") for x in range(bshape[0]): encoded_bias = self.get_map(biases[x]) for y in range(wshape[0]): local_sum = None for k in range(wshape[1]): f_name = pre_conv + "/pixel" + str( windows[y, k]) + "_filter" + str(x) p = PyCtxt() p.load(f_name, 'batch') if (local_sum == None): local_sum = p else: local_sum = self.py.add(local_sum, p) local_sum = self.py.add_plain(local_sum, encoded_bias) file_name = out_conv + "/" + str(y) + "_filter" + str(x) local_sum.save(file_name) if self.verbosity: perc = int(((x + 1) / bshape[0]) * 100) print( str(perc) + "% (" + str(x + 1) + "/" + str(bshape[0]) + ")") stop = timeit.default_timer() if self.verbosity: print("Convolution: output processed in " + str(stop - start) + " s.") print("") return out_conv def _get_conv_window_(self, size, kernel): """ Get the indices relative to the first convolutional window. """ res = [] x = 0 for i in range(kernel): x = size * i for j in range(kernel): res.append(x + j) return res def _get_conv_indexes_(self, pixel, size, kernel, stride, padding=0): """ Slide the given index in the flatten volume returning all the indexes to which the same convolution filter must be applied. """ res = [] output_size = int(((size - kernel + (2 * padding)) / stride)) + 1 x = pixel for i in range(output_size): x = pixel + ((size * stride) * i) for j in range(output_size): res.append(x) x += stride return res def get_conv_map(self, size, kernel, stride): """ Return the convolutional map of the input volume (given its width) according to the element index in its flatten version. """ window = self._get_conv_window_(size, kernel) conv_map = [] for i in range(window.__len__()): conv_map.append( self._get_conv_indexes_(window[i], size, kernel, stride)) return np.array(conv_map) def get_conv_windows(self, size, kernel, stride): cm = self.get_conv_map(size, kernel, stride) windows = [] for i in range(cm.shape[1]): w = [] for j in range(cm.shape[0]): w.append(cm[j, i]) windows.append(w) return np.array(windows) def get_map(self, el): el = [el] * self.n return self._encode_arr_(el) def get_enc_map(self, el): el = [el] * self.n return self._enc_arr_(el) # ========================================================================= # FIRST DENSE LAYER # ------------------------------------------------------------------------- # It is computed given the output files from the convolution layer and the # preprocessed weights (filters) and biases from the model # ========================================================================= def dense1(self, input_shape): if self.verbosity: print("Computing First Dense (square)") print("==================================") dense_folder = self.enclayers_dir + "/dense1" out_folder = dense_folder + "/output" conv_folder = self.enclayers_dir + "/conv" out_conv = conv_folder + "/output" wfile = "storage/layers/preprocessed/precision_" + str( self.precision) + "/pre_2_dense_9.npy" bfile = "storage/layers/preprocessed/precision_" + str( self.precision) + "/pre_bias_2_dense_9.npy" if not path.exists(dense_folder): createDir(dense_folder) if path.exists(out_folder): print("Processed before. You can found it in " + out_folder + " folder.") print("") elif not path.exists(wfile) or not path.exists(bfile): raise Exception( "First dense layer weights and biases need to be preprocessed before (with precision " + str(self.precision) + ").") elif not path.exists(out_conv): raise Exception( "Convolution output required. Please run Encryption.convolution(...) before." ) else: createDir(out_folder) w = np.load(wfile) b = np.load(bfile) start = timeit.default_timer() per = input_shape[0] * input_shape[1] filters = input_shape[2] flat = per * filters if flat != w.shape[0]: raise Exception("Input shape " + str(input_shape) + " is not compatible with preprocessed input " + str(w.shape)) if w.shape[1] != b.shape[0]: raise Exception("Preprocessed weights " + str(w.shape) + " and biases " + str(b.shape) + "are incopatible.") if self.verbosity: print("First Dense: output processing...") print("0%") for x in range(w.shape[1]): local_sum = None for i in range(per): for j in range(filters): fname = out_conv + "/" + str(i) + "_filter" + str(j) p = PyCtxt() p.load(fname, 'batch') row = (i * filters + j) encw = self.get_map(w[row][x]) el = self.py.multiply_plain(p, encw, True) if (local_sum == None): local_sum = el else: local_sum = self.py.add(local_sum, el) enc_b = self.get_map(b[x]) ts = self.py.add_plain(local_sum, enc_b, True) ts = self.py.square(ts) out_name = out_folder + "/square_" + str(x) ts.save(out_name) if self.verbosity: perc = int(((x + 1) / w.shape[1]) * 100) print( str(perc) + "% (" + str(x + 1) + "/" + str(w.shape[1]) + ")") stop = timeit.default_timer() if self.verbosity: print("First Dense: output processed in " + str(stop - start) + " s.") print("") # ========================================================================= # SECOND DENSE LAYER # ------------------------------------------------------------------------- # It is computed given the output files from first dense layer and the # weights (filters) and biases preprocessed from the model # ========================================================================= def dense2(self): if self.verbosity: print("Computing Second Dense (square)") print("==================================") input_folder = self.enclayers_dir + "/dense1/output" dense_folder = self.enclayers_dir + "/dense2" out_folder = dense_folder + "/output" wfile = "storage/layers/preprocessed/precision_" + str( self.precision) + "/pre_3_dense_10.npy" bfile = "storage/layers/preprocessed/precision_" + str( self.precision) + "/pre_bias_3_dense_10.npy" if not path.exists(dense_folder): createDir(dense_folder) if path.exists(out_folder): print("Processed before. You can found it in " + out_folder + " folder.") print("") elif not path.exists(wfile) or not path.exists(bfile): raise Exception( "Second dense layer weights and biases need to be preprocessed before (with precision " + str(self.precision) + ").") elif not path.exists(input_folder): raise Exception( "First dense output required. Please run Encryption.dense1(...) before." ) else: createDir(out_folder) w = np.load(wfile) b = np.load(bfile) if w.shape[1] != b.shape[0]: raise Exception("Preprocessed weights " + str(w.shape) + " and biases " + str(b.shape) + "are incopatible.") if self.verbosity: print("Second Dense: output processing...") print("0%") start = timeit.default_timer() for x in range(w.shape[1]): local_sum = None for i in range(w.shape[0]): fname = input_folder + "/square_" + str(i) p = PyCtxt() p.load(fname, 'batch') encw = self.get_map(w[i][x]) el = self.py.multiply_plain(p, encw, True) if (local_sum == None): local_sum = el else: local_sum = self.py.add(local_sum, el) enc_b = self.get_map(b[x]) ts = self.py.add_plain(local_sum, enc_b, True) ts = self.py.square(ts) out_name = out_folder + "/square_" + str(x) ts.save(out_name) if self.verbosity: perc = int(((x + 1) / w.shape[1]) * 100) print( str(perc) + "% (" + str(x + 1) + "/" + str(w.shape[1]) + ")") stop = timeit.default_timer() if self.verbosity: print("Second Dense: output processed in " + str(stop - start) + " s.") print("") # ========================================================================= # FULLY CONNECTED LAYER # ------------------------------------------------------------------------- # It is computed given the output files from the second dense layer and the # weights (filters) and biases preprocessed from the model # ========================================================================= def fully_connected(self): if self.verbosity: print("Computing Fully Connected") print("==================================") input_folder = self.enclayers_dir + "/dense2/output" fc_folder = self.enclayers_dir + "/fullyconnected" out_folder = fc_folder + "/output" wfile = "storage/layers/preprocessed/precision_" + str( self.precision) + "/pre_4_dense_11.npy" bfile = "storage/layers/preprocessed/precision_" + str( self.precision) + "/pre_bias_4_dense_11.npy" if not path.exists(fc_folder): createDir(fc_folder) if path.exists(out_folder): print("Processed before. You can found it in " + out_folder + " folder.") print("") elif not path.exists(wfile) or not path.exists(bfile): raise Exception( "Fully connected layer weights and biases need to be preprocessed before (with precision " + str(self.precision) + ").") elif not path.exists(input_folder): raise Exception( "Second dense output required. Please run Encryption.dense2(...) before." ) else: createDir(out_folder) w = np.load(wfile) b = np.load(bfile) if w.shape[1] != b.shape[0]: raise Exception("Preprocessed weights " + str(w.shape) + " and biases " + str(b.shape) + "are incopatible.") if self.verbosity: print("Fully Connected: output processing...") print("0%") start = timeit.default_timer() for x in range(w.shape[1]): local_sum = None for i in range(w.shape[0]): fname = input_folder + "/square_" + str(i) p = PyCtxt() p.load(fname, 'batch') encw = self.get_map(w[i][x]) el = self.py.multiply_plain(p, encw, True) if (local_sum == None): local_sum = el else: local_sum = self.py.add(local_sum, el) enc_b = self.get_map(b[x]) ts = self.py.add_plain(local_sum, enc_b, True) out_name = out_folder + "/fc_" + str(x) ts.save(out_name) if self.verbosity: perc = int(((x + 1) / w.shape[1]) * 100) print( str(perc) + "% (" + str(x + 1) + "/" + str(w.shape[1]) + ")") stop = timeit.default_timer() if self.verbosity: print("Fully Connected: output processed in " + str(stop - start) + " s.") print("") def get_results(self, test_labels): dense_folder = self.enclayers_dir + "/fullyconnected" out_folder = dense_folder + "/output" el = [] for i in range(test_labels.shape[1]): file = out_folder + "/fc_" + str(i) p = PyCtxt() p.load(file, 'batch') ptxt = self.py.decrypt(p) ptxt = self.py.decodeBatch(ptxt) if (el.__len__() <= i): el.append([]) for j in range(ptxt.__len__()): if (el.__len__() <= j): el.append([ptxt[j]]) else: el[j].append(ptxt[j]) return np.array(el) def predict(self, test_labels): if self.verbosity: print("Computing Prediction") print("==================================") fc_folder = self.enclayers_dir + "/fullyconnected" out_folder = fc_folder + "/output" if not path.exists(out_folder): raise Exception( "You need to compute the fully connected layer before.") print(test_labels[0]) # Only q predictions are done simultaneously # for i in range(self.n) el = [] start = timeit.default_timer() for i in range(test_labels.shape[1]): file = out_folder + "/fc_" + str(i) p = PyCtxt() p.load(file, 'batch') ptxt = self.py.decrypt(p) ptxt = self.py.decodeBatch(ptxt) ptxt = self.decode_tensor(ptxt, self.t, self.precision) if (el.__len__() <= i): el.append([]) for j in range(ptxt.__len__()): if (el.__len__() <= j): el.append([ptxt[j]]) else: el[j].append(ptxt[j]) el = np.array(el) print(el.shape) print(el[0]) pos = 0 for i in range(el.shape[0]): mp = np.argmax(el[i]) ml = np.argmax(test_labels[i]) if (mp == ml): pos += 1 stop = timeit.default_timer() print("Computation time: " + str(stop - start) + " s.") print("Positive prediction: " + str(pos)) print("Negative prediction: " + str(self.n - pos)) acc = (pos / self.n) * 100 print("Model Accurancy:" + str(acc) + "%") def _encode_(self, to_encode, t, precision): """ Check encode for the given value: Admitted intervals: + : [0, t/2] - : [(t/2)+1, t] ==> [-((t/2)+1), 0] Ex: positive: [0,982384640] ==> [0,982384640] ==> [0, t/2] negative: [-982384640, 0] ==> [982384641, 1964769281] ==> [(t/2)+1, t] """ precision = pow(10, precision) val = round((to_encode * precision)) t2 = t / 2 if val < 0: minval = -(t2 + 1) if val < minval: raise Exception("The value to encode (" + str(val) + ") is smaller than -((t/2)+1) = " + str(minval)) else: return (t + val) else: if val > t2: raise Exception("The value to encode (" + str(val) + ") is larger than t/2 = " + str(t2)) else: return val def _decode_(self, to_decode, t, precision): """ Decode the value encoded with _encode_ """ t2 = t / 2 if to_decode > t2: return (to_decode - t) / pow(10, precision) else: return to_decode / pow(10, precision) def decode_tensor(self, tensor, t, precision): ret = [] for i in range(tensor.__len__()): ret.append(self._decode_(tensor[i], t, precision)) return np.array(ret) def encrypt_input(self, get_result=False): """ Encrypt the input layer generating one file per encrypted pixel index """ pre_input_file = self.preprocess_dir + "precision_" + str( self.precision) + "/pre_input.npy" if not path.exists(pre_input_file): raise Exception("Preprocessed input not found in " + pre_input_file + " please run Encryption.preprocess before.") input_folder = self.enclayers_dir + "/input" if path.exists(input_folder): print("Input layer encrypted before. You can found it in: " + input_folder) if not get_result: return None createDir(input_folder) pre_input = np.load(self.preprocess_dir + "precision_" + str(self.precision) + "/pre_input.npy") if self.verbosity: print("") print("Encrypting (preprocessed) input layer with shape " + str(pre_input.shape) + "...") input_dim, dim, dim1 = pre_input.shape pre_flat = pre_input.flatten() arr = [] pixel_arr_dim = dim * dim1 for x in range(pre_flat.__len__()): if x < pixel_arr_dim: arr.append([pre_flat[x]]) else: arr[(x % pixel_arr_dim)].append(pre_flat[x]) arr = np.array(arr) enc = [] for i in range(arr.shape[0]): fname = input_folder + '/pixel_' + str(i) + ".pyctxt" enc.append(self._enc_arr_(arr[i], fname)) if self.verbosity: print("Input layer encrypted with success in " + str(enc.__len__()) + " files (one per pixel)") return np.array(enc) def getEncryptedPixel(self, index): pixel_file = self.enclayers_dir + "/input/pixel_" + str( index) + ".pyctxt" p = PyCtxt() p.load(pixel_file, 'batch') return p def _encode_arr_(self, arr): if not self.py.getflagBatch(): raise Exception("You need to initialize Batch for this context.") res = [] for x in range(self.n): res.append(arr[x]) res = np.array(res) encoded = self.py.encodeBatch(res) return encoded def _enc_arr_(self, arr, file_name=None): if not self.py.getflagBatch(): raise Exception("You need to initialize Batch for this context.") if file_name != None: if path.exists(file_name): ct = PyCtxt() ct.load(file_name, 'batch') return ct res = [] for x in range(self.n): res.append(arr[x]) res = np.array(res) encoded = self.py.encodeBatch(res) encrypted = self.py.encryptPtxt(encoded) if file_name != None: encrypted.save(file_name) return encrypted def preprocess(self, model, test_set): """ Start the preprocessing of the NN input and weights """ self._pre_process_input_( model, test_set, ) self._pre_process_layers_(model) def _pre_process_input_(self, model, test_set): """ Preprocess (encode) the input and save it in laysers/pre_input file """ input_size, input_dim, input_dim1, el_index = test_set.shape if (input_size < self.n): raise Exception("Too small input set. It must be at least " + str(self.n) + " len. " + str(input_size) + "given") base_dir = self.preprocess_dir + "precision_" + str( self.precision) + "/" createDir(base_dir, False) if path.exists(base_dir + 'pre_input.npy'): print("") print("Input layer encoded before. You can found it in " + base_dir + 'pre_input.npy') print("") else: encoded_input = np.empty(shape=(input_size, input_dim, input_dim), dtype=np.uint64) if self.verbosity: print("") print("Processing input...") print("=====================================================") print("Input shape: " + str(test_set.shape)) print("Precision: " + str(self.precision)) print("") for i in range(input_size): for x in range(input_dim): for y in range(input_dim1): encoded_input[i, x, y] = self._encode_( test_set[i, x, y, 0].item(), self.t, self.precision) if self.verbosity: print("Saving preprocessed input...") print("Input shape: " + str(encoded_input.shape)) np.save("./" + base_dir + "pre_input", encoded_input) if self.verbosity: print("Preprocessed input saved.") encoded_input = None def _pre_process_layers_(self, model): """ Preprocess (encode) NN weights and biases """ base_dir = self.preprocess_dir + "precision_" + str( self.precision) + "/" createDir(base_dir, False) for i in range(model.layers.__len__()): self._pre_process_layer_(model.layers[i], i) def _pre_process_layer_(self, layer, index=0): base_dir = self.preprocess_dir + "precision_" + str( self.precision) + "/" if self.verbosity: print("") print("Processing the " + str(index) + "_" + str(layer.name) + " layer...") print("=========================================================") if (path.exists(base_dir + "pre_" + str(index) + "_" + str(layer.name) + ".npy")): print("Layer prepocessed before. You can found it in " + base_dir + " folder.") else: if (layer.get_weights().__len__() > 0): weights = layer.get_weights()[0] biases = layer.get_weights()[1] #encoding layer weights and biases encoded_weights = None encoded_biases = np.empty(shape=biases.shape, dtype=np.uint64) if self.verbosity: print("Weights tensor shape: " + str(weights.shape)) print("Biases tensor shape: " + str(weights.shape)) print("") #The convolutional layer if (weights.shape == (3, 3, 1, 5)): encoded_weights = np.empty(shape=(3, 3, 5), dtype=np.uint64) for i in range(3): for x in range(3): for y in range(5): encoded_weights[i, x, y] = self._encode_( weights[i, x, 0, y].item(), self.t, self.precision) else: encoded_weights = np.empty(shape=weights.shape, dtype=np.uint64) for i in range(weights.shape[0]): for x in range(weights.shape[1]): encoded_weights[i, x] = self._encode_( weights[i, x].item(), self.t, self.precision) if self.verbosity: print("1/3) Weights encoded with success.") for i in range(biases.shape[0]): encoded_biases[i] = self._encode_(biases[i].item(), self.t, self.precision) if self.verbosity: print("2/3) Biases encoded with success.") np.save( './' + base_dir + 'pre_' + str(index) + "_" + str(layer.name), encoded_weights) np.save( './' + base_dir + 'pre_bias_' + str(index) + "_" + str(layer.name), encoded_biases) if self.verbosity: print("3/3) Layer " + str(layer.name) + "_" + str(index) + " weights and biases saved.") print("") print("Layer precomputation ends with success.") print("") encoded_weights = None encoded_biases = None else: print("[ERR] This layer is not pre processable.") print("")
class PyfhelTestCase(unittest.TestCase): def setUp(self): self.t0 = time.time() def tearDown(self): sys.stderr.write('({}s) ...'.format(round(time.time() - self.t0, 3))) def test_PyPtxt_PyCtxt(self): pass def test_PyPtxt_creation_deletion(self): try: self.ptxt = PyPtxt() self.ptxt2 = PyPtxt(other_ptxt=self.ptxt) self.pyfhel = Pyfhel() self.ptxt3 = PyPtxt(pyfhel=self.pyfhel) self.ptxt4 = PyPtxt(other_ptxt=self.ptxt3) except Exception as err: self.fail("PyPtxt() creation failed unexpectedly: ", err) self.assertEqual(self.ptxt._encoding, ENCODING_t.UNDEFINED) self.ptxt._encoding = ENCODING_t.INTEGER self.assertEqual(self.ptxt._encoding, ENCODING_t.INTEGER) del (self.ptxt._encoding) self.assertEqual(self.ptxt._encoding, ENCODING_t.UNDEFINED) self.ptxt._pyfhel = self.pyfhel self.ptxt2._pyfhel = self.ptxt._pyfhel try: del (self.ptxt) except Exception as err: self.fail("PyPtxt() deletion failed unexpectedly: ", err) def test_PyCtxt_creation_deletion(self): try: self.ctxt = PyCtxt() self.ctxt2 = PyCtxt(other_ctxt=self.ctxt) self.pyfhel = Pyfhel() self.ctxt3 = PyCtxt(pyfhel=self.pyfhel) self.ctxt4 = PyCtxt(other_ctxt=self.ctxt3) except Exception as err: self.fail("PyCtxt() creation failed unexpectedly: ", err) self.assertEqual(self.ctxt.size(), 2) self.assertEqual(self.ctxt._encoding, ENCODING_t.UNDEFINED) self.ctxt._encoding = ENCODING_t.FRACTIONAL self.assertEqual(self.ctxt._encoding, ENCODING_t.FRACTIONAL) del (self.ctxt._encoding) self.assertEqual(self.ctxt._encoding, ENCODING_t.UNDEFINED) self.assertEqual(self.ctxt.size(), 2) self.ctxt._pyfhel = self.pyfhel self.ctxt2._pyfhel = self.ctxt._pyfhel try: del (self.ctxt) except Exception as err: self.fail("PyCtxt() deletion failed unexpectedly: ", err) def test_Pyfhel_1_GENERATION(self): pass def test_Pyfhel_1a_creation_deletion(self): try: self.pyfhel = Pyfhel() except Exception as err: self.fail("Pyfhel() creation failed unexpectedly: ", err) try: del (self.pyfhel) except Exception as err: self.fail("Pyfhel() deletion failed unexpectedly: ", err) def test_Pyfhel_1b_context_n_key_generation(self): self.pyfhel = Pyfhel() self.pyfhel.contextGen(65537) self.pyfhel.keyGen() def test_Pyfhel_1c_rotate_key_generation(self): self.pyfhel = Pyfhel() self.pyfhel.contextGen(65537) self.pyfhel.keyGen() self.pyfhel.rotateKeyGen(30) self.pyfhel.rotateKeyGen(1) self.pyfhel.rotateKeyGen(60) self.assertRaises(SystemError, lambda: self.pyfhel.rotateKeyGen(61)) self.assertRaises(SystemError, lambda: self.pyfhel.rotateKeyGen(0)) def test_Pyfhel_1d_relin_key_generation(self): self.pyfhel = Pyfhel() self.pyfhel.contextGen(65537) self.pyfhel.keyGen() self.pyfhel.relinKeyGen(30, 5) self.pyfhel.relinKeyGen(1, 5) self.pyfhel.relinKeyGen(60, 5) self.assertRaises(SystemError, lambda: self.pyfhel.relinKeyGen(61, 5)) self.assertRaises(SystemError, lambda: self.pyfhel.relinKeyGen(0, 5)) def test_Pyfhel_2_ENCODING(self): pass def test_Pyfhel_2a_encode_decode_int(self): self.pyfhel = Pyfhel() self.pyfhel.contextGen(p=65537) self.pyfhel.keyGen() self.ptxt = self.pyfhel.encodeInt(127) self.assertEqual(self.ptxt.to_string(), b'1x^6 + 1x^5 + 1x^4 + 1x^3 + 1x^2 + 1x^1 + 1') self.assertEqual(self.pyfhel.decodeInt(self.ptxt), 127) self.ptxt2 = PyPtxt(self.ptxt) self.pyfhel.encodeInt(-2, self.ptxt) self.assertEqual(self.ptxt.to_string(), b'10000x^1') self.assertEqual(self.pyfhel.decodeInt(self.ptxt), -2) self.assertEqual(self.pyfhel.decodeInt(self.ptxt2), 127) def test_Pyfhel_2b_encode_decode_float(self): self.pyfhel = Pyfhel() self.pyfhel.contextGen(p=65537, m=8192, base=2, intDigits=80, fracDigits=20) self.pyfhel.keyGen() self.ptxt = self.pyfhel.encodeFrac(19.30) self.assertTrue(self.ptxt.to_string(), b'9x^8190 + 1x^4 + 1x^1 + 1') self.assertEqual(round(self.pyfhel.decodeFrac(self.ptxt), 2), 19.30) self.pyfhel.encodeFrac(-2.25, self.ptxt) self.assertEqual(self.ptxt.to_string(), b'1x^8190 + 10000x^1') self.assertEqual(round(self.pyfhel.decodeFrac(self.ptxt), 2), -2.25) def test_Pyfhel_2c_encode_decode_batch(self): self.pyfhel = Pyfhel() self.pyfhel.contextGen(p=1964769281, m=8192, base=2, sec=192, flagBatching=True) self.pyfhel.keyGen() self.assertTrue(self.pyfhel.batchEnabled()) self.ptxt = self.pyfhel.encodeBatch([1, 2, 3, 4, 5, 6]) self.assertEqual(self.pyfhel.getnSlots(), 8192) self.assertEqual( self.pyfhel.decodeBatch(self.ptxt)[:6], [1, 2, 3, 4, 5, 6]) #print(self.ptxt.to_string()) def test_Pyfhel_2d_encode_decode_array(self): self.pyfhel = Pyfhel() self.pyfhel.contextGen(p=1964769281, m=8192, base=2, sec=192, flagBatching=True) self.pyfhel.keyGen() self.assertTrue(self.pyfhel.batchEnabled()) self.ptxt = self.pyfhel.encodeArray(np.array([1, 2, 3, 4, 5, 6])) self.assertEqual(self.pyfhel.getnSlots(), 8192) self.assertTrue( np.alltrue( self.pyfhel.decodeArray(self.ptxt)[:6] == np.array( [1, 2, 3, 4, 5, 6]))) def test_Pyfhel_3_ENCRYPTING(self): pass def test_Pyfhel_3a_encrypt_decrypt_int(self): self.pyfhel = Pyfhel() self.pyfhel.contextGen(p=65537) self.pyfhel.keyGen() self.ctxt = self.pyfhel.encryptInt(127) self.assertEqual(self.pyfhel.decryptInt(self.ctxt), 127) self.ctxt2 = PyCtxt(self.ctxt) self.pyfhel.encryptInt(-2, self.ctxt) self.assertEqual(self.pyfhel.decryptInt(self.ctxt), -2) self.assertEqual(self.pyfhel.decryptInt(self.ctxt), -2) self.assertEqual(self.pyfhel.decryptInt(self.ctxt2), 127) def test_Pyfhel_3b_encrypt_decrypt_float(self): self.pyfhel = Pyfhel() self.pyfhel.contextGen(p=65537, m=8192, base=2, intDigits=80, fracDigits=20) self.pyfhel.keyGen() self.ctxt = self.pyfhel.encryptFrac(19.30) self.assertEqual(round(self.pyfhel.decryptFrac(self.ctxt), 2), 19.30) self.pyfhel.encryptFrac(-2.25, self.ctxt) self.assertEqual(round(self.pyfhel.decryptFrac(self.ctxt), 2), -2.25) def test_Pyfhel_3c_encrypt_decrypt_batch(self): self.pyfhel = Pyfhel() self.pyfhel.contextGen(p=1964769281, m=8192, base=2, sec=192, flagBatching=True) self.pyfhel.keyGen() self.assertTrue(self.pyfhel.batchEnabled()) self.ctxt = self.pyfhel.encryptBatch([1, 2, 3, 4, 5, 6]) self.assertEqual(self.pyfhel.getnSlots(), 8192) self.assertEqual( self.pyfhel.decryptBatch(self.ctxt)[:6], [1, 2, 3, 4, 5, 6]) #print(self.ptxt.to_string()) def test_Pyfhel_3d_encrypt_decrypt_array(self): self.pyfhel = Pyfhel() self.pyfhel.contextGen(p=1964769281, m=8192, base=2, sec=192, flagBatching=True) self.pyfhel.keyGen() self.assertTrue(self.pyfhel.batchEnabled()) self.ctxt = self.pyfhel.encryptArray(np.array([1, 2, 3, 4, 5, 6])) self.assertEqual(self.pyfhel.getnSlots(), 8192) self.assertTrue( np.alltrue( self.pyfhel.decryptArray(self.ctxt)[:6] == np.array( [1, 2, 3, 4, 5, 6]))) def test_Pyfhel_4_OPERATIONS(self): pass def test_Pyfhel_4a_operations_integer(self): self.pyfhel = Pyfhel() self.pyfhel.contextGen(p=1964769281, m=8192, base=3, sec=192) self.pyfhel.keyGen() #self.pyfhel.rotateKeyGen(60) #self.pyfhel.relinKeyGen(60) self.ctxti = self.pyfhel.encryptInt(127) self.ctxti2 = self.pyfhel.encryptInt(-2) self.ptxti = self.pyfhel.encodeInt(3) self.ctxt_add = self.pyfhel.add(self.ctxti, self.ctxti2, in_new_ctxt=True) self.ctxt_add2 = self.pyfhel.add_plain(self.ctxti, self.ptxti, in_new_ctxt=True) self.ctxt_sub = self.pyfhel.sub(self.ctxti, self.ctxti2, in_new_ctxt=True) self.ctxt_sub2 = self.pyfhel.sub_plain(self.ctxti, self.ptxti, in_new_ctxt=True) self.ctxt_mult = self.pyfhel.multiply(self.ctxti, self.ctxti2, in_new_ctxt=True) self.ctxt_mult2 = self.pyfhel.multiply_plain(self.ctxti, self.ptxti, in_new_ctxt=True) #self.ctxt_rotate = self.pyfhel.rotate(self.ctxti, 2) #self.ctxt_expon = self.pyfhel.power(self.ctxti, 3) #self.ctxt_expon2 = self.pyfhel.power(self.ctxti2, 3) #self.ctxt_polyEval = self.pyfhel.polyEval(self.ctxti, [1, 2, 1], in_new_ctxt=True) self.assertEqual(self.pyfhel.decryptInt(self.ctxt_add), 125) self.assertEqual(self.pyfhel.decryptInt(self.ctxt_add2), 130) self.assertEqual(self.pyfhel.decryptInt(self.ctxt_sub), 129) self.assertEqual(self.pyfhel.decryptInt(self.ctxt_sub2), 124) self.assertEqual(self.pyfhel.decryptInt(self.ctxt_mult), -254) self.assertEqual(self.pyfhel.decryptInt(self.ctxt_mult2), 381) #self.assertEqual(self.pyfhel.decryptInt(self.ctxt_expon), 2048383) #self.assertEqual(self.pyfhel.decryptInt(self.ctxt_expon2), -8) #self.assertEqual(self.pyfhel.decryptInt(self.ctxt_polyEval), 16510) def test_Pyfhel_4b_operations_frac(self): self.pyfhel = Pyfhel() self.pyfhel.contextGen(p=1964769281, m=8192, base=3, sec=192) self.pyfhel.keyGen() #self.pyfhel.rotateKeyGen(60) #self.pyfhel.relinKeyGen(60) self.ctxti = self.pyfhel.encryptFrac(19.37) self.ctxti2 = self.pyfhel.encryptFrac(-2.25) self.ptxti = self.pyfhel.encodeFrac(3.12) self.ctxt_add = self.pyfhel.add(self.ctxti, self.ctxti2, in_new_ctxt=True) self.ctxt_add2 = self.pyfhel.add_plain(self.ctxti, self.ptxti, in_new_ctxt=True) self.ctxt_sub = self.pyfhel.sub(self.ctxti, self.ctxti2, in_new_ctxt=True) self.ctxt_sub2 = self.pyfhel.sub_plain(self.ctxti, self.ptxti, in_new_ctxt=True) self.ctxt_mult = self.pyfhel.multiply(self.ctxti, self.ctxti2, in_new_ctxt=True) self.ctxt_mult2 = self.pyfhel.multiply_plain(self.ctxti, self.ptxti, in_new_ctxt=True) self.assertEqual(round(self.pyfhel.decryptFrac(self.ctxt_add), 2), 17.12) self.assertEqual(round(self.pyfhel.decryptFrac(self.ctxt_add2), 2), 22.49) self.assertEqual(round(self.pyfhel.decryptFrac(self.ctxt_sub), 2), 21.62) self.assertEqual(round(self.pyfhel.decryptFrac(self.ctxt_sub2), 2), 16.25) self.assertEqual(round(self.pyfhel.decryptFrac(self.ctxt_mult), 2), -43.58) self.assertEqual(round(self.pyfhel.decryptFrac(self.ctxt_mult2), 2), 60.43) def test_Pyfhel_4c_operations_batch_array(self): self.pyfhel = Pyfhel() self.pyfhel.contextGen(p=1964769281, m=8192, base=2, sec=192, flagBatching=True) self.pyfhel.keyGen() self.pyfhel.rotateKeyGen(60) self.ctxti = self.pyfhel.encryptBatch([1, 2, 3, 4, 5, 6]) self.ctxti2 = self.pyfhel.encryptArray( np.array([-6, -5, -4, -3, -2, -1])) self.ptxti = self.pyfhel.encodeArray(np.array([12, 15, 18, 21, 24, 27])) self.ctxt_add = self.pyfhel.add(self.ctxti, self.ctxti2, in_new_ctxt=True) self.ctxt_add2 = self.pyfhel.add_plain(self.ctxti, self.ptxti, in_new_ctxt=True) self.ctxt_sub = self.pyfhel.sub(self.ctxti, self.ctxti2, in_new_ctxt=True) self.ctxt_sub2 = self.pyfhel.sub_plain(self.ctxti, self.ptxti, in_new_ctxt=True) self.ctxt_mult = self.pyfhel.multiply(self.ctxti, self.ctxti2, in_new_ctxt=True) self.ctxt_mult2 = self.pyfhel.multiply_plain(self.ctxti, self.ptxti, in_new_ctxt=True) self.ctxt_rotate = self.pyfhel.rotate(self.ctxti, -2, in_new_ctxt=True) self.ctxt_rotate2 = self.pyfhel.rotate(self.ctxti, 2, in_new_ctxt=True) #self.ctxt_expon = self.pyfhel.power(self.ctxti, 3) #self.ctxt_expon2 = self.pyfhel.power(self.ctxti2, 3) #self.ctxt_polyEval = self.pyfhel.polyEval(self.ctxti, [1, 2, 1], in_new_ctxt=True) self.assertEqual( self.pyfhel.decryptBatch(self.ctxt_add)[:6], [-5, -3, -1, 1, 3, 5]) self.assertEqual( self.pyfhel.decryptBatch(self.ctxt_add2)[:6], [13, 17, 21, 25, 29, 33]) self.assertEqual( self.pyfhel.decryptBatch(self.ctxt_sub)[:6], [7, 7, 7, 7, 7, 7]) self.assertEqual( self.pyfhel.decryptBatch(self.ctxt_sub2)[:6], [-11, -13, -15, -17, -19, -21]) self.assertEqual( self.pyfhel.decryptBatch(self.ctxt_mult)[:6], [-6, -10, -12, -12, -10, -6]) self.assertEqual( self.pyfhel.decryptBatch(self.ctxt_mult2)[:6], [12, 30, 54, 84, 120, 162]) self.assertEqual( self.pyfhel.decryptBatch(self.ctxt_rotate)[:6], [0, 0, 1, 2, 3, 4]) self.assertEqual( self.pyfhel.decryptBatch(self.ctxt_rotate2)[:6], [3, 4, 5, 6, 0, 0]) def test_Pyfhel_5_IO_SAVE_RESTORE(self): pass def test_Pyfhel_5a_save_objects(self): self.pyfhel = Pyfhel() self.pyfhel.contextGen(p=1964769281, m=8192, base=2, sec=192, flagBatching=True) self.pyfhel.keyGen() self.pyfhel.rotateKeyGen(60) #self.pyfhel.relinKeyGen(60) self.assertTrue(self.pyfhel.saveContext(b"context.pycon")) self.assertTrue(self.pyfhel.savepublicKey(b"public_k.pypk")) self.assertTrue(self.pyfhel.savesecretKey(b"secret_k.pysk")) #self.assertTrue(self.pyfhel.saverelinKey(b"relin_k.pyrlk")) self.assertTrue(self.pyfhel.saverotateKey(b"rotate_k.pyrok")) def test_Pyfhel_5b_restore_objects(self): self.pyfhel = Pyfhel() self.assertTrue(self.pyfhel.restoreContext(b"context.pycon")) self.assertTrue(self.pyfhel.restoresecretKey(b"secret_k.pysk")) self.assertTrue(self.pyfhel.restorepublicKey(b"public_k.pypk")) #self.assertTrue(self.pyfhel.restorerelinKey(b"relin_k.pyrlk")) self.assertTrue(self.pyfhel.restorerotateKey(b"rotate_k.pyrok")) os.remove(b"context.pycon") os.remove(b"secret_k.pysk") os.remove(b"public_k.pypk") os.remove(b"rotate_k.pyrok")
print("-----------------------------------------------------") from Pyfhel import PyCtxt, Pyfhel, PyPtxt HE = Pyfhel() HE.contextGen(scheme='CKKS', n=16384, qi=[30,30,30,30,30], scale=1) HE.keyGen() ctxt_x = HE.encrypt(3.1, scale=2 ** 30) # implicit encode ctxt_y = HE.encrypt(4.1, scale=2 ** 30) ctxt_z = HE.encrypt(5.9, scale=2 ** 30) ctxtSum = HE.add(ctxt_x, ctxt_y) ctxtProd = HE.multiply_plain(ctxt_z, HE.encode(5)) ctxt_t = HE.multiply(ctxtSum, ctxtProd) ptxt_ten = HE.encode(10, scale=2 ** 30) try: ctxt_result = HE.add_plain(ctxt_t, ptxt_ten) #error: mismatched scales except ValueError as e: assert str(e) == "scale mismatch" print("CKKS: Mismatched scales detected!") ptxt_d = HE.encode(10, 2 ** 30) ctxt_d = HE.encrypt(ptxt_d) HE.rescale_to_next(ctxt_t) # 2^90 -> 2^60 HE.rescale_to_next(ctxt_t) # 2^60 -> 2^30 HE.mod_switch_to_next(ctxt_d) # match first rescale HE.mod_switch_to_next(ctxt_d) # match second rescale ctxt_t.set_scale(2**30) ctxt_result = HE.add(ctxt_t, ctxt_d) # final result