Пример #1
0
def inner_product(cypher1, cypher2):
    # We also set up an Encryptor, Evaluator, and Decryptor here.
    evaluator = Evaluator(context)
    decryptor = Decryptor(context, secret_key)

    for i in range(len(cypher1)):
        evaluator.multiply(cypher1[i], cypher2[i])

    encrypted_result = Ciphertext()
    evaluator.add_many(cypher1, encrypted_result)

    return encrypted_result
keygen = KeyGenerator(context)
public_key = keygen.public_key()
secret_key = keygen.secret_key()
encryptor = Encryptor(context, public_key)
evaluator = Evaluator(context)
decryptor = Decryptor(context, secret_key)

value=7
plain1 = encoder.encode(value1)
print("Encoded " + (str)(value) + " as polynomial " + plain1.to_string() + " (plain1)")

encrypted _data= Ciphertext()
encryptor.encrypt(plain, encrypted_data)
print("Noise budget in encrypted1: " + (str)(decryptor.invariant_noise_budget(encrypted_data)) + " bits")

# operations that can be performed --->

# result stored in encrypted1 data
evaluator.negate(encrypted1_data)

# result stored in encrypted1 data, encrpyted1 is modified
evaluator.add(encrypted1_data, encrypted2_data)

# result stored in encrypted1 data, encrpyted1 is modified
evaluator.multiply(encrypted1_data, encrypted2_data)


plain_result = Plaintext()
decryptor.decrypt(encrypted_data, plain_result)
print("Plaintext polynomial: " + plain_result.to_string())
print("Decoded integer: " + (str)(encoder.decode_int32(plain_result)))
Пример #3
0
def dot_product():
    print("Example: Weighted Average")

    # In this example we demonstrate the FractionalEncoder, and use it to compute
    # a weighted average of 10 encrypted rational numbers. In this computation we
    # perform homomorphic multiplications of ciphertexts by plaintexts, which is
    # much faster than regular multiplications of ciphertexts by ciphertexts.
    # Moreover, such `plain multiplications' never increase the ciphertext size,
    # which is why we have no need for evaluation keys in this example.

    # We start by creating encryption parameters, setting up the SEALContext, keys,
    # and other relevant objects. Since our computation has multiplicative depth of
    # only two, it suffices to use a small poly_modulus.
    parms = EncryptionParameters()
    parms.set_poly_modulus("1x^2048 + 1")
    parms.set_coeff_modulus(seal.coeff_modulus_128(2048))
    parms.set_plain_modulus(1 << 8)

    context = SEALContext(parms)
    print_parameters(context)

    keygen = KeyGenerator(context)
    keygen2 = KeyGenerator(context)
    public_key = keygen.public_key()
    secret_key = keygen.secret_key()

    secret_key2 = keygen.secret_key()

    # We also set up an Encryptor, Evaluator, and Decryptor here.
    encryptor = Encryptor(context, public_key)
    evaluator = Evaluator(context)
    decryptor = Decryptor(context, secret_key2)

    # Create a vector of 10 rational numbers (as doubles).
    # rational_numbers = [3.1, 4.159, 2.65, 3.5897, 9.3, 2.3, 8.46, 2.64, 3.383, 2.795]
    rational_numbers = np.random.rand(10)

    # Create a vector of weights.
    # coefficients = [0.1, 0.05, 0.05, 0.2, 0.05, 0.3, 0.1, 0.025, 0.075, 0.05]
    coefficients = np.random.rand(10)

    my_result = np.dot(rational_numbers, coefficients)

    # We need a FractionalEncoder to encode the rational numbers into plaintext
    # polynomials. In this case we decide to reserve 64 coefficients of the
    # polynomial for the integral part (low-degree terms) and expand the fractional
    # part to 32 digits of precision (in base 3) (high-degree terms). These numbers
    # can be changed according to the precision that is needed; note that these
    # choices leave a lot of unused space in the 2048-coefficient polynomials.
    encoder = FractionalEncoder(context.plain_modulus(), context.poly_modulus(), 64, 32, 3)

    # We create a vector of ciphertexts for encrypting the rational numbers.
    encrypted_rationals = []
    rational_numbers_string = "Encoding and encrypting: "
    for i in range(10):
        # We create our Ciphertext objects into the vector by passing the
        # encryption parameters as an argument to the constructor. This ensures
        # that enough memory is allocated for a size 2 ciphertext. In this example
        # our ciphertexts never grow in size (plain multiplication does not cause
        # ciphertext growth), so we can expect the ciphertexts to remain in the same
        # location in memory throughout the computation. In more complicated examples
        # one might want to call a constructor that reserves enough memory for the
        # ciphertext to grow to a specified size to avoid costly memory moves when
        # multiplications and relinearizations are performed.
        encrypted_rationals.append(Ciphertext(parms))
        encryptor.encrypt(encoder.encode(rational_numbers[i]), encrypted_rationals[i])
        rational_numbers_string += (str)(rational_numbers[i])[:6]
        if i < 9: rational_numbers_string += ", "
    print(rational_numbers_string)

    # Next we encode the coefficients. There is no reason to encrypt these since they
    # are not private data.
    encoded_coefficients = []
    encoded_coefficients_string = "Encoding plaintext coefficients: "


    encrypted_coefficients =[]

    for i in range(10):
        encoded_coefficients.append(encoder.encode(coefficients[i]))
        encrypted_coefficients.append(Ciphertext(parms))
        encryptor.encrypt(encoded_coefficients[i], encrypted_coefficients[i])
        encoded_coefficients_string += (str)(coefficients[i])[:6]
        if i < 9: encoded_coefficients_string += ", "
    print(encoded_coefficients_string)

    # We also need to encode 0.1. Multiplication by this plaintext will have the
    # effect of dividing by 10. Note that in SEAL it is impossible to divide
    # ciphertext by another ciphertext, but in this way division by a plaintext is
    # possible.
    div_by_ten = encoder.encode(0.1)

    # Now compute each multiplication.

    prod_result = [Ciphertext() for i in range(10)]
    prod_result2 = [Ciphertext() for i in range(10)]

    print("Computing products: ")
    for i in range(10):
        # Note how we use plain multiplication instead of usual multiplication. The
        # result overwrites the first argument in the function call.
        evaluator.multiply_plain(encrypted_rationals[i], encoded_coefficients[i], prod_result[i])
        evaluator.multiply(encrypted_rationals[i], encrypted_coefficients[i], prod_result2[i])
    print("Done")

    # To obtain the linear sum we need to still compute the sum of the ciphertexts
    # in encrypted_rationals. There is an easy way to add together a vector of
    # Ciphertexts.

    encrypted_result = Ciphertext()
    encrypted_result2 = Ciphertext()

    print("Adding up all 10 ciphertexts: ")
    evaluator.add_many(prod_result, encrypted_result)
    evaluator.add_many(prod_result2, encrypted_result2)

    print("Done")

    # Perform division by 10 by plain multiplication with div_by_ten.
    # print("Dividing by 10: ")
    # evaluator.multiply_plain(encrypted_result, div_by_ten)
    # print("Done")

    # How much noise budget do we have left?
    print("Noise budget in result: " + (str)(decryptor.invariant_noise_budget(encrypted_result)) + " bits")

    # Decrypt, decode, and print result.
    plain_result = Plaintext()
    plain_result2 = Plaintext()
    print("Decrypting result: ")
    decryptor.decrypt(encrypted_result, plain_result)
    decryptor.decrypt(encrypted_result2, plain_result2)
    print("Done")

    result = encoder.decode(plain_result)
    print("Weighted average: " + (str)(result)[:8])

    result2 = encoder.decode(plain_result2)
    print("Weighted average: " + (str)(result2)[:8])

    print('\n\n', my_result)
