Ejemplo n.º 1
0
def seal_obj():
    # params obj
    params = EncryptionParameters()
    # set params
    params.set_poly_modulus("1x^4096 + 1")
    params.set_coeff_modulus(seal.coeff_modulus_128(4096))
    params.set_plain_modulus(1 << 16)
    # get context
    context = SEALContext(params)
    # get evaluator
    evaluator = Evaluator(context)
    # gen keys
    keygen = KeyGenerator(context)
    public_key = keygen.public_key()
    private_key = keygen.secret_key()
    # evaluator keys
    ev_keys = EvaluationKeys()
    keygen.generate_evaluation_keys(30, ev_keys)
    # get encryptor and decryptor
    encryptor = Encryptor(context, public_key)
    decryptor = Decryptor(context, private_key)
    # float number encoder
    encoder = FractionalEncoder(context.plain_modulus(),
                                context.poly_modulus(), 64, 32, 3)
    return evaluator, encoder.encode, encoder.decode, encryptor.encrypt, decryptor.decrypt, ev_keys
Ejemplo n.º 2
0
    def _build_context(self, config):

        #set up encryption parameters and context
        parms = EncryptionParameters()
        parms.set_poly_modulus(config['poly_modulus'])
        parms.set_coeff_modulus(seal.coeff_modulus_128(
            config['coeff_modulus']))
        parms.set_plain_modulus(1 << 18)
        context = SEALContext(parms)

        return context
Ejemplo n.º 3
0
def example_rotation_ckks():
    print("Example: Rotation / Rotation in CKKS");

    #Rotations in the CKKS scheme work very similarly to rotations in BFV.
    parms = EncryptionParameters(scheme_type.CKKS)

    poly_modulus_degree = 8192
    parms.set_poly_modulus_degree(poly_modulus_degree)
    parms.set_coeff_modulus(CoeffModulus.Create(
        poly_modulus_degree, IntVector([40, 40, 40, 40, 40])))

    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()
    gal_keys = keygen.galois_keys()
    encryptor = Encryptor(context, public_key)
    evaluator = Evaluator(context)
    decryptor = Decryptor(context, secret_key)

    ckks_encoder = CKKSEncoder(context)

    slot_count = ckks_encoder.slot_count()
    print("Number of slots: {}".format(slot_count))

    step_size = 1.0 / (slot_count - 1)
    input = DoubleVector(list(map(lambda x: x*step_size, range(0, slot_count))))

    print_vector(input)

    scale = 2.0**50

    print("Encode and encrypt.")
    plain = Plaintext() 
    ckks_encoder.encode(input, scale, plain)
    encrypted = Ciphertext() 
    encryptor.encrypt(plain, encrypted)

    rotated = Ciphertext()  
    print("Rotate 2 steps left.")
    evaluator.rotate_vector(encrypted, 2, gal_keys, rotated)
    print("    + Decrypt and decode ...... Correct.")
    decryptor.decrypt(rotated, plain)
    result = DoubleVector()
    ckks_encoder.decode(plain, result)
    print_vector(result)
Ejemplo n.º 4
0
    def __init__(self):

        # set parameters for encryption
        parms = EncryptionParameters()
        parms.set_poly_modulus("1x^2048 + 1")
        parms.set_coeff_modulus(seal.coeff_modulus_128(2048))
        parms.set_plain_modulus(1 << 8)
       
        self.context = SEALContext(parms)
        keygen = KeyGenerator(self.context)
        self.encoder = IntegerEncoder(self.context.plain_modulus())
      
        public_key = keygen.public_key()
        self.encryptor = Encryptor(self.context, public_key)
      
        secret_key = keygen.secret_key()
        self.decryptor = Decryptor(self.context, secret_key)
Ejemplo n.º 5
0
def config():
    # PySEAL wrapper for the SEAL library is used.
    # SEAL implements somewhat FHE algorithmic solutions =>
    # each operation has a limit - 'invariant noise budget' in bits. Operations consume the noise budget
    # at a rate determined py the encryption parameters. Additions free of noise budget consumption, multiplications are not
    # Noise budget consumption is getting worse in sequential multiplications => multiplicative depth of the arithmetic circuit that needs to be evaluated.
    # Noise budget in a ciphertext -> 0 => ciphertext too corrupted to be decrypted => large enough parameters to be eble to restore the result
    #############
    # noise_budget = log2(coeff_modulus/plain_modulus) bits (in a freshly encrypted ciphertext)
    #############

    #############
    # noise_budget_cnsumption = log2(plain_modulus) + (other terms)
    #############
    params = EncryptionParameters()

    # set the polynomial modulus. (1x^(power of 2) +1) - power of 2 cyclotomic polynomial
    # affects the security of the scheme
    # larger more secure and larger ciphertext size, computation slower
    # from 1024 to 32768
    ##params.set_poly_modulus("1x^1024 + 1")

    params.set_poly_modulus("1x^2048 + 1")
    # coefficient modulus determines the noise budget of the ciphertext
    # the bigger the more the budget and lower security -> increase the polynomial modulus
    # choosing parameters for polynomial modulus http://HomomorphicEncryption.org
    ###params.set_coeff_modulus(seal.coeff_modulus_128(8192))
    params.set_coeff_modulus(
        seal.coeff_modulus_128(2048))  #plain_modulus = k*4096+1
    #~ log2(coeff_modulus/plain_modulus) (bits)
    # plaintext modulus determines the size of the plaintext datatype, affects the noise budget in multiplication => keep the plaintext data type as small as possible
    #65537
    params.set_plain_modulus(12289)
    #####For batching
    ###params.set_plain_modulus(65537)
    #params.set_plain_modulus(2049)
    #params.set_plain_modulus(8192)

    # check the validity of the parameters set, performs and stores several important pre-computations
    context = SEALContext(params)

    # print the chosen parameters
    #performance_test_st(context)

    return context, params
Ejemplo n.º 6
0
def initialize_encryption():
	print_example_banner("Example: Basics I");
	parms = EncryptionParameters()
	parms.set_poly_modulus("1x^2048 + 1")
	# factor: 0xfffffffff00001.
	parms.set_coeff_modulus(seal.coeff_modulus_128(2048))
	parms.set_plain_modulus(1 << 8)
	context = SEALContext(parms)
	print_parameters(context);
	# Here we choose to create an IntegerEncoder with base b=2.
	encoder = IntegerEncoder(context.plain_modulus())
	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)
	return encryptor, evaluator, decryptor, encoder, context
