Exemple #1
0
class ParticipantTest(unittest.TestCase):

    participant = Participant()
    parms = EncryptionParameters(scheme_type.BFV)
    parms.set_poly_modulus_degree(4096)
    parms.set_coeff_modulus(CoeffModulus.BFVDefault(4096))
    parms.set_plain_modulus(1 << 8)

    def test_set_get_parms(self):
        self.participant.set_parms(self.parms)
        self.assertIsInstance(Participant(), type(self.participant))
        self.assertEqual(self.participant.get_parms().poly_modulus_degree(),
                         4096)
        self.assertEqual(self.participant.get_parms().plain_modulus().value(),
                         1 << 8)

        self.assertIsInstance(self.participant._context, SEALContext)
        self.assertIsInstance(self.participant._encoder, IntegerEncoder)
        self.assertIsInstance(self.participant._evaluator, Evaluator)

    def test_save_load_c_array(self):
        client = Client()
        arr = np.array([3, 1, 0, -2, -4])
        self.participant.set_parms(self.parms)
        arr_cipher = client.enc(arr)
        arr_string = self.participant.save_c_arr(arr_cipher)
        arr_cipher_load = self.participant.load_c_arr(arr_string)
        arr_load = client.dec(arr_cipher_load)
        self.assertTrue(np.array_equal(arr, arr_load))
Exemple #2
0
 def set_parms_from_dict(self, **parms_dict):
     default_parms_dict = {"poly": 4096, "coeff": 4096, "plain": (1 << 8)}
     if parms_dict:
         for key, value in parms_dict.items():
             assert key in parms_dict, "Accepted parameters are poly, coeff, plain, and print_parms"
             default_parms_dict[key] = parms_dict[key]
     self._parms.set_poly_modulus_degree(default_parms_dict['poly'])
     self._parms.set_coeff_modulus(
         CoeffModulus.BFVDefault(default_parms_dict['coeff']))
     self._parms.set_plain_modulus(default_parms_dict['plain'])
     self.set_parms(self._parms)
Exemple #3
0
    def __init__(self, modulus, degree):
        self.modulus = modulus
        self.degree = degree
        self.acceptable_degree = [1024, 2048, 4096, 8192, 16384]
        if self.degree not in self.acceptable_degree:
            raise Exception(f"Get {self.degree}, but expect degree {self.acceptable_degree}")

        self.parms = EncryptionParameters(scheme_type.BFV)
        self.parms.set_poly_modulus_degree(self.degree)
        self.parms.set_coeff_modulus(CoeffModulus.BFVDefault(self.degree))
        # self.parms.set_coeff_modulus(CoeffModulus.Create(self.degree, [60]))
        self.parms.set_plain_modulus(self.modulus)
        # print(self.parms.coeff_modulus()[0].value())
        self.context = SEALContext.Create(self.parms)
        self.keygen = KeyGenerator(self.context)
        self.evaluator = Evaluator(self.context)
        self.batch_encoder = BatchEncoder(self.context)
