public void SEALContextCreateTest() { EncryptionParameters encParams1 = new EncryptionParameters(SchemeType.BFV); EncryptionParameters encParams2 = new EncryptionParameters(SchemeType.CKKS); SEALContext context1 = new SEALContext(encParams1); SEALContext context2 = new SEALContext(encParams2); Assert.IsNotNull(context1); Assert.IsNotNull(context2); Assert.IsFalse(context1.ParametersSet); Assert.IsFalse(context2.ParametersSet); Assert.AreNotSame(context1.FirstParmsId, context1.LastParmsId); Assert.AreEqual(context1.FirstParmsId, context1.LastParmsId); SEALContext.ContextData data1 = context2.FirstContextData; SEALContext.ContextData data2 = context2.GetContextData(context2.FirstParmsId); Assert.AreNotSame(data1, data2); ulong[] totalCoeffMod1 = data1.TotalCoeffModulus; ulong[] totalCoeffMod2 = data2.TotalCoeffModulus; int bitCount1 = data1.TotalCoeffModulusBitCount; int bitCount2 = data2.TotalCoeffModulusBitCount; Assert.AreEqual(bitCount1, bitCount2); Assert.AreEqual(totalCoeffMod1.Length, totalCoeffMod2.Length); for (int i = 0; i < totalCoeffMod1.Length; i++) { Assert.AreEqual(totalCoeffMod1[i], totalCoeffMod2[i]); } }
public void ResizeTest() { SEALContext context = GlobalContext.Context; ParmsId parms = context.FirstParmsId; Ciphertext cipher = new Ciphertext(context, parms); Assert.AreEqual(2ul, cipher.SizeCapacity); Assert.AreEqual(16384ul, cipher.UInt64CountCapacity); cipher.Reserve(context, parms, sizeCapacity: 10); Assert.AreEqual(10ul, cipher.SizeCapacity); Assert.AreEqual(16384ul * 5, cipher.UInt64CountCapacity); Ciphertext cipher2 = new Ciphertext(); Assert.AreEqual(2ul, cipher2.SizeCapacity); cipher2.Reserve(context, 5); Assert.AreEqual(5ul, cipher2.SizeCapacity); Ciphertext cipher3 = new Ciphertext(); Assert.AreEqual(2ul, cipher3.SizeCapacity); cipher3.Reserve(4); Assert.AreEqual(4ul, cipher3.SizeCapacity); Ciphertext cipher4 = new Ciphertext(context); cipher4.Resize(context, context.GetContextData(context.FirstParmsId).NextContextData.Parms.ParmsId, 4); Assert.AreEqual(10ul, cipher.SizeCapacity); Ciphertext cipher5 = new Ciphertext(context); cipher5.Resize(context, 6ul); Assert.AreEqual(2ul, cipher5.SizeCapacity); }
private static void ExampleCKKSBasics() { Utilities.PrintExampleBanner("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. */ using EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS); /* * We saw in `2_Encoders.cs' 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 CoeffModulus, 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.cs'). * As modulus switching, it removes the last of the primes from CoeffModulus, * 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 CoeffModulus. * * More precisely, suppose that the scale in a CKKS ciphertext is S, and the * last prime in the current CoeffModulus (for the ciphertext) is P. Rescaling * to the next level changes the scale to S/P, and removes the prime P from the * CoeffModulus, 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 CoeffModulus 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 CoeffModulus. This will * give the highest precision when decrypting; * (2) Choose another 60-bit prime as the last element of CoeffModulus, 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 CoeffModulus is 200 bits total, which is below the bound for our * PolyModulusDegree: CoeffModulus.MaxBitCount(8192) returns 218. */ ulong polyModulusDegree = 8192; parms.PolyModulusDegree = polyModulusDegree; parms.CoeffModulus = CoeffModulus.Create( polyModulusDegree, new int[] { 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. */ double scale = Math.Pow(2.0, 40); using SEALContext context = new SEALContext(parms); Utilities.PrintParameters(context); Console.WriteLine(); using KeyGenerator keygen = new KeyGenerator(context); using PublicKey publicKey = keygen.PublicKey; using SecretKey secretKey = keygen.SecretKey; using RelinKeys relinKeys = keygen.RelinKeysLocal(); using Encryptor encryptor = new Encryptor(context, publicKey); using Evaluator evaluator = new Evaluator(context); using Decryptor decryptor = new Decryptor(context, secretKey); using CKKSEncoder encoder = new CKKSEncoder(context); ulong slotCount = encoder.SlotCount; Console.WriteLine($"Number of slots: {slotCount}"); List <double> input = new List <double>((int)slotCount); double currPoint = 0, stepSize = 1.0 / (slotCount - 1); for (ulong i = 0; i < slotCount; i++, currPoint += stepSize) { input.Add(currPoint); } Console.WriteLine("Input vector:"); Utilities.PrintVector(input, 3, 7); Console.WriteLine("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. */ using Plaintext plainCoeff3 = new Plaintext(), plainCoeff1 = new Plaintext(), plainCoeff0 = new Plaintext(); encoder.Encode(3.14159265, scale, plainCoeff3); encoder.Encode(0.4, scale, plainCoeff1); encoder.Encode(1.0, scale, plainCoeff0); using Plaintext xPlain = new Plaintext(); Utilities.PrintLine(); Console.WriteLine("Encode input vectors."); encoder.Encode(input, scale, xPlain); using Ciphertext x1Encrypted = new Ciphertext(); encryptor.Encrypt(xPlain, x1Encrypted); /* * To compute x^3 we first compute x^2 and relinearize. However, the scale has * now grown to 2^80. */ using Ciphertext x3Encrypted = new Ciphertext(); Utilities.PrintLine(); Console.WriteLine("Compute x^2 and relinearize:"); evaluator.Square(x1Encrypted, x3Encrypted); evaluator.RelinearizeInplace(x3Encrypted, relinKeys); Console.WriteLine(" + Scale of x^2 before rescale: {0} bits", Math.Log(x3Encrypted.Scale, newBase: 2)); /* * 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. */ Utilities.PrintLine(); Console.WriteLine("Rescale x^2."); evaluator.RescaleToNextInplace(x3Encrypted); Console.WriteLine(" + Scale of x^2 after rescale: {0} bits", Math.Log(x3Encrypted.Scale, newBase: 2)); /* * Now x3Encrypted is at a different level than x1Encrypted, which prevents us * from multiplying them to compute x^3. We could simply switch x1Encrypted to * the next parameters in the modulus switching chain. However, since we still * need to multiply the x^3 term with PI (plainCoeff3), 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. */ Utilities.PrintLine(); Console.WriteLine("Compute and rescale PI*x."); using Ciphertext x1EncryptedCoeff3 = new Ciphertext(); evaluator.MultiplyPlain(x1Encrypted, plainCoeff3, x1EncryptedCoeff3); Console.WriteLine(" + Scale of PI*x before rescale: {0} bits", Math.Log(x1EncryptedCoeff3.Scale, newBase: 2)); evaluator.RescaleToNextInplace(x1EncryptedCoeff3); Console.WriteLine(" + Scale of PI*x after rescale: {0} bits", Math.Log(x1EncryptedCoeff3.Scale, newBase: 2)); /* * Since x3Encrypted and x1EncryptedCoeff3 have the same exact scale and use * the same encryption parameters, we can multiply them together. We write the * result to x3Encrypted, 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. */ Utilities.PrintLine(); Console.WriteLine("Compute, relinearize, and rescale (PI*x)*x^2."); evaluator.MultiplyInplace(x3Encrypted, x1EncryptedCoeff3); evaluator.RelinearizeInplace(x3Encrypted, relinKeys); Console.WriteLine(" + Scale of PI*x^3 before rescale: {0} bits", Math.Log(x3Encrypted.Scale, newBase: 2)); evaluator.RescaleToNextInplace(x3Encrypted); Console.WriteLine(" + Scale of PI*x^3 after rescale: {0} bits", Math.Log(x3Encrypted.Scale, newBase: 2)); /* * Next we compute the degree one term. All this requires is one MultiplyPlain * with plainCoeff1. We overwrite x1Encrypted with the result. */ Utilities.PrintLine(); Console.WriteLine("Compute and rescale 0.4*x."); evaluator.MultiplyPlainInplace(x1Encrypted, plainCoeff1); Console.WriteLine(" + Scale of 0.4*x before rescale: {0} bits", Math.Log(x1Encrypted.Scale, newBase: 2)); evaluator.RescaleToNextInplace(x1Encrypted); Console.WriteLine(" + Scale of 0.4*x after rescale: {0} bits", Math.Log(x1Encrypted.Scale, newBase: 2)); /* * 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 (ParmsId) match. If there * is a mismatch, Evaluator will throw an exception. */ Console.WriteLine(); Utilities.PrintLine(); Console.WriteLine("Parameters used by all three terms are different:"); Console.WriteLine(" + Modulus chain index for x3Encrypted: {0}", context.GetContextData(x3Encrypted.ParmsId).ChainIndex); Console.WriteLine(" + Modulus chain index for x1Encrypted: {0}", context.GetContextData(x1Encrypted.ParmsId).ChainIndex); Console.WriteLine(" + Modulus chain index for plainCoeff0: {0}", context.GetContextData(plainCoeff0.ParmsId).ChainIndex); Console.WriteLine(); /* * 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/P2 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. */ Utilities.PrintLine(); Console.WriteLine("The exact scales of all three terms are different:"); Console.WriteLine(" + Exact scale in PI*x^3: {0:0.0000000000}", x3Encrypted.Scale); Console.WriteLine(" + Exact scale in 0.4*x: {0:0.0000000000}", x1Encrypted.Scale); Console.WriteLine(" + Exact scale in 1: {0:0.0000000000}", plainCoeff0.Scale); Console.WriteLine(); /* * 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 MultiplyPlain * with 0.4*x, and finally rescale. In this case we would need to additionally * make sure to encode 1 with appropriate encryption parameters (ParmsId). * * 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. */ Utilities.PrintLine(); Console.WriteLine("Normalize scales to 2^40."); x3Encrypted.Scale = Math.Pow(2.0, 40); x1Encrypted.Scale = Math.Pow(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. */ Utilities.PrintLine(); Console.WriteLine("Normalize encryption parameters to the lowest level."); ParmsId lastParmsId = x3Encrypted.ParmsId; evaluator.ModSwitchToInplace(x1Encrypted, lastParmsId); evaluator.ModSwitchToInplace(plainCoeff0, lastParmsId); /* * All three ciphertexts are now compatible and can be added. */ Utilities.PrintLine(); Console.WriteLine("Compute PI*x^3 + 0.4*x + 1."); using Ciphertext encryptedResult = new Ciphertext(); evaluator.Add(x3Encrypted, x1Encrypted, encryptedResult); evaluator.AddPlainInplace(encryptedResult, plainCoeff0); /* * First print the true result. */ using Plaintext plainResult = new Plaintext(); Utilities.PrintLine(); Console.WriteLine("Decrypt and decode PI * x ^ 3 + 0.4x + 1."); Console.WriteLine(" + Expected result:"); List <double> trueResult = new List <double>(input.Count); foreach (double x in input) { trueResult.Add((3.14159265 * x * x + 0.4) * x + 1); } Utilities.PrintVector(trueResult, 3, 7); /* * We decrypt, decode, and print the result. */ decryptor.Decrypt(encryptedResult, plainResult); List <double> result = new List <double>(); encoder.Decode(plainResult, result); Console.WriteLine(" + Computed result ...... Correct."); Utilities.PrintVector(result, 3, 7); /* * While we did not show any computations on complex numbers in these examples, * the CKKSEncoder would allow us to have done that just as easily. Additions * and multiplications of complex numbers behave just as one would expect. */ }