Ejemplo n.º 7
0
def do_per_amount(amount, subtract_from=15):
    """
	Called on every message in the stream
	"""
    print("Transaction amount ", amount)
    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)

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

    # 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()

    encryptor = Encryptor(context, public_key)

    plain1 = encoder.encode(amount)
    encoded2 = encoder.encode(subtract_from)

    # Encrypt
    encrypted1 = Ciphertext(parms)
    encryptor.encrypt(plain1, encrypted1)

    # Evaluate
    evaluator = Evaluator(context)
    evaluated = evaluate_subtraction_from_plain(evaluator, encrypted1,
                                                encoded2)

    # Decrypt and decode
    decryptor = Decryptor(context, secret_key)
    plain_result = Plaintext()
    decryptor.decrypt(evaluated, plain_result)
    result = encoder.decode(plain_result)

    str_result = "Amount left = " + str(result)
    print(str_result)
    return str_result
Ejemplo n.º 8
0
	def __init__(self, poly_modulus = 2048 ,bit_strength = 128 ,plain_modulus = 1<<8, integral_coeffs = 64, fractional_coeffs = 32, fractional_base = 3):
		parms = EncryptionParameters()
		parms.set_poly_modulus("1x^{} + 1".format(poly_modulus))

		if (bit_strength == 128):
			parms.set_coeff_modulus(seal.coeff_modulus_128(poly_modulus))
		else:
			parms.set_coeff_modulus(seal.coeff_modulus_192(poly_modulus))
		parms.set_plain_modulus(plain_modulus)

		self.parms = parms
		context = SEALContext(parms)

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

		self.encryptor = Encryptor(context, public_key)
		self.evaluator = Evaluator(context)
		self.decryptor = Decryptor(context, secret_key)
		self.encoder = FractionalEncoder(context.plain_modulus(), context.poly_modulus(), integral_coeffs, fractional_coeffs, fractional_base)
Ejemplo n.º 9
0
    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)
Ejemplo n.º 10
0
def initialize_fractional(
        poly_modulus_degree=4096,
        security_level_bits=128,
        plain_modulus_power_of_two=10,
        plain_modulus=None,
        encoder_integral_coefficients=1024,
        encoder_fractional_coefficients=3072,
        encoder_base=2
):
    parameters = EncryptionParameters()

    poly_modulus = "1x^" + str(poly_modulus_degree) + " + 1"
    parameters.set_poly_modulus(poly_modulus)

    if security_level_bits == 128:
        parameters.set_coeff_modulus(seal.coeff_modulus_128(poly_modulus_degree))
    elif security_level_bits == 192:
        parameters.set_coeff_modulus(seal.coeff_modulus_192(poly_modulus_degree))
    else:
        parameters.set_coeff_modulus(seal.coeff_modulus_128(poly_modulus_degree))
        print("Info: security_level_bits unknown - using default security_level_bits = 128")

    if plain_modulus is None:
        plain_modulus = 1 << plain_modulus_power_of_two

    parameters.set_plain_modulus(plain_modulus)

    context = SEALContext(parameters)

    print_parameters(context)

    global encoder
    encoder = FractionalEncoder(
        context.plain_modulus(),
        context.poly_modulus(),
        encoder_integral_coefficients,
        encoder_fractional_coefficients,
        encoder_base
    )

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

    global encryptor
    encryptor = Encryptor(context, public_key)

    global evaluator
    evaluator = Evaluator(context)

    global decryptor
    decryptor = Decryptor(context, secret_key)

    global evaluation_keys
    evaluation_keys = EvaluationKeys()

    keygen.generate_evaluation_keys(16, evaluation_keys)
Ejemplo n.º 11
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())
Ejemplo n.º 12
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)))
Ejemplo n.º 13
0
			if M[rowIndex][colIndex]==None:
				M[rowIndex][colIndex]=Ciphertext(M[colIndex][rowIndex])

	print_plain(M)



if __name__ == '__main__':

	multiprocessing.freeze_support()

	########################## paramaters required #################################

	parms = EncryptionParameters()
	parms.set_poly_modulus("1x^16384 + 1")
	parms.set_coeff_modulus(seal.coeff_modulus_128(8192))
	parms.set_plain_modulus(1 << 25)
	context = SEALContext(parms)

	encoderF = FractionalEncoder(context.plain_modulus(), context.poly_modulus(), 34, 30, 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)

	num_cores = multiprocessing.cpu_count() -1

	N=4
Ejemplo n.º 14
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)
if __name__ == '__main__':
    comm = MPI.COMM_WORLD
    rank = comm.Get_rank()
    nworkers = comm.Get_size() - 1
    workers = list(range(1, nworkers + 1))
    start_time = time.time()
    print("Ids of workers: ", str(workers))
    print("Number of workers: ", str(nworkers))
    ## Generate encryption parameters at worker 1 and share them with all workers.
    # Share context object with the parameter server, so that it can aggregate encrypted values.
    if rank == 1:
        # Initialize the parameters used for FHE
        parms = EncryptionParameters()
        parms.set_poly_modulus("1x^4096 + 1")
        parms.set_coeff_modulus(
            seal.coeff_modulus_128(4096))  # 128-bit security
        parms.set_plain_modulus(40961)
        context = SEALContext(parms)
        comm.send(context, dest=0,
                  tag=0)  # send the context to the parameter server
        print("Sent context to server")
        for i in workers[1:]:
            print("Sent context to worker ", str(i))
            comm.send(context, dest=i,
                      tag=0)  # send the context to the other workers

        keygen = KeyGenerator(context)
        public_key = keygen.public_key()
        secret_key = keygen.secret_key()
        for i in workers[1:]:
            print("Sending keys to worker ", str(i))