def example_rotation_bfv():
    print("Example: Rotation / Rotation in BFV")

    parms = EncryptionParameters(scheme_type.BFV)

    poly_modulus_degree = 8192
    parms.set_poly_modulus_degree(poly_modulus_degree)
    parms.set_coeff_modulus(CoeffModulus.BFVDefault(poly_modulus_degree))
    parms.set_plain_modulus(PlainModulus.Batching(poly_modulus_degree, 20))

    context = SEALContext.Create(parms)
    print_parameters(context)

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

    batch_encoder = BatchEncoder(context)
    slot_count = batch_encoder.slot_count()
    row_size = int(slot_count / 2)
    print("Plaintext matrix row size: {}".format(row_size))

    pod_matrix = UInt64Vector([0]*slot_count)
    pod_matrix[0] = 0
    pod_matrix[1] = 1
    pod_matrix[2] = 2
    pod_matrix[3] = 3
    pod_matrix[row_size] = 4
    pod_matrix[row_size + 1] = 5
    pod_matrix[row_size + 2] = 6
    pod_matrix[row_size + 3] = 7

    print("Input plaintext matrix:")
    print_matrix(pod_matrix, row_size)

    #First we use BatchEncoder to encode the matrix into a plaintext. We encrypt
    #the plaintext as usual.

    plain_matrix = Plaintext()  
    print("Encode and encrypt.")
    batch_encoder.encode(pod_matrix, plain_matrix)
    encrypted_matrix = Ciphertext() 
    encryptor.encrypt(plain_matrix, encrypted_matrix)
    print("    + Noise budget in fresh encryption: {} bits".format(
        decryptor.invariant_noise_budget(encrypted_matrix))) 

    #Rotations require yet another type of special key called `Galois keys'. These
    #are easily obtained from the KeyGenerator.

    gal_keys = keygen.galois_keys()

    #Now rotate both matrix rows 3 steps to the left, decrypt, decode, and print.
    print("Rotate rows 3 steps left.")
    evaluator.rotate_rows_inplace(encrypted_matrix, 3, gal_keys)
    plain_result = Plaintext()  
    print("    + Noise budget after rotation: {} bits".format(
        decryptor.invariant_noise_budget(encrypted_matrix)))
    print("    + Decrypt and decode ...... Correct.")
    decryptor.decrypt(encrypted_matrix, plain_result)
    batch_encoder.decode(plain_result, pod_matrix)
    print_matrix(pod_matrix, row_size)

    #We can also rotate the columns, i.e., swap the rows.
    print("Rotate columns.")
    evaluator.rotate_columns_inplace(encrypted_matrix, gal_keys)
    print("    + Noise budget after rotation: {} bits".format(
        decryptor.invariant_noise_budget(encrypted_matrix)))
    print("    + Decrypt and decode ...... Correct.")
    decryptor.decrypt(encrypted_matrix, plain_result)
    batch_encoder.decode(plain_result, pod_matrix)
    print_matrix(pod_matrix, row_size)

    #Finally, we rotate the rows 4 steps to the right, decrypt, decode, and print.
    print("Rotate rows 4 steps right.")
    evaluator.rotate_rows_inplace(encrypted_matrix, -4, gal_keys)
    print("    + Noise budget after rotation: {} bits".format(
        decryptor.invariant_noise_budget(encrypted_matrix)))
    print("    + Decrypt and decode ...... Correct.")
    decryptor.decrypt(encrypted_matrix, plain_result)
    batch_encoder.decode(plain_result, pod_matrix)
    print_matrix(pod_matrix, row_size)