Пример #4
0
def pickle_ciphertext():
    parms = EncryptionParameters()

    parms.set_poly_modulus("1x^2048 + 1")

    parms.set_coeff_modulus(seal.coeff_modulus_128(2048))

    parms.set_plain_modulus(1 << 8)

    context = SEALContext(parms)

    # Print the parameters that we have chosen
    print_parameters(context);

    encoder = IntegerEncoder(context.plain_modulus())


    keygen = KeyGenerator(context)
    public_key = keygen.public_key()
    secret_key = keygen.secret_key()

    # To be able to encrypt, we need to construct an instance of Encryptor. Note that
    # the Encryptor only requires the public key.
    encryptor = Encryptor(context, public_key)

    # Computations on the ciphertexts are performed with the Evaluator class.
    evaluator = Evaluator(context)

    # We will of course want to decrypt our results to verify that everything worked,
    # so we need to also construct an instance of Decryptor. Note that the Decryptor
    # requires the secret key.
    decryptor = Decryptor(context, secret_key)

    # We start by encoding two integers as plaintext polynomials.
    value1 = 5;
    plain1 = encoder.encode(value1);
    print("Encoded " + (str)(value1) + " as polynomial " + plain1.to_string() + " (plain1)")

    value2 = -7;
    plain2 = encoder.encode(value2);
    print("Encoded " + (str)(value2) + " as polynomial " + plain2.to_string() + " (plain2)")

    # Encrypting the values is easy.
    encrypted1 = Ciphertext()
    encrypted2 = Ciphertext()
    print("Encrypting plain1: ", encrypted1)
    encryptor.encrypt(plain1, encrypted1)
    print("Done (encrypted1)", encrypted1)

    print("Encrypting plain2: ")
    encryptor.encrypt(plain2, encrypted2)
    print("Done (encrypted2)")






    # output = open('ciphertest.pkl', 'wb')
    # dill.dumps(encrypted_save, output)
    # output.close()
    # encrypted1 = dill.load(open('ciphertest.pkl', 'rb'))


    output = open('session.pkl', 'wb')
    dill.dump_session('session.pkl')

    del encrypted1
    sill.load_session('session.pkl')







    # To illustrate the concept of noise budget, we print the budgets in the fresh
    # encryptions.
    print("Noise budget in encrypted1: " + (str)(decryptor.invariant_noise_budget(encrypted1)) + " bits")
    print("Noise budget in encrypted2: " + (str)(decryptor.invariant_noise_budget(encrypted2)) + " bits")

    # As a simple example, we compute (-encrypted1 + encrypted2) * encrypted2.

    # Negation is a unary operation.
    evaluator.negate(encrypted1)

    # Negation does not consume any noise budget.
    print("Noise budget in -encrypted1: " + (str)(decryptor.invariant_noise_budget(encrypted1)) + " bits")

    # Addition can be done in-place (overwriting the first argument with the result,
    # or alternatively a three-argument overload with a separate destination variable
    # can be used. The in-place variants are always more efficient. Here we overwrite
    # encrypted1 with the sum.
    evaluator.add(encrypted1, encrypted2)

    # It is instructive to think that addition sets the noise budget to the minimum
    # of the input noise budgets. In this case both inputs had roughly the same
    # budget going on, and the output (in encrypted1) has just slightly lower budget.
    # Depending on probabilistic effects, the noise growth consumption may or may
    # not be visible when measured in whole bits.
    print("Noise budget in -encrypted1 + encrypted2: " + (str)(decryptor.invariant_noise_budget(encrypted1)) + " bits")

    # Finally multiply with encrypted2. Again, we use the in-place version of the
    # function, overwriting encrypted1 with the product.
    evaluator.multiply(encrypted1, encrypted2)

    # Multiplication consumes a lot of noise budget. This is clearly seen in the
    # print-out. The user can change the plain_modulus to see its effect on the
    # rate of noise budget consumption.
    print("Noise budget in (-encrypted1 + encrypted2) * encrypted2: " + (str)(
        decryptor.invariant_noise_budget(encrypted1)) + " bits")

    # Now we decrypt and decode our result.
    plain_result = Plaintext()
    print("Decrypting result: ")
    decryptor.decrypt(encrypted1, plain_result)
    print("Done")

    # Print the result plaintext polynomial.
    print("Plaintext polynomial: " + plain_result.to_string())

    # Decode to obtain an integer result.
    print("Decoded integer: " + (str)(encoder.decode_int32(plain_result)))