Ejemplo n.º 16
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()))
Ejemplo n.º 17
0
def example_ckks_basics():
    print("Example: CKKS Basics");

    #In this example we demonstrate evaluating a polynomial function
    #
    #    PI*x^3 + 0.4*x + 1
    #
    #on encrypted floating-point input data x for a set of 4096 equidistant points
    #in the interval [0, 1]. This example demonstrates many of the main features
    #of the CKKS scheme, but also the challenges in using it.
    #
    # We start by setting up the CKKS scheme.

    parms = EncryptionParameters(scheme_type.CKKS)

    #We saw in `2_encoders.cpp' that multiplication in CKKS causes scales
    #in ciphertexts to grow. The scale of any ciphertext must not get too close
    #to the total size of coeff_modulus, or else the ciphertext simply runs out of
    #room to store the scaled-up plaintext. The CKKS scheme provides a `rescale'
    #functionality that can reduce the scale, and stabilize the scale expansion.
    #
    #Rescaling is a kind of modulus switch operation (recall `3_levels.cpp').
    #As modulus switching, it removes the last of the primes from coeff_modulus,
    #but as a side-effect it scales down the ciphertext by the removed prime.
    #Usually we want to have perfect control over how the scales are changed,
    #which is why for the CKKS scheme it is more common to use carefully selected
    #primes for the coeff_modulus.
    #
    #More precisely, suppose that the scale in a CKKS ciphertext is S, and the
    #last prime in the current coeff_modulus (for the ciphertext) is P. Rescaling
    #to the next level changes the scale to S/P, and removes the prime P from the
    #coeff_modulus, as usual in modulus switching. The number of primes limits
    #how many rescalings can be done, and thus limits the multiplicative depth of
    #the computation.
    #
    #It is possible to choose the initial scale freely. One good strategy can be
    #to is to set the initial scale S and primes P_i in the coeff_modulus to be
    #very close to each other. If ciphertexts have scale S before multiplication,
    #they have scale S^2 after multiplication, and S^2/P_i after rescaling. If all
    #P_i are close to S, then S^2/P_i is close to S again. This way we stabilize the
    #scales to be close to S throughout the computation. Generally, for a circuit
    #of depth D, we need to rescale D times, i.e., we need to be able to remove D
    #primes from the coefficient modulus. Once we have only one prime left in the
    #coeff_modulus, the remaining prime must be larger than S by a few bits to
    #preserve the pre-decimal-point value of the plaintext.
    #
    #Therefore, a generally good strategy is to choose parameters for the CKKS
    #scheme as follows: 
    #
    #    (1) Choose a 60-bit prime as the first prime in coeff_modulus. This will
    #        give the highest precision when decrypting;
    #    (2) Choose another 60-bit prime as the last element of coeff_modulus, as
    #        this will be used as the special prime and should be as large as the
    #        largest of the other primes;
    #    (3) Choose the intermediate primes to be close to each other.
    #
    #We use CoeffModulus::Create to generate primes of the appropriate size. Note
    #that our coeff_modulus is 200 bits total, which is below the bound for our
    #poly_modulus_degree: CoeffModulus::MaxBitCount(8192) returns 218.

    poly_modulus_degree = 8192
    parms.set_poly_modulus_degree(poly_modulus_degree)
    parms.set_coeff_modulus(CoeffModulus.Create(
        poly_modulus_degree, IntVector([60, 40, 40, 60])))

    #We choose the initial scale to be 2^40. At the last level, this leaves us
    #60-40=20 bits of precision before the decimal point, and enough (roughly
    #10-20 bits) of precision after the decimal point. Since our intermediate
    #primes are 40 bits (in fact, they are very close to 2^40), we can achieve
    #scale stabilization as described above.

    scale = 2.0**40

    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)

    encoder = CKKSEncoder(context)
    slot_count = encoder.slot_count()
    print("Number of slots: {}".format(slot_count))

    step_size = 1.0 / (slot_count - 1)
    input = DoubleVector(list(map(lambda x: x*step_size, range(0, slot_count))))

    print("Input vector: ")
    print_vector(input)

    print("Evaluating polynomial PI*x^3 + 0.4x + 1 ...")

    #We create plaintexts for PI, 0.4, and 1 using an overload of CKKSEncoder::encode
    #that encodes the given floating-point value to every slot in the vector.

    plain_coeff3 = Plaintext()
    plain_coeff1 = Plaintext()
    plain_coeff0 = Plaintext()
    encoder.encode(3.14159265, scale, plain_coeff3)
    encoder.encode(0.4, scale, plain_coeff1)
    encoder.encode(1.0, scale, plain_coeff0)

    x_plain = Plaintext()
    print("Encode input vectors.")
    encoder.encode(input, scale, x_plain)
    x1_encrypted = Ciphertext() 
    encryptor.encrypt(x_plain, x1_encrypted)

    #To compute x^3 we first compute x^2 and relinearize. However, the scale has
    #now grown to 2^80.

    x3_encrypted = Ciphertext() 
    print("Compute x^2 and relinearize:")
    evaluator.square(x1_encrypted, x3_encrypted)
    evaluator.relinearize_inplace(x3_encrypted, relin_keys)
    print("    + Scale of x^2 before rescale: {} bits".format(log2(x3_encrypted.scale())))

    #Now rescale; in addition to a modulus switch, the scale is reduced down by
    #a factor equal to the prime that was switched away (40-bit prime). Hence, the
    #new scale should be close to 2^40. Note, however, that the scale is not equal
    #to 2^40: this is because the 40-bit prime is only close to 2^40.
    print("Rescale x^2.")
    evaluator.rescale_to_next_inplace(x3_encrypted)
    print("    + Scale of x^2 after rescale: {} bits".format(log2(x3_encrypted.scale())))

    #Now x3_encrypted is at a different level than x1_encrypted, which prevents us
    #from multiplying them to compute x^3. We could simply switch x1_encrypted to
    #the next parameters in the modulus switching chain. However, since we still
    #need to multiply the x^3 term with PI (plain_coeff3), we instead compute PI*x
    #first and multiply that with x^2 to obtain PI*x^3. To this end, we compute
    #PI*x and rescale it back from scale 2^80 to something close to 2^40.

    print("Compute and rescale PI*x.")
    x1_encrypted_coeff3 = Ciphertext() 
    evaluator.multiply_plain(x1_encrypted, plain_coeff3, x1_encrypted_coeff3)
    print("    + Scale of PI*x before rescale: {} bits".format(log2(x1_encrypted_coeff3.scale())))
    evaluator.rescale_to_next_inplace(x1_encrypted_coeff3)
    print("    + Scale of PI*x after rescale: {} bits".format(log2(x1_encrypted_coeff3.scale())))

    #Since x3_encrypted and x1_encrypted_coeff3 have the same exact scale and use
    #the same encryption parameters, we can multiply them together. We write the
    #result to x3_encrypted, relinearize, and rescale. Note that again the scale
    #is something close to 2^40, but not exactly 2^40 due to yet another scaling
    #by a prime. We are down to the last level in the modulus switching chain.

    print("Compute, relinearize, and rescale (PI*x)*x^2.")
    evaluator.multiply_inplace(x3_encrypted, x1_encrypted_coeff3)
    evaluator.relinearize_inplace(x3_encrypted, relin_keys)
    print("    + Scale of PI*x^3 before rescale: {} bits".format(log2(x3_encrypted.scale())))

    evaluator.rescale_to_next_inplace(x3_encrypted)
    print("    + Scale of PI*x^3 after rescale: {} bits".format(log2(x3_encrypted.scale())))

    #Next we compute the degree one term. All this requires is one multiply_plain
    #with plain_coeff1. We overwrite x1_encrypted with the result.

    print("Compute and rescale 0.4*x.")
    evaluator.multiply_plain_inplace(x1_encrypted, plain_coeff1)
    print("    + Scale of 0.4*x before rescale: {} bits".format(log2(x1_encrypted.scale())))
    evaluator.rescale_to_next_inplace(x1_encrypted)
    print("    + Scale of 0.4*x after rescale: {} bits".format(log2(x1_encrypted.scale())))

    #Now we would hope to compute the sum of all three terms. However, there is
    #a serious problem: the encryption parameters used by all three terms are
    #different due to modulus switching from rescaling.
    #
    #Encrypted addition and subtraction require that the scales of the inputs are
    #the same, and also that the encryption parameters (parms_id) match. If there
    #is a mismatch, Evaluator will throw an exception.

    print("Parameters used by all three terms are different.")
    print("    + Modulus chain index for x3_encrypted: {}".format(
        context.get_context_data(x3_encrypted.parms_id()).chain_index()))
    print("    + Modulus chain index for x1_encrypted: {}".format(
        context.get_context_data(x1_encrypted.parms_id()).chain_index()))
    print("    + Modulus chain index for plain_coeff0: {}".format(
        context.get_context_data(plain_coeff0.parms_id()).chain_index()))

    #Let us carefully consider what the scales are at this point. We denote the
    #primes in coeff_modulus as P_0, P_1, P_2, P_3, in this order. P_3 is used as
    #the special modulus and is not involved in rescalings. After the computations
    #above the scales in ciphertexts are:
    #
    #    - Product x^2 has scale 2^80 and is at level 2;
    #    - Product PI*x has scale 2^80 and is at level 2;
    #    - We rescaled both down to scale 2^80/P_2 and level 1;
    #    - Product PI*x^3 has scale (2^80/P_2)^2;
    #    - We rescaled it down to scale (2^80/P_2)^2/P_1 and level 0;
    #    - Product 0.4*x has scale 2^80;
    #    - We rescaled it down to scale 2^80/P_2 and level 1;
    #    - The contant term 1 has scale 2^40 and is at level 2.
    #
    #Although the scales of all three terms are approximately 2^40, their exact
    #values are different, hence they cannot be added together.

    print("The exact scales of all three terms are different:")
    print("    + Exact scale in PI*x^3: {0:0.10f}".format(x3_encrypted.scale()))
    print("    + Exact scale in  0.4*x: {0:0.10f}".format(x1_encrypted.scale()))
    print("    + Exact scale in      1: {0:0.10f}".format(plain_coeff0.scale()))

    #There are many ways to fix this problem. Since P_2 and P_1 are really close
    #to 2^40, we can simply "lie" to Microsoft SEAL and set the scales to be the
    #same. For example, changing the scale of PI*x^3 to 2^40 simply means that we
    #scale the value of PI*x^3 by 2^120/(P_2^2*P_1), which is very close to 1.
    #This should not result in any noticeable error.
    #
    #Another option would be to encode 1 with scale 2^80/P_2, do a multiply_plain
    #with 0.4*x, and finally rescale. In this case we would need to additionally
    #make sure to encode 1 with appropriate encryption parameters (parms_id).
    #
    #In this example we will use the first (simplest) approach and simply change
    #the scale of PI*x^3 and 0.4*x to 2^40.
    print("Normalize scales to 2^40.")
    x3_encrypted.set_scale(2.0**40)
    x1_encrypted.set_scale(2.0**40)

    #We still have a problem with mismatching encryption parameters. This is easy
    #to fix by using traditional modulus switching (no rescaling). CKKS supports
    #modulus switching just like the BFV scheme, allowing us to switch away parts
    #of the coefficient modulus when it is simply not needed.

    print("Normalize encryption parameters to the lowest level.")
    last_parms_id = x3_encrypted.parms_id()
    evaluator.mod_switch_to_inplace(x1_encrypted, last_parms_id)
    evaluator.mod_switch_to_inplace(plain_coeff0, last_parms_id)

    #All three ciphertexts are now compatible and can be added.

    print("Compute PI*x^3 + 0.4*x + 1.")
    encrypted_result = Ciphertext()
    evaluator.add(x3_encrypted, x1_encrypted, encrypted_result)
    evaluator.add_plain_inplace(encrypted_result, plain_coeff0)

    #First print the true result.

    plain_result = Plaintext() 
    print("Decrypt and decode PI*x^3 + 0.4x + 1.")
    print("    + Expected result:")
    true_result = DoubleVector(list(map(lambda x: (3.14159265 * x * x + 0.4)* x + 1, input)))
    print_vector(true_result)

    #Decrypt, decode, and print the result.
    decryptor.decrypt(encrypted_result, plain_result)
    result = DoubleVector()
    encoder.decode(plain_result, result)
    print("    + Computed result ...... Correct.")
    print_vector(result)