Exemple #5
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()))
Exemple #6
0
class ServerTest(unittest.TestCase):

    server = Server()
    fence = [[-3, -3], [-3, 3], [3, 3], [3, -3]]
    parms = EncryptionParameters(scheme_type.BFV)
    poly_modulus_degree = 4096
    parms.set_poly_modulus_degree(poly_modulus_degree)
    parms.set_coeff_modulus(CoeffModulus.BFVDefault(poly_modulus_degree))
    parms.set_plain_modulus(1 << 8)
    server.set_parms(parms)
    server.fence = fence

    def test_sever(self):
        server1 = Server()
        self.assertIsInstance(Server(), type(server1))

    def test_fence(self):
        self.server.fence = self.fence
        self.assertEqual(self.server.fence, self.fence)

    def test_load_fence(self):
        file_path = "hegeo/tests/resources/sics.json"
        self.server.load_fence(file_path)
        with open(file_path) as file:
            data = (json.load(file)[0]).get('path')
        self.assertEqual(self.server.fence, data)

    def test_sub_array_with_c(self):
        arr = np.array([5, 6, 3, 9, -2, -4, 0])
        client = Client()
        p = np.array([8])
        c = client.enc(p)
        delta = self.server.sub_array_with_c(arr, c[0])
        delta_p = client.dec(delta)
        self.assertTrue(np.array_equal(p - arr, delta_p))

    def test_compute_is_left(self):
        client = Client()
        arr1 = np.array([5, 6, 3, 9, -2, -4, 7])
        arr2 = np.array([6, 8, 3, 9, -2, -4, 5])
        arr1_c = client.enc(arr1)
        arr2_c = client.enc(arr2)
        is_left_c = self.server.compute_is_left(arr1_c, arr2_c)
        is_left_p = client.dec(is_left_c)
        arr1_next = np.roll(arr1, -1)
        arr2_next = np.roll(arr2, -1)
        is_left = arr1 * arr2_next - arr2 * arr1_next
        self.assertTrue(np.array_equal(is_left_p, is_left))

    def test_masking_demasking(self):
        client = Client()
        arr = np.array([39324356456, 144444, 0, -200, -49])
        arr_c = client.enc(arr)
        flag, arr_m = self.server.masking(arr_c)
        arr_m_p = client.dec(arr_m)
        arr_d = self.server.demasking(flag, arr_m_p)
        self.assertTrue(np.array_equal(arr, arr_d))

    def test_detection(self):
        def detection(point_c, fence):
            self.server.fence = fence
            is_left, dy = self.server.compute_intermediate(point_c)
            flag_is_left, is_left_m = self.server.masking(is_left)
            flag_dy, dy_m = self.server.masking(dy)
            is_left = client.dec(is_left_m)
            dy = client.dec(dy_m)
            # is_left = self.server.demasking(flag_is_left, is_left)
            # dy = self.server.demasking(flag_dy, dy)
            return self.server.detect_inclusion(is_left, dy, flag_is_left,
                                                flag_dy)

        client = Client()
        vertices = [[0, 0], [0, 20], [30, 20], [30, 0], [20, 0], [20, 10],
                    [10, 10], [10, 0]]

        point1 = [15, 15]  # inside vertical area
        point1_c = client.enc(point1)
        self.assertTrue(detection(point1_c, vertices))

        point2 = [15, 5]  # outside between horizontal areas
        point2_c = client.enc(point2)
        detection(point2_c, vertices)
        self.assertFalse(detection(point2_c, vertices))

        point3 = [40, 20]  # outside above
        point3_c = client.enc(point3)
        self.assertFalse(detection(point3_c, vertices))

        point4 = [25, 5]  # inside top horizontal area
        point4_c = client.enc(point4)
        self.assertTrue(detection(point4_c, vertices))
