Пример #1
0
def example_batching():
    print_example_banner("Example: Batching with PolyCRTBuilder");

    parms = EncryptionParameters()
    parms.set_poly_modulus("1x^4096 + 1")
    parms.set_coeff_modulus(seal.coeff_modulus_128(4096))

    parms.set_plain_modulus(40961)

    context = SEALContext(parms)
    print_parameters(context)

    qualifiers = context.qualifiers()

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

    gal_keys = GaloisKeys()
    keygen.generate_galois_keys(30, gal_keys)

    #ev_keys = EvaluationKeys()
    #keygen.generate_evaluation_keys(30, ev_keys)

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

    crtbuilder = PolyCRTBuilder(context)

    slot_count = (int)(crtbuilder.slot_count())
    row_size = (int)(slot_count / 2)
    print("Plaintext matrix row size: " + (str)(row_size))

    def print_matrix(matrix):
        print("")
        print_size = 5

        current_line = "    ["
        for i in range(print_size):
            current_line += ((str)(matrix[i]) + ", ")
        current_line += ("..., ")
        for i in range(row_size - print_size, row_size):
            current_line += ((str)(matrix[i]))
            if i != row_size-1: current_line += ", "
            else: current_line += "]"
        print(current_line)

        current_line = "    ["
        for i in range(row_size, row_size + print_size):
            current_line += ((str)(matrix[i]) + ", ")
        current_line += ("..., ")
        for i in range(2*row_size - print_size, 2*row_size):
            current_line += ((str)(matrix[i]))
            if i != 2*row_size-1: current_line += ", "
            else: current_line += "]"
        print(current_line)
        print("")

    #     [ 0,  1,  2,  3,  0,  0, ...,  0 ]
    #     [ 4,  5,  6,  7,  0,  0, ...,  0 ]
    pod_matrix = [0]*slot_count
    pod_matrix[0] = 0
    pod_matrix[1] = 1
    pod_matrix[2] = 2
    pod_matrix[3] = 3
    pod_matrix[row_size] = 4
    pod_matrix[row_size + 1] = 5
    pod_matrix[row_size + 2] = 6
    pod_matrix[row_size + 3] = 7

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

    plain_matrix = Plaintext()
    crtbuilder.compose(pod_matrix, plain_matrix)

    encrypted_matrix = Ciphertext()
    print("Encrypting: ")
    encryptor.encrypt(plain_matrix, encrypted_matrix)
    print("Done")
    print("Noise budget in fresh encryption: " +
        (str)(decryptor.invariant_noise_budget(encrypted_matrix)) + " bits")

    pod_matrix2 = []
    for i in range(slot_count): pod_matrix2.append((i % 2) + 1)
    plain_matrix2 = Plaintext()
    crtbuilder.compose(pod_matrix2, plain_matrix2)
    print("Second input plaintext matrix:")
    print_matrix(pod_matrix2)

    print("Adding and squaring: ")
    evaluator.add_plain(encrypted_matrix, plain_matrix2)
    evaluator.square(encrypted_matrix)
    evaluator.relinearize(encrypted_matrix, ev_keys)
    print("Done")

    print("Noise budget in result: " + (str)(decryptor.invariant_noise_budget(encrypted_matrix)) + " bits")

    plain_result = Plaintext()
    print("Decrypting result: ")
    decryptor.decrypt(encrypted_matrix, plain_result)
    print("Done")

    crtbuilder.decompose(plain_result)
    pod_result = [plain_result.coeff_at(i) for i in range(plain_result.coeff_count())]

    print("Result plaintext matrix:")
    print_matrix(pod_result)

    encryptor.encrypt(plain_matrix, encrypted_matrix)
    print("Unrotated matrix: ")
    print_matrix(pod_matrix)
    print("Noise budget in fresh encryption: " +
        (str)(decryptor.invariant_noise_budget(encrypted_matrix)) + " bits")

    # Now rotate the rows to the left 3 steps, decrypt, decompose, and print.
    evaluator.rotate_rows(encrypted_matrix, 3, gal_keys)
    print("Rotated rows 3 steps left: ")
    decryptor.decrypt(encrypted_matrix, plain_result)
    crtbuilder.decompose(plain_result)
    pod_result = [plain_result.coeff_at(i) for i in range(plain_result.coeff_count())]
    print_matrix(pod_result)
    print("Noise budget after rotation" +
        (str)(decryptor.invariant_noise_budget(encrypted_matrix)) + " bits")

    # Rotate columns (swap rows), decrypt, decompose, and print.
    evaluator.rotate_columns(encrypted_matrix, gal_keys)
    print("Rotated columns: ")
    decryptor.decrypt(encrypted_matrix, plain_result)
    crtbuilder.decompose(plain_result)
    pod_result = [plain_result.coeff_at(i) for i in range(plain_result.coeff_count())]
    print_matrix(pod_result)
    print("Noise budget after rotation: " +
        (str)(decryptor.invariant_noise_budget(encrypted_matrix)) + " bits")

    # Rotate rows to the right 4 steps, decrypt, decompose, and print.
    evaluator.rotate_rows(encrypted_matrix, -4, gal_keys)
    print("Rotated rows 4 steps right: ")
    decryptor.decrypt(encrypted_matrix, plain_result)
    crtbuilder.decompose(plain_result)
    pod_result = [plain_result.coeff_at(i) for i in range(plain_result.coeff_count())]
    print_matrix(pod_result)
    print("Noise budget after rotation: " +
        (str)(decryptor.invariant_noise_budget(encrypted_matrix)) + " bits")