Ejemplo n.º 18
0
def example_ckks_encoder():
    print("Example: Encoders / CKKS Encoder")

    #[CKKSEncoder] (For CKKS scheme only)
    #
    #In this example we demonstrate the Cheon-Kim-Kim-Song (CKKS) scheme for
    #computing on encrypted real or complex numbers. We start by creating
    #encryption parameters for the CKKS scheme. There are two important
    #differences compared to the BFV scheme:
    #
    #    (1) CKKS does not use the plain_modulus encryption parameter;
    #    (2) Selecting the coeff_modulus in a specific way can be very important
    #        when using the CKKS scheme. We will explain this further in the file
    #        `ckks_basics.cpp'. In this example we use CoeffModulus::Create to
    #        generate 5 40-bit prime numbers.

    parms = EncryptionParameters(scheme_type.CKKS)

    poly_modulus_degree = 8192
    parms.set_poly_modulus_degree(poly_modulus_degree)
    parms.set_coeff_modulus(
        CoeffModulus.Create(poly_modulus_degree,
                            IntVector([40, 40, 40, 40, 40])))

    #We create the SEALContext as usual and print the parameters.
    context = SEALContext.Create(parms)
    print_parameters(context)

    #Keys are created the same way as for the BFV scheme.
    keygen = KeyGenerator(context)
    public_key = keygen.public_key()
    secret_key = keygen.secret_key()
    relin_keys = keygen.relin_keys()

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

    #To create CKKS plaintexts we need a special encoder: there is no other way
    #to create them. The IntegerEncoder and BatchEncoder cannot be used with the
    #CKKS scheme. The CKKSEncoder encodes vectors of real or complex numbers into
    #Plaintext objects, which can subsequently be encrypted. At a high level this
    #looks a lot like what BatchEncoder does for the BFV scheme, but the theory
    #behind it is completely different.

    encoder = CKKSEncoder(context)

    #In CKKS the number of slots is poly_modulus_degree / 2 and each slot encodes
    #one real or complex number. This should be contrasted with BatchEncoder in
    #the BFV scheme, where the number of slots is equal to poly_modulus_degree
    #and they are arranged into a matrix with two rows.

    slot_count = encoder.slot_count()
    print("Number of slots: {}".format(slot_count))

    #We create a small vector to encode; the CKKSEncoder will implicitly pad it
    #with zeros to full size (poly_modulus_degree / 2) when encoding.

    input = DoubleVector([0.0, 1.1, 2.2, 3.3])
    print("Input vector: ")
    print_vector(input)

    #Now we encode it with CKKSEncoder. The floating-point coefficients of `input'
    #will be scaled up by the parameter `scale'. This is necessary since even in
    #the CKKS scheme the plaintext elements are fundamentally polynomials with
    #integer coefficients. It is instructive to think of the scale as determining
    #the bit-precision of the encoding; naturally it will affect the precision of
    #the result.
    #
    #In CKKS the message is stored modulo coeff_modulus (in BFV it is stored modulo
    #plain_modulus), so the scaled message must not get too close to the total size
    #of coeff_modulus. In this case our coeff_modulus is quite large (200 bits) so
    #we have little to worry about in this regard. For this simple example a 30-bit
    #scale is more than enough.

    plain = Plaintext()
    scale = 2.0**30
    print("Encode input vector.")
    encoder.encode(input, scale, plain)

    #We can instantly decode to check the correctness of encoding.
    output = DoubleVector()
    print("    + Decode input vector ...... Correct.")
    encoder.decode(plain, output)
    print_vector(output)

    #The vector is encrypted the same was as in BFV.
    encrypted = Ciphertext()
    print("Encrypt input vector, square, and relinearize.")
    encryptor.encrypt(plain, encrypted)

    #Basic operations on the ciphertexts are still easy to do. Here we square the
    #ciphertext, decrypt, decode, and print the result. We note also that decoding
    #returns a vector of full size (poly_modulus_degree / 2); this is because of
    #the implicit zero-padding mentioned above.

    evaluator.square_inplace(encrypted)
    evaluator.relinearize_inplace(encrypted, relin_keys)

    #We notice that the scale in the result has increased. In fact, it is now the
    #square of the original scale: 2^60.

    print("    + Scale in squared input: {} ( {} bits)".format(
        encrypted.scale(), log2(encrypted.scale())))

    print("Decrypt and decode.")
    decryptor.decrypt(encrypted, plain)
    encoder.decode(plain, output)
    print("    + Result vector ...... Correct.")
    print_vector(output)
