Ejemplo n.º 1
0
 def test_Pyfhel_2c_encode_decode_batch(self):
     pyfhel = Pyfhel()
     pyfhel.contextGen(p=1964769281, m=8192, base=2, sec=192, flagBatching=True)
     pyfhel.keyGen()
     self.assertTrue(pyfhel.batchEnabled())
     ptxt = pyfhel.encodeBatch([1, 2, 3, 4, 5, 6])
     self.assertEqual(pyfhel.getnSlots(), 8192)
     self.assertEqual(pyfhel.decodeBatch(ptxt)[:6], [1, 2, 3, 4, 5, 6])
Ejemplo n.º 2
0
    "      Just remember to operate only with ciphertexts with same encoding")
print("     ATTENTION: HE.decrypt will directly decrypt AND decode!!!! ")
ctxt1 = HE.encryptPtxt(ptxt_i1)
ctxt2 = HE.encrypt(ptxt_f1)
ctxt3 = HE.encryptPtxt(ptxt_b1)
ctxt4 = HE.encrypt(ptxt_a1)

integer1 = HE.decrypt(ctxt1)
float1 = HE.decrypt(ctxt2)
vector1 = HE.decrypt(ctxt3)

print("7. Decoding values")
res_i1 = HE.decodeInt(ptxt_i1)  # Decoding must use the corresponding decodeing
res_f1 = HE.decodeFrac(
    ptxt_f1)  # Decoding must use the corresponding decodeing
res_b1 = HE.decodeBatch(
    ptxt_b1)  # Decoding must use the corresponding decodeing
res_a1 = HE.decodeArray(
    ptxt_a1)  # Decoding must use the corresponding decodeing

print("    Integer (encodeInt, ENCODING_t.INTEGER, decodeInt)")
print("      decode(ptxt_i1) =  ", res_i1)

print("    Float (encodeFrac, ENCODING_t.FRACTIONAL, decodeFrac)")
print("     decode(ptxt_f1) =  ", res_f1)

print("    Batched list (encodeBatch, ENCODING_t.BATCH, decodeBatch)")
print("     decode(ptxt_b1) =  ", res_b1)

print("    NumPy 1D vector (encodeArray, ENCODING_t.BATCH, decodeArray)")
print("     decode(ptxt_a1) =  ", res_a1)
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("")
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
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")