Пример #2
0
def example_basics_ii():
    print_example_banner("Example: Basics II")

    # In this example we explain what relinearization is, how to use it, and how
    # it affects noise budget consumption.

    # First we set the parameters, create a SEALContext, and generate the public
    # and secret keys. We use slightly larger parameters than be fore to be able
    # to do more homomorphic multiplications.
    parms = EncryptionParameters()
    parms.set_poly_modulus("1x^8192 + 1")

    # The default coefficient modulus consists of the following primes:

    #     0x7fffffffba0001,
    #     0x7fffffffaa0001,
    #     0x7fffffff7e0001,
    #     0x3fffffffd60001.

    # The total size is 219 bits.
    parms.set_coeff_modulus(seal.coeff_modulus_128(8192))
    parms.set_plain_modulus(1 << 10)

    context = SEALContext(parms)
    print_parameters(context)

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

    # We also set up an Encryptor, Evaluator, and Decryptor here. We will
    # encrypt polynomials directly in this example, so there is no need for
    # an encoder.
    encryptor = Encryptor(context, public_key)
    evaluator = Evaluator(context)
    decryptor = Decryptor(context, secret_key)

    # There are actually two more types of keys in SEAL: `evaluation keys' and
    # `Galois keys'. Here we will discuss evaluation keys, and Galois keys will
    # be discussed later in example_batching().

    # In SEAL, a valid ciphertext consists of two or more polynomials with
    # coefficients integers modulo the product of the primes in coeff_modulus.
    # The current size of a ciphertext can be found using Ciphertext::size().
    # A freshly encrypted ciphertext always has size 2.
    #plain1 = Plaintext("1x^2 + 2x^1 + 3")
    plain1 = Plaintext("1x^2 + 2x^1 + 3")
    encrypted = Ciphertext()

    print("")
    print("Encrypting " + plain1.to_string() + ": ")
    encryptor.encrypt(plain1, encrypted)
    print("Done")
    print("Size of a fresh encryption: " + (str)(encrypted.size()))
    print("Noise budget in fresh encryption: " +
          (str)(decryptor.invariant_noise_budget(encrypted)) + " bits")

    # Homomorphic multiplication results in the output ciphertext growing in size.
    # More precisely, if the input ciphertexts have size M and N, then the output
    # ciphertext after homomorphic multiplication will have size M+N-1. In this
    # case we square encrypted twice to observe this growth (also observe noise
    # budget consumption).
    evaluator.square(encrypted)
    print("Size after squaring: " + (str)(encrypted.size()))
    print("Noise budget after squaring: " +
          (str)(decryptor.invariant_noise_budget(encrypted)) + " bits")
    plain2 = Plaintext()
    decryptor.decrypt(encrypted, plain2)
    print("Second power: " + plain2.to_string())

    evaluator.square(encrypted)
    print("Size after squaring: " + (str)(encrypted.size()))
    print("Noise budget after squaring: " +
          (str)(decryptor.invariant_noise_budget(encrypted)) + " bits")

    # It does not matter that the size has grown -- decryption works as usual.
    # Observe from the print-out that the coefficients in the plaintext have
    # grown quite large. One more squaring would cause some of them to wrap
    # around plain_modulus (0x400), and as a result we would no longer obtain
    # the expected result as an integer-coefficient polynomial. We can fix this
    # problem to some extent by increasing plain_modulus. This would make sense,
    # since we still have plenty of noise budget left.
    plain2 = Plaintext()
    decryptor.decrypt(encrypted, plain2)
    print("Fourth power: " + plain2.to_string())

    # The problem here is that homomorphic operations on large ciphertexts are
    # computationally much more costly than on small ciphertexts. Specifically,
    # homomorphic multiplication on input ciphertexts of size M and N will require
    # O(M*N) polynomial multiplications to be performed, and an addition will
    # require O(M+N) additions. Relinearization reduces the size of the ciphertexts
    # after multiplication back to the initial size (2). Thus, relinearizing one
    # or both inputs before the next multiplication, or e.g. before serializing the
    # ciphertexts, can have a huge positive impact on performance.

    # Another problem is that the noise budget consumption in multiplication is
    # bigger when the input ciphertexts sizes are bigger. In a complicated
    # computation the contribution of the sizes to the noise budget consumption
    # can actually become the dominant term. We will point this out again below
    # once we get to our example.

    # Relinearization itself has both a computational cost and a noise budget cost.
    # These both depend on a parameter called `decomposition bit count', which can
    # be any integer at least 1 [dbc_min()] and at most 60 [dbc_max()]. A large
    # decomposition bit count makes relinearization fast, but consumes more noise
    # budget. A small decomposition bit count can make relinearization slower, but
    # might not change the noise budget by any observable amount.

    # Relinearization requires a special type of key called `evaluation keys'.
    # These can be created by the KeyGenerator for any decomposition bit count.
    # To relinearize a ciphertext of size M >= 2 back to size 2, we actually need
    # M-2 evaluation keys. Attempting to relinearize a too large ciphertext with
    # too few evaluation keys will result in an exception being thrown.

    # We repeat our computation, but this time relinearize after both squarings.
    # Since our ciphertext never grows past size 3 (we relinearize after every
    # multiplication), it suffices to generate only one evaluation key.

    # First, we need to create evaluation keys. We use a decomposition bit count
    # of 16 here, which can be thought of as quite small.
    ev_keys16 = EvaluationKeys()

    # This function generates one single evaluation key. Another overload takes
    # the number of keys to be generated as an argument, but one is all we need
    # in this example (see above).
    keygen.generate_evaluation_keys(16, ev_keys16)

    print("")
    print("Encrypting " + plain1.to_string() + ": ")
    encryptor.encrypt(plain1, encrypted)
    print("Done")
    print("Size of a fresh encryption: " + (str)(encrypted.size()))
    print("Noise budget in fresh encryption: " +
          (str)(decryptor.invariant_noise_budget(encrypted)) + " bits")

    evaluator.square(encrypted)
    print("Size after squaring: " + (str)(encrypted.size()))
    print("Noise budget after squaring: " +
          (str)(decryptor.invariant_noise_budget(encrypted)) + " bits")

    evaluator.relinearize(encrypted, ev_keys16)
    print("Size after relinearization: " + (str)(encrypted.size()))
    print("Noise budget after relinearizing (dbs = " +
          (str)(ev_keys16.decomposition_bit_count()) + "): " +
          (str)(decryptor.invariant_noise_budget(encrypted)) + " bits")

    evaluator.square(encrypted)
    print("Size after second squaring: " + (str)(encrypted.size()) + " bits")
    print("Noise budget after second squaring: " +
          (str)(decryptor.invariant_noise_budget(encrypted)) + " bits")

    evaluator.relinearize(encrypted, ev_keys16)
    print("Size after relinearization: " + (str)(encrypted.size()))
    print("Noise budget after relinearizing (dbs = " +
          (str)(ev_keys16.decomposition_bit_count()) + "): " +
          (str)(decryptor.invariant_noise_budget(encrypted)) + " bits")

    decryptor.decrypt(encrypted, plain2)
    print("Fourth power: " + plain2.to_string())

    # Of course the result is still the same, but this time we actually
    # used less of our noise budget. This is not surprising for two reasons:

    #     - We used a very small decomposition bit count, which is why
    #       relinearization itself did not consume the noise budget by any
    #       observable amount;
    #     - Since our ciphertext sizes remain small throughout the two
    #       squarings, the noise budget consumption rate in multiplication
    #       remains as small as possible. Recall from above that operations
    #       on larger ciphertexts actually cause more noise growth.

    # To make matters even more clear, we repeat the computation a third time,
    # now using the largest possible decomposition bit count (60). We are not
    # measuring the time here, but relinearization with these evaluation keys
    # is significantly faster than with ev_keys16.
    ev_keys60 = EvaluationKeys()
    keygen.generate_evaluation_keys(seal.dbc_max(), ev_keys60)

    print("")
    print("Encrypting " + plain1.to_string() + ": ")
    encryptor.encrypt(plain1, encrypted)
    print("Done")
    print("Size of a fresh encryption: " + (str)(encrypted.size()))
    print("Noise budget in fresh encryption: " +
          (str)(decryptor.invariant_noise_budget(encrypted)) + " bits")

    evaluator.square(encrypted)
    print("Size after squaring: " + (str)(encrypted.size()))
    print("Noise budget after squaring: " +
          (str)(decryptor.invariant_noise_budget(encrypted)) + " bits")
    evaluator.relinearize(encrypted, ev_keys60)
    print("Size after relinearization: " + (str)(encrypted.size()))
    print("Noise budget after relinearizing (dbc = " +
          (str)(ev_keys60.decomposition_bit_count()) + "): " +
          (str)(decryptor.invariant_noise_budget(encrypted)) + " bits")

    evaluator.square(encrypted)
    print("Size after second squaring: " + (str)(encrypted.size()))
    print("Noise budget after second squaring: " +
          (str)(decryptor.invariant_noise_budget) + " bits")
    evaluator.relinearize(encrypted, ev_keys60)
    print("Size after relinearization: " + (str)(encrypted.size()))
    print("Noise budget after relinearizing (dbc = " +
          (str)(ev_keys60.decomposition_bit_count()) + "): " +
          (str)(decryptor.invariant_noise_budget(encrypted)) + " bits")

    decryptor.decrypt(encrypted, plain2)
    print("Fourth power: " + plain2.to_string())

    # Observe from the print-out that we have now used significantly more of our
    # noise budget than in the two previous runs. This is again not surprising,
    # since the first relinearization chops off a huge part of the noise budget.

    # However, note that the second relinearization does not change the noise
    # budget by any observable amount. This is very important to understand when
    # optimal performance is desired: relinearization always drops the noise
    # budget from the maximum (freshly encrypted ciphertext) down to a fixed
    # amount depending on the encryption parameters and the decomposition bit
    # count. On the other hand, homomorphic multiplication always consumes the
    # noise budget from its current level. This is why the second relinearization
    # does not change the noise budget anymore: it is already consumed past the
    # fixed amount determinted by the decomposition bit count and the encryption
    # parameters.

    # We now perform a third squaring and observe an even further compounded
    # decrease in the noise budget. Again, relinearization does not consume the
    # noise budget at this point by any observable amount, even with the largest
    # possible decomposition bit count.
    evaluator.square(encrypted)
    print("Size after third squaring " + (str)(encrypted.size()))
    print("Noise budget after third squaring: " +
          (str)(decryptor.invariant_noise_budget(encrypted)) + " bits")
    evaluator.relinearize(encrypted, ev_keys60)
    print("Size after relinearization: " + (str)(encrypted.size()))
    print("Noise budget after relinearizing (dbc = " +
          (str)(ev_keys60.decomposition_bit_count()) + "): " +
          (str)(decryptor.invariant_noise_budget(encrypted)) + " bits")

    decryptor.decrypt(encrypted, plain2)
    print("Eighth power: " + plain2.to_string())