Пример #5
0
class CipherMatrix:
    """

    """
    def __init__(self, matrix=None):
        """

        :param matrix: numpy.ndarray to be encrypted.
        """

        self.parms = EncryptionParameters()
        self.parms.set_poly_modulus("1x^2048 + 1")
        self.parms.set_coeff_modulus(seal.coeff_modulus_128(2048))
        self.parms.set_plain_modulus(1 << 8)

        self.context = SEALContext(self.parms)

        # self.encoder = IntegerEncoder(self.context.plain_modulus())
        self.encoder = FractionalEncoder(self.context.plain_modulus(),
                                         self.context.poly_modulus(), 64, 32,
                                         3)

        self.keygen = KeyGenerator(self.context)
        self.public_key = self.keygen.public_key()
        self.secret_key = self.keygen.secret_key()

        self.encryptor = Encryptor(self.context, self.public_key)
        self.decryptor = Decryptor(self.context, self.secret_key)

        self.evaluator = Evaluator(self.context)

        self._saved = False
        self._encrypted = False
        self._id = '{0:04d}'.format(np.random.randint(1000))

        if matrix is not None:
            assert len(
                matrix.shape) == 2, "Only 2D numpy matrices accepted currently"
            self.matrix = np.copy(matrix)
            self.encrypted_matrix = np.empty(self.matrix.shape, dtype=object)
            for i in range(self.matrix.shape[0]):
                for j in range(self.matrix.shape[1]):
                    self.encrypted_matrix[i, j] = Ciphertext()

        else:
            self.matrix = None
            self.encrypted_matrix = None

        print(self._id, "Created")

    def __repr__(self):
        """

        :return:
        """
        print("Encrypted:", self._encrypted)
        if not self._encrypted:
            print(self.matrix)
            return ""

        else:
            return '[]'

    def __str__(self):
        """

        :return:
        """
        print("| Encryption parameters:")
        print("| poly_modulus: " + self.context.poly_modulus().to_string())

        # Print the size of the true (product) coefficient modulus
        print("| coeff_modulus_size: " + (
            str)(self.context.total_coeff_modulus().significant_bit_count()) +
              " bits")

        print("| plain_modulus: " +
              (str)(self.context.plain_modulus().value()))
        print("| noise_standard_deviation: " +
              (str)(self.context.noise_standard_deviation()))

        if self.matrix is not None:
            print(self.matrix.shape)

        return str(type(self))

    def __add__(self, other):
        """

        :param other:
        :return:
        """
        assert isinstance(
            other, CipherMatrix), "Can only be added with a CipherMatrix"

        A_enc = self._encrypted
        B_enc = other._encrypted

        if A_enc:
            A = self.encrypted_matrix
        else:
            A = self.matrix

        if B_enc:
            B = other.encrypted_matrix
        else:
            B = other.matrix

        assert A.shape == B.shape, "Dimension mismatch, Matrices must be of same shape. Got {} and {}".format(
            A.shape, B.shape)

        shape = A.shape

        result = CipherMatrix(np.zeros(shape, dtype=np.int32))
        result._update_cryptors(self.get_keygen())

        if A_enc:
            if B_enc:

                res_mat = result.encrypted_matrix
                for i in range(shape[0]):
                    for j in range(shape[1]):
                        self.evaluator.add(A[i, j], B[i, j], res_mat[i, j])

                result._encrypted = True

            else:
                res_mat = result.encrypted_matrix
                for i in range(shape[0]):
                    for j in range(shape[1]):
                        self.evaluator.add_plain(A[i, j],
                                                 self.encoder.encode(B[i, j]),
                                                 res_mat[i, j])

                result._encrypted = True

        else:
            if B_enc:

                res_mat = result.encrypted_matrix
                for i in range(shape[0]):
                    for j in range(shape[1]):
                        self.evaluator.add_plain(B[i, j],
                                                 self.encoder.encode(A[i, j]),
                                                 res_mat[i, j])

                result._encrypted = True

            else:

                result.matrix = A + B
                result._encrypted = False

        return result

    def __sub__(self, other):
        """

        :param other:
        :return:
        """
        assert isinstance(other, CipherMatrix)
        if other._encrypted:
            shape = other.encrypted_matrix.shape

            for i in range(shape[0]):
                for j in range(shape[1]):
                    self.evaluator.negate(other.encrypted_matrix[i, j])

        else:
            other.matrix = -1 * other.matrix

        return self + other

    def __mul__(self, other):
        """

        :param other:
        :return:
        """

        assert isinstance(
            other, CipherMatrix), "Can only be multiplied with a CipherMatrix"

        # print("LHS", self._id, "RHS", other._id)
        A_enc = self._encrypted
        B_enc = other._encrypted

        if A_enc:
            A = self.encrypted_matrix
        else:
            A = self.matrix

        if B_enc:
            B = other.encrypted_matrix
        else:
            B = other.matrix

        Ashape = A.shape
        Bshape = B.shape

        assert Ashape[1] == Bshape[0], "Dimensionality mismatch"
        result_shape = [Ashape[0], Bshape[1]]

        result = CipherMatrix(np.zeros(shape=result_shape))

        if A_enc:
            if B_enc:

                for i in range(Ashape[0]):
                    for j in range(Bshape[1]):

                        result_array = []
                        for k in range(Ashape[1]):

                            res = Ciphertext()
                            self.evaluator.multiply(A[i, k], B[k, j], res)

                            result_array.append(res)

                        self.evaluator.add_many(result_array,
                                                result.encrypted_matrix[i, j])

                result._encrypted = True

            else:

                for i in range(Ashape[0]):
                    for j in range(Bshape[1]):

                        result_array = []
                        for k in range(Ashape[1]):
                            res = Ciphertext()
                            self.evaluator.multiply_plain(
                                A[i, k], self.encoder.encode(B[k, j]), res)

                            result_array.append(res)

                        self.evaluator.add_many(result_array,
                                                result.encrypted_matrix[i, j])

                result._encrypted = True

        else:
            if B_enc:

                for i in range(Ashape[0]):
                    for j in range(Bshape[1]):

                        result_array = []
                        for k in range(Ashape[1]):
                            res = Ciphertext()
                            self.evaluator.multiply_plain(
                                B[i, k], self.encoder.encode(A[k, j]), res)

                            result_array.append(res)

                        self.evaluator.add_many(result_array,
                                                result.encrypted_matrix[i, j])

                result._encrypted = True

            else:

                result.matrix = np.matmul(A, B)
                result._encrypted = False

        return result

    def save(self, path):
        """

        :param path:
        :return:
        """

        save_dir = os.path.join(path, self._id)

        if self._saved:
            print("CipherMatrix already saved")

        else:
            assert not os.path.isdir(save_dir), "Directory already exists"
            os.mkdir(save_dir)

        if not self._encrypted:
            self.encrypt()

        shape = self.encrypted_matrix.shape

        for i in range(shape[0]):
            for j in range(shape[1]):

                element_name = str(i) + '-' + str(j) + '.ahem'
                self.encrypted_matrix[i, j].save(
                    os.path.join(save_dir, element_name))

        self.secret_key.save("/keys/" + "." + self._id + '.wheskey')

        self._saved = True
        return save_dir

    def load(self, path, load_secret_key=False):
        """

        :param path:
        :param load_secret_key:
        :return:
        """

        self._id = path.split('/')[-1]
        print("Loading Matrix:", self._id)

        file_list = os.listdir(path)
        index_list = [[file.split('.')[0].split('-'), file]
                      for file in file_list]

        M = int(max([int(ind[0][0]) for ind in index_list])) + 1
        N = int(max([int(ind[0][1]) for ind in index_list])) + 1
        del self.encrypted_matrix
        self.encrypted_matrix = np.empty([M, N], dtype=object)

        for index in index_list:
            i = int(index[0][0])
            j = int(index[0][1])

            self.encrypted_matrix[i, j] = Ciphertext()
            self.encrypted_matrix[i, j].load(os.path.join(path, index[1]))

        if load_secret_key:
            self.secret_key.load("/keys/" + "." + self._id + '.wheskey')

        self.matrix = np.empty(self.encrypted_matrix.shape)
        self._encrypted = True

    def encrypt(self, matrix=None, keygen=None):
        """

        :param matrix:
        :return:
        """

        assert not self._encrypted, "Matrix already encrypted"

        if matrix is not None:
            assert self.matrix is None, "matrix already exists"
            self.matrix = np.copy(matrix)

        shape = self.matrix.shape

        self.encrypted_matrix = np.empty(shape, dtype=object)

        if keygen is not None:
            self._update_cryptors(keygen)

        for i in range(shape[0]):
            for j in range(shape[1]):
                val = self.encoder.encode(self.matrix[i, j])
                self.encrypted_matrix[i, j] = Ciphertext()
                self.encryptor.encrypt(val, self.encrypted_matrix[i, j])

        self._encrypted = True

    def decrypt(self, encrypted_matrix=None, keygen=None):
        """

        :return:
        """

        if encrypted_matrix is not None:
            self.encrypted_matrix = encrypted_matrix

        assert self._encrypted, "No encrypted matrix"

        del self.matrix
        shape = self.encrypted_matrix.shape

        self.matrix = np.empty(shape)

        if keygen is not None:
            self._update_cryptors(keygen)

        for i in range(shape[0]):
            for j in range(shape[1]):
                plain_text = Plaintext()
                self.decryptor.decrypt(self.encrypted_matrix[i, j], plain_text)
                self.matrix[i, j] = self.encoder.decode(plain_text)

        self._encrypted = False
        return np.copy(self.matrix)

    def get_keygen(self):
        """

        :return:
        """
        return self.keygen

    def _update_cryptors(self, keygen):
        """

        :param keygen:
        :return:
        """

        self.keygen = keygen
        self.public_key = keygen.public_key()
        self.secret_key = keygen.secret_key()

        self.encryptor = Encryptor(self.context, self.public_key)
        self.decryptor = Decryptor(self.context, self.secret_key)

        return
