def __neg__(self): result = EncNum(Ciphertext(self.encrypted)) evaluator.negate(result.encrypted) return result
evaluator = Evaluator(context) decryptor = Decryptor(context, secret_key) value1 = 11 plain1 = encoder.encode(value1) print("Encoded " + (str)(value1) + " as polynomial " + plain1.to_string() + " (plain1)") value2 = 15 plain2 = encoder.encode(value2) print("Encoded " + (str)(value2) + " as polynomial " + plain2.to_string() + " (plain2)") # Encrypting the values is easy. encrypted1 = Ciphertext() encrypted2 = Ciphertext() print("Encrypting plain1: ") encryptor.encrypt(plain1, encrypted1) print("Done (encrypted1)") print("Encrypting plain2: ") encryptor.encrypt(plain2, encrypted2) print("Done (encrypted2)") # To illustrate the concept of noise budget, we print the budgets in the fresh # encryptions. print("Noise budget in encrypted1: " + (str)(decryptor.invariant_noise_budget(encrypted1)) + " bits") print("Noise budget in encrypted2: " + (str)(decryptor.invariant_noise_budget(encrypted2)) + " bits")
def load_encrypted_value(filepath: str) -> EncryptedValue: """Loads a saved encrypted value from the given file.""" ciphertext = Ciphertext() ciphertext.load(simplefhe._context, filepath) return EncryptedValue(ciphertext)
def plainMultiplication(element, d): delta = encoderF.encode(d) temp = Ciphertext() evaluator.multiply_plain(element, delta, temp) evaluator.relinearize(temp, ev_keys) return (temp)
def __init__(self, rows: int, cols: int): self.rows = rows self.cols = cols self.data = tuple(Ciphertext() for i in range(rows))
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)
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")
public_key = keygen.public_key() secret_key = keygen.secret_key() #ev_keys40 = EvaluationKeys #ev_keys20 = EvaluationKeys() #keygen.generate_evaluation_keys(40,5,ev_keys40) #keygen.generate_evaluation_keys(20,3,ev_keys20) encryptor = Encryptor(context, public_key) evaluator = Evaluator(context) decryptor = Decryptor(context, secret_key) plain_A = [] A=[] n=int(input("Enter dimension: ")) for i in range(n): plain_a = [] a=[] for j in range(n): encrypted_data1= Ciphertext() ran=random.randint(0,10) plain_a.append(ran) encryptor.encrypt(encoderF.encode(ran), encrypted_data1) a.append(encrypted_data1) A.append(a) plain_A.append(plain_a) print(plain_a) delta, C = matrixOperations.inverseMatrix(A) print(delta)
def parallel_encryption(element): temp=Ciphertext() encryptor.encrypt(encoderF.encode(element), temp) return(temp)
def example_basics_i(): print_example_banner("Example: Basics I") # In this example we demonstrate setting up encryption parameters and other # relevant objects for performing simple computations on encrypted integers. # SEAL uses the Fan-Vercauteren (FV) homomorphic encryption scheme. We refer to # https://eprint.iacr.org/2012/144 for full details on how the FV scheme works. # For better performance, SEAL implements the "FullRNS" optimization of FV, as # described in https://eprint.iacr.org/2016/510. # The first task is to set up an instance of the EncryptionParameters class. # It is critical to understand how these different parameters behave, how they # affect the encryption scheme, performance, and the security level. There are # three encryption parameters that are necessary to set: # - poly_modulus (polynomial modulus); # - coeff_modulus ([ciphertext] coefficient modulus); # - plain_modulus (plaintext modulus). # A fourth parameter -- noise_standard_deviation -- has a default value of 3.19 # and should not be necessary to modify unless the user has a specific reason # to and knows what they are doing. # The encryption scheme implemented in SEAL cannot perform arbitrary computations # on encrypted data. Instead, each ciphertext has a specific quantity called the # `invariant noise budget' -- or `noise budget' for short -- measured in bits. # The noise budget of a freshly encrypted ciphertext (initial noise budget) is # determined by the encryption parameters. Homomorphic operations consume the # noise budget at a rate also determined by the encryption parameters. In SEAL # the two basic homomorphic operations are additions and multiplications, of # which additions can generally be thought of as being nearly free in terms of # noise budget consumption compared to multiplications. Since noise budget # consumption is compounding in sequential multiplications, the most significant # factor in choosing appropriate encryption parameters is the multiplicative # depth of the arithmetic circuit that needs to be evaluated. Once the noise # budget in a ciphertext reaches zero, it becomes too corrupted to be decrypted. # Thus, it is essential to choose the parameters to be large enough to support # the desired computation; otherwise the result is impossible to make sense of # even with the secret key. parms = EncryptionParameters() # We first set the polynomial modulus. This must be a power-of-2 cyclotomic # polynomial, i.e. a polynomial of the form "1x^(power-of-2) + 1". The polynomial # modulus should be thought of mainly affecting the security level of the scheme; # larger polynomial modulus makes the scheme more secure. At the same time, it # makes ciphertext sizes larger, and consequently all operations slower. # Recommended degrees for poly_modulus are 1024, 2048, 4096, 8192, 16384, 32768, # but it is also possible to go beyond this. Since we perform only a very small # computation in this example, it suffices to use a very small polynomial modulus parms.set_poly_modulus("1x^2048 + 1") # Next we choose the [ciphertext] coefficient modulus (coeff_modulus). The size # of the coefficient modulus should be thought of as the most significant factor # in determining the noise budget in a freshly encrypted ciphertext: bigger means # more noise budget. Unfortunately, a larger coefficient modulus also lowers the # security level of the scheme. Thus, if a large noise budget is required for # complicated computations, a large coefficient modulus needs to be used, and the # reduction in the security level must be countered by simultaneously increasing # the polynomial modulus. # To make parameter selection easier for the user, we have constructed sets of # largest allowed coefficient moduli for 128-bit and 192-bit security levels # for different choices of the polynomial modulus. These recommended parameters # follow the Security white paper at http://HomomorphicEncryption.org. However, # due to the complexity of this topic, we highly recommend the user to directly # consult an expert in homomorphic encryption and RLWE-based encryption schemes # to determine the security of their parameter choices. # Our recommended values for the coefficient modulus can be easily accessed # through the functions # coeff_modulus_128bit(int) # coeff_modulus_192bit(int) # for 128-bit and 192-bit security levels. The integer parameter is the degree # of the polynomial modulus. # In SEAL the coefficient modulus is a positive composite number -- a product # of distinct primes of size up to 60 bits. When we talk about the size of the # coefficient modulus we mean the bit length of the product of the small primes. # The small primes are represented by instances of the SmallModulus class; for # example coeff_modulus_128bit(int) returns a vector of SmallModulus instances. # It is possible for the user to select their own small primes. Since SEAL uses # the Number Theoretic Transform (NTT) for polynomial multiplications modulo the # factors of the coefficient modulus, the factors need to be prime numbers # congruent to 1 modulo 2*degree(poly_modulus). We have generated a list of such # prime numbers of various sizes, that the user can easily access through the # functions # small_mods_60bit(int) # small_mods_50bit(int) # small_mods_40bit(int) # small_mods_30bit(int) # each of which gives access to an array of primes of the denoted size. These # primes are located in the source file util/globals.cpp. # Performance is mainly affected by the size of the polynomial modulus, and the # number of prime factors in the coefficient modulus. Thus, it is important to # use as few factors in the coefficient modulus as possible. # In this example we use the default coefficient modulus for a 128-bit security # level. Concretely, this coefficient modulus consists of only one 56-bit prime # factor: 0xfffffffff00001. parms.set_coeff_modulus(seal.coeff_modulus_128(2048)) # The plaintext modulus can be any positive integer, even though here we take # it to be a power of two. In fact, in many cases one might instead want it to # be a prime number; we will see this in example_batching(). The plaintext # modulus determines the size of the plaintext data type, but it also affects # the noise budget in a freshly encrypted ciphertext, and the consumption of # the noise budget in homomorphic multiplication. Thus, it is essential to try # to keep the plaintext data type as small as possible for good performance. # The noise budget in a freshly encrypted ciphertext is # ~ log2(coeff_modulus/plain_modulus) (bits) # and the noise budget consumption in a homomorphic multiplication is of the # form log2(plain_modulus) + (other terms). parms.set_plain_modulus(1 << 8) # Now that all parameters are set, we are ready to construct a SEALContext # object. This is a heavy class that checks the validity and properties of # the parameters we just set, and performs and stores several important # pre-computations. context = SEALContext(parms) # Print the parameters that we have chosen print_parameters(context) # Plaintexts in the FV scheme are polynomials with coefficients integers modulo # plain_modulus. To encrypt for example integers instead, one can use an # `encoding scheme' to represent the integers as such polynomials. SEAL comes # with a few basic encoders: # [IntegerEncoder] # Given an integer base b, encodes integers as plaintext polynomials as follows. # First, a base-b expansion of the integer is computed. This expansion uses # a `balanced' set of representatives of integers modulo b as the coefficients. # Namely, when b is odd the coefficients are integers between -(b-1)/2 and # (b-1)/2. When b is even, the integers are between -b/2 and (b-1)/2, except # when b is two and the usual binary expansion is used (coefficients 0 and 1). # Decoding amounts to evaluating the polynomial at x=b. For example, if b=2, # the integer # 26 = 2^4 + 2^3 + 2^1 # is encoded as the polynomial 1x^4 + 1x^3 + 1x^1. When b=3, # 26 = 3^3 - 3^0 # is encoded as the polynomial 1x^3 - 1. In memory polynomial coefficients are # always stored as unsigned integers by storing their smallest non-negative # representatives modulo plain_modulus. To create a base-b integer encoder, # use the constructor IntegerEncoder(plain_modulus, b). If no b is given, b=2 # is used. # [FractionalEncoder] # The FractionalEncoder encodes fixed-precision rational numbers as follows. # It expands the number in a given base b, possibly truncating an infinite # fractional part to finite precision, e.g. # 26.75 = 2^4 + 2^3 + 2^1 + 2^(-1) + 2^(-2) # when b=2. For the sake of the example, suppose poly_modulus is 1x^1024 + 1. # It then represents the integer part of the number in the same way as in # IntegerEncoder (with b=2 here), and moves the fractional part instead to the # highest degree part of the polynomial, but with signs of the coefficients # changed. In this example we would represent 26.75 as the polynomial # -1x^1023 - 1x^1022 + 1x^4 + 1x^3 + 1x^1. # In memory the negative coefficients of the polynomial will be represented as # their negatives modulo plain_modulus. # [PolyCRTBuilder] # If plain_modulus is a prime congruent to 1 modulo 2*degree(poly_modulus), the # plaintext elements can be viewed as 2-by-(degree(poly_modulus) / 2) matrices # with elements integers modulo plain_modulus. When a desired computation can be # vectorized, using PolyCRTBuilder can result in massive performance improvements # over naively encrypting and operating on each input number separately. Thus, # in more complicated computations this is likely to be by far the most important # and useful encoder. In example_batching() we show how to use and operate on # encrypted matrix plaintexts. # For performance reasons, in homomorphic encryption one typically wants to keep # the plaintext data types as small as possible, which can make it challenging to # prevent data type overflow in more complicated computations, especially when # operating on rational numbers that have been scaled to integers. When using # PolyCRTBuilder estimating whether an overflow occurs is a fairly standard task, # as the matrix slots are integers modulo plain_modulus, and each slot is operated # on independently of the others. When using IntegerEncoder or FractionalEncoder # it is substantially more difficult to estimate when an overflow occurs in the # plaintext, and choosing the plaintext modulus very carefully to be large enough # is critical to avoid unexpected results. Specifically, one needs to estimate how # large the largest coefficient in the polynomial view of all of the plaintext # elements becomes, and choose the plaintext modulus to be larger than this value. # SEAL comes with an automatic parameter selection tool that can help with this # task, as is demonstrated in example_parameter_selection(). # Here we choose to create an IntegerEncoder with base b=2. encoder = IntegerEncoder(context.plain_modulus()) # We are now ready to generate the secret and public keys. For this purpose we need # an instance of the KeyGenerator class. Constructing a KeyGenerator automatically # generates the public and secret key, which can then be read to local variables. # To create a fresh pair of keys one can call KeyGenerator::generate() at any time. keygen = KeyGenerator(context) public_key = keygen.public_key() secret_key = keygen.secret_key() # To be able to encrypt, we need to construct an instance of Encryptor. Note that # the Encryptor only requires the public key. encryptor = Encryptor(context, public_key) # Computations on the ciphertexts are performed with the Evaluator class. evaluator = Evaluator(context) # We will of course want to decrypt our results to verify that everything worked, # so we need to also construct an instance of Decryptor. Note that the Decryptor # requires the secret key. decryptor = Decryptor(context, secret_key) # We start by encoding two integers as plaintext polynomials. value1 = 5 plain1 = encoder.encode(value1) print("Encoded " + (str)(value1) + " as polynomial " + plain1.to_string() + " (plain1)") value2 = -7 plain2 = encoder.encode(value2) print("Encoded " + (str)(value2) + " as polynomial " + plain2.to_string() + " (plain2)") # Encrypting the values is easy. encrypted1 = Ciphertext() encrypted2 = Ciphertext() print("Encrypting plain1: ") encryptor.encrypt(plain1, encrypted1) print("Done (encrypted1)") print("Encrypting plain2: ") encryptor.encrypt(plain2, encrypted2) print("Done (encrypted2)") # To illustrate the concept of noise budget, we print the budgets in the fresh # encryptions. print("Noise budget in encrypted1: " + (str)(decryptor.invariant_noise_budget(encrypted1)) + " bits") print("Noise budget in encrypted2: " + (str)(decryptor.invariant_noise_budget(encrypted2)) + " bits") # As a simple example, we compute (-encrypted1 + encrypted2) * encrypted2. # Negation is a unary operation. evaluator.negate(encrypted1) # Negation does not consume any noise budget. print("Noise budget in -encrypted1: " + (str)(decryptor.invariant_noise_budget(encrypted1)) + " bits") # Addition can be done in-place (overwriting the first argument with the result, # or alternatively a three-argument overload with a separate destination variable # can be used. The in-place variants are always more efficient. Here we overwrite # encrypted1 with the sum. evaluator.add(encrypted1, encrypted2) # It is instructive to think that addition sets the noise budget to the minimum # of the input noise budgets. In this case both inputs had roughly the same # budget going on, and the output (in encrypted1) has just slightly lower budget. # Depending on probabilistic effects, the noise growth consumption may or may # not be visible when measured in whole bits. print("Noise budget in -encrypted1 + encrypted2: " + (str)(decryptor.invariant_noise_budget(encrypted1)) + " bits") # Finally multiply with encrypted2. Again, we use the in-place version of the # function, overwriting encrypted1 with the product. evaluator.multiply(encrypted1, encrypted2) # Multiplication consumes a lot of noise budget. This is clearly seen in the # print-out. The user can change the plain_modulus to see its effect on the # rate of noise budget consumption. print("Noise budget in (-encrypted1 + encrypted2) * encrypted2: " + (str)(decryptor.invariant_noise_budget(encrypted1)) + " bits") # Now we decrypt and decode our result. plain_result = Plaintext() print("Decrypting result: ") decryptor.decrypt(encrypted1, plain_result) print("Done") # Print the result plaintext polynomial. print("Plaintext polynomial: " + plain_result.to_string()) # Decode to obtain an integer result. print("Decoded integer: " + (str)(encoder.decode_int32(plain_result)))
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())
def get_matched_scale_vectors(self, a: Ciphertext, b: Ciphertext) -> (Ciphertext, Ciphertext): a_tag = Ciphertext(a) b_tag = Ciphertext(b) a_index = self.context.get_context_data(a.parms_id()).chain_index() b_index = self.context.get_context_data(b.parms_id()).chain_index() # Changing the mod if required, else just setting the scale if a_index < b_index: self.evaluator.mod_switch_to_inplace(b_tag, a.parms_id()) elif a_index > b_index: self.evaluator.mod_switch_to_inplace(a_tag, b.parms_id()) a_tag.set_scale(self.scale) b_tag.set_scale(self.scale) return a_tag, b_tag
def trace(M): t = Ciphertext(M[0][0]) for i in range(1, len(M)): evaluator.add(t, M[i][i]) return (t)
def trace(M): e=Ciphertext() encryptor.encrypt("0", encrypted_data2) for i in range(0,n): evaluator.add(M[i][i],e) return (e)
def encrypt(n): plain = encoder.encode(n) encrypted = Ciphertext() encryptor.encrypt(plain, encrypted) return EncNum(encrypted)
def scal_encrypt(scal, encryption, encoding): plain = encoding.encode(scal) encrypted = Ciphertext() encryption.encrypt(plain, encrypted) return encrypted
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)
def _new_ciphertext(self, arg=None): if arg is None: return Ciphertext() else: return Ciphertext(arg)
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)))
encrypt_fullMatrix(S_encoded) print("asdk;vknasifnbsdf") normalize(rawX0) # have to find a way to make normalize an encrytped function for i in range(len(rawX0)): rawX0[i] = rawX0[i][1:] tX = [[1] * 245] + rawX0 # encrypting matrix tX tX_encrypted = [] for i in range(n): tx_enc = [] for j in range(m): temp = Ciphertext() encryptor.encrypt(encoderF.encode(S_encoded[i][j]), temp) tx_enc.append(temp) tX_encrypted.append(tx_enc) X = [list(tup) for tup in zip(*tX)] for i in range(len(y)): temp = Ciphertext() encryptor.encrypt(encoderF.encode(int(y[i])), temp) y[i] = temp k = len(X[0]) # k =3 print("here1") U1 = matMultiply(tX_encrypted, y)
def trace(M): t = Ciphertext() diag = np.diag(M) evaluator.add_many(diag, t) return (t)
def trace(M): # calculates trace of a matrix t = Ciphertext(M[0][0]) for i in range(1, n): evaluator.add(t, M[i][i]) return (t)
def inverseMatrix(M): n = len(M) Power_vector_Half = Power_vector_HalfCalculation(M) trace_vector = TraceCalculation(Power_vector_Half) coefficientPoly = coefficientPolyCreate(trace_vector, n) M_inverse = [] determinant = coefficientPoly.pop() print("determinant by HE: ", decryption_num(determinant)) # x = [0]*n-i-1 + [1] + [0]*i for i in range(n - 1, -1, -1): powerMatrix_X = [] for j in range(len(Power_vector_Half)): #a= Power_vector_Half[j][i] powerMatrix_X.append(Power_vector_Half[j][i]) #decrypt_matrix(a) # multiplies x with powers I, A, A^2 ... A^( [n/2 + 0.5] ) for j in range(len(Power_vector_Half), n): # to avoid budget of only one matrix to go down, we randomly choose vector. # differece will be noticable when matrix is large, here n is 4, so wont matter much here partition_1 = random.randint(n // 4 + 1, n // 2) if (j - partition_1 >= len(Power_vector_Half)): partition_1 = len(Power_vector_Half) - 1 partition_2 = j - partition_1 multiplier1 = Power_vector_Half[partition_1][:i + 1] multiplier2 = Power_vector_Half[partition_2][i] Z = matrixMultiply(multiplier1, multiplier2) powerMatrix_X.append(list(Z.flatten())) # powerMatrix_X is powerMatrix multiplied by x vector for j in range(len(powerMatrix_X)): powerMatrix_X[j] = powerMatrix_X[j][:i + 1] for l in range(len(powerMatrix_X[j])): evaluator.multiply(powerMatrix_X[j][l], coefficientPoly[n - 1 - j]) evaluator.relinearize(powerMatrix_X[j][l], ev_keys) tInverseRow = [list(tup) for tup in zip(*powerMatrix_X)] InverseRow = [] for z in range(len(tInverseRow)): temp = Ciphertext() evaluator.add_many(tInverseRow[z], temp) InverseRow.append(temp) M_inverse.append(InverseRow) M_inverse = multiplyDeterminant(M_inverse, determinant) assert (n * (n + 1) / 2 == len(M_inverse)) # recontruct the lower triangle X = [] sInd = 0 for i in range(n): X.append(M_inverse[sInd:sInd + n - i]) sInd += n - i X.reverse() # complete the symmetric matrix for rowIndex in range(n): assert (len(X[rowIndex]) <= n) X[rowIndex] += [None] * (n - len(X[rowIndex])) for rowIndex in range(n): for colIndex in range(rowIndex + 1, n): assert (X[rowIndex][colIndex] == None) X[rowIndex][colIndex] = X[colIndex][rowIndex] X_array = np.asarray(X) return (X_array)
parms.set_coeff_modulus(seal.coeff_modulus_128(8192)) parms.set_plain_modulus(1 << 21) context = SEALContext(parms) encoderF = FractionalEncoder(context.plain_modulus(), context.poly_modulus(), 30, 34, 3) keygen = KeyGenerator(context) public_key = keygen.public_key() secret_key = keygen.secret_key() encryptor = Encryptor(context, public_key) evaluator = Evaluator(context) decryptor = Decryptor(context, secret_key) ########################## encoding main matrix ################################ A = [Ciphertext(), Ciphertext(), Ciphertext(), Ciphertext()] for i in range(len(A)): encryptor.encrypt(encoderF.encode(i), A[i]) for j in range(10): evaluator.multiply(A[0], A[1]) evaluator.multiply(A[0], A[2]) evaluator.add(A[1], A[2]) for i in range(len(A)): print("Noise budget of [" + str(i) + "] :" + str((decryptor.invariant_noise_budget(A[i]))) + " bits") print("A[%d]: " % (i), ) print_value(A[i])