Ejemplo n.º 19
0
class FracContext:
    primes = [
        0xffffffffffc0001, 0xfffffffff840001, 0xfffffffff240001,
        0xffffffffe7c0001, 0xffffffffe740001, 0xffffffffe4c0001,
        0xffffffffe440001, 0xffffffffe400001, 0xffffffffdbc0001,
        0xffffffffd840001, 0xffffffffd680001, 0xffffffffd000001,
        0xffffffffcf00001, 0xffffffffcdc0001, 0xffffffffcc40001,
        0xffffffffc300001, 0xffffffffbf40001, 0xffffffffbdc0001,
        0xffffffffb880001, 0xffffffffaec0001, 0xffffffffa380001,
        0xffffffffa200001, 0xffffffffa0c0001, 0xffffffff9600001,
        0xffffffff91c0001, 0xffffffff8f40001, 0xffffffff8680001,
        0xffffffff7e40001, 0xffffffff7bc0001, 0xffffffff76c0001,
        0xffffffff7680001, 0xffffffff6fc0001, 0xffffffff6880001,
        0xffffffff6340001, 0xffffffff5d40001, 0xffffffff54c0001,
        0xffffffff4d40001, 0xffffffff4380001, 0xffffffff3e80001,
        0xffffffff37c0001, 0xffffffff36c0001, 0xffffffff2100001,
        0xffffffff1d80001, 0xffffffff1cc0001, 0xffffffff1900001,
        0xffffffff1740001, 0xffffffff15c0001, 0xffffffff0e80001,
        0xfffffffeff80001, 0xfffffffeff40001, 0xfffffffeefc0001,
        0xfffffffee8c0001, 0xfffffffede40001, 0xfffffffedcc0001,
        0xfffffffed040001, 0xfffffffecf40001, 0xfffffffecec0001,
        0xfffffffecb00001, 0xfffffffec380001, 0xfffffffebb40001
    ]

    def __init__(self,
                 poly_modulus="1x^1024 + 1",
                 coef_modulus_n_primes=20,
                 plain_modulus=1 << 32):
        """
        Set up encryption context for encoder and decoder
        :param poly_modulus:
        :param coef_modulus_n_primes:
        :param plain_modulus:
        """
        self.params = EncryptionParameters()
        self.params.set_poly_modulus(poly_modulus)
        self.params.set_coeff_modulus([
            seal.SmallModulus(p)
            for p in FracContext.primes[:coef_modulus_n_primes]
        ])
        self.params.set_plain_modulus(plain_modulus)

        self.context = SEALContext(self.params)
        self.print_parameters(self.context)

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

    def print_parameters(self, context: SEALContext):
        """
        Parameters description
        :param context: SEALContext object
        """
        print("/ Encryption parameters:")
        print("| poly_modulus: " + context.poly_modulus().to_string())

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

        print("| plain_modulus: " + (str)(context.plain_modulus().value()))
        print("| noise_standard_deviation: " +
              (str)(context.noise_standard_deviation()))
Ejemplo n.º 20
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)))
Ejemplo n.º 21
0
        if testSet[i][-1] == predictions[i]:
            correct += 1
    return (correct / float(len(testSet))) * 100.0


#importing the dataset
print("Importing dataset...")
data = pd.read_csv('/app/Social_Network_Ads.csv')
dataset = data.iloc[:, [2, 3, 4]].values
#dataset1 = data.iloc [:, [2, 3]].values
print("Done\n\n\n")

print("Setting encryption parameters...")
parms = EncryptionParameters()
parms.set_poly_modulus("1x^16384 + 1")
parms.set_coeff_modulus(seal.coeff_modulus_128(16384))
parms.set_plain_modulus(1 << 12)
print("Done\n\n\n")

context = SEALContext(parms)
#print_parameters(context);
encoder = IntegerEncoder(context.plain_modulus())
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)
encoder = FractionalEncoder(context.plain_modulus(), context.poly_modulus(),
                            2048, 32, 3)