Пример #6
0
def example_bfv_basics():
    print("Example: BFV Basics")
    #In this example, we demonstrate performing simple computations (a polynomial
    #evaluation) on encrypted integers using the BFV encryption scheme.
    #
    #The first task is to set up an instance of the EncryptionParameters class.
    #It is critical to understand how the different parameters behave, how they
    #affect the encryption scheme, performance, and the security level. There are
    #three encryption parameters that are necessary to set:
    #
    #    - poly_modulus_degree (degree of polynomial modulus);
    #    - coeff_modulus ([ciphertext] coefficient modulus);
    #    - plain_modulus (plaintext modulus; only for the BFV scheme).
    #
    #The BFV scheme cannot perform arbitrary computations on encrypted data.
    #Instead, each ciphertext has a specific quantity called the `invariant noise
    #budget' -- or `noise budget' for short -- measured in bits. The noise budget
    #in a freshly encrypted ciphertext (initial noise budget) is determined by
    #the encryption parameters. Homomorphic operations consume the noise budget
    #at a rate also determined by the encryption parameters. In BFV the two basic
    #operations allowed on encrypted data are additions and multiplications, of
    #which additions can generally be thought of as being nearly free in terms of
    #noise budget consumption compared to multiplications. Since noise budget
    #consumption compounds in sequential multiplications, the most significant
    #factor in choosing appropriate encryption parameters is the multiplicative
    #depth of the arithmetic circuit that the user wants to evaluate on encrypted
    #data. Once the noise budget of a ciphertext reaches zero it becomes too
    #corrupted to be decrypted. Thus, it is essential to choose the parameters to
    #be large enough to support the desired computation; otherwise the result is
    #impossible to make sense of even with the secret key.
    parms = EncryptionParameters(scheme_type.BFV)

    #The first parameter we set is the degree of the `polynomial modulus'. This
    #must be a positive power of 2, representing the degree of a power-of-two
    #cyclotomic polynomial; it is not necessary to understand what this means.
    #
    #Larger poly_modulus_degree makes ciphertext sizes larger and all operations
    #slower, but enables more complicated encrypted computations. Recommended
    #values are 1024, 2048, 4096, 8192, 16384, 32768, but it is also possible
    #to go beyond this range.
    #
    #In this example we use a relatively small polynomial modulus. Anything
    #smaller than this will enable only very restricted encrypted computations.

    poly_modulus_degree = 4096
    parms.set_poly_modulus_degree(poly_modulus_degree)

    #Next we set the [ciphertext] `coefficient modulus' (coeff_modulus). This
    #parameter is a large integer, which is a product of distinct prime numbers,
    #each up to 60 bits in size. It is represented as a vector of these prime
    #numbers, each represented by an instance of the SmallModulus class. The
    #bit-length of coeff_modulus means the sum of the bit-lengths of its prime
    #factors.
    #
    #A larger coeff_modulus implies a larger noise budget, hence more encrypted
    #computation capabilities. However, an upper bound for the total bit-length
    #of the coeff_modulus is determined by the poly_modulus_degree, as follows:
    #
    #    +----------------------------------------------------+
    #    | poly_modulus_degree | max coeff_modulus bit-length |
    #    +---------------------+------------------------------+
    #    | 1024                | 27                           |
    #    | 2048                | 54                           |
    #    | 4096                | 109                          |
    #    | 8192                | 218                          |
    #    | 16384               | 438                          |
    #    | 32768               | 881                          |
    #    +---------------------+------------------------------+
    #
    #These numbers can also be found in native/src/seal/util/hestdparms.h encoded
    #in the function SEAL_HE_STD_PARMS_128_TC, and can also be obtained from the
    #function
    #
    #    CoeffModulus::MaxBitCount(poly_modulus_degree).
    #
    #For example, if poly_modulus_degree is 4096, the coeff_modulus could consist
    #of three 36-bit primes (108 bits).
    #
    #Microsoft SEAL comes with helper functions for selecting the coeff_modulus.
    #For new users the easiest way is to simply use
    #
    #    CoeffModulus::BFVDefault(poly_modulus_degree),
    #
    #which returns std::vector<SmallModulus> consisting of a generally good choice
    #for the given poly_modulus_degree.
    parms.set_coeff_modulus(CoeffModulus.BFVDefault(poly_modulus_degree))

    #The plaintext modulus can be any positive integer, even though here we take
    #it to be a power of two. In fact, in many cases one might instead want it
    #to be a prime number; we will see this in later examples. The plaintext
    #modulus determines the size of the plaintext data type and the consumption
    #of noise budget in multiplications. Thus, it is essential to try to keep the
    #plaintext data type as small as possible for best performance. The noise
    #budget in a freshly encrypted ciphertext is
    #
    #    ~ log2(coeff_modulus/plain_modulus) (bits)
    #
    #and the noise budget consumption in a homomorphic multiplication is of the
    #form log2(plain_modulus) + (other terms).
    #
    #The plaintext modulus is specific to the BFV scheme, and cannot be set when
    #using the CKKS scheme.

    parms.set_plain_modulus(1024)

    #Now that all parameters are set, we are ready to construct a SEALContext
    #object. This is a heavy class that checks the validity and properties of the
    #parameters we just set.
    context = SEALContext.Create(parms)

    #Print the parameters that we have chosen.
    print("Set encryption parameters and print")
    print_parameters(context)
    print("~~~~~~ A naive way to calculate 4(x^2+1)(x+1)^2. ~~~~~~")

    #The encryption schemes in Microsoft SEAL are public key encryption schemes.
    #For users unfamiliar with this terminology, a public key encryption scheme
    #has a separate public key for encrypting data, and a separate secret key for
    #decrypting data. This way multiple parties can encrypt data using the same
    #shared public key, but only the proper recipient of the data can decrypt it
    #with the secret key.
    #
    #We are now ready to generate the secret and public keys. For this purpose
    #we need an instance of the KeyGenerator class. Constructing a KeyGenerator
    #automatically generates the public and secret key, which can immediately be
    #read to local variables.

    keygen = KeyGenerator(context)
    public_key = keygen.public_key()
    secret_key = keygen.secret_key()

    #To be able to encrypt we need to construct an instance of Encryptor. Note
    #that the Encryptor only requires the public key, as expected.
    encryptor = Encryptor(context, public_key)

    #Computations on the ciphertexts are performed with the Evaluator class. In
    #a real use-case the Evaluator would not be constructed by the same party
    #that holds the secret key.
    evaluator = Evaluator(context)

    #We will of course want to decrypt our results to verify that everything worked,
    #so we need to also construct an instance of Decryptor. Note that the Decryptor
    #requires the secret key.
    decryptor = Decryptor(context, secret_key)

    #As an example, we evaluate the degree 4 polynomial
    #
    #    4x^4 + 8x^3 + 8x^2 + 8x + 4
    #
    #over an encrypted x = 6. The coefficients of the polynomial can be considered
    #as plaintext inputs, as we will see below. The computation is done modulo the
    #plain_modulus 1024.
    #
    #While this examples is simple and easy to understand, it does not have much
    #practical value. In later examples we will demonstrate how to compute more
    #efficiently on encrypted integers and real or complex numbers.
    #
    #Plaintexts in the BFV scheme are polynomials of degree less than the degree
    #of the polynomial modulus, and coefficients integers modulo the plaintext
    #modulus. For readers with background in ring theory, the plaintext space is
    #the polynomial quotient ring Z_T[X]/(X^N + 1), where N is poly_modulus_degree
    #and T is plain_modulus.
    #
    #To get started, we create a plaintext containing the constant 6. For the
    #plaintext element we use a constructor that takes the desired polynomial as
    #a string with coefficients represented as hexadecimal numbers.
    x = 6
    x_plain = Plaintext(str(x))
    print("Express x = {} as a plaintext polynomial 0x{}.".format(
        x, x_plain.to_string()))

    #We then encrypt the plaintext, producing a ciphertext.
    x_encrypted = Ciphertext()
    print("Encrypt x_plain to x_encrypted.")
    encryptor.encrypt(x_plain, x_encrypted)

    #In Microsoft SEAL, a valid ciphertext consists of two or more polynomials
    #whose coefficients are integers modulo the product of the primes in the
    #coeff_modulus. The number of polynomials in a ciphertext is called its `size'
    #and is given by Ciphertext::size(). A freshly encrypted ciphertext always
    #has size 2.
    print("    + size of freshly encrypted x: {}".format(x_encrypted.size()))

    #There is plenty of noise budget left in this freshly encrypted ciphertext.
    print("    + noise budget in freshly encrypted x: {} bits".format(
        decryptor.invariant_noise_budget(x_encrypted)))

    #We decrypt the ciphertext and print the resulting plaintext in order to
    #demonstrate correctness of the encryption.

    x_decrypted = Plaintext()
    decryptor.decrypt(x_encrypted, x_decrypted)
    print("    + decryption of x_encrypted: 0x{} ...... Correct.".format(
        x_decrypted.to_string()))

    #When using Microsoft SEAL, it is typically advantageous to compute in a way
    #that minimizes the longest chain of sequential multiplications. In other
    #words, encrypted computations are best evaluated in a way that minimizes
    #the multiplicative depth of the computation, because the total noise budget
    #consumption is proportional to the multiplicative depth. For example, for
    #our example computation it is advantageous to factorize the polynomial as
    #
    #    4x^4 + 8x^3 + 8x^2 + 8x + 4 = 4(x + 1)^2 * (x^2 + 1)
    #
    #to obtain a simple depth 2 representation. Thus, we compute (x + 1)^2 and
    #(x^2 + 1) separately, before multiplying them, and multiplying by 4.
    #
    #First, we compute x^2 and add a plaintext "1". We can clearly see from the
    #print-out that multiplication has consumed a lot of noise budget. The user
    #can vary the plain_modulus parameter to see its effect on the rate of noise
    #budget consumption.

    print("Compute x_sq_plus_one (x^2+1).")
    x_sq_plus_one = Ciphertext()
    evaluator.square(x_encrypted, x_sq_plus_one)
    plain_one = Plaintext("1")
    evaluator.add_plain_inplace(x_sq_plus_one, plain_one)

    #Encrypted multiplication results in the output ciphertext growing in size.
    #More precisely, if the input ciphertexts have size M and N, then the output
    #ciphertext after homomorphic multiplication will have size M+N-1. In this
    #case we perform a squaring, and observe both size growth and noise budget
    #consumption.
    print("    + size of x_sq_plus_one: {}".format(x_sq_plus_one.size()))
    print("    + noise budget in x_sq_plus_one: {} bits".format(
        decryptor.invariant_noise_budget(x_sq_plus_one)))

    #Even though the size has grown, decryption works as usual as long as noise
    #budget has not reached 0.
    decrypted_result = Plaintext()
    decryptor.decrypt(x_sq_plus_one, decrypted_result)
    print("    + decryption of x_sq_plus_one: 0x{} ...... Correct.".format(
        decrypted_result.to_string()))

    #Next, we compute (x + 1)^2.
    print("Compute x_plus_one_sq ((x+1)^2).")
    x_plus_one_sq = Ciphertext()
    evaluator.add_plain(x_encrypted, plain_one, x_plus_one_sq)
    evaluator.square_inplace(x_plus_one_sq)
    print("    + size of x_plus_one_sq: {}".format(x_plus_one_sq.size()))
    print("    + noise budget in x_plus_one_sq: {} bits".format(
        decryptor.invariant_noise_budget(x_plus_one_sq)))
    decryptor.decrypt(x_plus_one_sq, decrypted_result)
    print("    + decryption of x_plus_one_sq: 0x{} ...... Correct.".format(
        decrypted_result.to_string()))

    #Finally, we multiply (x^2 + 1) * (x + 1)^2 * 4.
    print("Compute encrypted_result (4(x^2+1)(x+1)^2).")
    encrypted_result = Ciphertext()
    plain_four = Plaintext("4")
    evaluator.multiply_plain_inplace(x_sq_plus_one, plain_four)
    evaluator.multiply(x_sq_plus_one, x_plus_one_sq, encrypted_result)
    print("    + size of encrypted_result: {}".format(encrypted_result.size()))
    print("    + noise budget in encrypted_result: {} bits".format(
        decryptor.invariant_noise_budget(encrypted_result)))
    print("NOTE: Decryption can be incorrect if noise budget is zero.")
    print("~~~~~~ A better way to calculate 4(x^2+1)(x+1)^2. ~~~~~~")

    #Noise budget has reached 0, which means that decryption cannot be expected
    #to give the correct result. This is because both ciphertexts x_sq_plus_one
    #and x_plus_one_sq consist of 3 polynomials due to the previous squaring
    #operations, and homomorphic operations on large ciphertexts consume much more
    #noise budget than computations on small ciphertexts. Computing on smaller
    #ciphertexts is also computationally significantly cheaper.

    #`Relinearization' is an operation that reduces the size of a ciphertext after
    #multiplication back to the initial size, 2. Thus, relinearizing one or both
    #input ciphertexts before the next multiplication can have a huge positive
    #impact on both noise growth and performance, even though relinearization has
    #a significant computational cost itself. It is only possible to relinearize
    #size 3 ciphertexts down to size 2, so often the user would want to relinearize
    #after each multiplication to keep the ciphertext sizes at 2.

    #Relinearization requires special `relinearization keys', which can be thought
    #of as a kind of public key. Relinearization keys can easily be created with
    #the KeyGenerator.

    #Relinearization is used similarly in both the BFV and the CKKS schemes, but
    #in this example we continue using BFV. We repeat our computation from before,
    #but this time relinearize after every multiplication.

    #We use KeyGenerator::relin_keys() to create relinearization keys.
    print("Generate relinearization keys.")
    relin_keys = keygen.relin_keys()

    #We now repeat the computation relinearizing after each multiplication.
    print("Compute and relinearize x_squared (x^2),")
    print("then compute x_sq_plus_one (x^2+1)")
    x_squared = Ciphertext()
    evaluator.square(x_encrypted, x_squared)
    print("    + size of x_squared: {}".format(x_squared.size()))
    evaluator.relinearize_inplace(x_squared, relin_keys)
    print("    + size of x_squared (after relinearization): {}".format(
        x_squared.size()))
    evaluator.add_plain(x_squared, plain_one, x_sq_plus_one)
    print("    + noise budget in x_sq_plus_one: {} bits".format(
        decryptor.invariant_noise_budget(x_sq_plus_one)))
    decryptor.decrypt(x_sq_plus_one, decrypted_result)
    print("    + decryption of x_sq_plus_one: 0x{}  ...... Correct.".format(
        decrypted_result.to_string()))

    x_plus_one = Ciphertext()
    print("Compute x_plus_one (x+1),")
    print("then compute and relinearize x_plus_one_sq ((x+1)^2).")
    evaluator.add_plain(x_encrypted, plain_one, x_plus_one)
    evaluator.square(x_plus_one, x_plus_one_sq)
    print("    + size of x_plus_one_sq: {}".format(x_plus_one_sq.size()))
    evaluator.relinearize_inplace(x_plus_one_sq, relin_keys)
    print("    + noise budget in x_plus_one_sq: {} bits".format(
        decryptor.invariant_noise_budget(x_plus_one_sq)))
    decryptor.decrypt(x_plus_one_sq, decrypted_result)
    print("    + decryption of x_plus_one_sq: 0x{}  ...... Correct.".format(
        decrypted_result.to_string()))

    print("Compute and relinearize encrypted_result (4(x^2+1)(x+1)^2).")
    evaluator.multiply_plain_inplace(x_sq_plus_one, plain_four)
    evaluator.multiply(x_sq_plus_one, x_plus_one_sq, encrypted_result)
    print("    + size of encrypted_result: {}".format(encrypted_result.size()))
    evaluator.relinearize_inplace(encrypted_result, relin_keys)
    print("    + size of encrypted_result (after relinearization): {}".format(
        encrypted_result.size()))
    print("    + noise budget in encrypted_result: {} bits".format(
        decryptor.invariant_noise_budget(encrypted_result)))

    print("NOTE: Notice the increase in remaining noise budget.")

    #Relinearization clearly improved our noise consumption. We have still plenty
    #of noise budget left, so we can expect the correct answer when decrypting.
    print("Decrypt encrypted_result (4(x^2+1)(x+1)^2).")
    decryptor.decrypt(encrypted_result, decrypted_result)
    print("    + decryption of 4(x^2+1)(x+1)^2 = 0x{}  ...... Correct.".format(
        decrypted_result.to_string()))
