Ejemplo n.º 1
0
        public void SEALContextParamsTest()
        {
            EncryptionParameters parms = new EncryptionParameters(SchemeType.BFV)
            {
                PolyModulusDegree = 128,
                PlainModulus      = new Modulus(1 << 6),
                CoeffModulus      = CoeffModulus.Create(128, new int[] { 30, 30, 30 })
            };
            SEALContext context = new SEALContext(parms, expandModChain: true, secLevel: SecLevelType.None);

            SEALContext.ContextData data = context.KeyContextData;
            Assert.IsNotNull(data);

            EncryptionParameters parms2 = data.Parms;

            Assert.AreEqual(parms.PolyModulusDegree, parms2.PolyModulusDegree);

            EncryptionParameterQualifiers qualifiers = data.Qualifiers;

            Assert.IsNotNull(qualifiers);

            Assert.IsTrue(qualifiers.ParametersSet);
            Assert.IsFalse(qualifiers.UsingBatching);
            Assert.IsTrue(qualifiers.UsingFastPlainLift);
            Assert.IsTrue(qualifiers.UsingFFT);
            Assert.IsTrue(qualifiers.UsingNTT);
            Assert.AreEqual(SecLevelType.None, qualifiers.SecLevel);
            Assert.IsFalse(qualifiers.UsingDescendingModulusChain);
            Assert.IsTrue(context.UsingKeyswitching);

            ulong[] cdpm = data.CoeffDivPlainModulus;
            Assert.AreEqual(3, cdpm.Length);

            Assert.AreEqual(32ul, data.PlainUpperHalfThreshold);

            Assert.AreEqual(3, data.PlainUpperHalfIncrement.Length);
            Assert.IsNull(data.UpperHalfThreshold);
            Assert.IsNotNull(data.UpperHalfIncrement);
            Assert.AreEqual(3, data.UpperHalfIncrement.Length);
            Assert.AreEqual(2ul, data.ChainIndex);

            Assert.IsNull(data.PrevContextData);
            SEALContext.ContextData data2 = data.NextContextData;
            Assert.IsNotNull(data2);
            Assert.AreEqual(1ul, data2.ChainIndex);
            Assert.AreEqual(2ul, data2.PrevContextData.ChainIndex);

            SEALContext.ContextData data3 = data2.NextContextData;
            Assert.IsNotNull(data3);
            Assert.AreEqual(0ul, data3.ChainIndex);
            Assert.AreEqual(1ul, data3.PrevContextData.ChainIndex);
            Assert.IsNull(data3.NextContextData);

            parms = new EncryptionParameters(SchemeType.BFV)
            {
                PolyModulusDegree = 127,
                PlainModulus      = new Modulus(1 << 6),
                CoeffModulus      = CoeffModulus.Create(128, new int[] { 30, 30, 30 })
            };
            context = new SEALContext(parms, expandModChain: true, secLevel: SecLevelType.None);
            Assert.AreEqual(context.ParameterErrorName(), "invalid_poly_modulus_degree_non_power_of_two");
            Assert.AreEqual(context.ParameterErrorMessage(), "poly_modulus_degree is not a power of two");
        }