Exemple #7
0
def example_integer_encoder():
    print("Example: Encoders / Integer Encoder")
    #[IntegerEncoder] (For BFV scheme only)
    #
    #The IntegerEncoder encodes integers to BFV plaintext polynomials as follows.
    #First, a binary expansion of the integer is computed. Next, a polynomial is
    #created with the bits as coefficients. For example, the integer
    #
    #    26 = 2^4 + 2^3 + 2^1
    #
    #is encoded as the polynomial 1x^4 + 1x^3 + 1x^1. Conversely, plaintext
    #polynomials are decoded by evaluating them at x=2. For negative numbers the
    #IntegerEncoder simply stores all coefficients as either 0 or -1, where -1 is
    #represented by the unsigned integer plain_modulus - 1 in memory.
    #
    #Since encrypted computations operate on the polynomials rather than on the
    #encoded integers themselves, the polynomial coefficients will grow in the
    #course of such computations. For example, computing the sum of the encrypted
    #encoded integer 26 with itself will result in an encrypted polynomial with
    #larger coefficients: 2x^4 + 2x^3 + 2x^1. Squaring the encrypted encoded
    #integer 26 results also in increased coefficients due to cross-terms, namely,
    #
    #    (2x^4 + 2x^3 + 2x^1)^2 = 1x^8 + 2x^7 + 1x^6 + 2x^5 + 2x^4 + 1x^2;
    #
    #further computations will quickly increase the coefficients much more.
    #Decoding will still work correctly in this case (evaluating the polynomial
    #at x=2), but since the coefficients of plaintext polynomials are really
    #integers modulo plain_modulus, implicit reduction modulo plain_modulus may
    #yield unexpected results. For example, adding 1x^4 + 1x^3 + 1x^1 to itself
    #plain_modulus many times will result in the constant polynomial 0, which is
    #clearly not equal to 26 * plain_modulus. It can be difficult to predict when
    #such overflow will take place especially when computing several sequential
    #multiplications.
    #
    #The IntegerEncoder is easy to understand and use for simple computations,
    #and can be a good tool to experiment with for users new to Microsoft SEAL.
    #However, advanced users will probably prefer more efficient approaches,
    #such as the BatchEncoder or the CKKSEncoder.

    parms = EncryptionParameters(scheme_type.BFV)
    poly_modulus_degree = 4096
    parms.set_poly_modulus_degree(poly_modulus_degree)
    parms.set_coeff_modulus(CoeffModulus.BFVDefault(poly_modulus_degree))

    #There is no hidden logic behind our choice of the plain_modulus. The only
    #thing that matters is that the plaintext polynomial coefficients will not
    #exceed this value at any point during our computation; otherwise the result
    #will be incorrect.

    parms.set_plain_modulus(512)
    context = SEALContext.Create(parms)
    print_parameters(context)

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

    #We create an IntegerEncoder.
    encoder = IntegerEncoder(context)

    #First, we encode two integers as plaintext polynomials. Note that encoding
    #is not encryption: at this point nothing is encrypted.
    value1 = 5
    plain1 = encoder.encode(value1)
    print("Encode {} as polynomial {} (plain1), ".format(
        value1, plain1.to_string()))

    value2 = -7
    plain2 = encoder.encode(value2)
    print("    encode {} as polynomial {} (plain2)".format(
        value2, plain2.to_string()))

    #Now we can encrypt the plaintext polynomials.
    encrypted1 = Ciphertext()
    encrypted2 = Ciphertext()
    print("Encrypt plain1 to encrypted1 and plain2 to encrypted2.")
    encryptor.encrypt(plain1, encrypted1)
    encryptor.encrypt(plain2, encrypted2)
    print("    + Noise budget in encrypted1: {} bits".format(
        decryptor.invariant_noise_budget(encrypted1)))
    print("    + Noise budget in encrypted2: {} bits".format(
        decryptor.invariant_noise_budget(encrypted2)))

    #As a simple example, we compute (-encrypted1 + encrypted2) * encrypted2.
    encryptor.encrypt(plain2, encrypted2)
    encrypted_result = Ciphertext()
    print(
        "Compute encrypted_result = (-encrypted1 + encrypted2) * encrypted2.")
    evaluator.negate(encrypted1, encrypted_result)
    evaluator.add_inplace(encrypted_result, encrypted2)
    evaluator.multiply_inplace(encrypted_result, encrypted2)
    print("    + Noise budget in encrypted_result: {} bits".format(
        decryptor.invariant_noise_budget(encrypted_result)))
    plain_result = Plaintext()
    print("Decrypt encrypted_result to plain_result.")
    decryptor.decrypt(encrypted_result, plain_result)

    #Print the result plaintext polynomial. The coefficients are not even close
    #to exceeding our plain_modulus, 512.
    print("    + Plaintext polynomial: {}".format(plain_result.to_string()))

    #Decode to obtain an integer result.
    print("Decode plain_result.")
    print("    + Decoded integer: {} ...... Correct.".format(
        encoder.decode_int32(plain_result)))