# creates vector matrixPower_vector contaning each element as powers of matrix A upto A^n
# Also creates a vector trace_vector which contains trace of matrix A, A^2 ... A^(n-1)
for i in range(1, n):
    matrixPower_vector.append(raise_power(matrixPower_vector[i - 1]))
    trace_vector.append(trace(matrixPower_vector[i]))

# Vector c is defined as coefficint vector for the charactersitic equation of the matrix
c = [Ciphertext(trace_vector[0])]
evaluator.negate(c[0])

# The following is the implementation of Newton-identities to calculate the value of coeffecients
for i in range(1, n):
    c_new = Ciphertext(trace_vector[i])
    for j in range(i):
        tc = Ciphertext()
        evaluator.multiply(trace_vector[i - 1 - j], c[j], tc)
        evaluator.add(c_new, tc)
    evaluator.negate(c_new)
    frac = encoderF.encode(1 / (i + 1))
    evaluator.multiply_plain(c_new, frac)
    c.append(c_new)

matrixPower_vector = [iden_matrix(n)] + matrixPower_vector
c0 = Ciphertext()
encryptor.encrypt(encoderF.encode(1), c0)
c = [c0] + c

# Adding the matrices multiplie by their coefficients
for i in range(len(matrixPower_vector) - 1):
    for j in range(len(c)):
        if (i + j == n - 1):
D=A_cipherObject
#shallow copy

# reducing to diagnol matrix
for i in range(4):
	for j in range (8):
		if (j!=i):
			plain_result = Plaintext()
			X=D[i][i]
			decryptor.decrypt(X, plain_result)
			E=1/int(encoder.decode_int32(plain_result))
			Y=Ciphertext(parms)
			R=encoderF.encode(E)
			encryptor.encrypt(R,Y)
			evaluator.multiply(Y,D[j][i])
			for k in range(8):
				evaluator.multiply(Y,D[i][k])
				evaluator.negate(Y)
				evaluator.add(A_cipherObject[j][k],Y)
				
# reducing to unit matrix
for i in range (8):
	d=A_cipherObject[i][i]
	Y=Ciphertext()
	plain_result = Plaintext()
	decryptor.decrypt(X, plain_result)
	encryptor.encrpyt(encoder.encode(1/int(encoder.decode_int32(plain_result))),Y)
	for j in range (8):
		A_cipherObject[i][j]=evaluator.multiply(A_cipherObject[i][j],Y)