Ejemplo n.º 22
0
class FheBuilder(object):
    secret_key = None
    public_key = None
    galois_keys = None
    encryptor = None
    decryptor = None

    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 generate_keys(self):
        self.secret_key = self.keygen.secret_key()
        self.public_key = self.keygen.public_key()
        self.encryptor = Encryptor(self.context, self.public_key)
        self.decryptor = Decryptor(self.context, self.secret_key)

    def generate_galois_keys(self):
        self.galois_keys = self.keygen.galois_keys()

    def get_public_key_buffer(self) -> torch.Tensor:
        if self.public_key is None:
            self.public_key = self.keygen.public_key()
        return torch_from_buffer(self.public_key)

    def build_from_loaded_public_key(self):
        self.encryptor = Encryptor(self.context, self.public_key)

    def get_secret_key_buffer(self) -> torch.Tensor:
        if self.secret_key is None:
            self.secret_key = self.keygen.secret_key()
        return torch_from_buffer(self.secret_key)

    def build_from_loaded_secret_key(self):
        self.decryptor = Decryptor(self.context, self.secret_key)

    def build_enc(self, num_elem, is_cheap_init=False) -> FheEncTensor:
        return FheEncTensor(num_elem, self.modulus, self.degree,
                            self.context, self.evaluator, self.batch_encoder, self.encryptor,
                            is_cheap_init=is_cheap_init)

    def build_enc_from_torch(self, torch_tensor) -> FheEncTensor:
        res = self.build_enc(get_prod(torch_tensor.size()))
        res.encrypt_additive(torch_tensor)
        return res

    def build_plain(self, num_elem) -> FhePlainTensor:
        return FhePlainTensor(num_elem, self.modulus, self.degree, self.context, self.evaluator, self.batch_encoder)

    def build_plain_from_torch(self, torch_tensor) -> FhePlainTensor:
        res = self.build_plain(get_prod(torch_tensor.size()))
        res.load_from_torch(torch_tensor)
        return res

    def decrypt_to_torch(self, fhe_enc_tensor: FheEncTensor, dst=None, to="gpu") -> torch.Tensor:
        def sub_decrypt(enc):
            return enc.decrypt(self.decryptor, dst=dst, to=to)

        if isinstance(fhe_enc_tensor, FheEncTensor):
            return sub_decrypt(fhe_enc_tensor)
        elif isinstance(fhe_enc_tensor, list):
            return torch.stack(list(map(sub_decrypt, fhe_enc_tensor)))
        else:
            raise Exception(f"Unexpected type of fhe_enc_tensor: {fhe_enc_tensor}")

    def noise_budget(self, fhe_enc_tensor: FheEncTensor, name=None):
        noise_bits = self.decryptor.invariant_noise_budget(fhe_enc_tensor.cts[0])
        if name is not None:
            print(f"{name} noise budget {noise_bits} bits")
        return noise_bits