Exemple #8
0
def example_batch_encoder():
    print("Example: Encoders / Batch Encoder")

    #[BatchEncoder] (For BFV scheme only)
    #
    #Let N denote the poly_modulus_degree and T denote the plain_modulus. Batching
    #allows the BFV plaintext polynomials to be viewed as 2-by-(N/2) matrices, with
    #each element an integer modulo T. In the matrix view, encrypted operations act
    #element-wise on encrypted matrices, allowing the user to obtain speeds-ups of
    #several orders of magnitude in fully vectorizable computations. Thus, in all
    #but the simplest computations, batching should be the preferred method to use
    #with BFV, and when used properly will result in implementations outperforming
    #anything done with the IntegerEncoder.

    parms = EncryptionParameters(scheme_type.BFV)
    poly_modulus_degree = 8192
    parms.set_poly_modulus_degree(poly_modulus_degree)
    parms.set_coeff_modulus(CoeffModulus.BFVDefault(poly_modulus_degree))

    #To enable batching, we need to set the plain_modulus to be a prime number
    #congruent to 1 modulo 2*poly_modulus_degree. Microsoft SEAL provides a helper
    #method for finding such a prime. In this example we create a 20-bit prime
    #that supports batching.
    parms.set_plain_modulus(PlainModulus.Batching(poly_modulus_degree, 20))
    context = SEALContext.Create(parms)
    print_parameters(context)

    #We can verify that batching is indeed enabled by looking at the encryption
    #parameter qualifiers created by SEALContext.
    ##HERE
    qualifiers = context.qualifiers()
    print("Batching enabled: {}".format(qualifiers.using_batching))

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

    #Batching is done through an instance of the BatchEncoder class.
    batch_encoder = BatchEncoder(context)

    #The total number of batching `slots' equals the poly_modulus_degree, N, and
    #these slots are organized into 2-by-(N/2) matrices that can be encrypted and
    #computed on. Each slot contains an integer modulo plain_modulus.
    slot_count = batch_encoder.slot_count()
    row_size = int(slot_count / 2)
    print("Plaintext matrix row size: {}".format(row_size))

    #The matrix plaintext is simply given to BatchEncoder as a flattened vector
    #of numbers. The first `row_size' many numbers form the first row, and the
    #rest form the second row. Here we create the following matrix:
    #
    #    [ 0,  1,  2,  3,  0,  0, ...,  0 ]
    #    [ 4,  5,  6,  7,  0,  0, ...,  0 ]

    pod_matrix = Int64Vector([0] * slot_count)
    pod_matrix[0] = 0
    pod_matrix[1] = 1
    pod_matrix[2] = 2
    pod_matrix[3] = 3
    pod_matrix[row_size] = 4
    pod_matrix[row_size + 1] = 5
    pod_matrix[row_size + 2] = 6
    pod_matrix[row_size + 3] = 7

    print("Input plaintext matrix:")
    print_matrix(pod_matrix, row_size)

    #First we use BatchEncoder to encode the matrix into a plaintext polynomial.
    plain_matrix = Plaintext()
    print("Encode plaintext matrix:")
    batch_encoder.encode(pod_matrix, plain_matrix)

    #We can instantly decode to verify correctness of the encoding. Note that no
    #encryption or decryption has yet taken place.
    print("    + Decode plaintext matrix ...... Correct.")
    pod_result = Int64Vector([0] * slot_count)
    batch_encoder.decode(plain_matrix, pod_result)
    print_matrix(pod_result, row_size)

    #Next we encrypt the encoded plaintext.
    encrypted_matrix = Ciphertext()
    print("Encrypt plain_matrix to encrypted_matrix.")
    encryptor.encrypt(plain_matrix, encrypted_matrix)
    print("    + Noise budget in encrypted_matrix: {} bits".format(
        decryptor.invariant_noise_budget(encrypted_matrix)))

    #Operating on the ciphertext results in homomorphic operations being performed
    #simultaneously in all 8192 slots (matrix elements). To illustrate this, we
    #form another plaintext matrix
    #
    #    [ 1,  2,  1,  2,  1,  2, ..., 2 ]
    #    [ 1,  2,  1,  2,  1,  2, ..., 2 ]
    #
    #and encode it into a plaintext.

    pod_matrix2 = UInt64Vector([1, 2] * row_size)
    plain_matrix2 = Plaintext()
    batch_encoder.encode(pod_matrix2, plain_matrix2)
    print("Second input plaintext matrix:")
    print_matrix(pod_matrix2, row_size)

    #We now add the second (plaintext) matrix to the encrypted matrix, and square
    #the sum.
    print("Sum, square, and relinearize.")
    evaluator.add_plain_inplace(encrypted_matrix, plain_matrix2)
    evaluator.square_inplace(encrypted_matrix)
    evaluator.relinearize_inplace(encrypted_matrix, relin_keys)

    #How much noise budget do we have left?
    print("    + Noise budget in result: {} bits".format(
        decryptor.invariant_noise_budget(encrypted_matrix)))

    #We decrypt and decompose the plaintext to recover the result as a matrix.
    plain_result = Plaintext()
    print("Decrypt and decode result.")
    decryptor.decrypt(encrypted_matrix, plain_result)
    batch_encoder.decode(plain_result, pod_result)
    print("    + Result plaintext matrix ...... Correct.")
    print_matrix(pod_result, row_size)