Пример #9
0
def example_basics_i():
    print_example_banner("Example: Basics I")

    # In this example we demonstrate setting up encryption parameters and other
    # relevant objects for performing simple computations on encrypted integers.

    # SEAL uses the Fan-Vercauteren (FV) homomorphic encryption scheme. We refer to
    # https://eprint.iacr.org/2012/144 for full details on how the FV scheme works.
    # For better performance, SEAL implements the "FullRNS" optimization of FV, as
    # described in https://eprint.iacr.org/2016/510.

    # The first task is to set up an instance of the EncryptionParameters class.
    # It is critical to understand how these different parameters behave, how they
    # affect the encryption scheme, performance, and the security level. There are
    # three encryption parameters that are necessary to set:

    #     - poly_modulus (polynomial modulus);
    #     - coeff_modulus ([ciphertext] coefficient modulus);
    #     - plain_modulus (plaintext modulus).

    # A fourth parameter -- noise_standard_deviation -- has a default value of 3.19
    # and should not be necessary to modify unless the user has a specific reason
    # to and knows what they are doing.

    # The encryption scheme implemented in SEAL cannot perform arbitrary computations
    # on encrypted data. Instead, each ciphertext has a specific quantity called the
    # `invariant noise budget' -- or `noise budget' for short -- measured in bits.
    # The noise budget of a freshly encrypted ciphertext (initial noise budget) is
    # determined by the encryption parameters. Homomorphic operations consume the
    # noise budget at a rate also determined by the encryption parameters. In SEAL
    # the two basic homomorphic operations are additions and multiplications, of
    # which additions can generally be thought of as being nearly free in terms of
    # noise budget consumption compared to multiplications. Since noise budget
    # consumption is compounding in sequential multiplications, the most significant
    # factor in choosing appropriate encryption parameters is the multiplicative
    # depth of the arithmetic circuit that needs to be evaluated. Once the noise
    # budget in a ciphertext reaches zero, it becomes too corrupted to be decrypted.
    # Thus, it is essential to choose the parameters to be large enough to support
    # the desired computation; otherwise the result is impossible to make sense of
    # even with the secret key.
    parms = EncryptionParameters()

    # We first set the polynomial modulus. This must be a power-of-2 cyclotomic
    # polynomial, i.e. a polynomial of the form "1x^(power-of-2) + 1". The polynomial
    # modulus should be thought of mainly affecting the security level of the scheme;
    # larger polynomial modulus makes the scheme more secure. At the same time, it
    # makes ciphertext sizes larger, and consequently all operations slower.
    # Recommended degrees for poly_modulus are 1024, 2048, 4096, 8192, 16384, 32768,
    # but it is also possible to go beyond this. Since we perform only a very small
    # computation in this example, it suffices to use a very small polynomial modulus
    parms.set_poly_modulus("1x^2048 + 1")

    # Next we choose the [ciphertext] coefficient modulus (coeff_modulus). The size
    # of the coefficient modulus should be thought of as the most significant factor
    # in determining the noise budget in a freshly encrypted ciphertext: bigger means
    # more noise budget. Unfortunately, a larger coefficient modulus also lowers the
    # security level of the scheme. Thus, if a large noise budget is required for
    # complicated computations, a large coefficient modulus needs to be used, and the
    # reduction in the security level must be countered by simultaneously increasing
    # the polynomial modulus.

    # To make parameter selection easier for the user, we have constructed sets of
    # largest allowed coefficient moduli for 128-bit and 192-bit security levels
    # for different choices of the polynomial modulus. These recommended parameters
    # follow the Security white paper at http://HomomorphicEncryption.org. However,
    # due to the complexity of this topic, we highly recommend the user to directly
    # consult an expert in homomorphic encryption and RLWE-based encryption schemes
    # to determine the security of their parameter choices.

    # Our recommended values for the coefficient modulus can be easily accessed
    # through the functions

    #     coeff_modulus_128bit(int)
    #     coeff_modulus_192bit(int)

    # for 128-bit and 192-bit security levels. The integer parameter is the degree
    # of the polynomial modulus.

    # In SEAL the coefficient modulus is a positive composite number -- a product
    # of distinct primes of size up to 60 bits. When we talk about the size of the
    # coefficient modulus we mean the bit length of the product of the small primes.
    # The small primes are represented by instances of the SmallModulus class; for
    # example coeff_modulus_128bit(int) returns a vector of SmallModulus instances.

    # It is possible for the user to select their own small primes. Since SEAL uses
    # the Number Theoretic Transform (NTT) for polynomial multiplications modulo the
    # factors of the coefficient modulus, the factors need to be prime numbers
    # congruent to 1 modulo 2*degree(poly_modulus). We have generated a list of such
    # prime numbers of various sizes, that the user can easily access through the
    # functions

    #     small_mods_60bit(int)
    #     small_mods_50bit(int)
    #     small_mods_40bit(int)
    #     small_mods_30bit(int)

    # each of which gives access to an array of primes of the denoted size. These
    # primes are located in the source file util/globals.cpp.

    # Performance is mainly affected by the size of the polynomial modulus, and the
    # number of prime factors in the coefficient modulus. Thus, it is important to
    # use as few factors in the coefficient modulus as possible.

    # In this example we use the default coefficient modulus for a 128-bit security
    # level. Concretely, this coefficient modulus consists of only one 56-bit prime
    # factor: 0xfffffffff00001.
    parms.set_coeff_modulus(seal.coeff_modulus_128(2048))

    # The plaintext modulus can be any positive integer, even though here we take
    # it to be a power of two. In fact, in many cases one might instead want it to
    # be a prime number; we will see this in example_batching(). The plaintext
    # modulus determines the size of the plaintext data type, but it also affects
    # the noise budget in a freshly encrypted ciphertext, and the consumption of
    # the noise budget in homomorphic multiplication. Thus, it is essential to try
    # to keep the plaintext data type as small as possible for good performance.
    # The noise budget in a freshly encrypted ciphertext is

    #     ~ log2(coeff_modulus/plain_modulus) (bits)

    # and the noise budget consumption in a homomorphic multiplication is of the
    # form log2(plain_modulus) + (other terms).
    parms.set_plain_modulus(1 << 8)

    # Now that all parameters are set, we are ready to construct a SEALContext
    # object. This is a heavy class that checks the validity and properties of
    # the parameters we just set, and performs and stores several important
    # pre-computations.
    context = SEALContext(parms)

    # Print the parameters that we have chosen
    print_parameters(context)

    # Plaintexts in the FV scheme are polynomials with coefficients integers modulo
    # plain_modulus. To encrypt for example integers instead, one can use an
    # `encoding scheme' to represent the integers as such polynomials. SEAL comes
    # with a few basic encoders:

    # [IntegerEncoder]
    # Given an integer base b, encodes integers as plaintext polynomials as follows.
    # First, a base-b expansion of the integer is computed. This expansion uses
    # a `balanced' set of representatives of integers modulo b as the coefficients.
    # Namely, when b is odd the coefficients are integers between -(b-1)/2 and
    # (b-1)/2. When b is even, the integers are between -b/2 and (b-1)/2, except
    # when b is two and the usual binary expansion is used (coefficients 0 and 1).
    # Decoding amounts to evaluating the polynomial at x=b. For example, if b=2,
    # the integer

    #     26 = 2^4 + 2^3 + 2^1

    # is encoded as the polynomial 1x^4 + 1x^3 + 1x^1. When b=3,

    #     26 = 3^3 - 3^0

    # is encoded as the polynomial 1x^3 - 1. In memory polynomial coefficients are
    # always stored as unsigned integers by storing their smallest non-negative
    # representatives modulo plain_modulus. To create a base-b integer encoder,
    # use the constructor IntegerEncoder(plain_modulus, b). If no b is given, b=2
    # is used.

    # [FractionalEncoder]
    # The FractionalEncoder encodes fixed-precision rational numbers as follows.
    # It expands the number in a given base b, possibly truncating an infinite
    # fractional part to finite precision, e.g.

    #     26.75 = 2^4 + 2^3 + 2^1 + 2^(-1) + 2^(-2)

    # when b=2. For the sake of the example, suppose poly_modulus is 1x^1024 + 1.
    # It then represents the integer part of the number in the same way as in
    # IntegerEncoder (with b=2 here), and moves the fractional part instead to the
    # highest degree part of the polynomial, but with signs of the coefficients
    # changed. In this example we would represent 26.75 as the polynomial

    #     -1x^1023 - 1x^1022 + 1x^4 + 1x^3 + 1x^1.

    # In memory the negative coefficients of the polynomial will be represented as
    # their negatives modulo plain_modulus.

    # [PolyCRTBuilder]
    # If plain_modulus is a prime congruent to 1 modulo 2*degree(poly_modulus), the
    # plaintext elements can be viewed as 2-by-(degree(poly_modulus) / 2) matrices
    # with elements integers modulo plain_modulus. When a desired computation can be
    # vectorized, using PolyCRTBuilder can result in massive performance improvements
    # over naively encrypting and operating on each input number separately. Thus,
    # in more complicated computations this is likely to be by far the most important
    # and useful encoder. In example_batching() we show how to use and operate on
    # encrypted matrix plaintexts.

    # For performance reasons, in homomorphic encryption one typically wants to keep
    # the plaintext data types as small as possible, which can make it challenging to
    # prevent data type overflow in more complicated computations, especially when
    # operating on rational numbers that have been scaled to integers. When using
    # PolyCRTBuilder estimating whether an overflow occurs is a fairly standard task,
    # as the matrix slots are integers modulo plain_modulus, and each slot is operated
    # on independently of the others. When using IntegerEncoder or FractionalEncoder
    # it is substantially more difficult to estimate when an overflow occurs in the
    # plaintext, and choosing the plaintext modulus very carefully to be large enough
    # is critical to avoid unexpected results. Specifically, one needs to estimate how
    # large the largest coefficient in  the polynomial view of all of the plaintext
    # elements becomes, and choose the plaintext modulus to be larger than this value.
    # SEAL comes with an automatic parameter selection tool that can help with this
    # task, as is demonstrated in example_parameter_selection().

    # Here we choose to create an IntegerEncoder with base b=2.
    encoder = IntegerEncoder(context.plain_modulus())

    # We are now ready to generate the secret and public keys. For this purpose we need
    # an instance of the KeyGenerator class. Constructing a KeyGenerator automatically
    # generates the public and secret key, which can then be read to local variables.
    # To create a fresh pair of keys one can call KeyGenerator::generate() at any time.
    keygen = KeyGenerator(context)
    public_key = keygen.public_key()
    secret_key = keygen.secret_key()

    # To be able to encrypt, we need to construct an instance of Encryptor. Note that
    # the Encryptor only requires the public key.
    encryptor = Encryptor(context, public_key)

    # Computations on the ciphertexts are performed with the Evaluator class.
    evaluator = Evaluator(context)

    # We will of course want to decrypt our results to verify that everything worked,
    # so we need to also construct an instance of Decryptor. Note that the Decryptor
    # requires the secret key.
    decryptor = Decryptor(context, secret_key)

    # We start by encoding two integers as plaintext polynomials.
    value1 = 5
    plain1 = encoder.encode(value1)
    print("Encoded " + (str)(value1) + " as polynomial " + plain1.to_string() +
          " (plain1)")

    value2 = -7
    plain2 = encoder.encode(value2)
    print("Encoded " + (str)(value2) + " as polynomial " + plain2.to_string() +
          " (plain2)")

    # Encrypting the values is easy.
    encrypted1 = Ciphertext()
    encrypted2 = Ciphertext()
    print("Encrypting plain1: ")
    encryptor.encrypt(plain1, encrypted1)
    print("Done (encrypted1)")

    print("Encrypting plain2: ")
    encryptor.encrypt(plain2, encrypted2)
    print("Done (encrypted2)")

    # To illustrate the concept of noise budget, we print the budgets in the fresh
    # encryptions.
    print("Noise budget in encrypted1: " +
          (str)(decryptor.invariant_noise_budget(encrypted1)) + " bits")
    print("Noise budget in encrypted2: " +
          (str)(decryptor.invariant_noise_budget(encrypted2)) + " bits")

    # As a simple example, we compute (-encrypted1 + encrypted2) * encrypted2.

    # Negation is a unary operation.
    evaluator.negate(encrypted1)

    # Negation does not consume any noise budget.
    print("Noise budget in -encrypted1: " +
          (str)(decryptor.invariant_noise_budget(encrypted1)) + " bits")

    # Addition can be done in-place (overwriting the first argument with the result,
    # or alternatively a three-argument overload with a separate destination variable
    # can be used. The in-place variants are always more efficient. Here we overwrite
    # encrypted1 with the sum.
    evaluator.add(encrypted1, encrypted2)

    # It is instructive to think that addition sets the noise budget to the minimum
    # of the input noise budgets. In this case both inputs had roughly the same
    # budget going on, and the output (in encrypted1) has just slightly lower budget.
    # Depending on probabilistic effects, the noise growth consumption may or may
    # not be visible when measured in whole bits.
    print("Noise budget in -encrypted1 + encrypted2: " +
          (str)(decryptor.invariant_noise_budget(encrypted1)) + " bits")

    # Finally multiply with encrypted2. Again, we use the in-place version of the
    # function, overwriting encrypted1 with the product.
    evaluator.multiply(encrypted1, encrypted2)

    # Multiplication consumes a lot of noise budget. This is clearly seen in the
    # print-out. The user can change the plain_modulus to see its effect on the
    # rate of noise budget consumption.
    print("Noise budget in (-encrypted1 + encrypted2) * encrypted2: " +
          (str)(decryptor.invariant_noise_budget(encrypted1)) + " bits")

    # Now we decrypt and decode our result.
    plain_result = Plaintext()
    print("Decrypting result: ")
    decryptor.decrypt(encrypted1, plain_result)
    print("Done")

    # Print the result plaintext polynomial.
    print("Plaintext polynomial: " + plain_result.to_string())

    # Decode to obtain an integer result.
    print("Decoded integer: " + (str)(encoder.decode_int32(plain_result)))