Ejemplo n.º 23
0
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)
Ejemplo n.º 24
0
def example_levels():
    print("Example: Levels")

    #In this examples we describe the concept of `levels' in BFV and CKKS and the
    #related objects that represent them in Microsoft SEAL.
    #
    #In Microsoft SEAL a set of encryption parameters (excluding the random number
    #generator) is identified uniquely by a 256-bit hash of the parameters. This
    #hash is called the `parms_id' and can be easily accessed and printed at any
    #time. The hash will change as soon as any of the parameters is changed.
    #
    #When a SEALContext is created from a given EncryptionParameters instance,
    #Microsoft SEAL automatically creates a so-called `modulus switching chain',
    #which is a chain of other encryption parameters derived from the original set.
    #The parameters in the modulus switching chain are the same as the original
    #parameters with the exception that size of the coefficient modulus is
    #decreasing going down the chain. More precisely, each parameter set in the
    #chain attempts to remove the last coefficient modulus prime from the
    #previous set; this continues until the parameter set is no longer valid
    #(e.g., plain_modulus is larger than the remaining coeff_modulus). It is easy
    #to walk through the chain and access all the parameter sets. Additionally,
    #each parameter set in the chain has a `chain index' that indicates its
    #position in the chain so that the last set has index 0. We say that a set
    #of encryption parameters, or an object carrying those encryption parameters,
    #is at a higher level in the chain than another set of parameters if its the
    #chain index is bigger, i.e., it is earlier in the chain.
    #
    #Each set of parameters in the chain involves unique pre-computations performed
    #when the SEALContext is created, and stored in a SEALContext::ContextData
    #object. The chain is basically a linked list of SEALContext::ContextData
    #objects, and can easily be accessed through the SEALContext at any time. Each
    #node can be identified by the parms_id of its specific encryption parameters
    #(poly_modulus_degree remains the same but coeff_modulus varies).

    parms = EncryptionParameters(scheme_type.BFV)

    poly_modulus_degree = 8192
    parms.set_poly_modulus_degree(poly_modulus_degree)

    #In this example we use a custom coeff_modulus, consisting of 5 primes of
    #sizes 50, 30, 30, 50, and 50 bits. Note that this is still OK according to
    #the explanation in `1_bfv_basics.cpp'. Indeed,
    #
    #    CoeffModulus::MaxBitCount(poly_modulus_degree)
    #
    #returns 218 (greater than 50+30+30+50+50=210).
    #
    #Due to the modulus switching chain, the order of the 5 primes is significant.
    #The last prime has a special meaning and we call it the `special prime'. Thus,
    #the first parameter set in the modulus switching chain is the only one that
    #involves the special prime. All key objects, such as SecretKey, are created
    #at this highest level. All data objects, such as Ciphertext, can be only at
    #lower levels. The special prime should be as large as the largest of the
    #other primes in the coeff_modulus, although this is not a strict requirement.
    #
    #          special prime +---------+
    #                                  |
    #                                  v
    #coeff_modulus: { 50, 30, 30, 50, 50 }  +---+  Level 4 (all keys; `key level')
    #                                           |
    #                                           |
    #    coeff_modulus: { 50, 30, 30, 50 }  +---+  Level 3 (highest `data level')
    #                                           |
    #                                           |
    #        coeff_modulus: { 50, 30, 30 }  +---+  Level 2
    #                                           |
    #                                           |
    #            coeff_modulus: { 50, 30 }  +---+  Level 1
    #                                           |
    #                                           |
    #                coeff_modulus: { 50 }  +---+  Level 0 (lowest level)

    parms.set_coeff_modulus(
        CoeffModulus.Create(poly_modulus_degree,
                            IntVector([50, 30, 30, 50, 50])))

    #In this example the plain_modulus does not play much of a role; we choose
    #some reasonable value.

    parms.set_plain_modulus(PlainModulus.Batching(poly_modulus_degree, 20))

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

    #There are convenience method for accessing the SEALContext::ContextData for
    #some of the most important levels:
    #
    #    SEALContext::key_context_data(): access to key level ContextData
    #    SEALContext::first_context_data(): access to highest data level ContextData
    #    SEALContext::last_context_data(): access to lowest level ContextData
    #
    #We iterate over the chain and print the parms_id for each set of parameters.

    print("Print the modulus switching chain.")

    #First print the key level parameter information.

    context_data = context.key_context_data()
    print("----> Level (chain index): {} ...... key_context_data()".format(
        context_data.chain_index()))
    print("      parms_id: {}".format(list2hex(context_data.parms_id())))
    print("coeff_modulus primes:", end=' ')
    for prime in context_data.parms().coeff_modulus():
        print(hex(prime.value()), end=' ')
    print("")
    print("\\")
    print(" \\-->")

    #Next iterate over the remaining (data) levels.

    context_data = context.first_context_data()
    while (context_data):
        print(" Level (chain index): {} ".format(context_data.chain_index()),
              end='')
        if context_data.parms_id() == context.first_parms_id():
            print(" ...... first_context_data()")
        elif context_data.parms_id() == context.last_parms_id():
            print(" ...... last_context_data()")
        else:
            print("")
        print("      parms_id: {}".format(list2hex(context_data.parms_id())))
        print("coeff_modulus primes:", end=' ')
        for prime in context_data.parms().coeff_modulus():
            print(hex(prime.value()), end=' ')
        print("")
        print("\\")
        print(" \\-->")

        #Step forward in the chain.
        context_data = context_data.next_context_data()

    print(" End of chain reached")

    #We create some keys and check that indeed they appear at the highest level.

    keygen = KeyGenerator(context)
    public_key = keygen.public_key()
    secret_key = keygen.secret_key()
    relin_keys = keygen.relin_keys()
    galois_keys = keygen.galois_keys()
    print("Print the parameter IDs of generated elements.")
    print("    + public_key:  {}".format(list2hex(public_key.parms_id())))
    print("    + secret_key:  {}".format(list2hex(secret_key.parms_id())))
    print("    + relin_keys:  {}".format(list2hex(relin_keys.parms_id())))
    print("    + galois_keys: {}".format(list2hex(galois_keys.parms_id())))

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

    #In the BFV scheme plaintexts do not carry a parms_id, but ciphertexts do. Note
    #how the freshly encrypted ciphertext is at the highest data level.

    plain = Plaintext("1x^3 + 2x^2 + 3x^1 + 4")
    encrypted = Ciphertext()
    encryptor.encrypt(plain, encrypted)
    print("    + plain:       {} (not set in BFV)".format(
        list2hex(plain.parms_id())))
    print("    + encrypted:   {}".format(list2hex(encrypted.parms_id())))

    #`Modulus switching' is a technique of changing the ciphertext parameters down
    #in the chain. The function Evaluator::mod_switch_to_next always switches to
    #the next level down the chain, whereas Evaluator::mod_switch_to switches to
    #a parameter set down the chain corresponding to a given parms_id. However, it
    #is impossible to switch up in the chain.

    print("Perform modulus switching on encrypted and print.")
    context_data = context.first_context_data()
    print("---->")

    while (context_data.next_context_data()):
        print(" Level (chain index): {} ".format(context_data.chain_index()))
        print("      parms_id of encrypted: {}".format(
            list2hex(encrypted.parms_id())))
        print("      Noise budget at this level: {} bits".format(
            decryptor.invariant_noise_budget(encrypted)))
        print("\\")
        print(" \\-->")
        evaluator.mod_switch_to_next_inplace(encrypted)
        context_data = context_data.next_context_data()

    print(" Level (chain index): {}".format(context_data.chain_index()))
    print("      parms_id of encrypted: {}".format(encrypted.parms_id()))
    print("      Noise budget at this level: {} bits".format(
        decryptor.invariant_noise_budget(encrypted)))
    print("\\")
    print(" \\-->")
    print(" End of chain reached")

    #At this point it is hard to see any benefit in doing this: we lost a huge
    #amount of noise budget (i.e., computational power) at each switch and seemed
    #to get nothing in return. Decryption still works.

    print("Decrypt still works after modulus switching.")
    decryptor.decrypt(encrypted, plain)
    print("    + Decryption of encrypted: {} ...... Correct.".format(
        plain.to_string()))

    #However, there is a hidden benefit: the size of the ciphertext depends
    #linearly on the number of primes in the coefficient modulus. Thus, if there
    #is no need or intention to perform any further computations on a given
    #ciphertext, we might as well switch it down to the smallest (last) set of
    #parameters in the chain before sending it back to the secret key holder for
    #decryption.
    #
    #Also the lost noise budget is actually not an issue at all, if we do things
    #right, as we will see below.
    #
    #First we recreate the original ciphertext and perform some computations.

    print("Computation is more efficient with modulus switching.")
    print("Compute the 8th power.")
    encryptor.encrypt(plain, encrypted)
    print("    + Noise budget fresh:                   {} bits".format(
        decryptor.invariant_noise_budget(encrypted)))
    evaluator.square_inplace(encrypted)
    evaluator.relinearize_inplace(encrypted, relin_keys)
    print("    + Noise budget of the 2nd power:         {} bits".format(
        decryptor.invariant_noise_budget(encrypted)))
    evaluator.square_inplace(encrypted)
    evaluator.relinearize_inplace(encrypted, relin_keys)
    print("    + Noise budget of the 4th power:         {} bits".format(
        decryptor.invariant_noise_budget(encrypted)))

    #Surprisingly, in this case modulus switching has no effect at all on the
    #noise budget.

    evaluator.mod_switch_to_next_inplace(encrypted)
    print("    + Noise budget after modulus switching:  {} bits".format(
        decryptor.invariant_noise_budget(encrypted)))

    #This means that there is no harm at all in dropping some of the coefficient
    #modulus after doing enough computations. In some cases one might want to
    #switch to a lower level slightly earlier, actually sacrificing some of the
    #noise budget in the process, to gain computational performance from having
    #smaller parameters. We see from the print-out that the next modulus switch
    #should be done ideally when the noise budget is down to around 25 bits.

    evaluator.square_inplace(encrypted)
    evaluator.relinearize_inplace(encrypted, relin_keys)
    print("    + Noise budget of the 8th power:         {} bits".format(
        decryptor.invariant_noise_budget(encrypted)))
    evaluator.mod_switch_to_next_inplace(encrypted)
    print("    + Noise budget after modulus switching:  {} bits".format(
        decryptor.invariant_noise_budget(encrypted)))

    #At this point the ciphertext still decrypts correctly, has very small size,
    #and the computation was as efficient as possible. Note that the decryptor
    #can be used to decrypt a ciphertext at any level in the modulus switching
    #chain.

    decryptor.decrypt(encrypted, plain)
    print("    + Decryption of the 8th power (hexadecimal) ...... Correct.")
    print("    {}".format(plain.to_string()))

    #In BFV modulus switching is not necessary and in some cases the user might
    #not want to create the modulus switching chain, except for the highest two
    #levels. This can be done by passing a bool `false' to SEALContext::Create.

    context = SEALContext.Create(parms, False)

    #We can check that indeed the modulus switching chain has been created only
    #for the highest two levels (key level and highest data level). The following
    #loop should execute only once.

    print("Optionally disable modulus switching chain expansion.")
    print("Print the modulus switching chain.")
    print("---->")

    context_data = context.key_context_data()
    while (context_data):
        print(" Level (chain index): {}".format(context_data.chain_index()))
        print("      parms_id: {}".format(list2hex(context_data.parms_id())))
        print("coeff_modulus primes:", end=' ')
        for prime in context_data.parms().coeff_modulus():
            print(hex(prime.value()), end=' ')
        print("")
        print("\\")
        print(" \\-->")
        context_data = context_data.next_context_data()

    print(" End of chain reached")