Ejemplo n.º 2
0
        private static void ExampleBFVBasics()
        {
            Utilities.PrintExampleBanner("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:
             *
             *  - PolyModulusDegree (degree of polynomial modulus);
             *  - CoeffModulus ([ciphertext] coefficient modulus);
             *  - PlainModulus (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.
             */
            using EncryptionParameters parms = new EncryptionParameters(SchemeType.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 PolyModulusDegree 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.
             */
            ulong polyModulusDegree = 4096;

            parms.PolyModulusDegree = polyModulusDegree;

            /*
             * Next we set the [ciphertext] `coefficient modulus' (CoeffModulus). This
             * parameter is a large integer, which is a product of distinct prime numbers,
             * numbers, each represented by an instance of the Modulus class. The
             * bit-length of CoeffModulus means the sum of the bit-lengths of its prime
             * factors.
             *
             * A larger CoeffModulus implies a larger noise budget, hence more encrypted
             * computation capabilities. However, an upper bound for the total bit-length
             * of the CoeffModulus is determined by the PolyModulusDegree, as follows:
             *
             +----------------------------------------------------+
             | PolyModulusDegree   | max CoeffModulus 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(polyModulusDegree).
             |
             | For example, if PolyModulusDegree is 4096, the coeff_modulus could consist
             | of three 36-bit primes (108 bits).
             |
             | Microsoft SEAL comes with helper functions for selecting the CoeffModulus.
             | For new users the easiest way is to simply use
             |
             |  CoeffModulus.BFVDefault(polyModulusDegree),
             |
             | which returns IEnumerable<Modulus> consisting of a generally good choice
             | for the given PolyModulusDegree.
             */
            parms.CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree);

            /*
             * 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(CoeffModulus/PlainModulus) (bits)
             *
             * and the noise budget consumption in a homomorphic multiplication is of the
             * form log2(PlainModulus) + (other terms).
             *
             * The plaintext modulus is specific to the BFV scheme, and cannot be set when
             * using the CKKS scheme.
             */
            parms.PlainModulus = new 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.
             *
             * C# 8.0 introduced the `using` declaration for local variables. This is a very
             * convenient addition to the language: it causes the Dispose method for the
             * object to be called at the end of the enclosing scope (in this case the end
             * of this function), hence automatically releasing the native resources held by
             * the object. This is helpful, because releasing the native resources returns
             * the allocated memory to the memory pool, speeding up further allocations.
             * Another way would be to call GC::Collect() at a convenient point in the code,
             * but this may be less optimal as it may still cause unnecessary allocations
             * of memory if native resources were not released early enough. In this program
             * we call GC::Collect() after every example (see Examples.cs) to make sure
             * everything is returned to the memory pool at latest before running the next
             * example.
             */
            using SEALContext context = new SEALContext(parms);

            /*
             * Print the parameters that we have chosen.
             */
            Utilities.PrintLine();
            Console.WriteLine("Set encryption parameters and print");
            Utilities.PrintParameters(context);

            /*
             * When parameters are used to create SEALContext, Microsoft SEAL will first
             * validate those parameters. The parameters chosen here are valid.
             */
            Console.WriteLine("Parameter validation (success): {0}", context.ParameterErrorMessage());

            Console.WriteLine();
            Console.WriteLine("~~~~~~ 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.
             */
            using KeyGenerator keygen = new KeyGenerator(context);
            using PublicKey publicKey = keygen.PublicKey;
            using SecretKey secretKey = keygen.SecretKey;

            /*
             * To be able to encrypt we need to construct an instance of Encryptor. Note
             * that the Encryptor only requires the public key, as expected.
             */
            using Encryptor encryptor = new Encryptor(context, publicKey);

            /*
             * 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.
             */
            using Evaluator evaluator = new 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.
             */
            using Decryptor decryptor = new Decryptor(context, secretKey);

            /*
             * 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 PolyModulusDegree
             * and T is PlainModulus.
             *
             * 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.
             */
            Utilities.PrintLine();
            int x = 6;

            using Plaintext xPlain = new Plaintext(x.ToString());
            Console.WriteLine($"Express x = {x} as a plaintext polynomial 0x{xPlain}.");

            /*
             * We then encrypt the plaintext, producing a ciphertext.
             */
            Utilities.PrintLine();
            using Ciphertext xEncrypted = new Ciphertext();
            Console.WriteLine("Encrypt xPlain to xEncrypted.");
            encryptor.Encrypt(xPlain, xEncrypted);

            /*
             * 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.
             */
            Console.WriteLine($"    + size of freshly encrypted x: {xEncrypted.Size}");

            /*
             * There is plenty of noise budget left in this freshly encrypted ciphertext.
             */
            Console.WriteLine("    + noise budget in freshly encrypted x: {0} bits",
                              decryptor.InvariantNoiseBudget(xEncrypted));

            /*
             * We decrypt the ciphertext and print the resulting plaintext in order to
             * demonstrate correctness of the encryption.
             */
            using Plaintext xDecrypted = new Plaintext();
            Console.Write("    + decryption of encrypted_x: ");
            decryptor.Decrypt(xEncrypted, xDecrypted);
            Console.WriteLine($"0x{xDecrypted} ...... Correct.");

            /*
             * 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.
             */
            Utilities.PrintLine();
            Console.WriteLine("Compute xSqPlusOne (x^2+1).");
            using Ciphertext xSqPlusOne = new Ciphertext();
            evaluator.Square(xEncrypted, xSqPlusOne);
            using Plaintext plainOne = new Plaintext("1");
            evaluator.AddPlainInplace(xSqPlusOne, plainOne);

            /*
             * 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.
             */
            Console.WriteLine($"    + size of xSqPlusOne: {xSqPlusOne.Size}");
            Console.WriteLine("    + noise budget in xSqPlusOne: {0} bits",
                              decryptor.InvariantNoiseBudget(xSqPlusOne));

            /*
             * Even though the size has grown, decryption works as usual as long as noise
             * budget has not reached 0.
             */
            using Plaintext decryptedResult = new Plaintext();
            Console.Write("    + decryption of xSqPlusOne: ");
            decryptor.Decrypt(xSqPlusOne, decryptedResult);
            Console.WriteLine($"0x{decryptedResult} ...... Correct.");

            /*
             * Next, we compute (x + 1)^2.
             */
            Utilities.PrintLine();
            Console.WriteLine("Compute xPlusOneSq ((x+1)^2).");
            using Ciphertext xPlusOneSq = new Ciphertext();
            evaluator.AddPlain(xEncrypted, plainOne, xPlusOneSq);
            evaluator.SquareInplace(xPlusOneSq);
            Console.WriteLine($"    + size of xPlusOneSq: {xPlusOneSq.Size}");
            Console.WriteLine("    + noise budget in xPlusOneSq: {0} bits",
                              decryptor.InvariantNoiseBudget(xPlusOneSq));
            Console.Write("    + decryption of xPlusOneSq: ");
            decryptor.Decrypt(xPlusOneSq, decryptedResult);
            Console.WriteLine($"0x{decryptedResult} ...... Correct.");

            /*
             * Finally, we multiply (x^2 + 1) * (x + 1)^2 * 4.
             */
            Utilities.PrintLine();
            Console.WriteLine("Compute encryptedResult (4(x^2+1)(x+1)^2).");
            using Ciphertext encryptedResult = new Ciphertext();
            using Plaintext plainFour        = new Plaintext("4");
            evaluator.MultiplyPlainInplace(xSqPlusOne, plainFour);
            evaluator.Multiply(xSqPlusOne, xPlusOneSq, encryptedResult);
            Console.WriteLine($"    + size of encrypted_result: {encryptedResult.Size}");
            Console.WriteLine("    + noise budget in encrypted_result: {0} bits",
                              decryptor.InvariantNoiseBudget(encryptedResult));
            Console.WriteLine("NOTE: Decryption can be incorrect if noise budget is zero.");

            Console.WriteLine();
            Console.WriteLine("~~~~~~ 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 xSqPlusOne and
             * xPlusOneSq 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.
             *
             * Here we use the function KeyGenerator.RelinKeysLocal(). In production code
             * it is much better to use KeyGenerator.RelinKeys() instead. We will explain
             * and discuss these differences in `6_Serialization.cs'.
             */
            Utilities.PrintLine();
            Console.WriteLine("Generate locally usable relinearization keys.");
            using RelinKeys relinKeys = keygen.RelinKeysLocal();

            /*
             * We now repeat the computation relinearizing after each multiplication.
             */
            Utilities.PrintLine();
            Console.WriteLine("Compute and relinearize xSquared (x^2),");
            Console.WriteLine(new string(' ', 13) + "then compute xSqPlusOne (x^2+1)");
            using Ciphertext xSquared = new Ciphertext();
            evaluator.Square(xEncrypted, xSquared);
            Console.WriteLine($"    + size of xSquared: {xSquared.Size}");
            evaluator.RelinearizeInplace(xSquared, relinKeys);
            Console.WriteLine("    + size of xSquared (after relinearization): {0}",
                              xSquared.Size);
            evaluator.AddPlain(xSquared, plainOne, xSqPlusOne);
            Console.WriteLine("    + noise budget in xSqPlusOne: {0} bits",
                              decryptor.InvariantNoiseBudget(xSqPlusOne));
            Console.Write("    + decryption of xSqPlusOne: ");
            decryptor.Decrypt(xSqPlusOne, decryptedResult);
            Console.WriteLine($"0x{decryptedResult} ...... Correct.");

            Utilities.PrintLine();
            using Ciphertext xPlusOne = new Ciphertext();
            Console.WriteLine("Compute xPlusOne (x+1),");
            Console.WriteLine(new string(' ', 13) +
                              "then compute and relinearize xPlusOneSq ((x+1)^2).");
            evaluator.AddPlain(xEncrypted, plainOne, xPlusOne);
            evaluator.Square(xPlusOne, xPlusOneSq);
            Console.WriteLine($"    + size of xPlusOneSq: {xPlusOneSq.Size}");
            evaluator.RelinearizeInplace(xPlusOneSq, relinKeys);
            Console.WriteLine("    + noise budget in xPlusOneSq: {0} bits",
                              decryptor.InvariantNoiseBudget(xPlusOneSq));
            Console.Write("    + decryption of xPlusOneSq: ");
            decryptor.Decrypt(xPlusOneSq, decryptedResult);
            Console.WriteLine($"0x{decryptedResult} ...... Correct.");

            Utilities.PrintLine();
            Console.WriteLine("Compute and relinearize encryptedResult (4(x^2+1)(x+1)^2).");
            evaluator.MultiplyPlainInplace(xSqPlusOne, plainFour);
            evaluator.Multiply(xSqPlusOne, xPlusOneSq, encryptedResult);
            Console.WriteLine($"    + size of encryptedResult: {encryptedResult.Size}");
            evaluator.RelinearizeInplace(encryptedResult, relinKeys);
            Console.WriteLine("    + size of encryptedResult (after relinearization): {0}",
                              encryptedResult.Size);
            Console.WriteLine("    + noise budget in encryptedResult: {0} bits",
                              decryptor.InvariantNoiseBudget(encryptedResult));

            Console.WriteLine();
            Console.WriteLine("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.
             */
            Utilities.PrintLine();
            Console.WriteLine("Decrypt encrypted_result (4(x^2+1)(x+1)^2).");
            decryptor.Decrypt(encryptedResult, decryptedResult);
            Console.WriteLine("    + decryption of 4(x^2+1)(x+1)^2 = 0x{0} ...... Correct.",
                              decryptedResult);

            /*
             * For x=6, 4(x^2+1)(x+1)^2 = 7252. Since the plaintext modulus is set to 1024,
             * this result is computed in integers modulo 1024. Therefore the expected output
             * should be 7252 % 1024 == 84, or 0x54 in hexadecimal.
             */

            /*
             * Sometimes we create customized encryption parameters which turn out to be invalid.
             * Microsoft SEAL can interpret the reason why parameters are considered invalid.
             * Here we simply reduce the polynomial modulus degree to make the parameters not
             * compliant with the HomomorphicEncryption.org security standard.
             */
            Utilities.PrintLine();
            Console.WriteLine("An example of invalid parameters");
            parms.PolyModulusDegree       = 2048;
            using SEALContext new_context = new SEALContext(parms);
            Utilities.PrintParameters(context);
            Console.WriteLine("Parameter validation (failed): {0}", new_context.ParameterErrorMessage());

            /*
             * This information is helpful to fix invalid encryption parameters.
             */
        }
Ejemplo n.º 3
0
        static void Main(string[] args)
        {
            EncryptionParameters parms = new EncryptionParameters(SchemeType.BFV);

            parms.PolyModulusDegree = 4096;
            parms.CoeffModulus      = CoeffModulus.BFVDefault(4096);
            parms.PlainModulus      = new Modulus(4096 * 2);


            SEALContext context = new SEALContext(new EncryptionParameters(parms));

            Console.WriteLine(context.ParameterErrorMessage());
            KeyGenerator keygen    = new KeyGenerator(context);
            PublicKey    publicKey = new PublicKey(keygen.PublicKey);
            SecretKey    secretKey = new SecretKey(keygen.SecretKey);

            Encryptor encryptor = new Encryptor(context, publicKey);

            Evaluator evaluator = new Evaluator(context);

            Decryptor decryptor = new Decryptor(context, secretKey);


            Console.WriteLine("Generate locally usable relinearization keys.");
            using RelinKeys relinKeys = keygen.RelinKeysLocal();

            int x = 6;

            using Plaintext xPlain = new Plaintext(x.ToString());

            using Ciphertext xEncrypted = new Ciphertext();
            encryptor.Encrypt(xPlain, xEncrypted);
            using Ciphertext xSqPlusOne      = new Ciphertext();
            using Plaintext decryptedResult  = new Plaintext();
            using Ciphertext xPlusOneSq      = new Ciphertext();
            using Ciphertext encryptedResult = new Ciphertext();
            using Plaintext plainOne         = new Plaintext("1");
            using Plaintext plainFour        = new Plaintext("4");

            /*
             * We now repeat the computation relinearizing after each multiplication.
             */

            Console.WriteLine("Compute and relinearize xSquared (x^2),");
            Console.WriteLine(new string(' ', 13) + "then compute xSqPlusOne (x^2+1)");
            using Ciphertext xSquared = new Ciphertext();
            evaluator.Square(xEncrypted, xSquared);
            Console.WriteLine($"    + size of xSquared: {xSquared.Size}");
            evaluator.RelinearizeInplace(xSquared, relinKeys);
            Console.WriteLine("    + size of xSquared (after relinearization): {0}",
                              xSquared.Size);
            evaluator.AddPlain(xSquared, plainOne, xSqPlusOne);
            Console.WriteLine("    + noise budget in xSqPlusOne: {0} bits",
                              decryptor.InvariantNoiseBudget(xSqPlusOne));
            Console.Write("    + decryption of xSqPlusOne: ");
            decryptor.Decrypt(xSqPlusOne, decryptedResult);
            int q = Convert.ToInt32(decryptedResult.ToString(), 16);

            Console.WriteLine($"{q} ...... Correct.");
            //Console.WriteLine($"0x{decryptedResult} ...... Correct.");


            using Ciphertext xPlusOne = new Ciphertext();
            Console.WriteLine("Compute xPlusOne (x+1),");
            Console.WriteLine(new string(' ', 13) +
                              "then compute and relinearize xPlusOneSq ((x+1)^2).");
            evaluator.AddPlain(xEncrypted, plainOne, xPlusOne);
            evaluator.Square(xPlusOne, xPlusOneSq);
            Console.WriteLine($"    + size of xPlusOneSq: {xPlusOneSq.Size}");
            evaluator.RelinearizeInplace(xPlusOneSq, relinKeys);
            Console.WriteLine("    + noise budget in xPlusOneSq: {0} bits",
                              decryptor.InvariantNoiseBudget(xPlusOneSq));
            Console.Write("    + decryption of xPlusOneSq: ");
            decryptor.Decrypt(xPlusOneSq, decryptedResult);
            int r = Convert.ToInt32(decryptedResult.ToString(), 16);

            Console.WriteLine($"{r} ...... Correct.");


            Console.WriteLine("Compute and relinearize encryptedResult (4(x^2+1)(x+1)^2).");
            evaluator.MultiplyPlainInplace(xSqPlusOne, plainFour);
            evaluator.Multiply(xSqPlusOne, xPlusOneSq, encryptedResult);
            Console.WriteLine($"    + size of encryptedResult: {encryptedResult.Size}");
            evaluator.RelinearizeInplace(encryptedResult, relinKeys);
            Console.WriteLine("    + size of encryptedResult (after relinearization): {0}",
                              encryptedResult.Size);
            Console.WriteLine("    + noise budget in encryptedResult: {0} bits",
                              decryptor.InvariantNoiseBudget(encryptedResult));

            Console.WriteLine();
            Console.WriteLine("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.
             */

            Console.WriteLine("Decrypt encrypted_result (4(x^2+1)(x+1)^2).");
            decryptor.Decrypt(encryptedResult, decryptedResult);
            int t = Convert.ToInt32(decryptedResult.ToString(), 16);

            Console.WriteLine("    + decryption of 4(x^2+1)(x+1)^2 = 0x{0} ...... Correct.", t);


            Plaintext  four           = new Plaintext("4");
            Plaintext  four1          = new Plaintext("5");
            Ciphertext fourEncrypted  = new Ciphertext();
            Ciphertext four1Encrypted = new Ciphertext();

            encryptor.Encrypt(four, fourEncrypted);
            encryptor.Encrypt(four1, four1Encrypted);

            Ciphertext d = new Ciphertext();

            evaluator.Sub(fourEncrypted, four1Encrypted, d);
            Plaintext temp = new Plaintext();

            decryptor.Decrypt(d, temp);

            Console.WriteLine($"{Convert.ToInt32(temp.ToString(), 16)}");
        }