Пример #10
0
class SealOps:
    @classmethod
    def with_env(cls):
        parms = EncryptionParameters(scheme_type.CKKS)
        parms.set_poly_modulus_degree(POLY_MODULUS_DEGREE)
        parms.set_coeff_modulus(
            CoeffModulus.Create(POLY_MODULUS_DEGREE, PRIME_SIZE_LIST))

        context = SEALContext.Create(parms)

        keygen = KeyGenerator(context)
        public_key = keygen.public_key()
        secret_key = keygen.secret_key()
        relin_keys = keygen.relin_keys()
        galois_keys = keygen.galois_keys()

        return cls(context=context,
                   public_key=public_key,
                   secret_key=secret_key,
                   relin_keys=relin_keys,
                   galois_keys=galois_keys,
                   poly_modulus_degree=POLY_MODULUS_DEGREE,
                   scale=SCALE)

    def __init__(self,
                 context: SEALContext,
                 scale: float,
                 poly_modulus_degree: int,
                 public_key: PublicKey = None,
                 secret_key: SecretKey = None,
                 relin_keys: RelinKeys = None,
                 galois_keys: GaloisKeys = None):
        self.scale = scale
        self.context = context
        self.encoder = CKKSEncoder(context)
        self.evaluator = Evaluator(context)
        self.encryptor = Encryptor(context, public_key)
        self.decryptor = Decryptor(context, secret_key)
        self.relin_keys = relin_keys
        self.galois_keys = galois_keys
        self.poly_modulus_degree_log = np.log2(poly_modulus_degree)

    def encrypt(self, matrix: np.array):
        matrix = Matrix.from_numpy_array(array=matrix)
        cipher_matrix = CipherMatrix(rows=matrix.rows, cols=matrix.cols)

        for i in range(matrix.rows):
            encoded_row = Plaintext()
            self.encoder.encode(matrix[i], self.scale, encoded_row)
            self.encryptor.encrypt(encoded_row, cipher_matrix[i])

        return cipher_matrix

    def decrypt(self, cipher_matrix: CipherMatrix) -> Matrix:
        matrix = Matrix(rows=cipher_matrix.rows, cols=cipher_matrix.cols)

        for i in range(matrix.rows):
            row = Vector()
            encoded_row = Plaintext()
            self.decryptor.decrypt(cipher_matrix[i], encoded_row)
            self.encoder.decode(encoded_row, row)
            matrix[i] = row

        return matrix

    def add(self, matrix_a: CipherMatrix,
            matrix_b: CipherMatrix) -> CipherMatrix:
        self.validate_same_dimension(matrix_a, matrix_b)

        result_matrix = CipherMatrix(rows=matrix_a.rows, cols=matrix_a.cols)
        for i in range(matrix_a.rows):
            a_tag, b_tag = self.get_matched_scale_vectors(
                matrix_a[i], matrix_b[i])
            self.evaluator.add(a_tag, b_tag, result_matrix[i])

        return result_matrix

    def add_plain(self, matrix_a: CipherMatrix,
                  matrix_b: np.array) -> CipherMatrix:
        matrix_b = Matrix.from_numpy_array(matrix_b)
        self.validate_same_dimension(matrix_a, matrix_b)

        result_matrix = CipherMatrix(rows=matrix_a.rows, cols=matrix_a.cols)

        for i in range(matrix_a.rows):
            row = matrix_b[i]
            encoded_row = Plaintext()
            self.encoder.encode(row, self.scale, encoded_row)
            self.evaluator.mod_switch_to_inplace(encoded_row,
                                                 matrix_a[i].parms_id())
            self.evaluator.add_plain(matrix_a[i], encoded_row,
                                     result_matrix[i])

        return result_matrix

    def multiply_plain(self, matrix_a: CipherMatrix,
                       matrix_b: np.array) -> CipherMatrix:
        matrix_b = Matrix.from_numpy_array(matrix_b)
        self.validate_same_dimension(matrix_a, matrix_b)

        result_matrix = CipherMatrix(rows=matrix_a.rows, cols=matrix_a.cols)

        for i in range(matrix_a.rows):
            row = matrix_b[i]
            encoded_row = Plaintext()
            self.encoder.encode(row, self.scale, encoded_row)
            self.evaluator.mod_switch_to_inplace(encoded_row,
                                                 matrix_a[i].parms_id())
            self.evaluator.multiply_plain(matrix_a[i], encoded_row,
                                          result_matrix[i])

        return result_matrix

    def dot_vector(self, a: Ciphertext, b: Ciphertext) -> Ciphertext:
        result = Ciphertext()

        self.evaluator.multiply(a, b, result)
        self.evaluator.relinearize_inplace(result, self.relin_keys)
        self.vector_sum_inplace(result)
        self.get_vector_first_element(result)
        self.evaluator.rescale_to_next_inplace(result)

        return result

    def dot_vector_with_plain(self, a: Ciphertext,
                              b: DoubleVector) -> Ciphertext:
        result = Ciphertext()

        b_plain = Plaintext()
        self.encoder.encode(b, self.scale, b_plain)

        self.evaluator.multiply_plain(a, b_plain, result)
        self.vector_sum_inplace(result)
        self.get_vector_first_element(result)

        self.evaluator.rescale_to_next_inplace(result)

        return result

    def get_vector_range(self, vector_a: Ciphertext, i: int,
                         j: int) -> Ciphertext:
        cipher_range = Ciphertext()

        one_and_zeros = DoubleVector([0.0 if x < i else 1.0 for x in range(j)])
        plain = Plaintext()
        self.encoder.encode(one_and_zeros, self.scale, plain)
        self.evaluator.mod_switch_to_inplace(plain, vector_a.parms_id())
        self.evaluator.multiply_plain(vector_a, plain, cipher_range)

        return cipher_range

    def dot_matrix_with_matrix_transpose(self, matrix_a: CipherMatrix,
                                         matrix_b: CipherMatrix):
        result_matrix = CipherMatrix(rows=matrix_a.rows, cols=matrix_a.cols)

        rows_a = matrix_a.rows
        cols_b = matrix_b.rows

        for i in range(rows_a):
            vector_dot_products = []
            zeros = Plaintext()

            for j in range(cols_b):
                vector_dot_products += [
                    self.dot_vector(matrix_a[i], matrix_b[j])
                ]

                if j == 0:
                    zero = DoubleVector()
                    self.encoder.encode(zero, vector_dot_products[j].scale(),
                                        zeros)
                    self.evaluator.mod_switch_to_inplace(
                        zeros, vector_dot_products[j].parms_id())
                    self.evaluator.add_plain(vector_dot_products[j], zeros,
                                             result_matrix[i])
                else:
                    self.evaluator.rotate_vector_inplace(
                        vector_dot_products[j], -j, self.galois_keys)
                    self.evaluator.add_inplace(result_matrix[i],
                                               vector_dot_products[j])

        for vec in result_matrix:
            self.evaluator.relinearize_inplace(vec, self.relin_keys)
            self.evaluator.rescale_to_next_inplace(vec)

        return result_matrix

    def dot_matrix_with_plain_matrix_transpose(self, matrix_a: CipherMatrix,
                                               matrix_b: np.array):
        matrix_b = Matrix.from_numpy_array(matrix_b)
        result_matrix = CipherMatrix(rows=matrix_a.rows, cols=matrix_a.cols)

        rows_a = matrix_a.rows
        cols_b = matrix_b.rows

        for i in range(rows_a):
            vector_dot_products = []
            zeros = Plaintext()

            for j in range(cols_b):
                vector_dot_products += [
                    self.dot_vector_with_plain(matrix_a[i], matrix_b[j])
                ]

                if j == 0:
                    zero = DoubleVector()
                    self.encoder.encode(zero, vector_dot_products[j].scale(),
                                        zeros)
                    self.evaluator.mod_switch_to_inplace(
                        zeros, vector_dot_products[j].parms_id())
                    self.evaluator.add_plain(vector_dot_products[j], zeros,
                                             result_matrix[i])
                else:
                    self.evaluator.rotate_vector_inplace(
                        vector_dot_products[j], -j, self.galois_keys)
                    self.evaluator.add_inplace(result_matrix[i],
                                               vector_dot_products[j])

        for vec in result_matrix:
            self.evaluator.relinearize_inplace(vec, self.relin_keys)
            self.evaluator.rescale_to_next_inplace(vec)

        return result_matrix

    @staticmethod
    def validate_same_dimension(matrix_a, matrix_b):
        if matrix_a.rows != matrix_b.rows or matrix_a.cols != matrix_b.cols:
            raise ArithmeticError("Matrices aren't of the same dimension")

    def vector_sum_inplace(self, cipher: Ciphertext):
        rotated = Ciphertext()

        for i in range(int(self.poly_modulus_degree_log - 1)):
            self.evaluator.rotate_vector(cipher, pow(2, i), self.galois_keys,
                                         rotated)
            self.evaluator.add_inplace(cipher, rotated)

    def get_vector_first_element(self, cipher: Ciphertext):
        one_and_zeros = DoubleVector([1.0])
        plain = Plaintext()
        self.encoder.encode(one_and_zeros, self.scale, plain)
        self.evaluator.multiply_plain_inplace(cipher, plain)

    def get_matched_scale_vectors(self, a: Ciphertext,
                                  b: Ciphertext) -> (Ciphertext, Ciphertext):
        a_tag = Ciphertext(a)
        b_tag = Ciphertext(b)

        a_index = self.context.get_context_data(a.parms_id()).chain_index()
        b_index = self.context.get_context_data(b.parms_id()).chain_index()

        # Changing the mod if required, else just setting the scale
        if a_index < b_index:
            self.evaluator.mod_switch_to_inplace(b_tag, a.parms_id())

        elif a_index > b_index:
            self.evaluator.mod_switch_to_inplace(a_tag, b.parms_id())

        a_tag.set_scale(self.scale)
        b_tag.set_scale(self.scale)

        return a_tag, b_tag
Пример #11
0
parms.set_coeff_modulus(seal.coeff_modulus_128(8192))
parms.set_plain_modulus(1 << 21)
context = SEALContext(parms)

encoderF = FractionalEncoder(context.plain_modulus(), context.poly_modulus(),
                             30, 34, 3)
keygen = KeyGenerator(context)
public_key = keygen.public_key()
secret_key = keygen.secret_key()

encryptor = Encryptor(context, public_key)
evaluator = Evaluator(context)
decryptor = Decryptor(context, secret_key)

########################## encoding main matrix ################################

A = [Ciphertext(), Ciphertext(), Ciphertext(), Ciphertext()]

for i in range(len(A)):
    encryptor.encrypt(encoderF.encode(i), A[i])

for j in range(10):
    evaluator.multiply(A[0], A[1])
    evaluator.multiply(A[0], A[2])
    evaluator.add(A[1], A[2])
    for i in range(len(A)):
        print("Noise budget of [" + str(i) + "] :" +
              str((decryptor.invariant_noise_budget(A[i]))) + " bits")
        print("A[%d]: " % (i), )
        print_value(A[i])