public void KeyStepTest() { EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS) { PolyModulusDegree = 64, CoeffModulus = CoeffModulus.Create(64, new int[] { 60, 60 }) }; SEALContext context = new SEALContext(parms, expandModChain: false, secLevel: SecLevelType.None); KeyGenerator keygen = new KeyGenerator(context); GaloisKeys keys = keygen.GaloisKeysLocal(steps: new int[] { 1, 2, 3 }); Assert.IsNotNull(keys); Assert.AreEqual(3ul, keys.Size); Assert.IsFalse(keys.HasKey(1)); Assert.IsTrue(keys.HasKey(3)); Assert.IsFalse(keys.HasKey(5)); Assert.IsFalse(keys.HasKey(7)); Assert.IsTrue(keys.HasKey(9)); Assert.IsFalse(keys.HasKey(11)); Assert.IsFalse(keys.HasKey(13)); Assert.IsFalse(keys.HasKey(15)); Assert.IsFalse(keys.HasKey(17)); Assert.IsFalse(keys.HasKey(19)); Assert.IsFalse(keys.HasKey(21)); Assert.IsFalse(keys.HasKey(23)); Assert.IsFalse(keys.HasKey(25)); Assert.IsTrue(keys.HasKey(27)); }
public void KeyTest() { SEALContext context = GlobalContext.BFVContext; KeyGenerator keygen = new KeyGenerator(context); GaloisKeys keys = keygen.GaloisKeysLocal(); MemoryPoolHandle handle = keys.Pool; Assert.IsNotNull(keys); Assert.AreEqual(24ul, keys.Size); Assert.IsFalse(keys.HasKey(galoisElt: 1)); Assert.IsTrue(keys.HasKey(galoisElt: 3)); Assert.IsFalse(keys.HasKey(galoisElt: 5)); Assert.IsFalse(keys.HasKey(galoisElt: 7)); Assert.IsTrue(keys.HasKey(galoisElt: 9)); Assert.IsFalse(keys.HasKey(galoisElt: 11)); IEnumerable <PublicKey> key = keys.Key(3); Assert.AreEqual(4, key.Count()); IEnumerable <PublicKey> key2 = keys.Key(9); Assert.AreEqual(4, key2.Count()); Assert.IsTrue(handle.AllocByteCount > 0ul); }
public void ScaleTest() { EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS) { PolyModulusDegree = 8, CoeffModulus = CoeffModulus.Create(8, new int[] { 40, 40, 40, 40 }) }; SEALContext context = new SEALContext(parms, expandModChain: true, secLevel: SecLevelType.None); KeyGenerator keygen = new KeyGenerator(context); GaloisKeys galoisKeys = keygen.GaloisKeysLocal(); Encryptor encryptor = new Encryptor(context, keygen.PublicKey); Evaluator evaluator = new Evaluator(context); CKKSEncoder encoder = new CKKSEncoder(context); MemoryPoolHandle pool = MemoryManager.GetPool(MMProfOpt.ForceNew); Assert.AreEqual(0ul, pool.AllocByteCount); Ciphertext encrypted = new Ciphertext(pool); Plaintext plain = new Plaintext(); MemoryPoolHandle cipherPool = encrypted.Pool; Assert.IsNotNull(cipherPool); Assert.AreEqual(0ul, cipherPool.AllocByteCount); List <Complex> input = new List <Complex>() { new Complex(1, 1), new Complex(2, 2), new Complex(3, 3), new Complex(4, 4) }; double delta = Math.Pow(2, 70); encoder.Encode(input, context.FirstParmsId, delta, plain); encryptor.Encrypt(plain, encrypted); Assert.AreEqual(delta, encrypted.Scale, delta: Math.Pow(2, 60)); Ciphertext encrypted2 = new Ciphertext(); encrypted2.Set(encrypted); Assert.AreEqual(delta, encrypted2.Scale, delta: Math.Pow(2, 60)); evaluator.RescaleToNextInplace(encrypted); Assert.AreEqual(Math.Pow(2, 30), encrypted.Scale, delta: 10000); Assert.AreNotEqual(0ul, cipherPool.AllocByteCount); double newScale = Math.Pow(2, 10); encrypted.Scale = newScale; Assert.AreEqual(newScale, encrypted.Scale, delta: 100); }
public void SaveLoadTest() { SEALContext context = GlobalContext.BFVContext; KeyGenerator keyGen = new KeyGenerator(context); GaloisKeys keys = keyGen.GaloisKeysLocal(); GaloisKeys other = new GaloisKeys(); Assert.IsNotNull(keys); Assert.AreEqual(24ul, keys.Size); using (MemoryStream ms = new MemoryStream()) { keys.Save(ms); ms.Seek(offset: 0, loc: SeekOrigin.Begin); other.Load(context, ms); } Assert.AreEqual(24ul, other.Size); Assert.IsTrue(ValCheck.IsValidFor(other, context)); List <IEnumerable <PublicKey> > keysData = new List <IEnumerable <PublicKey> >(keys.Data); List <IEnumerable <PublicKey> > otherData = new List <IEnumerable <PublicKey> >(other.Data); Assert.AreEqual(keysData.Count, otherData.Count); for (int i = 0; i < keysData.Count; i++) { List <PublicKey> keysCiphers = new List <PublicKey>(keysData[i]); List <PublicKey> otherCiphers = new List <PublicKey>(otherData[i]); Assert.AreEqual(keysCiphers.Count, otherCiphers.Count); for (int j = 0; j < keysCiphers.Count; j++) { PublicKey keysCipher = keysCiphers[j]; PublicKey otherCipher = otherCiphers[j]; Assert.AreEqual(keysCipher.Data.Size, otherCipher.Data.Size); Assert.AreEqual(keysCipher.Data.PolyModulusDegree, otherCipher.Data.PolyModulusDegree); Assert.AreEqual(keysCipher.Data.CoeffModulusSize, otherCipher.Data.CoeffModulusSize); ulong coeffCount = keysCipher.Data.Size * keysCipher.Data.PolyModulusDegree * keysCipher.Data.CoeffModulusSize; for (ulong k = 0; k < coeffCount; k++) { Assert.AreEqual(keysCipher.Data[k], otherCipher.Data[k]); } } } }
public void CreateNonEmptyTest() { SEALContext context = GlobalContext.BFVContext; KeyGenerator keygen = new KeyGenerator(context); GaloisKeys keys = keygen.GaloisKeysLocal(); Assert.IsNotNull(keys); Assert.AreEqual(24ul, keys.Size); GaloisKeys copy = new GaloisKeys(keys); Assert.IsNotNull(copy); Assert.AreEqual(24ul, copy.Size); }
public void KeyEltTest() { SEALContext context = GlobalContext.BFVContext; KeyGenerator keygen = new KeyGenerator(context); GaloisKeys keys = keygen.GaloisKeysLocal(galoisElts: new uint[] { 1, 3 }); Assert.IsNotNull(keys); Assert.AreEqual(2ul, keys.Size); Assert.IsTrue(keys.HasKey(1)); Assert.IsTrue(keys.HasKey(3)); Assert.IsFalse(keys.HasKey(5)); }
public void SetTest() { SEALContext context = GlobalContext.BFVContext; KeyGenerator keygen = new KeyGenerator(context); GaloisKeys keys = keygen.GaloisKeysLocal(); Assert.IsNotNull(keys); Assert.AreEqual(24ul, keys.Size); GaloisKeys keys2 = new GaloisKeys(); Assert.IsNotNull(keys2); Assert.AreEqual(0ul, keys2.Size); keys2.Set(keys); Assert.AreNotSame(keys, keys2); Assert.AreEqual(24ul, keys2.Size); }
private static void CKKSPerformanceTest(SEALContext context) { Stopwatch timer; Utilities.PrintParameters(context); Console.WriteLine(); using EncryptionParameters parms = context.FirstContextData.Parms; ulong polyModulusDegree = parms.PolyModulusDegree; Console.Write("Generating secret/public keys: "); using KeyGenerator keygen = new KeyGenerator(context); Console.WriteLine("Done"); using SecretKey secretKey = keygen.SecretKey; using PublicKey publicKey = keygen.PublicKey; Func <RelinKeys> GetRelinKeys = () => { if (context.UsingKeyswitching) { /* * Generate relinearization keys. */ Console.Write("Generating relinearization keys: "); timer = Stopwatch.StartNew(); RelinKeys result = keygen.RelinKeysLocal(); int micros = (int)(timer.Elapsed.TotalMilliseconds * 1000); Console.WriteLine($"Done [{micros} microseconds]"); return(result); } else { return(null); } }; Func <GaloisKeys> GetGaloisKeys = () => { if (context.UsingKeyswitching) { if (!context.KeyContextData.Qualifiers.UsingBatching) { Console.WriteLine("Given encryption parameters do not support batching."); return(null); } /* * Generate Galois keys. In larger examples the Galois keys can use a lot of * memory, which can be a problem in constrained systems. The user should * try some of the larger runs of the test and observe their effect on the * memory pool allocation size. The key generation can also take a long time, * as can be observed from the print-out. */ Console.Write($"Generating Galois keys: "); timer = Stopwatch.StartNew(); GaloisKeys result = keygen.GaloisKeysLocal(); int micros = (int)(timer.Elapsed.TotalMilliseconds * 1000); Console.WriteLine($"Done [{micros} microseconds]"); return(result); } else { return(null); } }; using RelinKeys relinKeys = GetRelinKeys(); using GaloisKeys galKeys = GetGaloisKeys(); using Encryptor encryptor = new Encryptor(context, publicKey); using Decryptor decryptor = new Decryptor(context, secretKey); using Evaluator evaluator = new Evaluator(context); using CKKSEncoder ckksEncoder = new CKKSEncoder(context); Stopwatch timeEncodeSum = new Stopwatch(); Stopwatch timeDecodeSum = new Stopwatch(); Stopwatch timeEncryptSum = new Stopwatch(); Stopwatch timeDecryptSum = new Stopwatch(); Stopwatch timeAddSum = new Stopwatch(); Stopwatch timeMultiplySum = new Stopwatch(); Stopwatch timeMultiplyPlainSum = new Stopwatch(); Stopwatch timeSquareSum = new Stopwatch(); Stopwatch timeRelinearizeSum = new Stopwatch(); Stopwatch timeRescaleSum = new Stopwatch(); Stopwatch timeRotateOneStepSum = new Stopwatch(); Stopwatch timeRotateRandomSum = new Stopwatch(); Stopwatch timeConjugateSum = new Stopwatch(); Random rnd = new Random(); /* * How many times to run the test? */ int count = 10; /* * Populate a vector of floating-point values to batch. */ ulong slotCount = ckksEncoder.SlotCount; double[] podValues = new double[slotCount]; for (ulong i = 0; i < slotCount; i++) { podValues[i] = 1.001 * i; } Console.Write("Running tests "); for (int i = 0; i < count; i++) { /* * [Encoding] * For scale we use the square root of the last CoeffModulus prime * from parms. */ double scale = Math.Sqrt(parms.CoeffModulus.Last().Value); using Plaintext plain = new Plaintext(parms.PolyModulusDegree * (ulong)parms.CoeffModulus.Count(), 0); timeEncodeSum.Start(); ckksEncoder.Encode(podValues, scale, plain); timeEncodeSum.Stop(); /* * [Decoding] */ List <double> podList = new List <double>((int)slotCount); timeDecodeSum.Start(); ckksEncoder.Decode(plain, podList); timeDecodeSum.Stop(); /* * [Encryption] */ using Ciphertext encrypted = new Ciphertext(context); timeEncryptSum.Start(); encryptor.Encrypt(plain, encrypted); timeEncryptSum.Stop(); /* * [Decryption] */ using Plaintext plain2 = new Plaintext(polyModulusDegree, 0); timeDecryptSum.Start(); decryptor.Decrypt(encrypted, plain2); timeDecryptSum.Stop(); /* * [Add] */ using Ciphertext encrypted1 = new Ciphertext(context); ckksEncoder.Encode(i + 1, plain); encryptor.Encrypt(plain, encrypted1); using Ciphertext encrypted2 = new Ciphertext(context); ckksEncoder.Encode(i + 1, plain2); encryptor.Encrypt(plain2, encrypted2); timeAddSum.Start(); evaluator.AddInplace(encrypted1, encrypted2); evaluator.AddInplace(encrypted2, encrypted2); evaluator.AddInplace(encrypted1, encrypted2); timeAddSum.Stop(); /* * [Multiply] */ encrypted1.Reserve(3); timeMultiplySum.Start(); evaluator.MultiplyInplace(encrypted1, encrypted2); timeMultiplySum.Stop(); /* * [Multiply Plain] */ timeMultiplyPlainSum.Start(); evaluator.MultiplyPlainInplace(encrypted2, plain); timeMultiplyPlainSum.Stop(); /* * [Square] */ timeSquareSum.Start(); evaluator.SquareInplace(encrypted2); timeSquareSum.Stop(); if (context.UsingKeyswitching) { /* * [Relinearize] */ timeRelinearizeSum.Start(); evaluator.RelinearizeInplace(encrypted1, relinKeys); timeRelinearizeSum.Stop(); /* * [Rescale] */ timeRescaleSum.Start(); evaluator.RescaleToNextInplace(encrypted1); timeRescaleSum.Stop(); /* * [Rotate Vector] */ timeRotateOneStepSum.Start(); evaluator.RotateVectorInplace(encrypted, 1, galKeys); evaluator.RotateVectorInplace(encrypted, -1, galKeys); timeRotateOneStepSum.Stop(); /* * [Rotate Vector Random] */ // ckksEncoder.SlotCount is always a power of 2. int randomRotation = rnd.Next() & ((int)ckksEncoder.SlotCount - 1); timeRotateRandomSum.Start(); evaluator.RotateVectorInplace(encrypted, randomRotation, galKeys); timeRotateRandomSum.Stop(); /* * [Complex Conjugate] */ timeConjugateSum.Start(); evaluator.ComplexConjugateInplace(encrypted, galKeys); timeConjugateSum.Stop(); } /* * Print a dot to indicate progress. */ Console.Write("."); Console.Out.Flush(); } Console.WriteLine(" Done"); Console.WriteLine(); Console.Out.Flush(); int avgEncode = (int)(timeEncodeSum.Elapsed.TotalMilliseconds * 1000 / count); int avgDecode = (int)(timeDecodeSum.Elapsed.TotalMilliseconds * 1000 / count); int avgEncrypt = (int)(timeEncryptSum.Elapsed.TotalMilliseconds * 1000 / count); int avgDecrypt = (int)(timeDecryptSum.Elapsed.TotalMilliseconds * 1000 / count); int avgAdd = (int)(timeAddSum.Elapsed.TotalMilliseconds * 1000 / (3 * count)); int avgMultiply = (int)(timeMultiplySum.Elapsed.TotalMilliseconds * 1000 / count); int avgMultiplyPlain = (int)(timeMultiplyPlainSum.Elapsed.TotalMilliseconds * 1000 / count); int avgSquare = (int)(timeSquareSum.Elapsed.TotalMilliseconds * 1000 / count); int avgRelinearize = (int)(timeRelinearizeSum.Elapsed.TotalMilliseconds * 1000 / count); int avgRescale = (int)(timeRescaleSum.Elapsed.TotalMilliseconds * 1000 / count); int avgRotateOneStep = (int)(timeRotateOneStepSum.Elapsed.TotalMilliseconds * 1000 / (2 * count)); int avgRotateRandom = (int)(timeRotateRandomSum.Elapsed.TotalMilliseconds * 1000 / count); int avgConjugate = (int)(timeConjugateSum.Elapsed.TotalMilliseconds * 1000 / count); Console.WriteLine($"Average encode: {avgEncode} microseconds"); Console.WriteLine($"Average decode: {avgDecode} microseconds"); Console.WriteLine($"Average encrypt: {avgEncrypt} microseconds"); Console.WriteLine($"Average decrypt: {avgDecrypt} microseconds"); Console.WriteLine($"Average add: {avgAdd} microseconds"); Console.WriteLine($"Average multiply: {avgMultiply} microseconds"); Console.WriteLine($"Average multiply plain: {avgMultiplyPlain} microseconds"); Console.WriteLine($"Average square: {avgSquare} microseconds"); if (context.UsingKeyswitching) { Console.WriteLine($"Average relinearize: {avgRelinearize} microseconds"); Console.WriteLine($"Average rescale: {avgRescale} microseconds"); Console.WriteLine($"Average rotate vector one step: {avgRotateOneStep} microseconds"); Console.WriteLine($"Average rotate vector random: {avgRotateRandom} microseconds"); Console.WriteLine($"Average complex conjugate: {avgConjugate} microseconds"); } Console.Out.Flush(); }
private static void BFVPerformanceTest(SEALContext context) { Stopwatch timer; Utilities.PrintParameters(context); Console.WriteLine(); using EncryptionParameters parms = context.FirstContextData.Parms; using Modulus plainModulus = parms.PlainModulus; ulong polyModulusDegree = parms.PolyModulusDegree; Console.Write("Generating secret/public keys: "); using KeyGenerator keygen = new KeyGenerator(context); Console.WriteLine("Done"); using SecretKey secretKey = keygen.SecretKey; using PublicKey publicKey = keygen.PublicKey; Func <RelinKeys> GetRelinKeys = () => { if (context.UsingKeyswitching) { /* * Generate relinearization keys. */ Console.Write("Generating relinearization keys: "); timer = Stopwatch.StartNew(); RelinKeys result = keygen.RelinKeysLocal(); int micros = (int)(timer.Elapsed.TotalMilliseconds * 1000); Console.WriteLine($"Done [{micros} microseconds]"); return(result); } else { return(null); } }; Func <GaloisKeys> GetGaloisKeys = () => { if (context.UsingKeyswitching) { if (!context.KeyContextData.Qualifiers.UsingBatching) { Console.WriteLine("Given encryption parameters do not support batching."); return(null); } /* * Generate Galois keys. In larger examples the Galois keys can use a lot of * memory, which can be a problem in constrained systems. The user should * try some of the larger runs of the test and observe their effect on the * memory pool allocation size. The key generation can also take a long time, * as can be observed from the print-out. */ Console.Write($"Generating Galois keys: "); timer = Stopwatch.StartNew(); GaloisKeys result = keygen.GaloisKeysLocal(); int micros = (int)(timer.Elapsed.TotalMilliseconds * 1000); Console.WriteLine($"Done [{micros} microseconds]"); return(result); } else { return(null); } }; using RelinKeys relinKeys = GetRelinKeys(); using GaloisKeys galKeys = GetGaloisKeys(); using Encryptor encryptor = new Encryptor(context, publicKey); using Decryptor decryptor = new Decryptor(context, secretKey); using Evaluator evaluator = new Evaluator(context); using BatchEncoder batchEncoder = new BatchEncoder(context); using IntegerEncoder encoder = new IntegerEncoder(context); /* * These will hold the total times used by each operation. */ Stopwatch timeBatchSum = new Stopwatch(); Stopwatch timeUnbatchSum = new Stopwatch(); Stopwatch timeEncryptSum = new Stopwatch(); Stopwatch timeDecryptSum = new Stopwatch(); Stopwatch timeAddSum = new Stopwatch(); Stopwatch timeMultiplySum = new Stopwatch(); Stopwatch timeMultiplyPlainSum = new Stopwatch(); Stopwatch timeSquareSum = new Stopwatch(); Stopwatch timeRelinearizeSum = new Stopwatch(); Stopwatch timeRotateRowsOneStepSum = new Stopwatch(); Stopwatch timeRotateRowsRandomSum = new Stopwatch(); Stopwatch timeRotateColumnsSum = new Stopwatch(); /* * How many times to run the test? */ int count = 10; /* * Populate a vector of values to batch. */ ulong slotCount = batchEncoder.SlotCount; ulong[] podValues = new ulong[slotCount]; Random rnd = new Random(); for (ulong i = 0; i < batchEncoder.SlotCount; i++) { podValues[i] = (ulong)rnd.Next() % plainModulus.Value; } Console.Write("Running tests "); for (int i = 0; i < count; i++) { /* * [Batching] * There is nothing unusual here. We batch our random plaintext matrix * into the polynomial. Note how the plaintext we create is of the exactly * right size so unnecessary reallocations are avoided. */ using Plaintext plain = new Plaintext(parms.PolyModulusDegree, 0); timeBatchSum.Start(); batchEncoder.Encode(podValues, plain); timeBatchSum.Stop(); /* * [Unbatching] * We unbatch what we just batched. */ List <ulong> podList = new List <ulong>((int)slotCount); timeUnbatchSum.Start(); batchEncoder.Decode(plain, podList); timeUnbatchSum.Stop(); if (!podList.SequenceEqual(podValues)) { throw new InvalidOperationException("Batch/unbatch failed. Something is wrong."); } /* * [Encryption] * We make sure our ciphertext is already allocated and large enough * to hold the encryption with these encryption parameters. We encrypt * our random batched matrix here. */ using Ciphertext encrypted = new Ciphertext(context); timeEncryptSum.Start(); encryptor.Encrypt(plain, encrypted); timeEncryptSum.Stop(); /* * [Decryption] * We decrypt what we just encrypted. */ using Plaintext plain2 = new Plaintext(polyModulusDegree, 0); timeDecryptSum.Start(); decryptor.Decrypt(encrypted, plain2); timeDecryptSum.Stop(); if (!plain2.Equals(plain)) { throw new InvalidOperationException("Encrypt/decrypt failed. Something is wrong."); } /* * [Add] * We create two ciphertexts and perform a few additions with them. */ using Ciphertext encrypted1 = new Ciphertext(context); encryptor.Encrypt(encoder.Encode(i), encrypted1); using Ciphertext encrypted2 = new Ciphertext(context); encryptor.Encrypt(encoder.Encode(i + 1), encrypted2); timeAddSum.Start(); evaluator.AddInplace(encrypted1, encrypted1); evaluator.AddInplace(encrypted2, encrypted2); evaluator.AddInplace(encrypted1, encrypted2); timeAddSum.Stop(); /* * [Multiply] * We multiply two ciphertexts. Since the size of the result will be 3, * and will overwrite the first argument, we reserve first enough memory * to avoid reallocating during multiplication. */ encrypted1.Reserve(3); timeMultiplySum.Start(); evaluator.MultiplyInplace(encrypted1, encrypted2); timeMultiplySum.Stop(); /* * [Multiply Plain] * We multiply a ciphertext with a random plaintext. Recall that * MultiplyPlain does not change the size of the ciphertext so we use * encrypted2 here. */ timeMultiplyPlainSum.Start(); evaluator.MultiplyPlainInplace(encrypted2, plain); timeMultiplyPlainSum.Stop(); /* * [Square] * We continue to use encrypted2. Now we square it; this should be * faster than generic homomorphic multiplication. */ timeSquareSum.Start(); evaluator.SquareInplace(encrypted2); timeSquareSum.Stop(); if (context.UsingKeyswitching) { /* * [Relinearize] * Time to get back to encrypted1. We now relinearize it back * to size 2. Since the allocation is currently big enough to * contain a ciphertext of size 3, no costly reallocations are * needed in the process. */ timeRelinearizeSum.Start(); evaluator.RelinearizeInplace(encrypted1, relinKeys); timeRelinearizeSum.Stop(); /* * [Rotate Rows One Step] * We rotate matrix rows by one step left and measure the time. */ timeRotateRowsOneStepSum.Start(); evaluator.RotateRowsInplace(encrypted, 1, galKeys); evaluator.RotateRowsInplace(encrypted, -1, galKeys); timeRotateRowsOneStepSum.Stop(); /* * [Rotate Rows Random] * We rotate matrix rows by a random number of steps. This is much more * expensive than rotating by just one step. */ int rowSize = (int)batchEncoder.SlotCount / 2; // rowSize is always a power of 2. int randomRotation = rnd.Next() & (rowSize - 1); timeRotateRowsRandomSum.Start(); evaluator.RotateRowsInplace(encrypted, randomRotation, galKeys); timeRotateRowsRandomSum.Stop(); /* * [Rotate Columns] * Nothing surprising here. */ timeRotateColumnsSum.Start(); evaluator.RotateColumnsInplace(encrypted, galKeys); timeRotateColumnsSum.Stop(); } /* * Print a dot to indicate progress. */ Console.Write("."); Console.Out.Flush(); } Console.WriteLine(" Done"); Console.WriteLine(); Console.Out.Flush(); int avgBatch = (int)(timeBatchSum.Elapsed.TotalMilliseconds * 1000 / count); int avgUnbatch = (int)(timeUnbatchSum.Elapsed.TotalMilliseconds * 1000 / count); int avgEncrypt = (int)(timeEncryptSum.Elapsed.TotalMilliseconds * 1000 / count); int avgDecrypt = (int)(timeDecryptSum.Elapsed.TotalMilliseconds * 1000 / count); int avgAdd = (int)(timeAddSum.Elapsed.TotalMilliseconds * 1000 / (3 * count)); int avgMultiply = (int)(timeMultiplySum.Elapsed.TotalMilliseconds * 1000 / count); int avgMultiplyPlain = (int)(timeMultiplyPlainSum.Elapsed.TotalMilliseconds * 1000 / count); int avgSquare = (int)(timeSquareSum.Elapsed.TotalMilliseconds * 1000 / count); int avgRelinearize = (int)(timeRelinearizeSum.Elapsed.TotalMilliseconds * 1000 / count); int avgRotateRowsOneStep = (int)(timeRotateRowsOneStepSum.Elapsed.TotalMilliseconds * 1000 / (2 * count)); int avgRotateRowsRandom = (int)(timeRotateRowsRandomSum.Elapsed.TotalMilliseconds * 1000 / count); int avgRotateColumns = (int)(timeRotateColumnsSum.Elapsed.TotalMilliseconds * 1000 / count); Console.WriteLine($"Average batch: {avgBatch} microseconds"); Console.WriteLine($"Average unbatch: {avgUnbatch} microseconds"); Console.WriteLine($"Average encrypt: {avgEncrypt} microseconds"); Console.WriteLine($"Average decrypt: {avgDecrypt} microseconds"); Console.WriteLine($"Average add: {avgAdd} microseconds"); Console.WriteLine($"Average multiply: {avgMultiply} microseconds"); Console.WriteLine($"Average multiply plain: {avgMultiplyPlain} microseconds"); Console.WriteLine($"Average square: {avgSquare} microseconds"); if (context.UsingKeyswitching) { Console.WriteLine($"Average relinearize: {avgRelinearize} microseconds"); Console.WriteLine($"Average rotate rows one step: {avgRotateRowsOneStep} microseconds"); Console.WriteLine($"Average rotate rows random: {avgRotateRowsRandom} microseconds"); Console.WriteLine($"Average rotate columns: {avgRotateColumns} microseconds"); } Console.Out.Flush(); }
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 PublicKey publicKey = keygen.PublicKey; using SecretKey secretKey = keygen.SecretKey; using RelinKeys relinKeys = keygen.RelinKeysLocal(); /* * In this example we create a local version of the GaloisKeys object using * KeyGenerator.GaloisKeysLocal(). In a production setting where the Galois * keys would need to be communicated to a server, it would be much better to * use KeyGenerator.GaloisKeys(), which outputs a Serializable<GaloisKeys> * object for compressed serialization. */ using GaloisKeys galoisKeys = keygen.GaloisKeysLocal(); 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}"); Console.WriteLine($" + galoisKeys: {galoisKeys.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. */ }
/* * 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 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 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. */ using GaloisKeys galKeys = keygen.GaloisKeysLocal(); /* * 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, galKeys); 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, galKeys); 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, galKeys); 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 ExampleRotationCKKS() { Utilities.PrintExampleBanner("Example: Rotation / Rotation in CKKS"); using EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS); ulong polyModulusDegree = 8192; parms.PolyModulusDegree = polyModulusDegree; parms.CoeffModulus = CoeffModulus.Create( polyModulusDegree, new int[] { 40, 40, 40, 40, 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 GaloisKeys galKeys = keygen.GaloisKeysLocal(); using Encryptor encryptor = new Encryptor(context, publicKey); using Evaluator evaluator = new Evaluator(context); using Decryptor decryptor = new Decryptor(context, secretKey); using CKKSEncoder ckksEncoder = new CKKSEncoder(context); ulong slotCount = ckksEncoder.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); double scale = Math.Pow(2.0, 50); Utilities.PrintLine(); Console.WriteLine("Encode and encrypt."); using Plaintext plain = new Plaintext(); ckksEncoder.Encode(input, scale, plain); using Ciphertext encrypted = new Ciphertext(); encryptor.Encrypt(plain, encrypted); using Ciphertext rotated = new Ciphertext(); Utilities.PrintLine(); Console.WriteLine("Rotate 2 steps left."); evaluator.RotateVector(encrypted, 2, galKeys, rotated); Console.WriteLine(" + Decrypt and decode ...... Correct."); decryptor.Decrypt(encrypted, plain); List <double> result = new List <double>(); ckksEncoder.Decode(plain, result); Utilities.PrintVector(result, 3, 7); /* * With the CKKS scheme it is also possible to evaluate a complex conjugation on * a vector of encrypted complex numbers, using Evaluator.ComplexConjugate. This * is in fact a kind of rotation, and requires also Galois keys. */ }