Ejemplo n.º 25
0
 IntegerEncoder, \
 FractionalEncoder, \
 KeyGenerator, \
 MemoryPoolHandle, \
 Plaintext, \
 SEALContext, \
 EvaluationKeys, \
 GaloisKeys, \
 PolyCRTBuilder, \
 ChooserEncoder, \
 ChooserEvaluator, \
 ChooserPoly

parms = EncryptionParameters()
parms.set_poly_modulus("1x^4096 + 1")
parms.set_coeff_modulus(seal.coeff_modulus_128(4096))
# Note that 40961 is a prime number and 2*4096 divides 40960.
parms.set_plain_modulus(40961)
context = SEALContext(parms)

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


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)):
Ejemplo n.º 26
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")
Ejemplo n.º 27
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)
Ejemplo n.º 28
0
def unit_test():
    parms = EncryptionParameters()
    parms.set_poly_modulus("1x^8192 + 1")
    parms.set_coeff_modulus(seal.coeff_modulus_128(8192))
    parms.set_plain_modulus(1 << 10)
    context = SEALContext(parms)

    # Print the parameters that we have chosen
    print_parameters(context)
    encoder = FractionalEncoder(context.plain_modulus(),
                                context.poly_modulus(), 64, 32, 3)
    keygen = KeyGenerator(context)
    public_key = keygen.public_key()
    secret_key = keygen.secret_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)
    learningRate = 0.1
    learningRate_data = encoder.encode(learningRate)
    learningRate_e = Ciphertext()
    encryptor.encrypt(learningRate_data, learningRate_e)

    updatedVals = []
    updatedVals.append(50)
    updatedVals.append(50)
    updatedVals_unenc = []

    updatedVals_unenc.append(updatedVals[0])
    updatedVals_unenc.append(updatedVals[1])

    for i in range(15):
        x = 1
        y = 1
        updatedVals = learn(x, y, evaluator, updatedVals[0], updatedVals[1],
                            encoder, encryptor, learningRate_e, decryptor)
        ypred = updatedVals_unenc[0] * x + updatedVals_unenc[1]
        error = ypred - y
        updatedVals_unenc[0] = updatedVals_unenc[0] - x * error * learningRate
        updatedVals_unenc[1] = updatedVals_unenc[1] - error * learningRate
        print((str)(updatedVals[1]) + ":" + (str)(updatedVals[0]) + ":" +
              (str)(updatedVals_unenc[1]) + ":" + (str)(updatedVals_unenc[0]))

        x = 2
        y = 3
        updatedVals = learn(x, y, evaluator, updatedVals[0], updatedVals[1],
                            encoder, encryptor, learningRate_e, decryptor)
        ypred = updatedVals_unenc[0] * x + updatedVals_unenc[1]
        error = ypred - y
        updatedVals_unenc[0] = updatedVals_unenc[0] - x * error * learningRate
        updatedVals_unenc[1] = updatedVals_unenc[1] - error * learningRate
        print((str)(updatedVals[1]) + ":" + (str)(updatedVals[0]) + ":" +
              (str)(updatedVals_unenc[1]) + ":" + (str)(updatedVals_unenc[0]))

        x = 4
        y = 3
        updatedVals = learn(x, y, evaluator, updatedVals[0], updatedVals[1],
                            encoder, encryptor, learningRate_e, decryptor)
        ypred = updatedVals_unenc[0] * x + updatedVals_unenc[1]
        error = ypred - y
        updatedVals_unenc[0] = updatedVals_unenc[0] - x * error * learningRate
        updatedVals_unenc[1] = updatedVals_unenc[1] - error * learningRate
        print((str)(updatedVals[1]) + ":" + (str)(updatedVals[0]) + ":" +
              (str)(updatedVals_unenc[1]) + ":" + (str)(updatedVals_unenc[0]))

        x = 3
        y = 2
        updatedVals = learn(x, y, evaluator, updatedVals[0], updatedVals[1],
                            encoder, encryptor, learningRate_e, decryptor)
        ypred = updatedVals_unenc[0] * x + updatedVals_unenc[1]
        error = ypred - y
        updatedVals_unenc[0] = updatedVals_unenc[0] - x * error * learningRate
        updatedVals_unenc[1] = updatedVals_unenc[1] - error * learningRate
        print((str)(updatedVals[1]) + ":" + (str)(updatedVals[0]) + ":" +
              (str)(updatedVals_unenc[1]) + ":" + (str)(updatedVals_unenc[0]))

        x = 5
        y = 5
        updatedVals = learn(x, y, evaluator, updatedVals[0], updatedVals[1],
                            encoder, encryptor, learningRate_e, decryptor)
        ypred = updatedVals_unenc[0] * x + updatedVals_unenc[1]
        error = ypred - y
        updatedVals_unenc[0] = updatedVals_unenc[0] - x * error * learningRate
        updatedVals_unenc[1] = updatedVals_unenc[1] - error * learningRate
        print((str)(updatedVals[1]) + ":" + (str)(updatedVals[0]) + ":" +
              (str)(updatedVals_unenc[1]) + ":" + (str)(updatedVals_unenc[0]))
# but it is also possible to go beyond this.
parms.set_poly_modulus("1x^2048 + 1")
# 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)
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.
parms.set_plain_modulus(1 << 8)
# 1 << 8 is bitwise operation which means 1 shifted eight times ie 2^8=256
context = SEALContext(parms)


encoder = IntegerEncoder(context.plain_modulus())
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)
Ejemplo n.º 30
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