public static SEALContext GetContext() { var encryptionParameters = new EncryptionParameters(SchemeType.BFV) { PolyModulusDegree = 32768, CoeffModulus = CoeffModulus.BFVDefault(32768) }; //encryptionParameters.PlainModulus = new Modulus(1024); /* * To enable batching, we need to set the plain_modulus to be a prime number * congruent to 1 modulo 2*PolyModulusDegree. Microsoft SEAL provides a helper * method for finding such a prime. In this example we create a 20-bit prime * that supports batching. */ encryptionParameters.PlainModulus = PlainModulus.Batching(32768, 20); SEALContext new_context = new SEALContext(encryptionParameters); return(new_context); }
/* * In `1_BFV_Basics.cs' we showed how to perform a very simple computation using the * BFV scheme. The computation was performed modulo the PlainModulus parameter, and * utilized only one coefficient from a BFV plaintext polynomial. This approach has * two notable problems: * * (1) Practical applications typically use integer or real number arithmetic, * not modular arithmetic; * (2) We used only one coefficient of the plaintext polynomial. This is really * wasteful, as the plaintext polynomial is large and will in any case be * encrypted in its entirety. * * For (1), one may ask why not just increase the PlainModulus parameter until no * overflow occurs, and the computations behave as in integer arithmetic. The problem * is that increasing PlainModulus increases noise budget consumption, and decreases * the initial noise budget too. * * In these examples we will discuss other ways of laying out data into plaintext * elements (encoding) that allow more computations without data type overflow, and * can allow the full plaintext polynomial to be utilized. */ private static void ExampleBatchEncoder() { Utilities.PrintExampleBanner("Example: Encoders / Batch Encoder"); /* * [BatchEncoder] (For BFV scheme only) * * Let N denote the PolyModulusDegree and T denote the PlainModulus. 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 without batching. */ using EncryptionParameters parms = new EncryptionParameters(SchemeType.BFV); ulong polyModulusDegree = 8192; parms.PolyModulusDegree = polyModulusDegree; parms.CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree); /* * To enable batching, we need to set the plain_modulus to be a prime number * congruent to 1 modulo 2*PolyModulusDegree. Microsoft SEAL provides a helper * method for finding such a prime. In this example we create a 20-bit prime * that supports batching. */ parms.PlainModulus = PlainModulus.Batching(polyModulusDegree, 20); using SEALContext context = new SEALContext(parms); Utilities.PrintParameters(context); Console.WriteLine(); /* * We can verify that batching is indeed enabled by looking at the encryption * parameter qualifiers created by SEALContext. */ using var qualifiers = context.FirstContextData.Qualifiers; Console.WriteLine($"Batching enabled: {qualifiers.UsingBatching}"); using KeyGenerator keygen = new KeyGenerator(context); using SecretKey secretKey = keygen.SecretKey; keygen.CreatePublicKey(out PublicKey publicKey); keygen.CreateRelinKeys(out RelinKeys relinKeys); using Encryptor encryptor = new Encryptor(context, publicKey); using Evaluator evaluator = new Evaluator(context); using Decryptor decryptor = new Decryptor(context, secretKey); /* * Batching is done through an instance of the BatchEncoder class. */ using BatchEncoder batchEncoder = new BatchEncoder(context); /* * The total number of batching `slots' equals the PolyModulusDegree, 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 PlainModulus. */ ulong slotCount = batchEncoder.SlotCount; ulong rowSize = slotCount / 2; Console.WriteLine($"Plaintext matrix row size: {rowSize}"); /* * The matrix plaintext is simply given to BatchEncoder as a flattened vector * of numbers. The first `rowSize' 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 ] */ ulong[] podMatrix = new ulong[slotCount]; podMatrix[0] = 0; podMatrix[1] = 1; podMatrix[2] = 2; podMatrix[3] = 3; podMatrix[rowSize] = 4; podMatrix[rowSize + 1] = 5; podMatrix[rowSize + 2] = 6; podMatrix[rowSize + 3] = 7; Console.WriteLine("Input plaintext matrix:"); Utilities.PrintMatrix(podMatrix, (int)rowSize); /* * First we use BatchEncoder to encode the matrix into a plaintext polynomial. */ using Plaintext plainMatrix = new Plaintext(); Utilities.PrintLine(); Console.WriteLine("Encode plaintext matrix:"); batchEncoder.Encode(podMatrix, plainMatrix); /* * We can instantly decode to verify correctness of the encoding. Note that no * encryption or decryption has yet taken place. */ List <ulong> podResult = new List <ulong>(); Console.WriteLine(" + Decode plaintext matrix ...... Correct."); batchEncoder.Decode(plainMatrix, podResult); Utilities.PrintMatrix(podResult, (int)rowSize); /* * Next we encrypt the encoded plaintext. */ using Ciphertext encryptedMatrix = new Ciphertext(); Utilities.PrintLine(); Console.WriteLine("Encrypt plainMatrix to encryptedMatrix."); encryptor.Encrypt(plainMatrix, encryptedMatrix); Console.WriteLine(" + Noise budget in encryptedMatrix: {0} bits", decryptor.InvariantNoiseBudget(encryptedMatrix)); /* * 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. */ ulong[] podMatrix2 = new ulong[slotCount]; for (ulong i = 0; i < slotCount; i++) { podMatrix2[i] = (i & 1) + 1; } using Plaintext plainMatrix2 = new Plaintext(); batchEncoder.Encode(podMatrix2, plainMatrix2); Console.WriteLine(); Console.WriteLine("Second input plaintext matrix:"); Utilities.PrintMatrix(podMatrix2, (int)rowSize); /* * We now add the second (plaintext) matrix to the encrypted matrix, and square * the sum. */ Utilities.PrintLine(); Console.WriteLine("Sum, square, and relinearize."); evaluator.AddPlainInplace(encryptedMatrix, plainMatrix2); evaluator.SquareInplace(encryptedMatrix); evaluator.RelinearizeInplace(encryptedMatrix, relinKeys); /* * How much noise budget do we have left? */ Console.WriteLine(" + Noise budget in result: {0} bits", decryptor.InvariantNoiseBudget(encryptedMatrix)); /* * We decrypt and decompose the plaintext to recover the result as a matrix. */ using Plaintext plainResult = new Plaintext(); Utilities.PrintLine(); Console.WriteLine("Decrypt and decode result."); decryptor.Decrypt(encryptedMatrix, plainResult); batchEncoder.Decode(plainResult, podResult); Console.WriteLine(" + Result plaintext matrix ...... Correct."); Utilities.PrintMatrix(podResult, (int)rowSize); /* * Batching allows us to efficiently use the full plaintext polynomial when the * desired encrypted computation is highly parallelizable. However, it has not * solved the other problem mentioned in the beginning of this file: each slot * holds only an integer modulo plain_modulus, and unless plain_modulus is very * large, we can quickly encounter data type overflow and get unexpected results * when integer computations are desired. Note that overflow cannot be detected * in encrypted form. The CKKS scheme (and the CKKSEncoder) addresses the data * type overflow issue, but at the cost of yielding only approximate results. */ }
/* * Both the BFV scheme (with BatchEncoder) as well as the CKKS scheme support native * vectorized computations on encrypted numbers. In addition to computing slot-wise, * it is possible to rotate the encrypted vectors cyclically. */ private static void ExampleRotationBFV() { Utilities.PrintExampleBanner("Example: Rotation / Rotation in BFV"); using EncryptionParameters parms = new EncryptionParameters(SchemeType.BFV); ulong polyModulusDegree = 8192; parms.PolyModulusDegree = polyModulusDegree; parms.CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree); parms.PlainModulus = PlainModulus.Batching(polyModulusDegree, 20); using SEALContext context = new SEALContext(parms); Utilities.PrintParameters(context); Console.WriteLine(); using KeyGenerator keygen = new KeyGenerator(context); using SecretKey secretKey = keygen.SecretKey; keygen.CreatePublicKey(out PublicKey publicKey); keygen.CreateRelinKeys(out RelinKeys relinKeys); using Encryptor encryptor = new Encryptor(context, publicKey); using Evaluator evaluator = new Evaluator(context); using Decryptor decryptor = new Decryptor(context, secretKey); using BatchEncoder batchEncoder = new BatchEncoder(context); ulong slotCount = batchEncoder.SlotCount; ulong rowSize = slotCount / 2; Console.WriteLine($"Plaintext matrix row size: {rowSize}"); ulong[] podMatrix = new ulong[slotCount]; podMatrix[0] = 0; podMatrix[1] = 1; podMatrix[2] = 2; podMatrix[3] = 3; podMatrix[rowSize] = 4; podMatrix[rowSize + 1] = 5; podMatrix[rowSize + 2] = 6; podMatrix[rowSize + 3] = 7; Console.WriteLine("Input plaintext matrix:"); Utilities.PrintMatrix(podMatrix, (int)rowSize); Console.WriteLine(); /* * First we use BatchEncoder to encode the matrix into a plaintext. We encrypt * the plaintext as usual. */ Utilities.PrintLine(); using Plaintext plainMatrix = new Plaintext(); Console.WriteLine("Encode and encrypt."); batchEncoder.Encode(podMatrix, plainMatrix); using Ciphertext encryptedMatrix = new Ciphertext(); encryptor.Encrypt(plainMatrix, encryptedMatrix); Console.WriteLine(" + Noise budget in fresh encryption: {0} bits", decryptor.InvariantNoiseBudget(encryptedMatrix)); Console.WriteLine(); /* * Rotations require yet another type of special key called `Galois keys'. These * are easily obtained from the KeyGenerator. */ keygen.CreateGaloisKeys(out GaloisKeys galoisKeys); /* * Now rotate both matrix rows 3 steps to the left, decrypt, decode, and print. */ Utilities.PrintLine(); Console.WriteLine("Rotate rows 3 steps left."); evaluator.RotateRowsInplace(encryptedMatrix, 3, galoisKeys); using Plaintext plainResult = new Plaintext(); Console.WriteLine(" + Noise budget after rotation: {0} bits", decryptor.InvariantNoiseBudget(encryptedMatrix)); Console.WriteLine(" + Decrypt and decode ...... Correct."); decryptor.Decrypt(encryptedMatrix, plainResult); List <ulong> podResult = new List <ulong>(); batchEncoder.Decode(plainResult, podResult); Utilities.PrintMatrix(podResult, (int)rowSize); /* * We can also rotate the columns, i.e., swap the rows. */ Utilities.PrintLine(); Console.WriteLine("Rotate columns."); evaluator.RotateColumnsInplace(encryptedMatrix, galoisKeys); Console.WriteLine(" + Noise budget after rotation: {0} bits", decryptor.InvariantNoiseBudget(encryptedMatrix)); Console.WriteLine(" + Decrypt and decode ...... Correct."); decryptor.Decrypt(encryptedMatrix, plainResult); batchEncoder.Decode(plainResult, podResult); Utilities.PrintMatrix(podResult, (int)rowSize); /* * Finally, we rotate the rows 4 steps to the right, decrypt, decode, and print. */ Utilities.PrintLine(); Console.WriteLine("Rotate rows 4 steps right."); evaluator.RotateRowsInplace(encryptedMatrix, -4, galoisKeys); Console.WriteLine(" + Noise budget after rotation: {0} bits", decryptor.InvariantNoiseBudget(encryptedMatrix)); Console.WriteLine(" + Decrypt and decode ...... Correct."); decryptor.Decrypt(encryptedMatrix, plainResult); batchEncoder.Decode(plainResult, podResult); Utilities.PrintMatrix(podResult, (int)rowSize); /* * Note that rotations do not consume any noise budget. However, this is only * the case when the special prime is at least as large as the other primes. The * same holds for relinearization. Microsoft SEAL does not require that the * special prime is of any particular size, so ensuring this is the case is left * for the user to do. */ }
private static void ExampleLevels() { Utilities.PrintExampleBanner("Example: Levels"); /* * In this examples we describe the concept of `levels' in BFV and CKKS and the * related objects that represent them in Microsoft SEAL. * * In Microsoft SEAL a set of encryption parameters (excluding the random number * generator) is identified uniquely by a 256-bit hash of the parameters. This * hash is called the `ParmsId' and can be easily accessed and printed at any * time. The hash will change as soon as any of the parameters is changed. * * When a SEALContext is created from a given EncryptionParameters instance, * Microsoft SEAL automatically creates a so-called `modulus switching chain', * which is a chain of other encryption parameters derived from the original set. * The parameters in the modulus switching chain are the same as the original * parameters with the exception that size of the coefficient modulus is * decreasing going down the chain. More precisely, each parameter set in the * chain attempts to remove the last coefficient modulus prime from the * previous set; this continues until the parameter set is no longer valid * (e.g., PlainModulus is larger than the remaining CoeffModulus). It is easy * to walk through the chain and access all the parameter sets. Additionally, * each parameter set in the chain has a `chain index' that indicates its * position in the chain so that the last set has index 0. We say that a set * of encryption parameters, or an object carrying those encryption parameters, * is at a higher level in the chain than another set of parameters if its the * chain index is bigger, i.e., it is earlier in the chain. * * Each set of parameters in the chain involves unique pre-computations performed * when the SEALContext is created, and stored in a SEALContext.ContextData * object. The chain is basically a linked list of SEALContext.ContextData * objects, and can easily be accessed through the SEALContext at any time. Each * node can be identified by the ParmsId of its specific encryption parameters * (PolyModulusDegree remains the same but CoeffModulus varies). */ using EncryptionParameters parms = new EncryptionParameters(SchemeType.BFV); ulong polyModulusDegree = 8192; parms.PolyModulusDegree = polyModulusDegree; /* * In this example we use a custom CoeffModulus, consisting of 5 primes of * sizes 50, 30, 30, 50, and 50 bits. Note that this is still OK according to * the explanation in `1_BFV_Basics.cs'. Indeed, * * CoeffModulus.MaxBitCount(polyModulusDegree) * * returns 218 (greater than 50+30+30+50+50=210). * * Due to the modulus switching chain, the order of the 5 primes is significant. * The last prime has a special meaning and we call it the `special prime'. Thus, * the first parameter set in the modulus switching chain is the only one that * involves the special prime. All key objects, such as SecretKey, are created * at this highest level. All data objects, such as Ciphertext, can be only at * lower levels. The special modulus should be as large as the largest of the * other primes in the CoeffModulus, although this is not a strict requirement. * * special prime +---------+ | | v | CoeffModulus: { 50, 30, 30, 50, 50 } +---+ Level 4 (all keys; `key level') | | | CoeffModulus: { 50, 30, 30, 50 } +---+ Level 3 (highest `data level') | | | CoeffModulus: { 50, 30, 30 } +---+ Level 2 | | | CoeffModulus: { 50, 30 } +---+ Level 1 | | | CoeffModulus: { 50 } +---+ Level 0 (lowest level) */ parms.CoeffModulus = CoeffModulus.Create( polyModulusDegree, new int[] { 50, 30, 30, 50, 50 }); /* * In this example the PlainModulus does not play much of a role; we choose * some reasonable value. */ parms.PlainModulus = PlainModulus.Batching(polyModulusDegree, 20); using SEALContext context = new SEALContext(parms); Utilities.PrintParameters(context); /* * There are convenience method for accessing the SEALContext.ContextData for * some of the most important levels: * * SEALContext.KeyContextData: access to key level ContextData * SEALContext.FirstContextData: access to highest data level ContextData * SEALContext.LastContextData: access to lowest level ContextData * * We iterate over the chain and print the ParmsId for each set of parameters. */ Console.WriteLine(); Utilities.PrintLine(); Console.WriteLine("Print the modulus switching chain."); /* * First print the key level parameter information. */ SEALContext.ContextData contextData = context.KeyContextData; Console.WriteLine("----> Level (chain index): {0} ...... KeyContextData", contextData.ChainIndex); Console.WriteLine($" ParmsId: {contextData.ParmsId}"); Console.Write(" CoeffModulus primes: "); foreach (Modulus prime in contextData.Parms.CoeffModulus) { Console.Write($"{Utilities.ULongToString(prime.Value)} "); } Console.WriteLine(); Console.WriteLine("\\"); Console.Write(" \\--> "); /* * Next iterate over the remaining (data) levels. */ contextData = context.FirstContextData; while (null != contextData) { Console.Write($"Level (chain index): {contextData.ChainIndex}"); if (contextData.ParmsId.Equals(context.FirstParmsId)) { Console.WriteLine(" ...... FirstContextData"); } else if (contextData.ParmsId.Equals(context.LastParmsId)) { Console.WriteLine(" ...... LastContextData"); } else { Console.WriteLine(); } Console.WriteLine($" ParmsId: {contextData.ParmsId}"); Console.Write(" CoeffModulus primes: "); foreach (Modulus prime in contextData.Parms.CoeffModulus) { Console.Write($"{Utilities.ULongToString(prime.Value)} "); } Console.WriteLine(); Console.WriteLine("\\"); Console.Write(" \\--> "); /* * Step forward in the chain. */ contextData = contextData.NextContextData; } Console.WriteLine("End of chain reached"); Console.WriteLine(); /* * We create some keys and check that indeed they appear at the highest level. */ using KeyGenerator keygen = new KeyGenerator(context); using SecretKey secretKey = keygen.SecretKey; keygen.CreatePublicKey(out PublicKey publicKey); keygen.CreateRelinKeys(out RelinKeys relinKeys); Utilities.PrintLine(); Console.WriteLine("Print the parameter IDs of generated elements."); Console.WriteLine($" + publicKey: {publicKey.ParmsId}"); Console.WriteLine($" + secretKey: {secretKey.ParmsId}"); Console.WriteLine($" + relinKeys: {relinKeys.ParmsId}"); using Encryptor encryptor = new Encryptor(context, publicKey); using Evaluator evaluator = new Evaluator(context); using Decryptor decryptor = new Decryptor(context, secretKey); /* * In the BFV scheme plaintexts do not carry a ParmsId, but ciphertexts do. Note * how the freshly encrypted ciphertext is at the highest data level. */ using Plaintext plain = new Plaintext("1x^3 + 2x^2 + 3x^1 + 4"); using Ciphertext encrypted = new Ciphertext(); encryptor.Encrypt(plain, encrypted); Console.WriteLine($" + plain: {plain.ParmsId} (not set in BFV)"); Console.WriteLine($" + encrypted: {encrypted.ParmsId}"); Console.WriteLine(); /* * `Modulus switching' is a technique of changing the ciphertext parameters down * in the chain. The function Evaluator.ModSwitchToNext always switches to the * next level down the chain, whereas Evaluator.ModSwitchTo switches to a parameter * set down the chain corresponding to a given ParmsId. However, it is impossible * to switch up in the chain. */ Utilities.PrintLine(); Console.WriteLine("Perform modulus switching on encrypted and print."); contextData = context.FirstContextData; Console.Write("----> "); while (null != contextData.NextContextData) { Console.WriteLine($"Level (chain index): {contextData.ChainIndex}"); Console.WriteLine($" ParmsId of encrypted: {contextData.ParmsId}"); Console.WriteLine(" Noise budget at this level: {0} bits", decryptor.InvariantNoiseBudget(encrypted)); Console.WriteLine("\\"); Console.Write(" \\--> "); evaluator.ModSwitchToNextInplace(encrypted); contextData = contextData.NextContextData; } Console.WriteLine($"Level (chain index): {contextData.ChainIndex}"); Console.WriteLine($" ParmsId of encrypted: {contextData.ParmsId}"); Console.WriteLine(" Noise budget at this level: {0} bits", decryptor.InvariantNoiseBudget(encrypted)); Console.WriteLine("\\"); Console.Write(" \\--> "); Console.WriteLine("End of chain reached"); Console.WriteLine(); /* * At this point it is hard to see any benefit in doing this: we lost a huge * amount of noise budget (i.e., computational power) at each switch and seemed * to get nothing in return. Decryption still works. */ Utilities.PrintLine(); Console.WriteLine("Decrypt still works after modulus switching."); decryptor.Decrypt(encrypted, plain); Console.WriteLine($" + Decryption of encrypted: {plain} ...... Correct."); Console.WriteLine(); /* * However, there is a hidden benefit: the size of the ciphertext depends * linearly on the number of primes in the coefficient modulus. Thus, if there * is no need or intention to perform any further computations on a given * ciphertext, we might as well switch it down to the smallest (last) set of * parameters in the chain before sending it back to the secret key holder for * decryption. * * Also the lost noise budget is actually not an issue at all, if we do things * right, as we will see below. * * First we recreate the original ciphertext and perform some computations. */ Console.WriteLine("Computation is more efficient with modulus switching."); Utilities.PrintLine(); Console.WriteLine("Compute the eight power."); encryptor.Encrypt(plain, encrypted); Console.WriteLine(" + Noise budget fresh: {0} bits", decryptor.InvariantNoiseBudget(encrypted)); evaluator.SquareInplace(encrypted); evaluator.RelinearizeInplace(encrypted, relinKeys); Console.WriteLine(" + Noise budget of the 2nd power: {0} bits", decryptor.InvariantNoiseBudget(encrypted)); evaluator.SquareInplace(encrypted); evaluator.RelinearizeInplace(encrypted, relinKeys); Console.WriteLine(" + Noise budget of the 4th power: {0} bits", decryptor.InvariantNoiseBudget(encrypted)); /* * Surprisingly, in this case modulus switching has no effect at all on the * noise budget. */ evaluator.ModSwitchToNextInplace(encrypted); Console.WriteLine(" + Noise budget after modulus switching: {0} bits", decryptor.InvariantNoiseBudget(encrypted)); /* * This means that there is no harm at all in dropping some of the coefficient * modulus after doing enough computations. In some cases one might want to * switch to a lower level slightly earlier, actually sacrificing some of the * noise budget in the process, to gain computational performance from having * smaller parameters. We see from the print-out that the next modulus switch * should be done ideally when the noise budget is down to around 25 bits. */ evaluator.SquareInplace(encrypted); evaluator.RelinearizeInplace(encrypted, relinKeys); Console.WriteLine(" + Noise budget of the 8th power: {0} bits", decryptor.InvariantNoiseBudget(encrypted)); evaluator.ModSwitchToNextInplace(encrypted); Console.WriteLine(" + Noise budget after modulus switching: {0} bits", decryptor.InvariantNoiseBudget(encrypted)); /* * At this point the ciphertext still decrypts correctly, has very small size, * and the computation was as efficient as possible. Note that the decryptor * can be used to decrypt a ciphertext at any level in the modulus switching * chain. */ decryptor.Decrypt(encrypted, plain); Console.WriteLine(" + Decryption of the 8th power (hexadecimal) ...... Correct."); Console.WriteLine($" {plain}"); Console.WriteLine(); /* * In BFV modulus switching is not necessary and in some cases the user might * not want to create the modulus switching chain, except for the highest two * levels. This can be done by passing a bool `false' to SEALContext constructor. */ using SEALContext context2 = new SEALContext(parms, expandModChain: false); /* * We can check that indeed the modulus switching chain has been created only * for the highest two levels (key level and highest data level). The following * loop should execute only once. */ Console.WriteLine("Optionally disable modulus switching chain expansion."); Utilities.PrintLine(); Console.WriteLine("Print the modulus switching chain."); Console.Write("----> "); for (contextData = context2.KeyContextData; null != contextData; contextData = contextData.NextContextData) { Console.WriteLine($"Level (chain index): {contextData.ChainIndex}"); Console.WriteLine($" ParmsId of encrypted: {contextData.ParmsId}"); Console.Write(" CoeffModulus primes: "); foreach (Modulus prime in contextData.Parms.CoeffModulus) { Console.Write($"{Utilities.ULongToString(prime.Value)} "); } Console.WriteLine(); Console.WriteLine("\\"); Console.Write(" \\--> "); } Console.WriteLine("End of chain reached"); Console.WriteLine(); /* * It is very important to understand how this example works since in the CKKS * scheme modulus switching has a much more fundamental purpose and the next * examples will be difficult to understand unless these basic properties are * totally clear. */ }
private static void ExampleBGVBasics() { Utilities.PrintExampleBanner("Example: BGV Basics"); /* * As an example, we evaluate the degree 8 polynomial * * x^8 * * over an encrypted x over integers 1, 2, 3, 4. The coefficients of the * polynomial can be considered as plaintext inputs, as we will see below. The * computation is done modulo the plain_modulus 1032193. * * Computing over encrypted data in the BGV scheme is similar to that in BFV. * The purpose of this example is mainly to explain the differences between BFV * and BGV in terms of ciphertext coefficient modulus selection and noise control. * * Most of the following code are repeated from "BFV basics" and "encoders" examples. */ /* * Note that scheme_type is now "bgv". */ using EncryptionParameters parms = new EncryptionParameters(SchemeType.BGV); ulong polyModulusDegree = 8192; parms.PolyModulusDegree = polyModulusDegree; /* * We can certainly use BFVDefault coeff_modulus. In later parts of this example, * we will demonstrate how to choose coeff_modulus that is more useful in BGV. */ parms.CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree); parms.PlainModulus = PlainModulus.Batching(polyModulusDegree, 20); 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); using KeyGenerator keygen = new KeyGenerator(context); using SecretKey secretKey = keygen.SecretKey; keygen.CreatePublicKey(out PublicKey publicKey); keygen.CreateRelinKeys(out RelinKeys relinKeys); using Encryptor encryptor = new Encryptor(context, publicKey); using Evaluator evaluator = new Evaluator(context); using Decryptor decryptor = new Decryptor(context, secretKey); /* * Batching and slot operations are the same in BFV and BGV. */ using BatchEncoder batchEncoder = new BatchEncoder(context); ulong slotCount = batchEncoder.SlotCount; ulong rowSize = slotCount / 2; Console.WriteLine($"Plaintext matrix row size: {rowSize}"); /* * Here we create the following matrix: * [ 1, 2, 3, 4, 0, 0, ..., 0 ] * [ 0, 0, 0, 0, 0, 0, ..., 0 ] */ ulong[] podMatrix = new ulong[slotCount]; podMatrix[0] = 1; podMatrix[1] = 2; podMatrix[2] = 3; podMatrix[3] = 4; Console.WriteLine("Input plaintext matrix:"); Utilities.PrintMatrix(podMatrix, (int)rowSize); using Plaintext xPlain = new Plaintext(); Console.WriteLine("Encode plaintext matrix to xPlain:"); batchEncoder.Encode(podMatrix, xPlain); /* * Next we encrypt the encoded plaintext. */ using Ciphertext xEncrypted = new Ciphertext(); Utilities.PrintLine(); Console.WriteLine("Encrypt xPlain to xEncrypted."); encryptor.Encrypt(xPlain, xEncrypted); Console.WriteLine(" + noise budget in freshly encrypted x: {0} bits", decryptor.InvariantNoiseBudget(xEncrypted)); Console.WriteLine(); /* * Then we compute x^2. */ Utilities.PrintLine(); Console.WriteLine("Compute and relinearize xSquared (x^2),"); 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); Console.WriteLine(" + noise budget in xSquared: {0} bits", decryptor.InvariantNoiseBudget(xSquared)); using Plaintext decryptedResult = new Plaintext(); decryptor.Decrypt(xSquared, decryptedResult); List <ulong> podResult = new List <ulong>(); batchEncoder.Decode(decryptedResult, podResult); Console.WriteLine(" + result plaintext matrix ...... Correct."); Utilities.PrintMatrix(podResult, (int)rowSize); /* * Next we compute x^4. */ Utilities.PrintLine(); Console.WriteLine("Compute and relinearize x4th (x^4),"); using Ciphertext x4th = new Ciphertext(); evaluator.Square(xSquared, x4th); Console.WriteLine($" + size of x4th: {x4th.Size}"); evaluator.RelinearizeInplace(x4th, relinKeys); Console.WriteLine(" + size of x4th (after relinearization): {0}", x4th.Size); Console.WriteLine(" + noise budget in x4th: {0} bits", decryptor.InvariantNoiseBudget(x4th)); decryptor.Decrypt(x4th, decryptedResult); batchEncoder.Decode(decryptedResult, podResult); Console.WriteLine(" + result plaintext matrix ...... Correct."); Utilities.PrintMatrix(podResult, (int)rowSize); /* * Last we compute x^8. We run out of noise budget. */ Utilities.PrintLine(); Console.WriteLine("Compute and relinearize x8th (x^8),"); using Ciphertext x8th = new Ciphertext(); evaluator.Square(x4th, x8th); Console.WriteLine($" + size of x8th: {x8th.Size}"); evaluator.RelinearizeInplace(x8th, relinKeys); Console.WriteLine(" + size of x8th (after relinearization): {0}", x8th.Size); Console.WriteLine(" + noise budget in x8th: {0} bits", decryptor.InvariantNoiseBudget(x8th)); Console.WriteLine("NOTE: Notice the increase in remaining noise budget."); Console.WriteLine(); Console.WriteLine("~~~~~~ Use modulus switching to calculate x^8. ~~~~~~"); /* * Noise budget has reached 0, which means that decryption cannot be expected * to give the correct result. BGV requires modulus switching to reduce noise * growth. In the following demonstration, we will insert a modulus switching * after each relinearization. */ Utilities.PrintLine(); Console.WriteLine("Encrypt xPlain to xEncrypted."); encryptor.Encrypt(xPlain, xEncrypted); Console.WriteLine(" + noise budget in freshly encrypted x: {0} bits", decryptor.InvariantNoiseBudget(xEncrypted)); Console.WriteLine(); /* * Then we compute x^2. */ Utilities.PrintLine(); Console.WriteLine("Compute and relinearize xSquared (x^2),"); Console.WriteLine(" + noise budget in xSquared (previously): {0} bits", decryptor.InvariantNoiseBudget(xSquared)); evaluator.Square(xEncrypted, xSquared); evaluator.RelinearizeInplace(xSquared, relinKeys); evaluator.ModSwitchToNextInplace(xSquared); Console.WriteLine(" + noise budget in xSquared (with modulus switching): {0} bits", decryptor.InvariantNoiseBudget(xSquared)); decryptor.Decrypt(xSquared, decryptedResult); batchEncoder.Decode(decryptedResult, podResult); Console.WriteLine(" + result plaintext matrix ...... Correct."); Utilities.PrintMatrix(podResult, (int)rowSize); /* * Next we compute x^4. */ Utilities.PrintLine(); Console.WriteLine("Compute and relinearize x4th (x^4),"); Console.WriteLine(" + noise budget in x4th (previously): {0} bits", decryptor.InvariantNoiseBudget(x4th)); evaluator.Square(xSquared, x4th); evaluator.RelinearizeInplace(x4th, relinKeys); evaluator.ModSwitchToNextInplace(x4th); Console.WriteLine(" + noise budget in x4th (with modulus switching): {0} bits", decryptor.InvariantNoiseBudget(x4th)); decryptor.Decrypt(x4th, decryptedResult); batchEncoder.Decode(decryptedResult, podResult); Console.WriteLine(" + result plaintext matrix ...... Correct."); Utilities.PrintMatrix(podResult, (int)rowSize); /* * Last we compute x^8. We still have budget left. */ Utilities.PrintLine(); Console.WriteLine("Compute and relinearize x8th (x^8),"); Console.WriteLine(" + noise budget in x8th (previously): {0} bits", decryptor.InvariantNoiseBudget(x8th)); evaluator.Square(x4th, x8th); evaluator.RelinearizeInplace(x8th, relinKeys); evaluator.ModSwitchToNextInplace(x8th); Console.WriteLine(" + noise budget in x8th (with modulus switching): {0} bits", decryptor.InvariantNoiseBudget(x8th)); decryptor.Decrypt(x8th, decryptedResult); batchEncoder.Decode(decryptedResult, podResult); Console.WriteLine(" + result plaintext matrix ...... Correct."); Utilities.PrintMatrix(podResult, (int)rowSize); /* * Although with modulus switching x_squared has less noise budget than before, * noise budget is consumed at a slower rate. To achieve the optimal consumption * rate of noise budget in an application, one needs to carefully choose the * location to insert modulus switching and manually choose coeff_modulus. */ }